View Javadoc

1   package org.apache.maven.plugin.antrun;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.LineNumberReader;
25  import java.io.StringWriter;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Hashtable;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Properties;
32  import java.util.Set;
33  
34  import org.apache.maven.artifact.Artifact;
35  import org.apache.maven.artifact.DependencyResolutionRequiredException;
36  import org.apache.maven.artifact.repository.ArtifactRepository;
37  import org.apache.maven.plugin.AbstractMojo;
38  import org.apache.maven.plugin.MojoExecutionException;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.maven.project.MavenProjectHelper;
41  import org.apache.tools.ant.BuildException;
42  import org.apache.tools.ant.DefaultLogger;
43  import org.apache.tools.ant.Project;
44  import org.apache.tools.ant.ProjectHelper;
45  import org.apache.tools.ant.taskdefs.Typedef;
46  import org.apache.tools.ant.types.Path;
47  import org.codehaus.plexus.configuration.PlexusConfiguration;
48  import org.codehaus.plexus.configuration.PlexusConfigurationException;
49  import org.codehaus.plexus.util.FileUtils;
50  import org.codehaus.plexus.util.IOUtil;
51  import org.codehaus.plexus.util.ReaderFactory;
52  import org.codehaus.plexus.util.StringUtils;
53  
54  /**
55   * Maven AntRun Mojo.
56   * <br/>
57   * This plugin provides the capability of calling Ant tasks
58   * from a POM by running the nested ant tasks inside the &lt;tasks/&gt;
59   * parameter. It is encouraged to move the actual tasks to
60   * a separate build.xml file and call that file with an
61   * &lt;ant/&gt; task.
62   *
63   * @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
64   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
65   * @version $Id: AntRunMojo.html 816173 2012-05-06 22:12:29Z hboutemy $
66   * @goal run
67   * @threadSafe
68   * @requiresDependencyResolution test
69   */
70  public class AntRunMojo
71      extends AbstractMojo
72  {
73      /**
74       * The refid used to store the Maven project object in the Ant build.
75       */
76      public final static String DEFAULT_MAVEN_PROJECT_REFID = "maven.project";
77  
78      /**
79       * The refid used to store the Maven project object in the Ant build.
80       */
81      public final static String DEFAULT_MAVEN_PROJECT_HELPER_REFID = "maven.project.helper";
82  
83      /**
84       * The default target name.
85       */
86      public final static String DEFAULT_ANT_TARGET_NAME = "main";
87  
88      /**
89       * The default encoding to use for the generated Ant build.
90       */
91      public final static String UTF_8 = "UTF-8";
92  
93      /**
94       * The name used for the ant target
95       */
96      private String antTargetName;
97  
98      /**
99       * The path to The XML file containing the definition of the Maven tasks.
100      */
101     public final static String ANTLIB = "org/apache/maven/ant/tasks/antlib.xml";
102 
103     /**
104      * The URI which defines the built in Ant tasks
105      */
106     public final static String TASK_URI = "antlib:org.apache.maven.ant.tasks";
107 
108     /**
109      * The Maven project object
110      *
111      * @parameter expression="${project}"
112      * @readonly
113      */
114     private MavenProject project;
115 
116     /**
117      * The Maven project helper object
118      *
119      * @component
120      */
121     private MavenProjectHelper projectHelper;
122 
123     /**
124      * The plugin dependencies.
125      *
126      * @parameter expression="${plugin.artifacts}"
127      * @required
128      * @readonly
129      */
130     private List pluginArtifacts;
131 
132     /**
133      * The local Maven repository
134      *
135      * @parameter expression="${localRepository}"
136      * @readonly
137      */
138     protected ArtifactRepository localRepository;
139 
140     /**
141      * String to prepend to project and dependency property names.
142      *
143      * @parameter default-value=""
144      * @since 1.4
145      */
146     private String propertyPrefix;
147 
148     /**
149      * The xml tag prefix to use for the built in Ant tasks. This prefix needs to be prepended to each task referenced
150      * in the antrun target config. For example, a prefix of "mvn" means that the attachartifact task is referenced by
151      * "&lt;mvn:attachartifact&gt;" The default value of an empty string means that no prefix is used for the tasks.
152      *
153      * @parameter default-value=""
154      * @since 1.5
155      */
156     private String customTaskPrefix = "";
157 
158     /**
159      * The name of a property containing the list of all dependency versions. This is used for the removing the versions
160      * from the filenames.
161      *
162      * @parameter default-value="maven.project.dependencies.versions"
163      */
164     private String versionsPropertyName;
165 
166     /**
167      * The XML for the Ant task. You can add anything you can add between &lt;target&gt; and &lt;/target&gt; in a
168      * build.xml.
169      *
170      * @deprecated Use target instead
171      * @parameter
172      */
173     private PlexusConfiguration tasks;
174 
175     /**
176      * The XML for the Ant target. You can add anything you can add between &lt;target&gt; and &lt;/target&gt; in a
177      * build.xml.
178      *
179      * @parameter
180      * @since 1.5
181      */
182     private PlexusConfiguration target;
183 
184     /**
185      * This folder is added to the list of those folders containing source to be compiled. Use this if your ant script
186      * generates source code.
187      *
188      * @parameter expression="${sourceRoot}"
189      * @deprecated Use the build-helper-maven-plugin to bind source directories
190      */
191     private File sourceRoot;
192 
193     /**
194      * This folder is added to the list of those folders containing source to be compiled for testing. Use this if your
195      * ant script generates test source code.
196      *
197      * @parameter expression="${testSourceRoot}"
198      * @deprecated Use the build-helper-maven-plugin to bind test source directories
199      */
200     private File testSourceRoot;
201 
202     /**
203      * Specifies whether the Antrun execution should be skipped.
204      *
205      * @parameter expression="${maven.antrun.skip}" default-value="false"
206      * @since 1.7
207      */
208     private boolean skip;
209 
210     /**
211      * Specifies whether the Ant properties should be propagated to the Maven properties.
212      *
213      * @parameter default-value="false"
214      * @since 1.7
215      */
216     private boolean exportAntProperties;
217     
218     /**
219      * Specifies whether a failure in the ant build leads to a failure of the Maven build.
220      * 
221      * If this value is 'true', the Maven build will proceed even if the ant build fails.
222      * If it is 'false', then the Maven build fails if the ant build fails.
223      * 
224      * @parameter default-value="true"
225      * @since 1.7
226      */
227     private boolean failOnError;
228 
229     /**
230      * @see org.apache.maven.plugin.Mojo#execute()
231      */
232     public void execute()
233         throws MojoExecutionException
234     {
235         if ( skip )
236         {
237             getLog().info( "Skipping Antrun execution" );
238             return;
239         }
240 
241         MavenProject mavenProject = getMavenProject();
242 
243         if ( tasks != null )
244         {
245             getLog().warn( "Parameter tasks is deprecated, use target instead" );
246             target = tasks;
247         }
248 
249         if ( target == null )
250         {
251             getLog().info( "No ant target defined - SKIPPED" );
252             return;
253         }
254 
255         if ( propertyPrefix == null )
256         {
257             propertyPrefix = "";
258         }
259 
260         try
261         {
262             Project antProject = new Project();
263             File antBuildFile = this.writeTargetToProjectFile( );
264             ProjectHelper.configureProject( antProject, antBuildFile );
265             antProject.init();
266 
267             DefaultLogger antLogger = new DefaultLogger();
268             antLogger.setOutputPrintStream( System.out );
269             antLogger.setErrorPrintStream( System.err );
270             
271             if ( getLog().isDebugEnabled() )
272             {
273                 antLogger.setMessageOutputLevel( Project.MSG_DEBUG );
274             }
275             else if ( getLog().isInfoEnabled() )
276             {
277                 antLogger.setMessageOutputLevel( Project.MSG_INFO );
278             }
279             else if ( getLog().isWarnEnabled() )
280             {
281                 antLogger.setMessageOutputLevel( Project.MSG_WARN );
282             }
283             else if ( getLog().isErrorEnabled() )
284             {
285                 antLogger.setMessageOutputLevel( Project.MSG_ERR );
286             }
287             else
288             {
289                 antLogger.setMessageOutputLevel( Project.MSG_VERBOSE );
290             }
291 
292             antProject.addBuildListener( antLogger );
293             antProject.setBaseDir( mavenProject.getBasedir() );
294 
295             Path p = new Path( antProject );
296             p.setPath( StringUtils.join( mavenProject.getCompileClasspathElements().iterator(), File.pathSeparator ) );
297 
298             /* maven.dependency.classpath it's deprecated as it's equal to maven.compile.classpath */
299             antProject.addReference( "maven.dependency.classpath", p );
300             antProject.addReference( "maven.compile.classpath", p );
301 
302             p = new Path( antProject );
303             p.setPath( StringUtils.join( mavenProject.getRuntimeClasspathElements().iterator(), File.pathSeparator ) );
304             antProject.addReference( "maven.runtime.classpath", p );
305 
306             p = new Path( antProject );
307             p.setPath( StringUtils.join( mavenProject.getTestClasspathElements().iterator(), File.pathSeparator ) );
308             antProject.addReference( "maven.test.classpath", p );
309 
310             /* set maven.plugin.classpath with plugin dependencies */
311             antProject.addReference( "maven.plugin.classpath", getPathFromArtifacts( pluginArtifacts, antProject ) );
312 
313             antProject.addReference( DEFAULT_MAVEN_PROJECT_REFID, getMavenProject() );
314             antProject.addReference( DEFAULT_MAVEN_PROJECT_HELPER_REFID, projectHelper );
315             antProject.addReference( "maven.local.repository", localRepository );
316             initMavenTasks( antProject );
317 
318             // The ant project needs actual properties vs. using expression evaluator when calling an external build
319             // file.
320             copyProperties( mavenProject, antProject );
321 
322             if ( getLog().isInfoEnabled() )
323             {
324                 getLog().info( "Executing tasks" );
325             }
326 
327             antProject.executeTarget( antTargetName );
328 
329             if ( getLog().isInfoEnabled() )
330             {
331                 getLog().info( "Executed tasks" );
332             }
333 
334             copyProperties( antProject, mavenProject );
335         }
336         catch ( DependencyResolutionRequiredException e )
337         {
338             throw new MojoExecutionException( "DependencyResolutionRequiredException: " + e.getMessage(), e );
339         }
340         catch ( BuildException e )
341         {
342             StringBuffer sb = new StringBuffer();
343             sb.append( "An Ant BuildException has occured: " + e.getMessage() );
344             String fragment = findFragment( e );
345             if ( fragment != null )
346             {
347                 sb.append( "\n" ).append( fragment );
348             }
349             if ( !failOnError ) 
350             {
351                 getLog().info( sb.toString(), e );
352                 return; // do not register roots.
353             } else 
354             {
355                 throw new MojoExecutionException( sb.toString(), e );
356             }
357         }
358         catch ( Throwable e )
359         {
360             throw new MojoExecutionException( "Error executing ant tasks: " + e.getMessage(), e );
361         }
362 
363         if ( sourceRoot != null )
364         {
365             getLog().info( "Registering compile source root " + sourceRoot );
366             getMavenProject().addCompileSourceRoot( sourceRoot.toString() );
367         }
368 
369         if ( testSourceRoot != null )
370         {
371             getLog().info( "Registering compile test source root " + testSourceRoot );
372             getMavenProject().addTestCompileSourceRoot( testSourceRoot.toString() );
373         }
374     }
375 
376     /**
377      * @param artifacts
378      * @param antProject
379      * @return a path
380      * @throws DependencyResolutionRequiredException
381      */
382     public Path getPathFromArtifacts( Collection artifacts, Project antProject )
383         throws DependencyResolutionRequiredException
384     {
385         if ( artifacts == null )
386         {
387             return new Path( antProject );
388         }
389 
390         List list = new ArrayList( artifacts.size() );
391         for ( Iterator i = artifacts.iterator(); i.hasNext(); )
392         {
393             Artifact a = (Artifact) i.next();
394             File file = a.getFile();
395             if ( file == null )
396             {
397                 throw new DependencyResolutionRequiredException( a );
398             }
399             list.add( file.getPath() );
400         }
401 
402         Path p = new Path( antProject );
403         p.setPath( StringUtils.join( list.iterator(), File.pathSeparator ) );
404 
405         return p;
406     }
407 
408     /**
409      * Copy properties from the maven project to the ant project.
410      *
411      * @param mavenProject
412      * @param antProject
413      */
414     public void copyProperties( MavenProject mavenProject, Project antProject )
415     {
416         Properties mavenProps = mavenProject.getProperties();
417         Iterator iter = mavenProps.keySet().iterator();
418         while ( iter.hasNext() )
419         {
420             String key = (String) iter.next();
421             antProject.setProperty( key, mavenProps.getProperty( key ) );
422         }
423 
424         // Set the POM file as the ant.file for the tasks run directly in Maven.
425         antProject.setProperty( "ant.file", mavenProject.getFile().getAbsolutePath() );
426 
427         // Add some of the common maven properties
428         getLog().debug("Setting properties with prefix: " + propertyPrefix );
429         antProject.setProperty( ( propertyPrefix + "project.groupId" ), mavenProject.getGroupId() );
430         antProject.setProperty( ( propertyPrefix + "project.artifactId" ), mavenProject.getArtifactId() );
431         antProject.setProperty( ( propertyPrefix + "project.name" ), mavenProject.getName() );
432         if ( mavenProject.getDescription() != null)
433         {
434             antProject.setProperty( ( propertyPrefix + "project.description" ), mavenProject.getDescription() );            
435         }
436         antProject.setProperty( ( propertyPrefix + "project.version" ), mavenProject.getVersion() );
437         antProject.setProperty( ( propertyPrefix + "project.packaging" ), mavenProject.getPackaging() );
438         antProject.setProperty( ( propertyPrefix + "project.build.directory" ), mavenProject.getBuild().getDirectory() );
439         antProject.setProperty( ( propertyPrefix + "project.build.outputDirectory" ),
440                                 mavenProject.getBuild().getOutputDirectory() );
441         antProject.setProperty( ( propertyPrefix + "project.build.testOutputDirectory" ),
442                                 mavenProject.getBuild().getTestOutputDirectory() );
443         antProject.setProperty( ( propertyPrefix + "project.build.sourceDirectory" ),
444                                 mavenProject.getBuild().getSourceDirectory() );
445         antProject.setProperty( ( propertyPrefix + "project.build.testSourceDirectory" ),
446                                 mavenProject.getBuild().getTestSourceDirectory() );
447         antProject.setProperty( ( propertyPrefix + "localRepository" ), localRepository.toString() );
448         antProject.setProperty( ( propertyPrefix + "settings.localRepository" ), localRepository.getBasedir() );
449 
450         // Add properties for depenedency artifacts
451         Set depArtifacts = mavenProject.getArtifacts();
452         for ( Iterator it = depArtifacts.iterator(); it.hasNext(); )
453         {
454             Artifact artifact = (Artifact) it.next();
455 
456             String propName = artifact.getDependencyConflictId();
457 
458             antProject.setProperty( propertyPrefix + propName, artifact.getFile().getPath() );
459         }
460 
461         // Add a property containing the list of versions for the mapper
462         StringBuffer versionsBuffer = new StringBuffer();
463         for ( Iterator it = depArtifacts.iterator(); it.hasNext(); )
464         {
465             Artifact artifact = (Artifact) it.next();
466 
467             versionsBuffer.append( artifact.getVersion() + File.pathSeparator );
468         }
469         antProject.setProperty( versionsPropertyName, versionsBuffer.toString() );
470 
471         // Add properties in deprecated format to depenedency artifacts
472         // This should be removed in future versions of the antrun plugin.
473         for ( Iterator it = depArtifacts.iterator(); it.hasNext(); )
474         {
475             Artifact artifact = (Artifact) it.next();
476 
477             String propName = getDependencyArtifactPropertyName( artifact );
478 
479             antProject.setProperty( propName, artifact.getFile().getPath() );
480         }
481     }
482 
483     /**
484      * Copy properties from the ant project to the maven project.
485      *
486      * @param antProject not null
487      * @param mavenProject not null
488      * @since 1.7
489      */
490     public void copyProperties( Project antProject, MavenProject mavenProject )
491     {
492         if ( !exportAntProperties )
493         {
494             return;
495         }
496 
497         getLog().debug( "Propagated Ant properties to Maven properties" );
498         Hashtable antProps = antProject.getProperties();
499 
500         Iterator iter = antProps.keySet().iterator();
501         while ( iter.hasNext() )
502         {
503             String key = (String) iter.next();
504 
505             Properties mavenProperties = mavenProject.getProperties();
506             if ( mavenProperties.getProperty( key ) != null )
507             {
508                 getLog().debug( "Ant property '" + key +"=" +mavenProperties.getProperty( key )
509                                    + "' clashs with an existing Maven property, SKIPPING this Ant property propagation." );
510                 continue;
511             }
512             mavenProperties.setProperty( key, antProps.get( key ).toString() );
513         }
514     }
515 
516     /**
517      * Prefix for legacy property format.
518      * @deprecated This should only be used for generating the old property format.
519      */
520     public static final String DEPENDENCY_PREFIX = "maven.dependency.";
521 
522     /**
523      * Returns a property name for a dependency artifact.  The name is in the format
524      * maven.dependency.groupId.artifactId[.classifier].type.path
525      *
526      * @param artifact
527      * @return property name
528      * @deprecated The dependency conflict ID should be used as the property name.
529      */
530     public static String getDependencyArtifactPropertyName( Artifact artifact )
531     {
532         String key = DEPENDENCY_PREFIX + artifact.getGroupId() + "." + artifact.getArtifactId()
533             + ( artifact.getClassifier() != null ? "." + artifact.getClassifier() : "" )
534             + ( artifact.getType() != null ? "." + artifact.getType() : "" ) + ".path";
535         return key;
536     }
537 
538     /**
539      * Get the current Maven project
540      *
541      * @return current Maven project
542      */
543     public MavenProject getMavenProject()
544     {
545         return this.project;
546     }
547 
548     public void initMavenTasks( Project antProject )
549     {
550         getLog().debug( "Initialize Maven Ant Tasks" );
551         Typedef typedef = new Typedef();
552         typedef.setProject( antProject );
553         typedef.setResource( ANTLIB );
554         if ( ! customTaskPrefix.equals( "" ) )
555         {
556             typedef.setURI( TASK_URI );
557         }
558         typedef.execute();
559     }
560 
561     /**
562      * Write the ant target and surrounding tags to a temporary file
563      *
564      * @throws PlexusConfigurationException
565      */
566     private File writeTargetToProjectFile()
567         throws IOException, PlexusConfigurationException
568     {
569         // Have to use an XML writer because in Maven 2.x the PlexusConfig toString() method loses XML attributes
570         StringWriter writer = new StringWriter();
571         AntrunXmlPlexusConfigurationWriter xmlWriter = new AntrunXmlPlexusConfigurationWriter();
572         xmlWriter.write( target, writer );
573 
574         StringBuffer antProjectConfig = writer.getBuffer();
575 
576         // replace deprecated tasks tag with standard Ant target
577         stringReplace( antProjectConfig, "<tasks", "<target" );
578         stringReplace( antProjectConfig, "</tasks", "</target" );
579 
580         antTargetName = target.getAttribute( "name" );
581 
582         if ( antTargetName == null )
583         {
584             antTargetName = DEFAULT_ANT_TARGET_NAME;
585             stringReplace( antProjectConfig, "<target", "<target name=\"" + antTargetName + "\"" );
586         }
587 
588         String xmlns = "";
589         if ( ! customTaskPrefix.trim().equals( "" ) )
590         {
591             xmlns = "xmlns:" + customTaskPrefix + "=\"" + TASK_URI + "\"";
592         }
593 
594         final String xmlHeader = "<?xml version=\"1.0\" encoding=\"" + UTF_8 + "\" ?>\n";
595         antProjectConfig.insert( 0, xmlHeader );
596         final String projectOpen = "<project name=\"maven-antrun-\" default=\"" + antTargetName + "\" " + xmlns +" >\n";
597         int index = antProjectConfig.indexOf( "<target" );
598         antProjectConfig.insert( index, projectOpen );
599 
600         final String projectClose = "\n</project>";
601         antProjectConfig.append( projectClose );
602 
603         // The fileName should probably use the plugin executionId instead of the targetName
604         String fileName = "build-" + antTargetName + ".xml";
605         File buildFile = new File( project.getBuild().getDirectory(), "/antrun/" + fileName );
606 
607         buildFile.getParentFile().mkdirs();
608         FileUtils.fileWrite( buildFile.getAbsolutePath(), UTF_8, antProjectConfig.toString() );
609         return buildFile;
610     }
611 
612     /**
613      * Replace text in a StringBuffer.  If the match text is not found, the StringBuffer
614      * is returned unchanged.
615      *
616      * @param text The string buffer containing the text
617      * @param match The string to match and remove
618      * @param with The string to insert
619      */
620     public void stringReplace( StringBuffer text, String match, String with )
621     {
622         int index = text.indexOf( match );
623         if ( index != -1 )
624         {
625             text.replace( index, index + match.length(), with );
626         }
627     }
628 
629     public String checkTargetName( PlexusConfiguration antTargetConfig )
630         throws PlexusConfigurationException
631     {
632         String targetName = antTargetConfig.getAttribute( "name" );
633         if ( targetName == null )
634         {
635             targetName = DEFAULT_ANT_TARGET_NAME;
636         }
637         return targetName;
638     }
639 
640     /**
641      * @param buildException not null
642      * @return the fragment XML part where the buildException occurs.
643      * @since 1.7
644      */
645     private String findFragment( BuildException buildException )
646     {
647         if ( buildException == null || buildException.getLocation() == null
648             || buildException.getLocation().getFileName() == null )
649         {
650             return null;
651         }
652 
653         File antFile = new File( buildException.getLocation().getFileName() );
654         if ( !antFile.exists() )
655         {
656             return null;
657         }
658 
659         LineNumberReader reader = null;
660         try
661         {
662             reader = new LineNumberReader( ReaderFactory.newXmlReader( antFile ) );
663             String line = "";
664             while ( ( line = reader.readLine() ) != null )
665             {
666                 if ( reader.getLineNumber() == buildException.getLocation().getLineNumber() )
667                 {
668                     return "around Ant part ..." + line.trim() + "... @ " + buildException.getLocation().getLineNumber() + ":"
669                         + buildException.getLocation().getColumnNumber() + " in " + antFile.getAbsolutePath();
670                 }
671             }
672         }
673         catch ( Exception e )
674         {
675             getLog().debug( e.getMessage(), e );
676             return null;
677         }
678         finally
679         {
680             IOUtil.close( reader );
681         }
682 
683         return null;
684     }
685 }