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