// Gant -- A Groovy way of scripting Ant tasks.
//
// Copyright © 2008-9 Russel Winder
//
// Licensed 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.codehaus.gant.ant ;
import java.io.File ;
import java.util.ArrayList ;
import java.util.Enumeration;
import java.util.HashMap ;
import java.util.Hashtable;
import java.util.List ;
import java.util.Map ;
import org.apache.tools.ant.AntClassLoader ;
import org.apache.tools.ant.BuildException ;
import org.apache.tools.ant.BuildListener ;
import org.apache.tools.ant.MagicNames ;
import org.apache.tools.ant.Project ;
import org.apache.tools.ant.Task ;
import org.codehaus.gant.GantBinding ;
import org.codehaus.gant.GantBuilder ;
/**
* Execute a Gant script.
*
* <p>This Ant task provides a Gant calling capability. The original intention behind this was to support
* continuous integration systems that do not directly support Gant but only Ant. However it also allows
* for gradual evolution of an Ant build into a Gant build.</p>
*
* <p>Possible attributes are:</p>
*
* <ul>
* <li>file – the path of the Gant script to execute.</li>
* <li>target – the target to execute; must be a single target name. For specifying than a
* single target, use nested gantTarget tags.</li>
* </ul>
*
* <p>Both of these are optional. The file 'build.gant' and the default target are used by default. An
* error results if there is no default target and no target is specified.</p>
*
* <p>Definitions, if needed, are specified using nested <code>definition</code> tags, one for each symbol
* to be defined. Each <code>definition</code> tag takes a compulsory <code>name</code> attribute and an
* optional <code>value</code> attribute.</p>
*
* @author Russel Winder
*/
public class Gant extends Task {
/**
* The path to the file to use to drive the Gant build. The default is build.gant. This path is
* relative to the basedir of the Ant project if it is set, or the directory in which the job was started
* if the basedir is not set.
*/
private String file = "build.gant" ;
/**
* Flag determining whether properties are inherited from the parent project.
*/
private boolean inheritAll = false ;
/**
* A class representing a nested definition tag.
*/
public static final class Definition {
private String name ;
private String value ;
public void setName ( final String s ) { name = s ; }
public String getName ( ) { return name ; }
public void setValue ( final String s ) { value = s ; }
public String getValue ( ) { return value ; }
}
/**
* A list of definitions to be set in the Gant instance.
*/
private final List<Definition> definitions = new ArrayList<Definition> ( ) ;
/**
* A class representing a nested target tag.
*/
public static final class GantTarget {
private String value ;
public void setValue ( final String s ) { value = s ; }
public String getValue ( ) { return value ; }
}
/**
* A list of targets to be achieved by the Gant instance.
*/
private final List<GantTarget> targets = new ArrayList<GantTarget> ( ) ;
/**
* Set the name of the build file to use. This path is relative to the basedir of the Ant project if it
* is set, or the directory in which the job was started if the basedir is not set.
*
* @param f The name of the file to be used to drive the build.
*/
public void setFile ( final String f ) { file = f ; }
/**
* Set the target to be achieved.
*
* @param t The target to achieve.
*/
public void setTarget ( final String t ) {
final GantTarget gt = new GantTarget ( ) ;
gt.setValue ( t ) ;
targets.add ( gt ) ;
}
/**
* Create a node to represent a nested <code>gantTarget</code> tag.
*
* @return a new <code>GantTarget</code> instance ready for values to be added.
*/
public GantTarget createGantTarget ( ) {
final GantTarget gt = new GantTarget ( ) ;
targets.add ( gt ) ;
return gt ;
}
/**
* Create a node to represent a nested <code>definition</code> tag.
*
* @return a new <code>Definition</code> instance ready for values to be added.
*/
public Definition createDefinition ( ) {
final Definition definition = new Definition ( ) ;
definitions.add ( definition ) ;
return definition ;
}
/**
* If true, pass all properties to the new Ant project.
*
* @param value if true pass all properties to the new Ant project.
*/
public void setInheritAll ( final boolean value ) { inheritAll = value ; }
/**
* Load the file and then execute it.
*/
@Override public void execute ( ) throws BuildException {
//
// At first it might seem appropriate to use the Project object from the calling Ant instance as the
// Project object used by the AntBuilder object and hence GantBuilder object associated with the Gant
// instance we are going to create here. However, if we just use that Project object directly then
// there are problems with proper annotation of the lines of output, so it isn't really an option.
// Therefore create a new Project instance and set the things appropriately from the original Project
// object.
//
// Issues driving things here are GANT-50 and GANT-80. GANT-50 is about having the correct base
// directory for operations, GANT-80 is about ensuring that all output generation actually generated
// observable output.
//
// NB As this class is called Gant, we have to use fully qualified name to get to the Gant main class.
//
final Project antProject = getOwningTarget ( ).getProject ( ) ;
final Project newProject = new Project ( ) ;
newProject.init ( ) ;
// Deal with GANT-80 by getting all the the loggers from the Ant instance Project object and adding
// them to the new Project Object. This was followed up by GANT-91 so the code was amended to copying
// over all listeners except the class loader if present.
for ( final Object o : antProject.getBuildListeners ( ) ) {
final BuildListener listener = (BuildListener) o ;
if ( ! ( listener instanceof AntClassLoader ) ) { newProject.addBuildListener ( listener ) ; }
}
// Deal with GANT-50 by getting the base directory from the Ant instance Project object and use it for
// the new Project object. GANT-93 leads to change in the way the Gant file is extracted.
newProject.setBaseDir ( antProject.getBaseDir ( ) ) ;
// Deal with GANT-110 by using the strategy proposed by Eric Van Dewoestine.
if ( inheritAll ) { addAlmostAll ( newProject , antProject ) ; }
final File gantFile = newProject.resolveFile( file ) ;
if ( ! gantFile.exists ( ) ) { throw new BuildException ( "Gantfile does not exist." , getLocation ( ) ) ; }
final GantBuilder ant = new GantBuilder ( newProject ) ;
final Map<String,String> environmentParameter = new HashMap<String,String> ( ) ;
environmentParameter.put ( "environment" , "environment" ) ;
ant.invokeMethod ( "property" , new Object[] { environmentParameter } ) ;
final GantBinding binding = new GantBinding ( ) ;
binding.forcedSettingOfVariable ( "ant" , ant ) ;
for ( final Definition definition : definitions ) {
final Map<String,String> definitionParameter = new HashMap<String,String> ( ) ;
definitionParameter.put ( "name" , definition.getName ( ) ) ;
definitionParameter.put ( "value" , definition.getValue ( ) ) ;
ant.invokeMethod ( "property" , new Object[] { definitionParameter } ) ;
}
final gant.Gant gant = new gant.Gant ( binding ) ;
gant.loadScript ( gantFile ) ;
final List<String> targetsAsStrings = new ArrayList<String> ( ) ;
for ( final GantTarget g : targets ) { targetsAsStrings.add ( g.getValue ( ) ) ; }
final int returnCode = gant.processTargets ( targetsAsStrings ) ;
if ( returnCode != 0 ) { throw new BuildException ( "Gant execution failed with return code " + returnCode + '.' , getLocation ( ) ) ; }
}
/**
* Copy all properties from the given project to the new project -- omitting those that have already been
* set in the new project as well as properties named basedir or ant.file. Inspired by the {@code
* org.apache.tools.ant.taskdefs.Ant} source.
*
* @param newProject the {@code Project} to copy into.
* @param oldProject the {@code Project} to copy properties from.
*/
// Russel Winder rehacked the code provided by Eric Van Dewoestine.
private void addAlmostAll ( final Project newProject , final Project oldProject ) {
@SuppressWarnings ( "unchecked" ) final Hashtable<String,String> properties = oldProject.getProperties ( ) ;
final Enumeration<String> e = properties.keys ( ) ;
while ( e.hasMoreElements ( ) ) {
final String key = e.nextElement ( ) ;
if ( ! ( MagicNames.PROJECT_BASEDIR.equals ( key ) || MagicNames.ANT_FILE.equals ( key ) ) ) {
if ( newProject.getProperty ( key ) == null ) { newProject.setNewProperty ( key , properties.get ( key ) ) ; }
}
}
}
}