/* * 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 * * https://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.tools.ant; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.Vector; import java.util.stream.Collectors; import org.apache.tools.ant.input.DefaultInputHandler; import org.apache.tools.ant.input.InputHandler; import org.apache.tools.ant.launch.AntMain; import org.apache.tools.ant.listener.SilentLogger; import org.apache.tools.ant.property.GetProperty; import org.apache.tools.ant.property.ResolvePropertyMap; import org.apache.tools.ant.util.ClasspathUtils; import org.apache.tools.ant.util.FileUtils; import org.apache.tools.ant.util.ProxySetup; import org.apache.tools.ant.util.StreamUtils; /** * Command line entry point into Ant. 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. *
* If you integrating Ant into some other tool, this is not the class
* to use as an entry point. Please see the source code of this
* class to see how it manipulates the Ant project classes.
*
*/
public class Main implements AntMain {
/**
* A Set of args that are handled by the launcher and should
* not be seen by Main.
*/
private static final Set
* Added to simulate File.getParentFile() from JDK 1.2.
* @deprecated since 1.6.x
*
* @param file File to find parent of. Must not be
* Takes the given target as a suffix to append to each
* parent directory in search of a build file. Once the
* root of the file-system has been reached null.
*
* @param t Throwable to print the message of.
* Must not be null.
*/
private static void printMessage(final Throwable t) {
final 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 null.
* @param additionalUserProperties Any extra properties to use in this
* build. May be null, which is the equivalent to
* passing in an empty set of properties.
* @param coreLoader Classloader used for core classes. May be
* null in which case the system classloader is used.
*/
public static void start(final String[] args, final Properties additionalUserProperties,
final ClassLoader coreLoader) {
final Main m = new Main();
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(final String[] args, final Properties additionalUserProperties,
final ClassLoader coreLoader) {
try {
processArgs(args);
} catch (final Throwable exc) {
handleLogfile();
printMessage(exc);
exit(1);
return;
}
if (additionalUserProperties != null) {
additionalUserProperties.stringPropertyNames()
.forEach(key -> definedProps.put(key, additionalUserProperties.getProperty(key)));
}
// expect the worst
int exitCode = 1;
try {
try {
runBuild(coreLoader);
exitCode = 0;
} catch (final ExitStatusException ese) {
exitCode = ese.getStatus();
if (exitCode != 0) {
throw ese;
}
}
} catch (final BuildException be) {
if (err != System.err) {
printMessage(be);
}
} catch (final Throwable exc) {
exc.printStackTrace(); //NOSONAR
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(final int exitCode) {
System.exit(exitCode);
}
/**
* Close logfiles, if we have been writing to them.
*
* @since Ant 1.6
*/
private void handleLogfile() {
if (isLogFileUsed) {
FileUtils.close(out);
FileUtils.close(err);
}
}
/**
* 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 null.
*/
public static void main(final String[] args) {
start(args, null, null);
}
/**
* Constructor used when creating Main for later arg processing
* and startup
*/
public Main() {
}
/**
* Sole constructor, which parses and deals with command line
* arguments.
*
* @param args Command line arguments. Must not be null.
*
* @exception BuildException if the specified build file doesn't exist
* or is a directory.
*
* @deprecated since 1.6.x
*/
@Deprecated
protected Main(final String[] args) throws BuildException {
processArgs(args);
}
/**
* Process command line arguments.
* When ant is started from Launcher, launcher-only arguments do not get
* passed through to this routine.
*
* @param args the command line arguments.
*
* @since Ant 1.6
*/
private void processArgs(final String[] args) {
String searchForThis = null;
boolean searchForFile = false;
PrintStream logTo = null;
// cycle through given args
boolean justPrintUsage = false;
boolean justPrintVersion = false;
boolean justPrintDiagnostics = false;
final ArgumentProcessorRegistry processorRegistry = ArgumentProcessorRegistry.getInstance();
for (int i = 0; i < args.length; i++) {
final String arg = args[i];
if (arg.equals("-help") || arg.equals("-h")) {
justPrintUsage = true;
} else if (arg.equals("-version")) {
justPrintVersion = true;
} else if (arg.equals("-diagnostics")) {
justPrintDiagnostics = true;
} else if (arg.equals("-quiet") || arg.equals("-q")) {
msgOutputLevel = Project.MSG_WARN;
} else if (arg.equals("-verbose") || arg.equals("-v")) {
msgOutputLevel = Project.MSG_VERBOSE;
} else if (arg.equals("-debug") || arg.equals("-d")) {
msgOutputLevel = Project.MSG_DEBUG;
} else if (arg.equals("-silent") || arg.equals("-S")) {
silent = true;
} else if (arg.equals("-noinput")) {
allowInput = false;
} else if (arg.equals("-logfile") || arg.equals("-l")) {
try {
final File logFile = new File(args[i + 1]);
i++;
// life-cycle of OutputStream is controlled by
// logTo which becomes "out" and is closed in
// handleLogfile
logTo = new PrintStream(Files.newOutputStream(logFile.toPath())); //NOSONAR
isLogFileUsed = true;
} catch (final IOException ioe) {
final String msg = "Cannot write on the specified log file. "
+ "Make sure the path exists and you have write "
+ "permissions.";
throw new BuildException(msg);
} catch (final ArrayIndexOutOfBoundsException aioobe) {
final String msg = "You must specify a log file when "
+ "using the -log argument";
throw new BuildException(msg);
}
} else if (arg.equals("-buildfile") || arg.equals("-file")
|| arg.equals("-f")) {
i = handleArgBuildFile(args, i);
} else if (arg.equals("-listener")) {
i = handleArgListener(args, i);
} else if (arg.startsWith("-D")) {
i = handleArgDefine(args, i);
} else if (arg.equals("-logger")) {
i = handleArgLogger(args, i);
} else if (arg.equals("-inputhandler")) {
i = handleArgInputHandler(args, i);
} else if (arg.equals("-emacs") || arg.equals("-e")) {
emacsMode = true;
} else if (arg.equals("-projecthelp") || arg.equals("-p")) {
// set the flag to display the targets and quit
projectHelp = true;
} else if (arg.equals("-find") || arg.equals("-s")) {
searchForFile = true;
// eat up next arg if present, default to build.xml
if (i < args.length - 1) {
searchForThis = args[++i];
}
} else if (arg.startsWith("-propertyfile")) {
i = handleArgPropertyFile(args, i);
} else if (arg.equals("-k") || arg.equals("-keep-going")) {
keepGoingMode = true;
} else if (arg.equals("-nice")) {
i = handleArgNice(args, i);
} else if (LAUNCH_COMMANDS.contains(arg)) {
//catch script/ant mismatch with a meaningful message
//we could ignore it, but there are likely to be other
//version problems, so we stamp down on the configuration now
final String msg = "Ant's Main method is being handed "
+ "an option " + arg + " that is only for the launcher class."
+ "\nThis can be caused by a version mismatch between "
+ "the ant script/.bat file and Ant itself.";
throw new BuildException(msg);
} else if (arg.equals("-autoproxy")) {
proxy = true;
} else if (arg.startsWith("-")) {
boolean processed = false;
for (final ArgumentProcessor processor : processorRegistry.getProcessors()) {
final int newI = processor.readArguments(args, i);
if (newI != -1) {
Listnull.
* @return Parent file or null if none
*/
@Deprecated
private File getParentFile(final File file) {
final File parent = file.getParentFile();
if (parent != null && msgOutputLevel >= Project.MSG_VERBOSE) {
System.out.println("Searching in " + parent.getAbsolutePath());
}
return parent;
}
/**
* Search parent directories for the build file.
* null
* is returned.
*
* @param start Leaf directory of search.
* Must not be null.
* @param suffix Suffix filename to look for in parents.
* Must not be null.
*
* @return A handle to the build file if one is found, null if not
*/
private File findBuildFile(final String start, final String suffix) {
if (msgOutputLevel >= Project.MSG_INFO) {
System.out.println("Searching for " + suffix + " ...");
}
File parent = new File(new File(start).getAbsolutePath());
File file = new File(parent, suffix);
// check if the target file exists in the current directory
while (!file.exists()) {
// change to parent directory
parent = getParentFile(parent);
// if parent is null, then we are at the root of the fs,
// complain that we can't find the build file.
if (parent == null) {
return null;
}
// refresh our file handle
file = new File(parent, suffix);
}
return file;
}
/**
* Executes the build. If the constructor for this instance failed
* (e.g. returned after issuing a warning), this method returns
* immediately.
*
* @param coreLoader The classloader to use to find core classes.
* May be null, in which case the
* system classloader is used.
*
* @exception BuildException if the build fails
*/
private void runBuild(final ClassLoader coreLoader) throws BuildException {
if (!readyToRun) {
return;
}
final ArgumentProcessorRegistry processorRegistry = ArgumentProcessorRegistry.getInstance();
for (final ArgumentProcessor processor : processorRegistry.getProcessors()) {
final Listnull.
*/
protected void addBuildListeners(final Project project) {
// Add the default listener
project.addBuildListener(createLogger());
final int count = listeners.size();
for (int i = 0; i < count; i++) {
final String className = listeners.elementAt(i);
final BuildListener listener =
ClasspathUtils.newInstance(className,
Main.class.getClassLoader(), BuildListener.class);
project.setProjectReference(listener);
project.addBuildListener(listener);
}
}
/**
* Creates the InputHandler and adds it to the project.
*
* @param project the project instance.
*
* @exception BuildException if a specified InputHandler
* implementation could not be loaded.
*/
private void addInputHandler(final Project project) throws BuildException {
InputHandler handler = null;
if (inputHandlerClassname == null) {
handler = new DefaultInputHandler();
} else {
handler = ClasspathUtils.newInstance(
inputHandlerClassname, Main.class.getClassLoader(),
InputHandler.class);
project.setProjectReference(handler);
}
project.setInputHandler(handler);
}
/**
* Creates the default build logger for sending build events to the ant
* log.
*
* @return the logger instance for this build.
*/
private BuildLogger createLogger() {
BuildLogger logger = null;
if (silent) {
logger = new SilentLogger();
msgOutputLevel = Project.MSG_WARN;
emacsMode = true;
} else if (loggerClassname != null) {
try {
logger = ClasspathUtils.newInstance(
loggerClassname, Main.class.getClassLoader(),
BuildLogger.class);
} catch (final BuildException e) {
System.err.println("The specified logger class "
+ loggerClassname
+ " could not be used because " + e.getMessage());
throw e;
}
} else {
logger = new DefaultLogger();
}
logger.setMessageOutputLevel(msgOutputLevel);
logger.setOutputPrintStream(out);
logger.setErrorPrintStream(err);
logger.setEmacsMode(emacsMode);
return logger;
}
/**
* Prints the usage information for this class to System.out.
*/
private static void printUsage() {
System.out.println("ant [options] [target [target2 [target3] ...]]");
System.out.println("Options: ");
System.out.println(" -help, -h print this message and exit");
System.out.println(" -projecthelp, -p print project help information and exit");
System.out.println(" -version print the version information and exit");
System.out.println(" -diagnostics print information that might be helpful to");
System.out.println(" diagnose or report problems and exit");
System.out.println(" -quiet, -q be extra quiet");
System.out.println(" -silent, -S print nothing but task outputs and build failures");
System.out.println(" -verbose, -v be extra verbose");
System.out.println(" -debug, -d print debugging information");
System.out.println(" -emacs, -e produce logging information without adornments");
System.out.println(" -lib System.out.
*
* @exception BuildException if the version information is unavailable
*/
private static void printVersion(final int logLevel) throws BuildException {
System.out.println(getAntVersion());
}
/**
* Cache of the Ant version information when it has been loaded.
*/
private static String antVersion = null;
/**
* Cache of the short Ant version information when it has been loaded.
*/
private static String shortAntVersion = 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-null)
*
* @exception BuildException if the version information is unavailable
*/
public static synchronized String getAntVersion() throws BuildException {
if (antVersion == null) {
try {
final Properties props = new Properties();
final InputStream in =
Main.class.getResourceAsStream("/org/apache/tools/ant/version.txt");
props.load(in);
in.close();
shortAntVersion = props.getProperty("VERSION");
antVersion = "Apache Ant(TM) version " +
shortAntVersion +
" compiled on " +
props.getProperty("DATE");
} catch (final IOException ioe) {
throw new BuildException("Could not load the version information:"
+ ioe.getMessage());
} catch (final NullPointerException npe) {
throw new BuildException("Could not load the version information.");
}
}
return antVersion;
}
/**
* Returns the short 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 short Ant version information as a String
* (always non-null)
*
* @throws BuildException BuildException if the version information is unavailable
* @since Ant 1.9.3
*/
public static String getShortAntVersion() throws BuildException {
if (shortAntVersion == null) {
getAntVersion();
}
return shortAntVersion;
}
/**
* Prints the description of a project (if there is one) to
* System.out.
*
* @param project The project to display a description of.
* Must not be null.
*/
private static void printDescription(final Project project) {
if (project.getDescription() != null) {
project.log(project.getDescription());
}
}
/**
* Targets in imported files with a project name
* and not overloaded by the main build file will
* be in the target map twice. This method
* removes the duplicate target.
* @param targets the targets to filter.
* @return the filtered targets.
*/
private static MapSystem.out, optionally including subtargets.
*
* @param project The project to display a description of.
* Must not be null.
* @param printSubTargets Whether or not subtarget names should also be
* printed.
*/
private static void printTargets(final Project project, boolean printSubTargets,
final boolean printDependencies) {
// find the target with the longest name
int maxLength = 0;
final Mapnull.
* @param name The name to find a place for.
* Must not be null.
*
* @return the correct place in the list for the given name
*/
private static int findTargetPosition(final VectorSystem.out
* with an optional description.
*
*
* @param project the project instance.
* @param names The names to be printed.
* Must not be null.
* @param descriptions The associated target descriptions.
* May be null, in which case
* no descriptions are displayed.
* If non-null, this should have
* as many elements as names.
* @param heading The heading to display.
* Should not be null.
* @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
* are shorter than this).
*/
private static void printTargets(final Project project, final Vector