diff --git a/WHATSNEW b/WHATSNEW index 811246f24..791cce684 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -93,6 +93,9 @@ Other changes: * Make extension point bindable to imported prefixed targets Bugzilla Report 53550. + * Add the possibility to register a custom command line argument processor. + See org.apache.tools.ant.ArgumentProcessor and manual/argumentprocessor.html + Changes from Ant 1.8.3 TO Ant 1.8.4 =================================== diff --git a/manual/argumentprocessor.html b/manual/argumentprocessor.html new file mode 100644 index 000000000..22160ccb5 --- /dev/null +++ b/manual/argumentprocessor.html @@ -0,0 +1,76 @@ + + + + + + +The Command Line Processor Plugin: ArgumentProcessor + + + +

The Command Line Processor Plugin: ArgumentProcessor

+ +

What is an ArgumentProcessor?

+ +

+An ArgumentProcessor is a parser of command line argument which is +then call before and after the build file is being parsed. Third party +libraries may then be able to have custom argument line argument which modify +Ant behaviour. +

+ +

+An ArgumentProcessor is called each time Ant parse an unknown +argument, an ArgumentProcessor doesn't take precedence over Ant to +parse already suported options. It is then recommended to thrid party +ArgumentProcessor implementation to chose specific 'enough' +argument name, avoiding for instance one letter arguments. +

+ +

+It is also called at the different phases so different behaviour can be +implemented. It is called just after every arguments are parsed, just +before the project is being configured (the build file being parsed), +and just after. Some of the methods to be implemented return a boolean: +if true is returned, Ant will terminate immediatly, without +error. +

+ +

+Being called during all these phases, an ArgumentProcessor +can just print some specific system properties and quit (like +-diagnose), or print some specific properties of a project after +being parsed and quit (like -projectHelp), or just set some +custom properties on the project and let it run. +

+ +

How to register it's own ArgumentProcessor

+ +

First, the ArgumentProcessor must be an implementation of +org.apache.tools.ant.ArgumentProcessor. +

+ +

Then to decare it: create a file +META-INF/services/org.apache.tools.ant.ArgumentProcessor which +contains only one line the fully qualified name of the class of the +implementation. This file together with the implementation class need then to +be found in Ant's classpath. +

