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