com.redhat.ceylon.ant.CeylonAntTask Maven / Gradle / Ivy
/*
* Copyright Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the authors tag. All rights reserved.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU General Public License version 2.
*
* This particular file is subject to the "Classpath" exception as provided in the
* LICENSE file that accompanied this code.
*
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License,
* along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package com.redhat.ceylon.ant;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Execute;
import org.apache.tools.ant.taskdefs.LogStreamHandler;
import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.util.FileUtils;
import com.redhat.ceylon.launcher.CeylonClassLoader;
import com.redhat.ceylon.launcher.ClassLoaderSetupException;
import com.redhat.ceylon.launcher.Launcher;
/**
* Baseclass for ant tasks which execute a ceylon tool in a subprocess.
*/
public abstract class CeylonAntTask extends Task {
private static final int MAX_COMMAND_LENGTH = 4096;
@AntDoc("The value for the system property can "
+ "either be passed as a `value` attribute:\n"
+ "\n"
+ " \n"
+ "\n"
+ "or it can be the text between the begin and end tags:\n"
+ "\n"
+ "\n"
+ " Hi \n"
+ "\n"
+ "Alternatively, it is "
+ "posible to dispense with the attributes and use the "
+ "syntax\n"
+ "\n"
+ " org.example.helloworld.greeting=Hi \n")
public static class Define {
String key;
String value;
@AntDoc("The property to be defined")
public void setKey(String key) {
this.key = key;
}
@AntDoc("The value of the define")
public void setValue(String value) {
this.value = value;
}
@AntDocIgnore
public void addText(String value) {
this.value = value;
}
}
private final String toolName;
private File executable;
private ExitHandler exitHandler = new ExitHandler();
private Boolean fork;
private Boolean inheritAll;
private boolean stacktraces;
private String cwd;
private String config;
private String verbose;
private List defines = new ArrayList(0);
private CeylonClassLoader loader;
/**
* @param toolName The name of the ceylon tool which this task executes.
*/
protected CeylonAntTask(String toolName) {
this.toolName = toolName;
}
protected CeylonClassLoader getLoader() throws ClassLoaderSetupException {
if(loader == null){
loader = Util.getCeylonClassLoaderCachedInProject(getProject());
}
return loader;
}
protected String getCwd() {
return cwd;
}
/**
* Sets the current working directory
* @param cwd path to the current working directory
*/
@OptionEquivalent("--cwd")
public void setCwd(String cwd) {
this.cwd = cwd;
}
protected String getConfig() {
return config;
}
/**
* Specifies the configuration file to use for this task
* @param config path to the configuration file
*/
@OptionEquivalent("--config")
public void setConfig(String config) {
this.config = config;
}
@OptionEquivalent("--verbose")
public void setVerbose(String verbose){
this.verbose = verbose;
}
/** Adds an define (key=value) to be passed to the tool */
@AntDoc("A `` element is used to set system properties for the ant "
+ "task being executed.")
@OptionEquivalent("--define")
public void addConfiguredDefine(Define define) {
this.defines.add(define);
}
public String getExecutable() {
return executable.getPath();
}
/** Sets the location of the ceylon executable script */
@AntDoc("The location of the ceylon executable script.")
public void setExecutable(String executable) {
this.executable = new File(Util.getScriptName(executable));
}
public boolean getFork() {
return (fork != null) ? fork : shouldSpawnJvm();
}
/** Sets whether the task should be run in a separate VM */
@AntDoc("Whether the task should be run in a separate VM (default: `false`)")
public void setFork(boolean fork) {
this.fork = fork;
}
public boolean getInheritAll() {
return (inheritAll != null) ? inheritAll : getFork();
}
/**
* Sets whether a task should inherit environment and properties.
* Only applies when forked == true
*/
@AntDoc("Whether a task should inherit environment and properties. "
+ "Only applies when `forked == true`.")
public void setInheritAll(boolean inheritAll) {
this.inheritAll = inheritAll;
}
public boolean getStacktraces() {
return stacktraces;
}
/** Sets whether the task should show any stacktraces that are generated during execution */
@OptionEquivalent("--stacktraces")
public void setStacktraces(boolean stacktraces) {
this.stacktraces = stacktraces;
}
public boolean getFailOnError() {
return exitHandler.isFailOnError();
}
/** Sets whether an error in executing this task should fail the build */
@AntDoc("Whether an error in executing this task should fail the ant build")
public void setFailOnError(boolean failOnError) {
this.exitHandler.setFailOnError(failOnError);
}
public String getErrorProperty() {
return exitHandler.getErrorProperty();
}
/** Sets name of a property to set to true in the event of an error */
@AntDoc("The ant property to set to true in the event of an error")
public void setErrorProperty(String errorProperty) {
this.exitHandler.setErrorProperty(errorProperty);
}
/**
* Sets the Ceylon program exit code into the given property
* @param resultProperty the property to set to the Ceylon program exit code
*/
@AntDoc("The ant property to set to the Ceylon program exit code")
public void setResultProperty(String resultProperty){
this.exitHandler.setResultProperty(resultProperty);
}
/** Executes the task */
public void execute() {
Java7Checker.check();
checkParameters();
Commandline cmd = buildCommandline();
if (cmd != null) {
executeCommandline(cmd);
}
}
/**
* Executes a Commandline
* @param cmd The commandline
*/
protected void executeCommandline(Commandline cmd) {
File tmpFile = null;
try {
int exitValue;
if(getFork()){
log("Spawning new process: " + Arrays.toString(cmd.getCommandline()), Project.MSG_VERBOSE);
String[] args = cmd.getCommandline();
if (Commandline.toString(args).length() > MAX_COMMAND_LENGTH) {
BufferedWriter out = null;
try {
tmpFile = File.createTempFile("ceylon-ant-", "cmd");
out = new BufferedWriter(new FileWriter(tmpFile));
for (int i = 1; i < args.length; i++) {
out.write(Commandline.quoteArgument(args[i]));
out.newLine();
}
out.flush();
String[] newArgs = new String[2];
newArgs[0] = args[0];
newArgs[1] = "@" + tmpFile.getAbsolutePath();
args = newArgs;
} finally {
FileUtils.close(out);
}
}
Execute exe = new Execute(new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN));
exe.setAntRun(getProject());
exe.setWorkingDirectory(getProject().getBaseDir());
exe.setCommandline(args);
exe.setNewenvironment(!getInheritAll());
exe.execute();
exitValue = exe.getExitValue();
}else{
log("Launching Launcher in this JVM: " + Arrays.toString(cmd.getArguments()), Project.MSG_VERBOSE);
exitValue = Launcher.runInJava7Checked(getLoader(), cmd.getArguments());
}
if (exitValue != 0) {
String message = formatFailureMessage(cmd);
exitHandler.handleExit(this, exitValue, message);
}else{
exitHandler.handleExit(this, exitValue, null);
}
}catch(BuildException e){
// let build exceptions through, since we throw them ourselves in handleExit!
throw e;
} catch (Throwable e) {
throw new BuildException("Error running Ceylon " + toolName + " tool (an exception was thrown, run ant with -v parameter to see the exception)", e, getLocation());
} finally {
if (tmpFile != null) {
tmpFile.delete();
}
}
}
/**
* For now this is not a setting, because we must have it for ceylon-run and it's kinda useless for the
* other tools.
* See https://github.com/ceylon/ceylon-compiler/issues/1076
* See https://github.com/ceylon/ceylon-compiler/issues/1366
*/
protected boolean shouldSpawnJvm() {
return false;
}
protected String formatFailureMessage(Commandline cmd) {
StringBuilder sb = new StringBuilder("While executing command").append(System.lineSeparator());
for (String s : cmd.getCommandline()) {
sb.append(" ").append(s).append(System.lineSeparator());
}
sb.append(getFailMessage());
String message = sb.toString();
return message;
}
/**
* Returns the message to pass to {@link ExitHandler#handleExit(Task, int, String)}
*/
protected abstract String getFailMessage();
/**
* Builds the command line to be executed
* @return the command line to be executed,
* or null if there is no command to be executed
*/
protected Commandline buildCommandline() {
Commandline cmd = new Commandline();
cmd.setExecutable(findExecutable());
if (stacktraces) {
appendOption(cmd, "--stacktraces");
}
if (getCwd() != null) {
appendOption(cmd, "--cwd=" + getCwd());
} else {
appendOption(cmd, "--cwd=" + getProject().getBaseDir().getPath());
}
if (getConfig() != null) {
appendOption(cmd, "--config=" + getConfig());
}
if (getFork() && getInheritAll()) {
@SuppressWarnings("rawtypes")
Hashtable props = getProject().getUserProperties();
for (Object k : props.keySet()) {
Object val = props.get(k);
String arg = k + "=" + val;
if (isSafeProperty(arg)) {
appendOption(cmd, "--define=" + encodeProperty(arg));
}
}
}
for (Define d : defines) {
String arg = (d.key != null) ? d.key + "=" + d.value : d.value;
appendOption(cmd, "--define=" + arg);
}
cmd.createArgument().setValue(toolName);
completeCommandline(cmd);
return cmd;
}
private boolean isSafeProperty(String str) {
return !str.contains("\"") || !str.contains("'");
}
private String encodeProperty(String str) {
return str.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
}
/**
* Completes the building of a partially configured Commandline
* @param cmd
*/
protected void completeCommandline(Commandline cmd) {
appendVerboseOption(cmd, verbose);
}
/**
* Checks the parameters
*/
protected void checkParameters() {
findExecutable();
}
/**
* Tries to find a ceylonc compiler either user-specified or detected
*/
private String findExecutable() {
return Util.findCeylonScript(this.executable, getProject());
}
/**
* Adds a {@code --verbose} (or {@code --verbose=flags}) to the given
* command line
* @param cmd The command line
* @param verbose The verbose flags: {@code "true"}, {@code "yes"} or
* {@code "on"} just adds a plain {@code --verbose} option,
* {@code "false"}, {@code "no"} or
* {@code "off"} doesn't add any option,
* otherwise the value is
* treated as a list of flags and is passed verbatim via {@code --verbose=...}.
*/
protected static void appendVerboseOption(Commandline cmd, String verbose) {
if (verbose != null
&& !"false".equals(verbose)
&& !"no".equals(verbose)
&& !"off".equals(verbose)) {
if ("true".equals(verbose)
|| "yes".equals(verbose)
|| "on".equals(verbose)) {
// backward compat with verbose previously being a Boolean
appendOption(cmd, "--verbose");
} else {
appendOption(cmd, "--verbose="+verbose);
}
}
}
/**
* Adds a long option argument to the given command line iff the given
* value is not null.
* @param cmd The command line
* @param longOption The long option name (including the {@code --}, but
* excluding the {@code =}).
* @param value The option value
*/
protected static void appendOptionArgument(Commandline cmd, String longOption, String value) {
if (value != null){
cmd.createArgument().setValue(longOption);
cmd.createArgument().setValue(value);
}
}
/**
* Adds a long option (with no argument) to the given command line.
* @param cmd The command line
* @param longOption The long option name (including the {@code --}
*/
protected static void appendOption(Commandline cmd, String longOption) {
cmd.createArgument().setValue(longOption);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy