/* * 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:", maxLength); } 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; } }