diff --git a/WHATSNEW b/WHATSNEW index fd5202377..1b260c79a 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -174,6 +174,11 @@ Changes that could break older environments: (i.e. in a very unlikely event) then the new syntax would yield a different result (an expanded property) than Ant 1.7.1 did. + * A ProjectHelper implementation can now provide the default build file + name it is expecting, and can specify if they can support a specific build + file. So Ant is now capable of supporting several ProjectHelper + implementations, deciding on which to use depending of the input build file. + Fixed bugs: ----------- diff --git a/docs/manual/developlist.html b/docs/manual/developlist.html index 1d8420e01..4aaadcb70 100644 --- a/docs/manual/developlist.html +++ b/docs/manual/developlist.html @@ -37,6 +37,7 @@ Source-code Integration
InputHandler
Using Ant Tasks Outside of Ant
+The Ant frontend: ProjectHelper

Tutorials
Hello World with Ant
diff --git a/docs/manual/projecthelper.html b/docs/manual/projecthelper.html new file mode 100644 index 000000000..ac84f749b --- /dev/null +++ b/docs/manual/projecthelper.html @@ -0,0 +1,131 @@ + + + + + + +The Ant frontend: ProjectHelper + + + +

The Ant frontend: ProjectHelper

+ +

What is a ProjectHelper?

+ +

+The ProjectHelper in Ant is responsible to parse the build file +and create java instances representing the build workflow. It also declares which +kind of file it can parse, and which file name it expects as default input file. +

+

+So in Ant there is a default ProjectHelper +(org.apache.tools.ant.helper.ProjectHelper2) which will parse the +usual build.xml files. And if no build file is specified on the command line, it +will expect to find a build.xml file. +

+ +

+The immediate benefit of a such abstraction it that it is possible to make Ant +understand other kind of descriptive language than XML. Some experiment have +been done around a pure java frontend, and a groovy one too (ask the dev mailing +list for further info about these). +

+ +

How is Ant is selecting the proper ProjectHelper

+ +

+Ant can now know about several implementations of ProjectHelper +and have to decide which to use for each build file. +

+ +

So Ant at startup will list the found implementations and will keep it +ordered as it finds them in an internal 'repository': +

+In case of error while trying to instanciate a ProjectHelper, Ant +will log an error but still won't stop. If you want further debugging +info about the ProjectHelper internal 'repository', use the system +property ant.project-helper-repo.debug and set it to +true; the full stack trace will then also be printed. +

+ +

+Then when Ant is expected to parse a file, it will ask the +ProjectHelper repository to found an implementation that will be +able to parse the input file. Actually it will just iterate on the ordered list +and the first implementation that returns true to +supportsBuildFile(File buildFile) will be selected. +

+ +

+And when Ant is launching and there is no input file specified, it will search for +a default input file. It will iterate on the list of ProjectHelper +and will select the first one that expects a default file that actually exist. +

+ +

Writing your own ProjectHelper

+ +

+The class org.apache.tools.ant.ProjectHelper is the API expected to +be implemented. So write your own ProjectHelper by extending that +abstract class. You are then expected to implement at least the function +parse(Project project, Object source). Note also that your +implementation will be instanciated by Ant, and it is expecting a default +constructor with no arguments. +

+ +

+Then there are some functions that will help you define what your helper is +capable of and what is is expecting: +

+

+ +

+Now that you have your implementation ready, you have to declare it to Ant. Two +solutions here: +

+

+ + + + diff --git a/docs/manual/running.html b/docs/manual/running.html index e8b99b579..502bbd21d 100644 --- a/docs/manual/running.html +++ b/docs/manual/running.html @@ -415,7 +415,6 @@ org.apache.tools.ant.Executor implementation specified here. see its dedicated page, no default value see its dedicated page - file.encoding @@ -455,18 +454,25 @@ org.apache.tools.ant.Executor implementation specified here. - websphere.home + websphere.home path Points to home directory of websphere. see EJB Tasks - XmlLogger.file + XmlLogger.file filename (default 'log.xml') Name for the logfile for MailLogger. + + ant.project-helper-repo.debug + boolean (default 'false') + Set it to true to enable debuging with Ant's + ProjectHelper internal repository. + +

@@ -529,7 +535,7 @@ have some documentation inside.

Unix(-like) systems