+ + + diff --git a/manual/developlist.html b/manual/developlist.html index 0b90661ac..3235c835a 100644 --- a/manual/developlist.html +++ b/manual/developlist.html @@ -39,6 +39,7 @@
  • InputHandler
  • Using Ant Tasks Outside of Ant
  • The Ant frontend: ProjectHelper
  • +
  • The Command Line Processor Plugin:ArgumentProcessor
  • Tutorials

    diff --git a/manual/running.html b/manual/running.html index e0f0669a6..ed8c7154b 100644 --- a/manual/running.html +++ b/manual/running.html @@ -445,11 +445,18 @@ org.apache.tools.ant.Executor implementation specified here. org.apache.tools.ant.ProjectHelper - classname (optional, default 'org.apache.tools.ant.ProjectHelper') + classname (optional, default 'org.apache.tools.ant.ProjectHelper2') specifies the classname to use as ProjectHelper. The class must extend org.apache.tools.ant.ProjectHelper. + + org.apache.tools.ant.ArgumentProcessor + classname (optional) + specifies the classname to use as ArgumentProcessor. The class must extend + org.apache.tools.ant.ArgumentProcessor. + + p4.port, p4.client, p4.user several formats @@ -477,6 +484,13 @@ org.apache.tools.ant.Executor implementation specified here. ProjectHelper internal repository. + + ant.argument-processor-repo.debug + boolean (default 'false') + Set it to true to enable debugging with Ant's + ArgumentProcessor internal repository. + +

    diff --git a/src/main/org/apache/tools/ant/ArgumentProcessor.java b/src/main/org/apache/tools/ant/ArgumentProcessor.java new file mode 100644 index 000000000..301e5b089 --- /dev/null +++ b/src/main/org/apache/tools/ant/ArgumentProcessor.java @@ -0,0 +1,72 @@ +/* + * 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.tools.ant; + +import java.io.PrintStream; +import java.util.List; + +/** + * Processor of arguments of the command line. + *

    + * Arguments supported by third party code should not conflict with Ant core + * ones. It is then recommended to chose specific 'enough' argument name, + * avoiding for instance one letter arguments. By the way, if there any + * conflict, Ant will take precedence. + * + * @since 1.9 + */ +public interface ArgumentProcessor { + + /** + * Read the arguments from the command line at the specified position + *

    + * If the argument is not supported, returns -1. Else, the position of the + * first argument not supported. + */ + int readArguments(String[] args, int pos); + + /** + * If some arguments matched with {@link #readArguments(String[], int)}, + * this method is called after all arguments were parsed. Returns + * true if Ant should stop there, ie the build file not parsed + * and the project should not be executed. + */ + boolean handleArg(List args); + + /** + * If some arguments matched with {@link #readArguments(String[], int)}, + * this method is called just before the project being configured + */ + void prepareConfigure(Project project, List args); + + /** + * Handle the arguments with {@link #readArguments(String[], int)}, just + * after the project being configured. Returns true if Ant + * should stop there, ie the build file not parsed and the project should + * not be executed. + */ + boolean handleArg(Project project, List arg); + + /** + * Print the usage of the supported arguments + * + * @see org.apache.tools.ant.Main.printUsage() + */ + void printUsage(PrintStream writer); + +} diff --git a/src/main/org/apache/tools/ant/ArgumentProcessorRegistry.java b/src/main/org/apache/tools/ant/ArgumentProcessorRegistry.java new file mode 100644 index 000000000..286433aa7 --- /dev/null +++ b/src/main/org/apache/tools/ant/ArgumentProcessorRegistry.java @@ -0,0 +1,168 @@ +/* + * 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.tools.ant; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import org.apache.tools.ant.util.LoaderUtils; + +/** + * The global registry for {@link ArgumentProcessor}s. + *

    + * An {@link ArgumentProcessor} implementation can be registered via the system + * property org.apache.tools.ant.ArgumentProcessor, or via a JDK1.3 + * 'service', by putting the fully qualified name of the implementation into the + * file META-INF/services/org.apache.tools.ant.ArgumentProcessor + *

    + * Use the system property ant.argument-processor.debug to enable + * the print of debug log. + * + * @since 1.9 + */ +public class ArgumentProcessorRegistry { + + private static final String DEBUG_ARGUMENT_PROCESSOR_REPOSITORY = "ant.argument-processor-repo.debug"; + + // The message log level is not accessible here because everything + // is instanciated statically + private static final boolean DEBUG = "true".equals(System.getProperty(DEBUG_ARGUMENT_PROCESSOR_REPOSITORY)); + + private static final String SERVICE_ID = "META-INF/services/org.apache.tools.ant.ArgumentProcessor"; + + private static ArgumentProcessorRegistry instance = new ArgumentProcessorRegistry(); + + private List processors = new ArrayList(); + + public static ArgumentProcessorRegistry getInstance() { + return instance; + } + + private ArgumentProcessorRegistry() { + collectArgumentProcessors(); + } + + public List getProcessors() { + return processors; + } + + private void collectArgumentProcessors() { + try { + ClassLoader classLoader = LoaderUtils.getContextClassLoader(); + if (classLoader != null) { + Enumeration resources = classLoader.getResources(SERVICE_ID); + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + ArgumentProcessor processor = getProcessorByService(resource.openStream()); + registerArgumentProcessor(processor); + } + } + + InputStream systemResource = ClassLoader.getSystemResourceAsStream(SERVICE_ID); + if (systemResource != null) { + ArgumentProcessor processor = getProcessorByService(systemResource); + registerArgumentProcessor(processor); + } + } catch (Exception e) { + System.err.println("Unable to load ArgumentProcessor from service " + + SERVICE_ID + " (" + e.getClass().getName() + ": " + + e.getMessage() + ")"); + if (DEBUG) { + e.printStackTrace(System.err); + } + } + } + + public void registerArgumentProcessor(String helperClassName) + throws BuildException { + registerArgumentProcessor(getProcessor(helperClassName)); + } + + public void registerArgumentProcessor( + Class< ? extends ArgumentProcessor> helperClass) + throws BuildException { + registerArgumentProcessor(getProcessor(helperClass)); + } + + private ArgumentProcessor getProcessor(String helperClassName) { + try { + @SuppressWarnings("unchecked") + Class< ? extends ArgumentProcessor> cl = (Class< ? extends ArgumentProcessor>) Class.forName(helperClassName); + return getProcessor(cl); + } catch (ClassNotFoundException e) { + throw new BuildException("Argument processor class " + + helperClassName + " was not found", e); + } + } + + private ArgumentProcessor getProcessor( + Class< ? extends ArgumentProcessor> processorClass) { + ArgumentProcessor processor; + try { + processor = processorClass.getConstructor().newInstance(); + } catch (Exception e) { + throw new BuildException("The argument processor class" + + processorClass.getClass().getName() + + " could not be instanciated with a default constructor", + e); + } + return processor; + } + + public void registerArgumentProcessor(ArgumentProcessor processor) { + if (processor == null) { + return; + } + processors.add(processor); + if (DEBUG) { + System.out.println("Argument processor " + + processor.getClass().getName() + " registered."); + } + } + + private ArgumentProcessor getProcessorByService(InputStream is) + throws IOException { + InputStreamReader isr = null; + try { + try { + isr = new InputStreamReader(is, "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + isr = new InputStreamReader(is); + } + BufferedReader rd = new BufferedReader(isr); + String processorClassName = rd.readLine(); + if (processorClassName != null && !"".equals(processorClassName)) { + return getProcessor(processorClassName); + } + } finally { + try { + isr.close(); + } catch (IOException e) { + // ignore + } + } + return null; + } + +} diff --git a/src/main/org/apache/tools/ant/Main.java b/src/main/org/apache/tools/ant/Main.java index 2a6a9fe5c..320bfd82a 100644 --- a/src/main/org/apache/tools/ant/Main.java +++ b/src/main/org/apache/tools/ant/Main.java @@ -24,12 +24,15 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; +import java.io.PrintWriter; +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.Map.Entry; import java.util.Properties; @@ -152,6 +155,7 @@ public class Main implements AntMain { */ private boolean proxy = false; + private Map, List> extraArguments = new HashMap, List>(); private static final GetProperty NOPROPERTIES = new GetProperty(){ public Object getProperty(String aName) { @@ -324,6 +328,8 @@ public class Main implements AntMain { boolean justPrintVersion = false; boolean justPrintDiagnostics = false; + ArgumentProcessorRegistry processorRegistry = ArgumentProcessorRegistry.getInstance(); + for (int i = 0; i < args.length; i++) { String arg = args[i]; @@ -399,11 +405,29 @@ public class Main implements AntMain { } else if (arg.equals("-autoproxy")) { proxy = true; } else if (arg.startsWith("-")) { - // we don't have any more args to recognize! - String msg = "Unknown argument: " + arg; - System.err.println(msg); - printUsage(); - throw new BuildException(""); + boolean processed = false; + for (ArgumentProcessor processor : processorRegistry.getProcessors()) { + int newI = processor.readArguments(args, i); + if (newI != -1) { + List extraArgs = extraArguments.get(processor.getClass()); + if (extraArgs == null) { + extraArgs = new ArrayList(); + extraArguments.put(processor.getClass(), extraArgs); + } + for (; i < newI && i < args.length; i++) { + extraArgs.add(args[i]); + } + processed = true; + break; + } + } + if (!processed) { + // we don't have any more args to recognize! + String msg = "Unknown argument: " + arg; + System.err.println(msg); + printUsage(); + throw new BuildException(""); + } } else { // if it's no other arg, it may be the target targets.addElement(arg); @@ -726,6 +750,17 @@ public class Main implements AntMain { return; } + ArgumentProcessorRegistry processorRegistry = ArgumentProcessorRegistry.getInstance(); + + for (ArgumentProcessor processor : processorRegistry.getProcessors()) { + List extraArgs = extraArguments.get(processor.getClass()); + if (extraArgs != null) { + if (processor.handleArg(extraArgs)) { + return; + } + } + } + final Project project = new Project(); project.setCoreLoader(coreLoader); @@ -781,8 +816,24 @@ public class Main implements AntMain { proxySetup.enableProxies(); } + for (ArgumentProcessor processor : processorRegistry.getProcessors()) { + List extraArgs = extraArguments.get(processor.getClass()); + if (extraArgs != null) { + processor.prepareConfigure(project, extraArgs); + } + } + ProjectHelper.configureProject(project, buildFile); + for (ArgumentProcessor processor : processorRegistry.getProcessors()) { + List extraArgs = extraArguments.get(processor.getClass()); + if (extraArgs != null) { + if (processor.handleArg(project, extraArgs)) { + return; + } + } + } + if (projectHelp) { printDescription(project); printTargets(project, msgOutputLevel > Project.MSG_INFO, @@ -954,50 +1005,45 @@ public class Main implements AntMain { * Prints the usage information for this class to System.out. */ private static void printUsage() { - String lSep = System.getProperty("line.separator"); - StringBuffer msg = new StringBuffer(); - msg.append("ant [options] [target [target2 [target3] ...]]" + lSep); - msg.append("Options: " + lSep); - msg.append(" -help, -h print this message" + lSep); - msg.append(" -projecthelp, -p print project help information" + lSep); - msg.append(" -version print the version information and exit" + lSep); - msg.append(" -diagnostics print information that might be helpful to" + lSep); - msg.append(" diagnose or report problems." + lSep); - msg.append(" -quiet, -q be extra quiet" + lSep); - msg.append(" -silent, -S print nothing but task outputs and build failures" + lSep); - msg.append(" -verbose, -v be extra verbose" + lSep); - msg.append(" -debug, -d print debugging information" + lSep); - msg.append(" -emacs, -e produce logging information without adornments" - + lSep); - msg.append(" -lib specifies a path to search for jars and classes" - + lSep); - msg.append(" -logfile use given file for log" + lSep); - msg.append(" -l ''" + lSep); - msg.append(" -logger the class which is to perform logging" + lSep); - msg.append(" -listener add an instance of class as a project listener" - + lSep); - msg.append(" -noinput do not allow interactive input" + lSep); - msg.append(" -buildfile use given buildfile" + lSep); - msg.append(" -file ''" + lSep); - msg.append(" -f ''" + lSep); - msg.append(" -D= use value for given property" + lSep); - msg.append(" -keep-going, -k execute all targets that do not depend" + lSep); - msg.append(" on failed target(s)" + lSep); - msg.append(" -propertyfile load all properties from file with -D" + lSep); - msg.append(" properties taking precedence" + lSep); - msg.append(" -inputhandler the class which will handle input requests" + lSep); - msg.append(" -find (s)earch for buildfile towards the root of" + lSep); - msg.append(" -s the filesystem and use it" + lSep); - msg.append(" -nice number A niceness value for the main thread:" + lSep - + " 1 (lowest) to 10 (highest); 5 is the default" - + lSep); - msg.append(" -nouserlib Run ant without using the jar files from" + lSep - + " ${user.home}/.ant/lib" + lSep); - msg.append(" -noclasspath Run ant without using CLASSPATH" + lSep); - msg.append(" -autoproxy Java1.5+: use the OS proxy settings" - + lSep); - msg.append(" -main override Ant's normal entry point"); - System.out.println(msg.toString()); + System.out.println("ant [options] [target [target2 [target3] ...]]"); + System.out.println("Options: "); + System.out.println(" -help, -h print this message"); + System.out.println(" -projecthelp, -p print project help information"); + 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."); + 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 specifies a path to search for jars and classes"); + System.out.println(" -logfile use given file for log"); + System.out.println(" -l ''"); + System.out.println(" -logger the class which is to perform logging"); + System.out.println(" -listener add an instance of class as a project listener"); + System.out.println(" -noinput do not allow interactive input"); + System.out.println(" -buildfile use given buildfile"); + System.out.println(" -file ''"); + System.out.println(" -f ''"); + System.out.println(" -D= use value for given property"); + System.out.println(" -keep-going, -k execute all targets that do not depend"); + System.out.println(" on failed target(s)"); + System.out.println(" -propertyfile load all properties from file with -D"); + System.out.println(" properties taking precedence"); + System.out.println(" -inputhandler the class which will handle input requests"); + System.out.println(" -find (s)earch for buildfile towards the root of"); + System.out.println(" -s the filesystem and use it"); + System.out.println(" -nice number A niceness value for the main thread:" + + " 1 (lowest) to 10 (highest); 5 is the default"); + System.out.println(" -nouserlib Run ant without using the jar files from" + + " ${user.home}/.ant/lib"); + System.out.println(" -noclasspath Run ant without using CLASSPATH"); + System.out.println(" -autoproxy Java1.5+: use the OS proxy settings"); + System.out.println(" -main override Ant's normal entry point"); + for (ArgumentProcessor processor : ArgumentProcessorRegistry.getInstance().getProcessors()) { + processor.printUsage(System.out); + } } /**