blob: 8a6db9d0b7caa4b8146b422976bf3c6afdcc20f0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.easyant.core;
import org.apache.commons.cli.*;
import org.apache.easyant.core.ant.ProjectUtils;
import org.apache.easyant.core.configuration.EasyAntConfiguration;
import org.apache.easyant.core.configuration.EasyantConfigurationFactory;
import org.apache.easyant.man.*;
import org.apache.tools.ant.*;
import org.apache.tools.ant.launch.AntMain;
import org.apache.tools.ant.util.FileUtils;
import java.io.*;
import java.util.*;
/**
* Command line entry point into EasyAnt. This class is entered via the canonical `public static void main` entry point
* and reads the command line arguments. It then assembles and executes an Ant project.
* <p>
* If you integrating EasyAnt into some other tool, this is not the class to use as an entry point. Instead you should
* have a look at {@link EasyAntEngine}.
* </p>
*/
public class EasyAntMain implements AntMain {
/**
* A Set of args are are handled by the launcher and should not be seen by Main.
*/
private static final Set<String> LAUNCH_COMMANDS = new HashSet<String>();
private boolean isLogFileUsed;
static {
LAUNCH_COMMANDS.add("-lib");
LAUNCH_COMMANDS.add("-cp");
LAUNCH_COMMANDS.add("-noclasspath");
LAUNCH_COMMANDS.add("--noclasspath");
LAUNCH_COMMANDS.add("-nouserlib");
LAUNCH_COMMANDS.add("-main");
}
private EasyAntConfiguration easyAntConfiguration;
private boolean projectHelp;
private Options options = new Options();
/**
* Whether or not this instance has successfully been constructed and is ready to run.
*/
private boolean readyToRun;
private List<String> propertyFiles = new ArrayList<String>(1);
/**
* Prints the message of the Throwable if it (the message) is not <code>null</code>.
*
* @param t Throwable to print the message of. Must not be <code>null</code>.
*/
private static void printMessage(Throwable t) {
String message = t.getMessage();
if (message != null) {
System.err.println(message);
}
}
/**
* Creates a new instance of this class using the arguments specified, gives it any extra user properties which have
* been specified, and then runs the build using the classloader provided.
*
* @param args Command line arguments. Must not be <code>null</code>.
* @param additionalUserProperties Any extra properties to use in this build. May be <code>null</code>, which is the equivalent to
* passing in an empty set of properties.
* @param coreLoader Classloader used for core classes. May be <code>null</code> in which case the system classloader is
* used.
*/
public static void start(String[] args, Properties additionalUserProperties, ClassLoader coreLoader) {
EasyAntMain m = new EasyAntMain();
m.startAnt(args, additionalUserProperties, coreLoader);
}
/**
* Start Ant
*
* @param args command line args
* @param additionalUserProperties properties to set beyond those that may be specified on the args list
* @param coreLoader - not used
* @since Ant 1.6
*/
public void startAnt(String[] args, Properties additionalUserProperties, ClassLoader coreLoader) {
easyAntConfiguration.setCoreLoader(coreLoader);
configureOptions();
CommandLineParser parser = new GnuParser();
CommandLine line;
try {
line = parser.parse(options, args);
processArgs(line);
} catch (ParseException exc) {
if (easyAntConfiguration.getMsgOutputLevel() >= Project.MSG_VERBOSE) {
exc.printStackTrace();
}
handleLogfile();
printMessage(exc);
exit(1);
return;
}
if (additionalUserProperties != null) {
Enumeration<?> properties = additionalUserProperties.propertyNames();
while (properties.hasMoreElements()) {
String key = (String) properties.nextElement();
String property = additionalUserProperties.getProperty(key);
easyAntConfiguration.getDefinedProps().put(key, property);
}
}
// expect the worst
int exitCode = 1;
try {
try {
runBuild(line);
exitCode = 0;
} catch (ExitStatusException ese) {
exitCode = ese.getStatus();
if (exitCode != 0) {
throw ese;
}
}
} catch (BuildException be) {
// do nothing they have been already logged by our logger
} catch (Throwable exc) {
exc.printStackTrace();
printMessage(exc);
} finally {
handleLogfile();
}
exit(exitCode);
}
/**
* This operation is expected to call {@link System#exit(int)}, which is what the base version does. However, it is
* possible to do something else.
*
* @param exitCode code to exit with
*/
protected void exit(int exitCode) {
System.exit(exitCode);
}
/**
* Close logfiles, if we have been writing to them.
*
* @since Ant 1.6
*/
private void handleLogfile() {
if (isLogFileUsed && easyAntConfiguration != null) {
FileUtils.close(easyAntConfiguration.getOut());
FileUtils.close(easyAntConfiguration.getErr());
}
}
/**
* Command line entry point. This method kicks off the building of a project object and executes a build using
* either a given target or the default target.
*
* @param args Command line arguments. Must not be <code>null</code>.
*/
public static void main(String[] args) {
start(args, null, null);
}
/**
* Constructor used when creating Main for later arg processing and startup
*/
public EasyAntMain() {
easyAntConfiguration = EasyantConfigurationFactory.getInstance().createDefaultConfiguration();
}
/**
* Process command line arguments. When ant is started from Launcher, launcher-only arguments do not get passed
* through to this routine.
*
* @since Ant 1.6
*/
private void processArgs(CommandLine line) {
String searchForThis;
PrintStream logTo = null;
if (line.hasOption("help")) {
printUsage();
return;
}
if (easyAntConfiguration.getMsgOutputLevel() >= Project.MSG_VERBOSE || line.hasOption("version")) {
printVersion();
if (line.hasOption("version")) {
return;
}
}
if (line.hasOption("showMemoryDetails")) {
easyAntConfiguration.setShowMemoryDetails(true);
}
if (line.hasOption("diagnostics")) {
Diagnostics.doReport(System.out, easyAntConfiguration.getMsgOutputLevel());
return;
}
if (line.hasOption("quiet")) {
easyAntConfiguration.setMsgOutputLevel(Project.MSG_WARN);
}
if (line.hasOption("verbose")) {
easyAntConfiguration.setMsgOutputLevel(Project.MSG_VERBOSE);
}
if (line.hasOption("debug")) {
easyAntConfiguration.setMsgOutputLevel(Project.MSG_DEBUG);
}
if (line.hasOption("noinput")) {
easyAntConfiguration.setAllowInput(false);
}
if (line.hasOption("logfile")) {
try {
File logFile = new File(line.getOptionValue("logfile"));
logTo = new PrintStream(new FileOutputStream(logFile));
isLogFileUsed = true;
} catch (IOException ioe) {
String msg = "Cannot write on the specified log file. "
+ "Make sure the path exists and you have write " + "permissions.";
throw new BuildException(msg);
} catch (ArrayIndexOutOfBoundsException aioobe) {
String msg = "You must specify a log file when " + "using the -log argument";
throw new BuildException(msg);
}
}
if (line.hasOption("buildmodule")) {
File buildModule = new File(line.getOptionValue("buildmodule").replace('/', File.separatorChar));
easyAntConfiguration.setBuildModule(buildModule);
}
if (line.hasOption("buildfile")) {
File buildFile = new File(line.getOptionValue("buildfile").replace('/', File.separatorChar));
easyAntConfiguration.setBuildFile(buildFile);
}
if (line.hasOption("buildconf")) {
easyAntConfiguration.getActiveBuildConfigurations().add(line.getOptionValue("buildconf"));
}
File easyantConfFile = null;
if (line.hasOption("configfile")) {
easyantConfFile = new File(line.getOptionValue("configfile").replace('/', File.separatorChar));
} else {
// if no command line switch is specified check the default location
File easyantHome = new File(System.getProperty(EasyAntMagicNames.EASYANT_HOME).replace('/',
File.separatorChar));
File defaultGlobalEasyantConfFile = new File(easyantHome, EasyAntConstants.DEFAULT_GLOBAL_EASYANT_CONF_FILE);
if (defaultGlobalEasyantConfFile.exists()) {
easyantConfFile = defaultGlobalEasyantConfFile;
}
}
if (easyantConfFile != null) {
try {
easyAntConfiguration = EasyantConfigurationFactory.getInstance().createConfigurationFromFile(
easyAntConfiguration, easyantConfFile.toURI().toURL());
} catch (Exception e) {
throw new BuildException(e);
}
}
if (line.hasOption("listener")) {
easyAntConfiguration.getListeners().add(line.getOptionValue("listener"));
}
if (line.hasOption("D")) {
easyAntConfiguration.getDefinedProps().putAll(line.getOptionProperties("D"));
}
if (line.hasOption("logger")) {
if (easyAntConfiguration.getLoggerClassname() != null) {
throw new BuildException("Only one logger class may be specified.");
}
easyAntConfiguration.setLoggerClassname(line.getOptionValue("logger"));
}
if (line.hasOption("inputhandler")) {
if (easyAntConfiguration.getInputHandlerClassname() != null) {
throw new BuildException("Only one input handler class may " + "be specified.");
}
easyAntConfiguration.setInputHandlerClassname(line.getOptionValue("inputhandler"));
}
if (line.hasOption("emacs")) {
easyAntConfiguration.setEmacsMode(true);
}
if (line.hasOption("projecthelp")) {
// set the flag to display the targets and quit
projectHelp = true;
}
if (line.hasOption("find")) {
// eat up next arg if present, default to module.ivy
if (line.getOptionValues("find").length > 0) {
searchForThis = line.getOptionValue("find");
} else {
searchForThis = EasyAntConstants.DEFAULT_BUILD_MODULE;
}
easyAntConfiguration.setBuildModule(new File(searchForThis));
easyAntConfiguration.setBuildModuleLookupEnabled(true);
}
if (line.hasOption("propertyfile")) {
propertyFiles.add(line.getOptionValue("propertyfile"));
}
if (line.hasOption("keep-going")) {
easyAntConfiguration.setKeepGoingMode(true);
}
if (line.hasOption("offline")) {
easyAntConfiguration.setOffline(true);
}
if (line.hasOption("nice")) {
easyAntConfiguration.setThreadPriority(Integer.decode(line.getOptionValue("nice")));
if (easyAntConfiguration.getThreadPriority() < Thread.MIN_PRIORITY
|| easyAntConfiguration.getThreadPriority() > Thread.MAX_PRIORITY) {
throw new BuildException("Niceness value is out of the range 1-10");
}
}
if (line.hasOption("autoproxy")) {
easyAntConfiguration.setProxy(true);
}
if (!line.getArgList().isEmpty()) {
for (Object o : line.getArgList()) {
String target = (String) o;
easyAntConfiguration.getTargets().add(target);
}
}
// Load the property files specified by -propertyfile
loadPropertyFiles();
if (logTo != null) {
easyAntConfiguration.setOut(logTo);
easyAntConfiguration.setErr(logTo);
System.setOut(easyAntConfiguration.getOut());
System.setErr(easyAntConfiguration.getErr());
}
readyToRun = true;
}
// --------------------------------------------------------
// other methods
// --------------------------------------------------------
/**
* Load the property files specified by -propertyfile
*/
private void loadPropertyFiles() {
for (String filename : propertyFiles) {
Properties props = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(filename);
props.load(fis);
} catch (IOException e) {
System.out.println("Could not load property file " + filename + ": " + e.getMessage());
} finally {
FileUtils.close(fis);
}
// ensure that -D properties take precedence
Enumeration<?> properties = props.propertyNames();
while (properties.hasMoreElements()) {
String name = (String) properties.nextElement();
if (easyAntConfiguration.getDefinedProps().getProperty(name) == null) {
easyAntConfiguration.getDefinedProps().put(name, props.getProperty(name));
}
}
}
}
/**
* Executes the build. If the constructor for this instance failed (e.g. returned after issuing a warning), this
* method returns immediately.
*
* @throws BuildException if the build fails
*/
private void runBuild(CommandLine line) throws BuildException {
if (!readyToRun) {
return;
}
if (projectHelp) {
displayProjectHelp();
} else {
EasyAntEngine eaEngine = new EasyAntEngine(easyAntConfiguration);
Project project = new Project();
eaEngine.configureEasyAnt(project);
eaEngine.loadProject(project);
// handle other easyant option (-listTargets,-describe,etc..)
for (int i = 0; i < line.getOptions().length; i++) {
if (line.getOptions()[i] instanceof EasyantOption) {
EasyantOption eaoption = (EasyantOption) line.getOptions()[i];
eaoption.setProject(project);
eaoption.execute();
if (eaoption.isStopBuild()) {
return;
}
}
}
eaEngine.doBuild(project);
}
}
/**
* Prints the description of a project (if there is one) to <code>System.out</code>.
*
* @param project The project to display a description of. Must not be <code>null</code>.
*/
protected void printDescription(Project project) {
if (project.getDescription() != null) {
project.log(project.getDescription());
}
}
/**
* Searches for the correct place to insert a name into a list so as to keep the list sorted alphabetically.
*
* @param names The current list of names. Must not be <code>null</code>.
* @param name The name to find a place for. Must not be <code>null</code>.
* @return the correct place in the list for the given name
*/
private static int findTargetPosition(List<String> names, String name) {
int res = names.size();
for (int i = 0; i < names.size() && res == names.size(); i++) {
if (name.compareTo(names.get(i)) < 0) {
res = i;
}
}
return res;
}
/**
* Writes a formatted list of target names to <code>System.out</code> with an optional description.
*
* @param project the project instance.
* @param names The names to be printed. Must not be <code>null</code>.
* @param descriptions The associated target descriptions. May be <code>null</code>, in which case no descriptions are
* displayed. If non- <code>null</code>, this should have as many elements as <code>names</code>.
* @param heading The heading to display. Should not be <code>null</code>.
* @param maxlen The maximum length of the names of the targets. If descriptions are given, they are padded to this
* position so they line up (so long as the names really <i>are</i> shorter than this).
*/
private static void printTargets(Project project, List<String> names, List<String> descriptions, String heading,
int maxlen) {
if (!names.isEmpty()) {
// now, start printing the targets and their descriptions
String lSep = System.getProperty("line.separator");
String spaces = String.format("%" + maxlen + "s", ' ');
StringBuilder msg = new StringBuilder();
msg.append(lSep).append(heading).append(lSep).append(lSep);
for (int i = 0; i < names.size(); i++) {
msg.append(" ");
msg.append(names.get(i));
if (descriptions != null) {
msg.append(spaces.substring(0, maxlen - (names.get(i)).length() + 2));
msg.append(descriptions.get(i));
}
msg.append(lSep);
}
project.log(msg.toString(), Project.MSG_WARN);
}
}
/**
* Prints a list of all targets in the specified project to <code>System.out</code>, optionally including
* subtargets.
*
* @param project The project to display a description of. Must not be <code>null</code>.
* @param printSubTargets Whether or not subtarget names should also be printed.
*/
protected static void printTargets(Project project, boolean printSubTargets) {
// find the target with the longest name
int maxLength = 0;
Map<String, Target> ptargets = ProjectUtils.removeDuplicateTargets(project.getTargets());
String targetName;
String targetDescription;
// split the targets in top-level and sub-targets depending
// on the presence of a description
List<String> topNames = new ArrayList<String>();
List<String> topDescriptions = new ArrayList<String>();
List<String> subNames = new ArrayList<String>();
List<String> highLevelTargets = new ArrayList<String>();
List<String> highLevelTargetsDescriptions = new ArrayList<String>();
for (Target currentTarget : ptargets.values()) {
targetName = currentTarget.getName();
if (targetName.equals("")) {
continue;
}
targetDescription = currentTarget.getDescription();
// maintain a sorted list of targets
if (currentTarget instanceof ExtensionPoint && !currentTarget.getName().contains(":")) {
int pos = findTargetPosition(highLevelTargets, targetName);
highLevelTargets.add(pos, targetName);
highLevelTargetsDescriptions.add(pos, targetDescription);
} else if (targetDescription != null) {
int pos = findTargetPosition(topNames, targetName);
topNames.add(pos, targetName);
topDescriptions.add(pos, targetDescription);
} else {
int pos = findTargetPosition(subNames, targetName);
subNames.add(pos, targetName);
}
if (targetName.length() > maxLength) {
maxLength = targetName.length();
}
}
printTargets(project, highLevelTargets, highLevelTargetsDescriptions, "High level targets:", maxLength);
printTargets(project, topNames, topDescriptions, "Main targets:", maxLength);
// if there were no main targets, we list all subtargets
// as it means nothing has a description
if (topNames.isEmpty()) {
printSubTargets = true;
}
if (printSubTargets) {
printTargets(project, subNames, null, "Other targets:", 0);
} else {
project.log("Run easyant with '-v' or '--verbose' option to have the whole list of available targets / extension points");
}
String defaultTarget = project.getDefaultTarget();
if (defaultTarget != null && !"".equals(defaultTarget)) {
// shouldn't need to check but...
project.log("Default target: " + defaultTarget);
}
}
private void displayProjectHelp() {
final Project project = new Project();
Throwable error = null;
try {
EasyAntEngine.configureAndLoadProject(project, easyAntConfiguration);
printDescription(project);
printTargets(project, easyAntConfiguration.getMsgOutputLevel() > Project.MSG_INFO);
} catch (RuntimeException exc) {
error = exc;
throw exc;
} finally {
if (error != null) {
project.log(error.toString(), Project.MSG_ERR);
}
}
}
/**
* Configure command line options
*/
@SuppressWarnings("static-access")
public void configureOptions() {
options.addOption("h", "help", false, "print this message");
options.addOption("p", "projecthelp", false, "print project help information");
options.addOption("version", false, "print the version information and exit");
options.addOption("diagnostics", false,
"print information that might be helpful to diagnose or report problems");
options.addOption("showMemoryDetails", false, "print memory details (used/free/total)");
options.addOption("q", "quiet", false, "be extra quiet");
options.addOption("v", "verbose", false, "be extra verbose");
options.addOption("d", "debug", false, "print debugging information");
options.addOption("e", "emacs", false, "produce logging information without adornments");
Option lib = OptionBuilder.withArgName("path").hasArg()
.withDescription("specifies a path to search for jars and classes").create("lib");
options.addOption(lib);
Option logfile = OptionBuilder.withArgName("file").hasArg().withDescription("use given file for log")
.create("logfile");
options.addOption(logfile);
Option logger = OptionBuilder.withArgName("classname").hasArg()
.withDescription("the class which it to perform " + "logging").create("logger");
options.addOption(logger);
Option listener = OptionBuilder.withArgName("classname").hasArg()
.withDescription("add an instance of class as " + "a project listener").create("listener");
options.addOption(listener);
Option buildfile = OptionBuilder.withArgName("file").hasArg().withDescription("use given buildfile")
.create("buildfile");
options.addOption(buildfile);
Option find = OptionBuilder.withArgName("file").hasOptionalArg()
.withDescription("search for buildfile towards the " + "root of the filesystem and use it")
.withLongOpt("find").create("s");
options.addOption(find);
options.addOption("noinput", false, "do not allow interactive input");
Option buildmodule = OptionBuilder.withArgName("file").hasArg().withDescription("use given buildmodule")
.withLongOpt("buildmodule").create("f");
options.addOption(buildmodule);
Option buildconf = OptionBuilder.withArgName("confs").hasArg()
.withDescription("specify build configurations (profiles)").withLongOpt("buildconf").create("C");
options.addOption(buildconf);
Option configFile = OptionBuilder.withArgName("file").hasArg()
.withDescription("use given easyant configuration").create("configfile");
options.addOption(configFile);
Option property = OptionBuilder.withArgName("property=value").hasArgs(2).withValueSeparator()
.withDescription("use value for given property").create("D");
options.addOption(property);
options.addOption("k", "keep-going", false, "execute all targets that do not depend on failed target(s)");
Option propertiesfile = OptionBuilder.withArgName("file").hasArg()
.withDescription("load all properties from file with -D properties taking precedence")
.create("propertyfile");
options.addOption(propertiesfile);
Option inputhandler = OptionBuilder.withArgName("classname").hasArg()
.withDescription("the class which will handle input requests").create("inputhandler");
options.addOption(inputhandler);
Option nice = OptionBuilder.withArgName("number").hasArg()
.withDescription("A niceness value for the main thread: 1 (lowest) to 10 (highest); 5 is the default")
.create("nice");
options.addOption(nice);
options.addOption("nouserlib", false, "Run ant without using the jar files from ${user.home}/.ant/lib");
options.addOption("noclasspath", false, "Run ant without using CLASSPATH");
options.addOption("autoproxy", false, "Java1.5+: use the OS proxy settings");
Option main = OptionBuilder.withArgName("classname").hasArg()
.withDescription("override EasyAnt's normal entry point").create("main");
options.addOption(main);
options.addOption("o", "offline", false, "turns EasyAnt in offline mode");
options.addOption(new Describe());
options.addOption(new ListExtensionPoints());
options.addOption(new ListTargets());
options.addOption(new ListProps());
options.addOption(new ListParameters());
options.addOption(new ListPlugins());
}
/**
* Prints the usage information for this class to <code>System.out</code>.
*/
private void printUsage() {
HelpFormatter help = new HelpFormatter();
help.printHelp("easyant [options] [target [target2 [target3] ...]]", options);
}
/**
* Prints the EasyAnt version information to <code>System.out</code>.
*
* @throws BuildException if the version information is unavailable
*/
private static void printVersion() throws BuildException {
System.out.println(EasyAntEngine.getEasyAntVersion());
System.out.println(getAntVersion());
}
/**
* Cache of the Ant version information when it has been loaded.
*/
private static String antVersion = null;
/**
* Returns the Ant version information, if available. Once the information has been loaded once, it's cached and
* returned from the cache on future calls.
*
* @return the Ant version information as a String (always non- <code>null</code>)
* @throws BuildException if the version information is unavailable
*/
public static synchronized String getAntVersion() throws BuildException {
if (antVersion == null) {
InputStream in = null;
try {
Properties props = new Properties();
in = Main.class.getResourceAsStream("/org/apache/tools/ant/version.txt");
if (in == null) {
throw new BuildException("Could not load the version information.");
}
props.load(in);
antVersion = "Apache Ant version " + props.getProperty("VERSION") + " compiled on " + props.getProperty("DATE");
} catch (IOException ioe) {
throw new BuildException("Could not load the version information", ioe);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// do nothing
}
}
}
}
return antVersion;
}
}