If you start Ant as a background process (like in ant - &) and the build process creates another process, Ant will + &) and the build process creates another process, Ant will immediately try to read from standard input, which in turn will most likely suspend the process. In order to avoid this, you must redirect Ant's standard input or explicitly provide input to each diff --git a/src/main/org/apache/tools/ant/Main.java b/src/main/org/apache/tools/ant/Main.java index 97a1e9046..820ebb95a 100644 --- a/src/main/org/apache/tools/ant/Main.java +++ b/src/main/org/apache/tools/ant/Main.java @@ -20,6 +20,7 @@ package org.apache.tools.ant; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -301,6 +302,7 @@ public class Main implements AntMain { */ private void processArgs(String[] args) { String searchForThis = null; + boolean searchForFile = false; PrintStream logTo = null; // cycle through given args @@ -359,11 +361,10 @@ public class Main implements AntMain { // 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 { - searchForThis = DEFAULT_BUILD_FILENAME; } } else if (arg.startsWith("-propertyfile")) { i = handleArgPropertyFile(args, i); @@ -411,11 +412,37 @@ public class Main implements AntMain { // if buildFile was not specified on the command line, if (buildFile == null) { // but -find then search for it - if (searchForThis != null) { - buildFile = findBuildFile(System.getProperty("user.dir"), - searchForThis); + if (searchForFile) { + if (searchForThis != null) { + buildFile = findBuildFile(System.getProperty("user.dir"), searchForThis); + if (buildFile == null) { + throw new BuildException("Could not locate a build file!"); + } + } else { + // no search file specified: so search an existing default file + Iterator it = ProjectHelperRepository.getInstance().getHelpers(); + do { + ProjectHelper helper = (ProjectHelper) it.next(); + searchForThis = helper.getDefaultBuildFile(); + if (msgOutputLevel >= Project.MSG_VERBOSE) { + System.out.println("Searching the default build file: " + searchForThis); + } + buildFile = findBuildFile(System.getProperty("user.dir"), searchForThis); + } while (buildFile == null && it.hasNext()); + if (buildFile == null) { + throw new BuildException("Could not locate a build file!"); + } + } } else { - buildFile = new File(DEFAULT_BUILD_FILENAME); + // no build file specified: so search an existing default file + Iterator it = ProjectHelperRepository.getInstance().getHelpers(); + do { + ProjectHelper helper = (ProjectHelper) it.next(); + buildFile = new File(helper.getDefaultBuildFile()); + if (msgOutputLevel >= Project.MSG_VERBOSE) { + System.out.println("Trying the default build file: " + buildFile); + } + } while (!buildFile.exists() && it.hasNext()); } } @@ -633,20 +660,17 @@ public class Main implements AntMain { *

