| /* |
| * 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.taskdefs; |
| |
| import java.io.File; |
| import java.io.IOException; |
| |
| import java.util.Vector; |
| import java.util.Enumeration; |
| |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.BuildException; |
| |
| import org.apache.tools.ant.types.Path; |
| import org.apache.tools.ant.types.DirSet; |
| import org.apache.tools.ant.types.FileSet; |
| import org.apache.tools.ant.types.FileList; |
| import org.apache.tools.ant.types.PropertySet; |
| import org.apache.tools.ant.types.Reference; |
| import org.apache.tools.ant.types.ResourceCollection; |
| |
| import org.apache.tools.ant.taskdefs.Ant.TargetElement; |
| |
| |
| /** |
| * Calls a given target for all defined sub-builds. This is an extension |
| * of ant for bulk project execution. |
| * <p> |
| * <h2> Use with directories </h2> |
| * <p> |
| * subant can be used with directory sets to execute a build from different directories. |
| * 2 different options are offered |
| * </p> |
| * <ul> |
| * <li> |
| * run the same build file /somepath/otherpath/mybuild.xml |
| * with different base directories use the genericantfile attribute |
| * </li> |
| * <li>if you want to run directory1/build.xml, directory2/build.xml, .... |
| * use the antfile attribute. The base directory does not get set by the subant task in this case, |
| * because you can specify it in each build file. |
| * </li> |
| * </ul> |
| * @since Ant1.6 |
| * @ant.task name="subant" category="control" |
| */ |
| public class SubAnt |
| extends Task { |
| |
| private Path buildpath; |
| |
| private Ant ant = null; |
| private String subTarget = null; |
| private String antfile = "build.xml"; |
| private File genericantfile = null; |
| private boolean verbose = false; |
| private boolean inheritAll = false; |
| private boolean inheritRefs = false; |
| private boolean failOnError = true; |
| private String output = null; |
| |
| private Vector properties = new Vector(); |
| private Vector references = new Vector(); |
| private Vector propertySets = new Vector(); |
| |
| /** the targets to call on the new project */ |
| private Vector/*<TargetElement>*/ targets = new Vector(); |
| |
| /** |
| * Pass output sent to System.out to the new project. |
| * |
| * @param output a line of output |
| * @since Ant 1.6.2 |
| */ |
| public void handleOutput(String output) { |
| if (ant != null) { |
| ant.handleOutput(output); |
| } else { |
| super.handleOutput(output); |
| } |
| } |
| |
| /** |
| * Process input into the ant task |
| * |
| * @param buffer the buffer into which data is to be read. |
| * @param offset the offset into the buffer at which data is stored. |
| * @param length the amount of data to read |
| * |
| * @return the number of bytes read |
| * |
| * @exception IOException if the data cannot be read |
| * |
| * @see Task#handleInput(byte[], int, int) |
| * |
| * @since Ant 1.6.2 |
| */ |
| public int handleInput(byte[] buffer, int offset, int length) |
| throws IOException { |
| if (ant != null) { |
| return ant.handleInput(buffer, offset, length); |
| } else { |
| return super.handleInput(buffer, offset, length); |
| } |
| } |
| |
| /** |
| * Pass output sent to System.out to the new project. |
| * |
| * @param output The output to log. Should not be <code>null</code>. |
| * |
| * @since Ant 1.6.2 |
| */ |
| public void handleFlush(String output) { |
| if (ant != null) { |
| ant.handleFlush(output); |
| } else { |
| super.handleFlush(output); |
| } |
| } |
| |
| /** |
| * Pass output sent to System.err to the new project. |
| * |
| * @param output The error output to log. Should not be <code>null</code>. |
| * |
| * @since Ant 1.6.2 |
| */ |
| public void handleErrorOutput(String output) { |
| if (ant != null) { |
| ant.handleErrorOutput(output); |
| } else { |
| super.handleErrorOutput(output); |
| } |
| } |
| |
| /** |
| * Pass output sent to System.err to the new project. |
| * |
| * @param output The error output to log. Should not be <code>null</code>. |
| * |
| * @since Ant 1.6.2 |
| */ |
| public void handleErrorFlush(String output) { |
| if (ant != null) { |
| ant.handleErrorFlush(output); |
| } else { |
| super.handleErrorFlush(output); |
| } |
| } |
| |
| /** |
| * Runs the various sub-builds. |
| */ |
| public void execute() { |
| if (buildpath == null) { |
| throw new BuildException("No buildpath specified"); |
| } |
| |
| final String[] filenames = buildpath.list(); |
| final int count = filenames.length; |
| if (count < 1) { |
| log("No sub-builds to iterate on", Project.MSG_WARN); |
| return; |
| } |
| /* |
| //REVISIT: there must be cleaner way of doing this, if it is merited at all |
| if (subTarget == null) { |
| subTarget = getOwningTarget().getName(); |
| } |
| */ |
| BuildException buildException = null; |
| for (int i = 0; i < count; ++i) { |
| File file = null; |
| String subdirPath = null; |
| Throwable thrownException = null; |
| try { |
| File directory = null; |
| file = new File(filenames[i]); |
| if (file.isDirectory()) { |
| if (verbose) { |
| subdirPath = file.getPath(); |
| log("Entering directory: " + subdirPath + "\n", Project.MSG_INFO); |
| } |
| if (genericantfile != null) { |
| directory = file; |
| file = genericantfile; |
| } else { |
| file = new File(file, antfile); |
| } |
| } |
| execute(file, directory); |
| if (verbose && subdirPath != null) { |
| log("Leaving directory: " + subdirPath + "\n", Project.MSG_INFO); |
| } |
| } catch (RuntimeException ex) { |
| if (!(getProject().isKeepGoingMode())) { |
| if (verbose && subdirPath != null) { |
| log("Leaving directory: " + subdirPath + "\n", Project.MSG_INFO); |
| } |
| throw ex; // throw further |
| } |
| thrownException = ex; |
| } catch (Throwable ex) { |
| if (!(getProject().isKeepGoingMode())) { |
| if (verbose && subdirPath != null) { |
| log("Leaving directory: " + subdirPath + "\n", Project.MSG_INFO); |
| } |
| throw new BuildException(ex); |
| } |
| thrownException = ex; |
| } |
| if (thrownException != null) { |
| if (thrownException instanceof BuildException) { |
| log("File '" + file |
| + "' failed with message '" |
| + thrownException.getMessage() + "'.", Project.MSG_ERR); |
| // only the first build exception is reported |
| if (buildException == null) { |
| buildException = (BuildException) thrownException; |
| } |
| } else { |
| log("Target '" + file |
| + "' failed with message '" |
| + thrownException.getMessage() + "'.", Project.MSG_ERR); |
| thrownException.printStackTrace(System.err); |
| if (buildException == null) { |
| buildException = |
| new BuildException(thrownException); |
| } |
| } |
| if (verbose && subdirPath != null) { |
| log("Leaving directory: " + subdirPath + "\n", Project.MSG_INFO); |
| } |
| } |
| } |
| // check if one of the builds failed in keep going mode |
| if (buildException != null) { |
| throw buildException; |
| } |
| } |
| |
| /** |
| * Runs the given target on the provided build file. |
| * |
| * @param file the build file to execute |
| * @param directory the directory of the current iteration |
| * @throws BuildException is the file cannot be found, read, is |
| * a directory, or the target called failed, but only if |
| * <code>failOnError</code> is <code>true</code>. Otherwise, |
| * a warning log message is simply output. |
| */ |
| private void execute(File file, File directory) |
| throws BuildException { |
| if (!file.exists() || file.isDirectory() || !file.canRead()) { |
| String msg = "Invalid file: " + file; |
| if (failOnError) { |
| throw new BuildException(msg); |
| } |
| log(msg, Project.MSG_WARN); |
| return; |
| } |
| |
| ant = createAntTask(directory); |
| String antfilename = file.getAbsolutePath(); |
| ant.setAntfile(antfilename); |
| for (int i = 0; i < targets.size(); i++) { |
| TargetElement targetElement = (TargetElement) targets.get(i); |
| ant.addConfiguredTarget(targetElement); |
| } |
| |
| try { |
| ant.execute(); |
| } catch (BuildException e) { |
| if (failOnError) { |
| throw e; |
| } |
| log("Failure for target '" + subTarget |
| + "' of: " + antfilename + "\n" |
| + e.getMessage(), Project.MSG_WARN); |
| } catch (Throwable e) { |
| if (failOnError) { |
| throw new BuildException(e); |
| } |
| log("Failure for target '" + subTarget |
| + "' of: " + antfilename + "\n" |
| + e.toString(), |
| Project.MSG_WARN); |
| } finally { |
| ant = null; |
| } |
| } |
| |
| /** |
| * This method builds the file name to use in conjunction with directories. |
| * |
| * <p>Defaults to "build.xml". |
| * If <code>genericantfile</code> is set, this attribute is ignored.</p> |
| * |
| * @param antfile the short build file name. Defaults to "build.xml". |
| */ |
| public void setAntfile(String antfile) { |
| this.antfile = antfile; |
| } |
| |
| /** |
| * This method builds a file path to use in conjunction with directories. |
| * |
| * <p>Use <code>genericantfile</code>, in order to run the same build file |
| * with different basedirs.</p> |
| * If this attribute is set, <code>antfile</code> is ignored. |
| * |
| * @param afile (path of the generic ant file, absolute or relative to |
| * project base directory) |
| * */ |
| public void setGenericAntfile(File afile) { |
| this.genericantfile = afile; |
| } |
| |
| /** |
| * Sets whether to fail with a build exception on error, or go on. |
| * |
| * @param failOnError the new value for this boolean flag. |
| */ |
| public void setFailonerror(boolean failOnError) { |
| this.failOnError = failOnError; |
| } |
| |
| /** |
| * The target to call on the different sub-builds. Set to "" to execute |
| * the default target. |
| * @param target the target |
| * <p> |
| */ |
| // REVISIT: Defaults to the target name that contains this task if not specified. |
| public void setTarget(String target) { |
| this.subTarget = target; |
| } |
| |
| /** |
| * Add a target to this Ant invocation. |
| * @param t the <code>TargetElement</code> to add. |
| * @since Ant 1.7 |
| */ |
| public void addConfiguredTarget(TargetElement t) { |
| String name = t.getName(); |
| if ("".equals(name)) { |
| throw new BuildException("target name must not be empty"); |
| } |
| targets.add(t); |
| } |
| |
| /** |
| * Enable/ disable verbose log messages showing when each sub-build path is entered/ exited. |
| * The default value is "false". |
| * @param on true to enable verbose mode, false otherwise (default). |
| */ |
| public void setVerbose(boolean on) { |
| this.verbose = on; |
| } |
| |
| /** |
| * Corresponds to <code><ant></code>'s |
| * <code>output</code> attribute. |
| * |
| * @param s the filename to write the output to. |
| */ |
| public void setOutput(String s) { |
| this.output = s; |
| } |
| |
| /** |
| * Corresponds to <code><ant></code>'s |
| * <code>inheritall</code> attribute. |
| * |
| * @param b the new value for this boolean flag. |
| */ |
| public void setInheritall(boolean b) { |
| this.inheritAll = b; |
| } |
| |
| /** |
| * Corresponds to <code><ant></code>'s |
| * <code>inheritrefs</code> attribute. |
| * |
| * @param b the new value for this boolean flag. |
| */ |
| public void setInheritrefs(boolean b) { |
| this.inheritRefs = b; |
| } |
| |
| /** |
| * Corresponds to <code><ant></code>'s |
| * nested <code><property></code> element. |
| * |
| * @param p the property to pass on explicitly to the sub-build. |
| */ |
| public void addProperty(Property p) { |
| properties.addElement(p); |
| } |
| |
| /** |
| * Corresponds to <code><ant></code>'s |
| * nested <code><reference></code> element. |
| * |
| * @param r the reference to pass on explicitly to the sub-build. |
| */ |
| public void addReference(Ant.Reference r) { |
| references.addElement(r); |
| } |
| |
| /** |
| * Corresponds to <code><ant></code>'s |
| * nested <code><propertyset></code> element. |
| * @param ps the propertset |
| */ |
| public void addPropertyset(PropertySet ps) { |
| propertySets.addElement(ps); |
| } |
| |
| /** |
| * Adds a directory set to the implicit build path. |
| * <p> |
| * <em>Note that the directories will be added to the build path |
| * in no particular order, so if order is significant, one should |
| * use a file list instead!</em> |
| * |
| * @param set the directory set to add. |
| */ |
| public void addDirset(DirSet set) { |
| add(set); |
| } |
| |
| /** |
| * Adds a file set to the implicit build path. |
| * <p> |
| * <em>Note that the directories will be added to the build path |
| * in no particular order, so if order is significant, one should |
| * use a file list instead!</em> |
| * |
| * @param set the file set to add. |
| */ |
| public void addFileset(FileSet set) { |
| add(set); |
| } |
| |
| /** |
| * Adds an ordered file list to the implicit build path. |
| * <p> |
| * <em>Note that contrary to file and directory sets, file lists |
| * can reference non-existent files or directories!</em> |
| * |
| * @param list the file list to add. |
| */ |
| public void addFilelist(FileList list) { |
| add(list); |
| } |
| |
| /** |
| * Adds a resource collection to the implicit build path. |
| * |
| * @param rc the resource collection to add. |
| * @since Ant 1.7 |
| */ |
| public void add(ResourceCollection rc) { |
| getBuildpath().add(rc); |
| } |
| |
| /** |
| * Set the buildpath to be used to find sub-projects. |
| * |
| * @param s an Ant Path object containing the buildpath. |
| */ |
| public void setBuildpath(Path s) { |
| getBuildpath().append(s); |
| } |
| |
| /** |
| * Creates a nested build path, and add it to the implicit build path. |
| * |
| * @return the newly created nested build path. |
| */ |
| public Path createBuildpath() { |
| return getBuildpath().createPath(); |
| } |
| |
| /** |
| * Creates a nested <code><buildpathelement></code>, |
| * and add it to the implicit build path. |
| * |
| * @return the newly created nested build path element. |
| */ |
| public Path.PathElement createBuildpathElement() { |
| return getBuildpath().createPathElement(); |
| } |
| |
| /** |
| * Gets the implicit build path, creating it if <code>null</code>. |
| * |
| * @return the implicit build path. |
| */ |
| private Path getBuildpath() { |
| if (buildpath == null) { |
| buildpath = new Path(getProject()); |
| } |
| return buildpath; |
| } |
| |
| /** |
| * Buildpath to use, by reference. |
| * |
| * @param r a reference to an Ant Path object containing the buildpath. |
| */ |
| public void setBuildpathRef(Reference r) { |
| createBuildpath().setRefid(r); |
| } |
| |
| /** |
| * Creates the <ant> task configured to run a specific target. |
| * |
| * @param directory : if not null the directory where the build should run |
| * |
| * @return the ant task, configured with the explicit properties and |
| * references necessary to run the sub-build. |
| */ |
| private Ant createAntTask(File directory) { |
| Ant antTask = new Ant(this); |
| antTask.init(); |
| if (subTarget != null && subTarget.length() > 0) { |
| antTask.setTarget(subTarget); |
| } |
| |
| |
| if (output != null) { |
| antTask.setOutput(output); |
| } |
| |
| if (directory != null) { |
| antTask.setDir(directory); |
| } |
| |
| antTask.setInheritAll(inheritAll); |
| for (Enumeration i = properties.elements(); i.hasMoreElements();) { |
| copyProperty(antTask.createProperty(), (Property) i.nextElement()); |
| } |
| |
| for (Enumeration i = propertySets.elements(); i.hasMoreElements();) { |
| antTask.addPropertyset((PropertySet) i.nextElement()); |
| } |
| |
| antTask.setInheritRefs(inheritRefs); |
| for (Enumeration i = references.elements(); i.hasMoreElements();) { |
| antTask.addReference((Ant.Reference) i.nextElement()); |
| } |
| |
| return antTask; |
| } |
| |
| /** |
| * Assigns an Ant property to another. |
| * |
| * @param to the destination property whose content is modified. |
| * @param from the source property whose content is copied. |
| */ |
| private static void copyProperty(Property to, Property from) { |
| to.setName(from.getName()); |
| |
| if (from.getValue() != null) { |
| to.setValue(from.getValue()); |
| } |
| if (from.getFile() != null) { |
| to.setFile(from.getFile()); |
| } |
| if (from.getResource() != null) { |
| to.setResource(from.getResource()); |
| } |
| if (from.getPrefix() != null) { |
| to.setPrefix(from.getPrefix()); |
| } |
| if (from.getRefid() != null) { |
| to.setRefid(from.getRefid()); |
| } |
| if (from.getEnvironment() != null) { |
| to.setEnvironment(from.getEnvironment()); |
| } |
| if (from.getClasspath() != null) { |
| to.setClasspath(from.getClasspath()); |
| } |
| } |
| |
| } // END class SubAnt |