blob: 1b0affd29218aba31bca12f876e0d2e751eba23e [file] [log] [blame]
/*
* Copyright (C) The Apache Software Foundation. All rights reserved.
*
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package org.apache.ant;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.ant.launcher.AntClassLoader;
import org.apache.ant.launcher.AntLoader;
import org.apache.ant.project.LogTargetToListenerAdapter;
import org.apache.ant.project.Project;
import org.apache.ant.project.ProjectBuilder;
import org.apache.ant.project.ProjectEngine;
import org.apache.ant.project.ProjectListener;
import org.apache.ant.tasklet.JavaVersion;
import org.apache.ant.tasklet.TaskletContext;
import org.apache.ant.tasklet.engine.TaskletEngine;
import org.apache.avalon.Disposable;
import org.apache.avalon.Initializable;
import org.apache.avalon.camelot.CamelotUtil;
import org.apache.avalon.camelot.Deployer;
import org.apache.avalon.camelot.DeploymentException;
import org.apache.avalon.util.ObjectUtil;
import org.apache.avalon.util.StringUtil;
import org.apache.avalon.util.cli.AbstractMain;
import org.apache.avalon.util.cli.CLOption;
import org.apache.avalon.util.cli.CLOptionDescriptor;
import org.apache.avalon.util.io.ExtensionFileFilter;
import org.apache.log.Category;
import org.apache.log.LogKit;
import org.apache.log.Logger;
import org.apache.log.Priority;
/**
* The class to kick the tires and light the fires.
* Starts ant, loads ProjectBuilder, builds project then uses ProjectEngine
* to run project.
*
* @author <a href="mailto:donaldp@apache.org">Peter Donald</a>
*/
public class Main
extends AbstractMain
{
//Constants to indicate the build of Ant/Myrmidon
public final static String VERSION =
"Ant " + Constants.BUILD_VERSION + " compiled on " + Constants.BUILD_DATE;
//default log level
protected final static String DEFAULT_LOGLEVEL = "WARN";
//Some defaults for file locations/names
protected final static String DEFAULT_FILENAME = "build.xmk";
protected final static String DEFAULT_LISTENER =
"org.apache.ant.project.DefaultProjectListener";
//defines for the Command Line options
private static final int HELP_OPT = 'h';
private static final int QUIET_OPT = 'q';
private static final int VERBOSE_OPT = 'v';
private static final int FILE_OPT = 'f';
private static final int LOG_LEVEL_OPT = 'l';
private static final int DEFINE_OPT = 'D';
private static final int VERSION_OPT = 1;
private static final int LISTENER_OPT = 2;
private static final int TASKLIB_DIR_OPT = 5;
private static final int INCREMENTAL_OPT = 6;
private static final int HOME_DIR_OPT = 7;
//incompatable options for info options
private static final int[] INFO_OPT_INCOMPAT = new int[]
{
HELP_OPT, QUIET_OPT, VERBOSE_OPT, FILE_OPT,
LOG_LEVEL_OPT, VERSION_OPT, LISTENER_OPT,
DEFINE_OPT //TASKLIB_DIR_OPT, HOME_DIR_OPT
};
//incompatable options for other logging options
private static final int[] LOG_OPT_INCOMPAT = new int[]
{
QUIET_OPT, VERBOSE_OPT, LOG_LEVEL_OPT
};
protected ProjectListener m_listener;
protected File m_homeDir;
/**
* Main entry point called to run standard Ant.
*
* @param args the args
*/
public static void main( final String[] args )
{
final Main main = new Main();
try { main.execute( args ); }
catch( final AntException ae )
{
main.getLogger().error( "Error: " + ae.getMessage() );
main.getLogger().debug( "Exception..." + StringUtil.printStackTrace( ae ) );
}
catch( final Throwable throwable )
{
main.getLogger().error( "Error: " + throwable );
main.getLogger().debug( "Exception..." + StringUtil.printStackTrace( throwable ) );
}
}
/**
* Initialise the options for command line parser.
* This is called by super-class.
*/
protected CLOptionDescriptor[] createCLOptions()
{
//TODO: localise
final CLOptionDescriptor[] options = new CLOptionDescriptor[ 11 ];
options[0] =
new CLOptionDescriptor( "help",
CLOptionDescriptor.ARGUMENT_DISALLOWED,
HELP_OPT,
"display this help message",
INFO_OPT_INCOMPAT );
options[1] =
new CLOptionDescriptor( "file",
CLOptionDescriptor.ARGUMENT_REQUIRED,
FILE_OPT,
"the build file." );
options[2] =
new CLOptionDescriptor( "log-level",
CLOptionDescriptor.ARGUMENT_REQUIRED,
LOG_LEVEL_OPT,
"the verbosity level at which to log messages. " +
"(DEBUG|INFO|WARN|ERROR|FATAL_ERROR)",
LOG_OPT_INCOMPAT );
options[3] =
new CLOptionDescriptor( "quiet",
CLOptionDescriptor.ARGUMENT_DISALLOWED,
QUIET_OPT,
"equivelent to --log-level=FATAL_ERROR",
LOG_OPT_INCOMPAT );
options[4] =
new CLOptionDescriptor( "verbose",
CLOptionDescriptor.ARGUMENT_DISALLOWED,
VERBOSE_OPT,
"equivelent to --log-level=INFO",
LOG_OPT_INCOMPAT );
options[5] =
new CLOptionDescriptor( "listener",
CLOptionDescriptor.ARGUMENT_REQUIRED,
LISTENER_OPT,
"the listener for log events." );
options[6] =
new CLOptionDescriptor( "version",
CLOptionDescriptor.ARGUMENT_DISALLOWED,
VERSION_OPT,
"display version",
INFO_OPT_INCOMPAT );
options[7] =
new CLOptionDescriptor( "task-lib-dir",
CLOptionDescriptor.ARGUMENT_REQUIRED,
TASKLIB_DIR_OPT,
"the task lib directory to scan for .tsk files." );
options[8] =
new CLOptionDescriptor( "incremental",
CLOptionDescriptor.ARGUMENT_DISALLOWED,
INCREMENTAL_OPT,
"Run in incremental mode" );
options[9] =
new CLOptionDescriptor( "ant-home",
CLOptionDescriptor.ARGUMENT_REQUIRED,
HOME_DIR_OPT,
"Specify ant home directory" );
options[10] =
new CLOptionDescriptor( "define",
CLOptionDescriptor.ARGUMENTS_REQUIRED_2,
DEFINE_OPT,
"Define a variable (ie -Dfoo=var)",
new int[ 0 ] );
return options;
}
/**
* Entry point for standard ant.
*
* @param clOptions the list of command line options
*/
protected void execute( final List clOptions )
throws Exception
{
final int size = clOptions.size();
final ArrayList targets = new ArrayList();
String filename = null;
String listenerName = null;
String logLevel = null;
String homeDir = null;
String taskLibDir = null;
boolean incremental = false;
HashMap defines = new HashMap();
for( int i = 0; i < size; i++ )
{
final CLOption option = (CLOption)clOptions.get( i );
switch( option.getId() )
{
case 0: targets.add( option.getArgument() ); break;
case HELP_OPT: usage(); return;
case VERSION_OPT: System.out.println( VERSION ); return;
case FILE_OPT: filename = option.getArgument(); break;
case HOME_DIR_OPT: homeDir = option.getArgument(); break;
case TASKLIB_DIR_OPT: taskLibDir = option.getArgument(); break;
case VERBOSE_OPT: logLevel = "INFO"; break;
case QUIET_OPT: logLevel = "ERROR"; break;
case LOG_LEVEL_OPT: logLevel = option.getArgument(); break;
case LISTENER_OPT: listenerName = option.getArgument(); break;
case INCREMENTAL_OPT: incremental = true; break;
case DEFINE_OPT:
defines.put( option.getArgument( 0 ), option.getArgument( 1 ) );
break;
}
}
if( null == logLevel ) logLevel = DEFAULT_LOGLEVEL;
if( null == listenerName ) listenerName = DEFAULT_LISTENER;
if( null == filename ) filename = DEFAULT_FILENAME;
//handle logging...
setLogger( createLogger( logLevel ) );
//if ant home not set then use system property ant.home
//that was set up by launcher.
if( null == homeDir ) homeDir = System.getProperty( "ant.home" );
final Properties properties = new Properties();
properties.setProperty( "ant.home", homeDir );
if( null != taskLibDir ) properties.setProperty( "ant.path.task-lib", taskLibDir );
m_homeDir = (new File( homeDir )).getAbsoluteFile();
if( !m_homeDir.isDirectory() )
{
throw new AntException( "ant-home (" + m_homeDir + ") is not a directory" );
}
final File libDir = new File( m_homeDir, "lib" );
final File buildFile = (new File( filename )).getCanonicalFile();
if( !buildFile.isFile() )
{
throw new AntException( "File " + buildFile + " is not a file or doesn't exist" );
}
//setup classloader so that it will correctly load
//the Project/ProjectBuilder/ProjectEngine and all dependencies
final ClassLoader classLoader = createClassLoader( libDir );
Thread.currentThread().setContextClassLoader( classLoader );
//handle listener..
final ProjectListener listener = createListener( listenerName );
getLogger().warn( "Ant Build File: " + buildFile );
getLogger().info( "Ant Home Directory: " + m_homeDir );
//getLogger().info( "Ant Bin Directory: " + m_binDir );
//getLogger().debug( "Ant Lib Directory: " + m_libDir );
//getLogger().debug( "Ant Task Lib Directory: " + m_taskLibDir );
final AntEngine antEngine = new DefaultAntEngine();
setupLogger( antEngine );
antEngine.setProperties( properties );
antEngine.init();
final ProjectBuilder builder = antEngine.getProjectBuilder();
//create the project
final Project project = builder.build( buildFile );
setupProjectContext( project, defines );
final ProjectEngine engine = antEngine.getProjectEngine();
engine.addProjectListener( listener );
BufferedReader reader = null;
//loop over build if we are in incremental mode..
while( true )
{
//actually do the build ...
doBuild( engine, project, targets );
if( !incremental ) break;
System.out.println( "Continue ? (Enter no to stop)" );
if( null == reader )
{
reader = new BufferedReader( new InputStreamReader( System.in ) );
}
String line = reader.readLine();
if( line.equalsIgnoreCase( "no" ) ) break;
}
antEngine.dispose();
}
/**
* Actually do the build.
*
* @param engine the engine
* @param project the project
* @param targets the targets to build as passed by CLI
*/
protected void doBuild( final ProjectEngine engine,
final Project project,
final ArrayList targets )
{
try
{
final int targetCount = targets.size();
//if we didn't specify a target on CLI then choose default
if( 0 == targetCount )
{
engine.execute( project, project.getDefaultTargetName() );
}
else
{
for( int i = 0; i < targetCount; i++ )
{
engine.execute( project, (String)targets.get( i ) );
}
}
}
catch( final AntException ae )
{
getLogger().error( "BUILD FAILED" );
getLogger().error( "Reason:\n" + StringUtil.printStackTrace( ae, 5, true ) );
}
}
/**
* Create Logger of appropriate log-level.
*
* @param logLevel the log-level
* @return the logger
* @exception AntException if an error occurs
*/
protected Logger createLogger( final String logLevel )
throws AntException
{
final String logLevelCapitalized = logLevel.toUpperCase();
final Priority.Enum priority = LogKit.getPriorityForName( logLevelCapitalized );
if( !priority.getName().equals( logLevelCapitalized ) )
{
throw new AntException( "Unknown log level - " + logLevel );
}
final Category category = LogKit.createCategory( "ant", priority );
return LogKit.createLogger( category );
}
/**
* Setup project listener.
*
* @param listenerName the name of project listener
*/
protected ProjectListener createListener( final String listenerName )
throws AntException
{
ProjectListener result = null;
try { result = (ProjectListener)ObjectUtil.createObject( listenerName ); }
catch( final Throwable t )
{
throw new AntException( "Error creating the listener " + listenerName +
" due to " + StringUtil.printStackTrace( t, 5, true ),
t );
}
getLogger().addLogTarget( new LogTargetToListenerAdapter( result ) );
return result;
}
/**
* Try to load all extra zipz/jars from lib directory into CURRENT classloader.
*
* @param libDir the directory of lib files to add
*/
protected ClassLoader createClassLoader( final File libDir )
{
final ClassLoader candidate = getClass().getClassLoader();
if( !(candidate instanceof AntClassLoader) )
{
getLogger().warn( "Warning: Unable to add entries from " +
"lib-path to classloader" );
return candidate;
}
final AntClassLoader classLoader = (AntClassLoader)candidate;
final ExtensionFileFilter filter =
new ExtensionFileFilter( new String[] { ".jar", ".zip" } );
final File[] files = libDir.listFiles( filter );
for( int i = 0; i < files.length; i++ )
{
//except for a few *special* files add all the
//.zip/.jars to classloader
if( !files[ i ].getName().equals( "ant.jar" ) &&
!files[ i ].getName().equals( "myrmidon.jar" ) &&
!files[ i ].getName().equals( "avalonapi.jar" ) )
{
try { classLoader.addURL( files[ i ].toURL() ); }
catch( final MalformedURLException mue ) {}
}
}
return classLoader;
}
/**
* Setup the projects context so all the "default" properties are defined.
* This also takes a hashmap that is added to context. Usually these are the
* ones defined on command line.
*
* @param project the project
* @param defines the defines
* @exception AntException if an error occurs
*/
protected void setupProjectContext( final Project project, final HashMap defines )
throws AntException
{
//put these values into defines so that they overide
//user-defined proeprties
//defines.put( AntContextResources.HOME_DIR, m_homeDir );
//defines.put( AntContextResources.BIN_DIR, m_binDir );
//defines.put( AntContextResources.LIB_DIR, m_libDir );
//defines.put( AntContextResources.TASKLIB_DIR, m_taskLibDir );
//defines.put( TaskletContext.JAVA_VERSION, getJavaVersion() );
final TaskletContext context = project.getContext();
addToContext( context, defines );
//Add system properties second so that they overide user-defined properties
addToContext( context, System.getProperties() );
}
/**
* Helper method to add values to a context
*
* @param context the context
* @param map the map of names->values
*/
protected void addToContext( final TaskletContext context, final Map map )
{
final Iterator keys = map.keySet().iterator();
while( keys.hasNext() )
{
final String key = (String)keys.next();
final Object value = map.get( key );
context.setProperty( key, value );
}
}
}