* 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 an exception - * is thrown. + * root of the file-system has been reached 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 - * - * @exception BuildException if no build file is found + * @return A handle to the build file if one is found, null if not */ - private File findBuildFile(String start, String suffix) - throws BuildException { + private File findBuildFile(String start, String suffix) { if (msgOutputLevel >= Project.MSG_INFO) { System.out.println("Searching for " + suffix + " ..."); } @@ -662,7 +686,7 @@ public class Main implements AntMain { // 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) { - throw new BuildException("Could not locate a build file!"); + return null; } // refresh our file handle diff --git a/src/main/org/apache/tools/ant/ProjectHelper.java b/src/main/org/apache/tools/ant/ProjectHelper.java index fc49c2cf6..f03a23dbd 100644 --- a/src/main/org/apache/tools/ant/ProjectHelper.java +++ b/src/main/org/apache/tools/ant/ProjectHelper.java @@ -17,37 +17,21 @@ */ package org.apache.tools.ant; -import java.io.BufferedReader; import java.io.File; -import java.io.InputStream; -import java.io.InputStreamReader; import java.net.URL; import java.util.Hashtable; import java.util.Locale; import java.util.Vector; -import org.xml.sax.AttributeList; - -import org.apache.tools.ant.helper.ProjectHelper2; import org.apache.tools.ant.util.LoaderUtils; +import org.xml.sax.AttributeList; /** * Configures a Project (complete with Targets and Tasks) based on - * a XML build file. It'll rely on a plugin to do the actual processing - * of the xml file. - * + * a build file. It'll rely on a plugin to do the actual processing + * of the file. + *

* This class also provide static wrappers for common introspection. - * - * All helper plugins must provide backward compatibility with the - * original ant patterns, unless a different behavior is explicitly - * specified. For example, if namespace is used on the <project> tag - * the helper can expect the entire build file to be namespace-enabled. - * Namespaces or helper-specific tags can provide meta-information to - * the helper, allowing it to use new ( or different policies ). - * - * However, if no namespace is used the behavior should be exactly - * identical with the default helper. - * */ public class ProjectHelper { /** The URI for ant name space */ @@ -89,7 +73,7 @@ public class ProjectHelper { * @exception BuildException if the configuration is invalid or cannot be read */ public static void configureProject(Project project, File buildFile) throws BuildException { - ProjectHelper helper = ProjectHelper.getProjectHelper(); + ProjectHelper helper = ProjectHelperRepository.getInstance().getProjectHelper(buildFile); project.addReference(PROJECTHELPER_REFERENCE, helper); helper.parse(project, buildFile); } @@ -224,103 +208,13 @@ public class ProjectHelper { } /** - * Discovers a project helper instance. Uses the same patterns - * as JAXP, commons-logging, etc: a system property, a JDK1.3 - * service discovery, default. - * - * @return a ProjectHelper, either a custom implementation - * if one is available and configured, or the default implementation - * otherwise. - * - * @exception BuildException if a specified helper class cannot - * be loaded/instantiated. - */ - public static ProjectHelper getProjectHelper() throws BuildException { - // Identify the class loader we will be using. Ant may be - // in a webapp or embedded in a different app - ProjectHelper helper = null; - - // First, try the system property - String helperClass = System.getProperty(HELPER_PROPERTY); - try { - if (helperClass != null) { - helper = newHelper(helperClass); - } - } catch (SecurityException e) { - System.out.println("Unable to load ProjectHelper class \"" - + helperClass + " specified in system property " - + HELPER_PROPERTY); - } - - // A JDK1.3 'service' ( like in JAXP ). That will plug a helper - // automatically if in CLASSPATH, with the right META-INF/services. - if (helper == null) { - try { - ClassLoader classLoader = LoaderUtils.getContextClassLoader(); - InputStream is = null; - if (classLoader != null) { - is = classLoader.getResourceAsStream(SERVICE_ID); - } - if (is == null) { - is = ClassLoader.getSystemResourceAsStream(SERVICE_ID); - } - if (is != null) { - // This code is needed by EBCDIC and other strange systems. - // It's a fix for bugs reported in xerces - InputStreamReader isr; - try { - isr = new InputStreamReader(is, "UTF-8"); - } catch (java.io.UnsupportedEncodingException e) { - isr = new InputStreamReader(is); - } - BufferedReader rd = new BufferedReader(isr); - - String helperClassName = rd.readLine(); - rd.close(); - - if (helperClassName != null && !"".equals(helperClassName)) { - helper = newHelper(helperClassName); - } - } - } catch (Exception ex) { - System.out.println("Unable to load ProjectHelper from service " + SERVICE_ID); - } - } - return helper == null ? new ProjectHelper2() : helper; - } - - /** - * Creates a new helper instance from the name of the class. - * It'll first try the thread class loader, then Class.forName() - * will load from the same loader that loaded this class. - * - * @param helperClass The name of the class to create an instance - * of. Must not be null. - * - * @return a new instance of the specified class. - * - * @exception BuildException if the class cannot be found or - * cannot be appropriate instantiated. + * Get the first project helper found in the classpath + * + * @return an project helper, never null + * @see #getHelpers() */ - private static ProjectHelper newHelper(String helperClass) - throws BuildException { - ClassLoader classLoader = LoaderUtils.getContextClassLoader(); - try { - Class clazz = null; - if (classLoader != null) { - try { - clazz = classLoader.loadClass(helperClass); - } catch (ClassNotFoundException ex) { - // try next method - } - } - if (clazz == null) { - clazz = Class.forName(helperClass); - } - return ((ProjectHelper) clazz.newInstance()); - } catch (Exception e) { - throw new BuildException(e); - } + public static ProjectHelper getProjectHelper() { + return (ProjectHelper) ProjectHelperRepository.getInstance().getHelpers().next(); } /** @@ -618,4 +512,27 @@ public class ProjectHelper { URL source) { throw new BuildException("can't parse antlib descriptors"); } + + /** + * Check if the helper supports the kind of file. Some basic check on the + * extension's file should be done here. + * + * @param buildFile + * the file expected to be parsed (never null) + * @return true if the helper supports it + * @since Ant 1.8.0 + */ + public boolean supportsBuildFile(File buildFile) { + return true; + } + + /** + * The file name of the build script to be parsed if none specified on the command line + * + * @return the name of the default file (never null) + * @since Ant 1.8.0 + */ + public String getDefaultBuildFile() { + return Main.DEFAULT_BUILD_FILENAME; + } } diff --git a/src/main/org/apache/tools/ant/ProjectHelperRepository.java b/src/main/org/apache/tools/ant/ProjectHelperRepository.java new file mode 100644 index 000000000..b0d6dd22d --- /dev/null +++ b/src/main/org/apache/tools/ant/ProjectHelperRepository.java @@ -0,0 +1,203 @@ +package org.apache.tools.ant; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; + +import org.apache.tools.ant.helper.ProjectHelper2; +import org.apache.tools.ant.util.LoaderUtils; + +/** + * Repository of {@link ProjectHelper} found in the classpath or via some System + * properties. + *

+ * See the ProjectHelper documentation in the manual. + * + * @since Ant 1.8.0 + */ +public class ProjectHelperRepository { + + private static final String DEBUG_PROJECT_HELPER_REPOSITORY = "ant.project-helper-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_PROJECT_HELPER_REPOSITORY)); + + private static ProjectHelperRepository instance = new ProjectHelperRepository(); + + private List/* */helpers = new ArrayList(); + + public static ProjectHelperRepository getInstance() { + return instance; + } + + private ProjectHelperRepository() { + collectProjectHelpers(); + } + + private void collectProjectHelpers() { + // First, try the system property + ProjectHelper projectHelper = getProjectHelperBySystemProperty(); + registerProjectHelper(projectHelper); + + // A JDK1.3 'service' ( like in JAXP ). That will plug a helper + // automatically if in CLASSPATH, with the right META-INF/services. + try { + ClassLoader classLoader = LoaderUtils.getContextClassLoader(); + if (classLoader != null) { + Enumeration resources = classLoader.getResources(ProjectHelper.SERVICE_ID); + while (resources.hasMoreElements()) { + URL resource = (URL) resources.nextElement(); + projectHelper = getProjectHelperBySerice(resource.openStream()); + registerProjectHelper(projectHelper); + } + } + + InputStream systemResource = ClassLoader.getSystemResourceAsStream(ProjectHelper.SERVICE_ID); + if (systemResource != null) { + projectHelper = getProjectHelperBySerice(systemResource); + registerProjectHelper(projectHelper); + } + } catch (Exception e) { + System.err.println("Unable to load ProjectHelper from service " + + ProjectHelper.SERVICE_ID + " (" + e.getClass().getName() + ": " + + e.getMessage() + ")"); + if (DEBUG) { + e.printStackTrace(System.err); + } + } + + // last but not least, ant default project helper + projectHelper = new ProjectHelper2(); + registerProjectHelper(projectHelper); + } + + private void registerProjectHelper(ProjectHelper projectHelper) { + if (projectHelper == null) { + return; + } + if (DEBUG) { + System.out.println("ProjectHelper " + + projectHelper.getClass().getName() + " registered."); + } + helpers.add(projectHelper); + } + + private ProjectHelper getProjectHelperBySystemProperty() { + String helperClass = System.getProperty(ProjectHelper.HELPER_PROPERTY); + try { + if (helperClass != null) { + return newHelper(helperClass); + } + } catch (SecurityException e) { + System.err.println("Unable to load ProjectHelper class \"" + + helperClass + " specified in system property " + + ProjectHelper.HELPER_PROPERTY + " (" + e.getMessage() + ")"); + if (DEBUG) { + e.printStackTrace(System.err); + } + } + return null; + } + + private ProjectHelper getProjectHelperBySerice(InputStream is) { + try { + // This code is needed by EBCDIC and other strange systems. + // It's a fix for bugs reported in xerces + InputStreamReader isr; + try { + isr = new InputStreamReader(is, "UTF-8"); + } catch (java.io.UnsupportedEncodingException e) { + isr = new InputStreamReader(is); + } + BufferedReader rd = new BufferedReader(isr); + + String helperClassName = rd.readLine(); + rd.close(); + + if (helperClassName != null && !"".equals(helperClassName)) { + return newHelper(helperClassName); + } + } catch (Exception e) { + System.out.println("Unable to load ProjectHelper from service " + + ProjectHelper.SERVICE_ID + " (" + e.getMessage() + ")"); + if (DEBUG) { + e.printStackTrace(System.err); + } + } + return null; + } + + /** + * Creates a new helper instance from the name of the class. It'll first try + * the thread class loader, then Class.forName() will load from the same + * loader that loaded this class. + * + * @param helperClass + * The name of the class to create an instance of. Must not be + * null. + * + * @return a new instance of the specified class. + * + * @exception BuildException + * if the class cannot be found or cannot be appropriate + * instantiated. + */ + private ProjectHelper newHelper(String helperClass) throws BuildException { + ClassLoader classLoader = LoaderUtils.getContextClassLoader(); + try { + Class clazz = null; + if (classLoader != null) { + try { + clazz = classLoader.loadClass(helperClass); + } catch (ClassNotFoundException ex) { + // try next method + } + } + if (clazz == null) { + clazz = Class.forName(helperClass); + } + return ((ProjectHelper) clazz.newInstance()); + } catch (Exception e) { + throw new BuildException(e); + } + } + + /** + * Get the helper that will be able to parse the specified file. The helper + * will be chosen among the ones found in the classpath + * + * @return the first ProjectHelper that fit the requirement (never null). + */ + public ProjectHelper getProjectHelper(File buildFile) throws BuildException { + Iterator it = helpers.iterator(); + while (it.hasNext()) { + ProjectHelper helper = (ProjectHelper) it.next(); + if (helper.supportsBuildFile(buildFile)) { + if (DEBUG) { + System.out.println("ProjectHelper " + + helper.getClass().getName() + " selected for the file " + + buildFile); + } + return helper; + } + } + throw new RuntimeException("BUG: at least the ProjectHelper2 should have supported the file " + buildFile); + } + + /** + * Get an iterator on the list of project helpers configured. The iterator + * will always return at least one element as there will always be the + * default project helper configured. + * + * @return an iterator of {@link ProjectHelper} + */ + public Iterator getHelpers() { + return helpers.iterator(); + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/Ant.java b/src/main/org/apache/tools/ant/taskdefs/Ant.java index 58e165049..4f0dc08da 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Ant.java +++ b/src/main/org/apache/tools/ant/taskdefs/Ant.java @@ -359,7 +359,7 @@ public class Ant extends Task { overrideProperties(); if (antFile == null) { - antFile = Main.DEFAULT_BUILD_FILENAME; + antFile = getDefaultBuildFile(); } File file = FILE_UTILS.resolveFile(dir, antFile); @@ -469,6 +469,19 @@ public class Ant extends Task { } } + /** + * Get the default build file name to use when launching the task. + *

+ * This function may be overrided by providers of custom ProjectHelper so they can implement easily their sub + * launcher. + * + * @return the name of the default file + * @since Ant 1.8.0 + */ + protected String getDefaultBuildFile() { + return Main.DEFAULT_BUILD_FILENAME; + } + /** * Override the properties in the new project with the one * explicitly defined as nested elements here. diff --git a/src/main/org/apache/tools/ant/taskdefs/SubAnt.java b/src/main/org/apache/tools/ant/taskdefs/SubAnt.java index 358bd5dad..f3c72e4f2 100644 --- a/src/main/org/apache/tools/ant/taskdefs/SubAnt.java +++ b/src/main/org/apache/tools/ant/taskdefs/SubAnt.java @@ -67,7 +67,7 @@ public class SubAnt extends Task { private Ant ant = null; private String subTarget = null; - private String antfile = Main.DEFAULT_BUILD_FILENAME; + private String antfile = getDefaultBuildFile(); private File genericantfile = null; private boolean verbose = false; private boolean inheritAll = false; @@ -82,6 +82,19 @@ public class SubAnt extends Task { /** the targets to call on the new project */ private Vector/**/ targets = new Vector(); + /** + * Get the default build file name to use when launching the task. + *

+ * This function may be overrided by providers of custom ProjectHelper so they can implement easily their sub + * launcher. + * + * @return the name of the default file + * @since Ant 1.8.0 + */ + protected String getDefaultBuildFile() { + return Main.DEFAULT_BUILD_FILENAME; + } + /** * Pass output sent to System.out to the new project. * diff --git a/src/main/org/apache/tools/ant/types/Description.java b/src/main/org/apache/tools/ant/types/Description.java index 27f9cea36..cb0004ff8 100644 --- a/src/main/org/apache/tools/ant/types/Description.java +++ b/src/main/org/apache/tools/ant/types/Description.java @@ -50,7 +50,7 @@ public class Description extends DataType { */ public void addText(String text) { - ProjectHelper ph = ProjectHelper.getProjectHelper(); + ProjectHelper ph = (ProjectHelper) getProject().getReference(ProjectHelper.PROJECTHELPER_REFERENCE); if (!(ph instanceof ProjectHelperImpl)) { // New behavior for delayed task creation. Description // will be evaluated in Project.getDescription()