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      * The xml tag prefix to use for the built in Ant tasks. This prefix needs to be prepended to each task referenced
157      * in the antrun target config. For example, a prefix of "mvn" means that the attachartifact task is referenced by
158      * "&lt;mvn:attachartifact&gt;" The default value of an empty string means that no prefix is used for the tasks.
159      *
160      * @since 1.5
161      */
162     @Parameter( defaultValue = "" )
163     private String customTaskPrefix = "";
164 
165     /**
166      * The name of a property containing the list of all dependency versions. This is used for the removing the versions
167      * from the filenames.
168      */
169     @Parameter( defaultValue = "maven.project.dependencies.versions" )
170     private String versionsPropertyName;
171 
172     /**
173      * The XML for the Ant task. You can add anything you can add between &lt;target&gt; and &lt;/target&gt; in a
174      * build.xml.
175      *
176      * @deprecated Use {@link #target} instead. For version 3.0.0, this parameter is only defined to break the build if
177      *             you use it!
178      */
179     @Deprecated
180     @Parameter
181     private PlexusConfiguration tasks;
182 
183     /**
184      * The XML for the Ant target. You can add anything you can add between &lt;target&gt; and &lt;/target&gt; in a
185      * build.xml.
186      *
187      * @since 1.5
188      */
189     @Parameter
190     private PlexusConfiguration target;
191 
192     /**
193      * This folder is added to the list of those folders containing source to be compiled. Use this if your Ant script
194      * generates source code.
195      *
196      * @deprecated Use the <code>build-helper-maven-plugin</code> to bind source directories. For version 3.0.0, this
197      *             parameter is only defined to break the build if you use it!
198      */
199     @SuppressWarnings( "DeprecatedIsStillUsed" )
200     @Deprecated
201     @Parameter( property = "sourceRoot" )
202     private File sourceRoot;
203 
204     /**
205      * This folder is added to the list of those folders containing source to be compiled for testing. Use this if your
206      * Ant script generates test source code.
207      *
208      * @deprecated Use the <code>build-helper-maven-plugin</code> to bind test source directories. For version 3.0.0,
209      *             this parameter is only defined to break the build if you use it!
210      */
211     @SuppressWarnings( "DeprecatedIsStillUsed" )
212     @Deprecated
213     @Parameter( property = "testSourceRoot" )
214     private File testSourceRoot;
215 
216     /**
217      * Specifies whether the Antrun execution should be skipped.
218      *
219      * @since 1.7
220      */
221     @Parameter( property = "maven.antrun.skip", defaultValue = "false" )
222     private boolean skip;
223 
224     /**
225      * Specifies whether the Ant properties should be propagated to the Maven properties.
226      *
227      * @since 1.7
228      */
229     @Parameter( defaultValue = "false" )
230     private boolean exportAntProperties;
231 
232     /**
233      * Specifies whether a failure in the Ant build leads to a failure of the Maven build. If this value is
234      * {@code false}, the Maven build will proceed even if the Ant build fails. If it is {@code true}, then the Maven
235      * build fails if the Ant build fails.
236      *
237      * @since 1.7
238      */
239     @Parameter( defaultValue = "true" )
240     private boolean failOnError;
241 
242     @Override
243     public void execute()
244         throws MojoExecutionException, MojoFailureException
245     {
246         checkDeprecatedParameterUsage( tasks, "tasks", "target" );
247         checkDeprecatedParameterUsage( sourceRoot, "sourceRoot", "the build-helper-maven-plugin" );
248         checkDeprecatedParameterUsage( testSourceRoot, "testSourceRoot", "the build-helper-maven-plugin" );
249         if ( skip )
250         {
251             getLog().info( "Skipping Antrun execution" );
252             return;
253         }
254 
255         if ( target == null )
256         {
257             getLog().info( "No Ant target defined - SKIPPED" );
258             return;
259         }
260 
261         if ( propertyPrefix == null )
262         {
263             propertyPrefix = "";
264         }
265 
266         String antTargetName = target.getAttribute( "name", DEFAULT_ANT_TARGET_NAME );
267         target.setAttribute( "name", antTargetName );
268 
269         Project antProject = new Project();
270         antProject.addBuildListener( getConfiguredBuildLogger() );
271         try
272         {
273             File antBuildFile = writeTargetToProjectFile( antTargetName );
274             ProjectHelper.configureProject( antProject, antBuildFile );
275             antProject.init();
276 
277             antProject.setBaseDir( mavenProject.getBasedir() );
278 
279             addAntProjectReferences( mavenProject, antProject );
280             initMavenTasks( antProject );
281 
282             // The Ant project needs actual properties vs. using expression evaluator when calling an external build
283             // file.
284             copyProperties( mavenProject, antProject );
285 
286             getLog().info( "Executing tasks" );
287             antProject.executeTarget( antTargetName );
288             getLog().info( "Executed tasks" );
289 
290             copyProperties( antProject, mavenProject );
291         }
292         catch ( BuildException e )
293         {
294             StringBuilder sb = new StringBuilder();
295             sb.append( "An Ant BuildException has occured: " ).append( e.getMessage() );
296             String fragment = findFragment( e );
297             if ( fragment != null )
298             {
299                 sb.append( "\n" ).append( fragment );
300             }
301             if ( !failOnError )
302             {
303                 getLog().info( sb.toString(), e );
304                 return; // do not register roots.
305             }
306             else
307             {
308                 throw new MojoExecutionException( sb.toString(), e );
309             }
310         }
311         catch ( Throwable e )
312         {
313             throw new MojoExecutionException( "Error executing Ant tasks: " + e.getMessage(), e );
314         }
315     }
316 
317     private void checkDeprecatedParameterUsage( Object parameter, String name, String replacement )
318         throws MojoFailureException
319     {
320         if ( parameter != null )
321         {
322             throw new MojoFailureException( "You are using '" + name + "' which has been removed"
323                 + " from the maven-antrun-plugin. Please use '" + replacement
324                 + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site." );
325         }
326     }
327 
328     private DefaultLogger getConfiguredBuildLogger()
329     {
330         DefaultLogger antLogger = new MavenLogger( getLog() );
331         if ( getLog().isDebugEnabled() )
332         {
333             antLogger.setMessageOutputLevel( Project.MSG_DEBUG );
334         }
335         else if ( getLog().isInfoEnabled() )
336         {
337             antLogger.setMessageOutputLevel( Project.MSG_INFO );
338         }
339         else if ( getLog().isWarnEnabled() )
340         {
341             antLogger.setMessageOutputLevel( Project.MSG_WARN );
342         }
343         else if ( getLog().isErrorEnabled() )
344         {
345             antLogger.setMessageOutputLevel( Project.MSG_ERR );
346         }
347         else
348         {
349             antLogger.setMessageOutputLevel( Project.MSG_VERBOSE );
350         }
351         return antLogger;
352     }
353 
354     private void addAntProjectReferences( MavenProject mavenProject, Project antProject )
355         throws DependencyResolutionRequiredException
356     {
357         Path p = new Path( antProject );
358         p.setPath( StringUtils.join( mavenProject.getCompileClasspathElements().iterator(), File.pathSeparator ) );
359 
360         /* maven.dependency.classpath it's deprecated as it's equal to maven.compile.classpath */
361         antProject.addReference( MAVEN_REFID_PREFIX + "dependency.classpath", p );
362         antProject.addReference( MAVEN_REFID_PREFIX + "compile.classpath", p );
363 
364         p = new Path( antProject );
365         p.setPath( StringUtils.join( mavenProject.getRuntimeClasspathElements().iterator(), File.pathSeparator ) );
366         antProject.addReference( MAVEN_REFID_PREFIX + "runtime.classpath", p );
367 
368         p = new Path( antProject );
369         p.setPath( StringUtils.join( mavenProject.getTestClasspathElements().iterator(), File.pathSeparator ) );
370         antProject.addReference( MAVEN_REFID_PREFIX + "test.classpath", p );
371 
372         /* set maven.plugin.classpath with plugin dependencies */
373         antProject.addReference( MAVEN_REFID_PREFIX + "plugin.classpath",
374                                  getPathFromArtifacts( pluginArtifacts, antProject ) );
375 
376         antProject.addReference( DEFAULT_MAVEN_PROJECT_REFID, mavenProject );
377         antProject.addReference( DEFAULT_MAVEN_PROJECT_REF_REFID, new MavenAntRunProject( mavenProject ) );
378         antProject.addReference( DEFAULT_MAVEN_PROJECT_HELPER_REFID, projectHelper );
379         antProject.addReference( MAVEN_REFID_PREFIX + "local.repository", localRepository );
380     }
381 
382     /**
383      * @param artifacts {@link Artifact} collection.
384      * @param antProject {@link Project}
385      * @return {@link Path}
386      * @throws DependencyResolutionRequiredException In case of a failure.
387      */
388     private Path getPathFromArtifacts( Collection<Artifact> artifacts, Project antProject )
389         throws DependencyResolutionRequiredException
390     {
391         if ( artifacts == null )
392         {
393             return new Path( antProject );
394         }
395 
396         List<String> list = new ArrayList<>( artifacts.size() );
397         for ( Artifact a : artifacts )
398         {
399             File file = a.getFile();
400             if ( file == null )
401             {
402                 throw new DependencyResolutionRequiredException( a );
403             }
404             list.add( file.getPath() );
405         }
406 
407         Path p = new Path( antProject );
408         p.setPath( StringUtils.join( list.iterator(), File.pathSeparator ) );
409 
410         return p;
411     }
412 
413     /**
414      * Copy properties from the Maven project to the Ant project.
415      *
416      * @param mavenProject {@link MavenProject}
417      * @param antProject {@link Project}
418      */
419     public void copyProperties( MavenProject mavenProject, Project antProject )
420     {
421         Properties mavenProps = mavenProject.getProperties();
422         Properties userProps = session.getUserProperties();
423         List<String> allPropertyKeys = new ArrayList<>( mavenProps.stringPropertyNames() );
424         allPropertyKeys.addAll( userProps.stringPropertyNames() );
425         for ( String key : allPropertyKeys )
426         {
427             String value = userProps.getProperty( key, mavenProps.getProperty( key ) );
428             antProject.setProperty( key, value );
429         }
430 
431         // Set the POM file as the ant.file for the tasks run directly in Maven.
432         antProject.setProperty( "ant.file", mavenProject.getFile().getAbsolutePath() );
433 
434         // Add some of the common Maven properties
435         getLog().debug( "Setting properties with prefix: " + propertyPrefix );
436         antProject.setProperty( ( propertyPrefix + "project.groupId" ), mavenProject.getGroupId() );
437         antProject.setProperty( ( propertyPrefix + "project.artifactId" ), mavenProject.getArtifactId() );
438         antProject.setProperty( ( propertyPrefix + "project.name" ), mavenProject.getName() );
439         if ( mavenProject.getDescription() != null )
440         {
441             antProject.setProperty( ( propertyPrefix + "project.description" ), mavenProject.getDescription() );
442         }
443         antProject.setProperty( ( propertyPrefix + "project.version" ), mavenProject.getVersion() );
444         antProject.setProperty( ( propertyPrefix + "project.packaging" ), mavenProject.getPackaging() );
445         antProject.setProperty( ( propertyPrefix + "project.build.directory" ),
446                                 mavenProject.getBuild().getDirectory() );
447         antProject.setProperty( ( propertyPrefix + "project.build.outputDirectory" ),
448                                 mavenProject.getBuild().getOutputDirectory() );
449         antProject.setProperty( ( propertyPrefix + "project.build.testOutputDirectory" ),
450                                 mavenProject.getBuild().getTestOutputDirectory() );
451         antProject.setProperty( ( propertyPrefix + "project.build.sourceDirectory" ),
452                                 mavenProject.getBuild().getSourceDirectory() );
453         antProject.setProperty( ( propertyPrefix + "project.build.testSourceDirectory" ),
454                                 mavenProject.getBuild().getTestSourceDirectory() );
455         antProject.setProperty( ( propertyPrefix + "localRepository" ), localRepository.toString() );
456         antProject.setProperty( ( propertyPrefix + "settings.localRepository" ), localRepository.getBasedir() );
457 
458         // Add properties for dependency artifacts
459         Set<Artifact> depArtifacts = mavenProject.getArtifacts();
460         for ( Artifact artifact : depArtifacts )
461         {
462             String propName = artifact.getDependencyConflictId();
463 
464             antProject.setProperty( propertyPrefix + propName, artifact.getFile().getPath() );
465         }
466 
467         // Add a property containing the list of versions for the mapper
468         StringBuilder versionsBuffer = new StringBuilder();
469         for ( Artifact artifact : depArtifacts )
470         {
471             versionsBuffer.append( artifact.getVersion() ).append( File.pathSeparator );
472         }
473         antProject.setProperty( versionsPropertyName, versionsBuffer.toString() );
474     }
475 
476     /**
477      * Copy properties from the Ant project to the Maven project.
478      *
479      * @param antProject not null
480      * @param mavenProject not null
481      * @since 1.7
482      */
483     public void copyProperties( Project antProject, MavenProject mavenProject )
484     {
485         if ( !exportAntProperties )
486         {
487             return;
488         }
489 
490         getLog().debug( "Propagated Ant properties to Maven properties" );
491         Hashtable<String, Object> antProps = antProject.getProperties();
492         Properties mavenProperties = mavenProject.getProperties();
493 
494         for ( Map.Entry<String, Object> entry : antProps.entrySet() )
495         {
496             String key = entry.getKey();
497             if ( mavenProperties.getProperty( key ) != null )
498             {
499                 getLog().debug( "Ant property '" + key + "=" + mavenProperties.getProperty( key )
500                     + "' clashs with an existing Maven property, SKIPPING this Ant property propagation." );
501                 continue;
502             }
503             // it is safe to call toString directly since the value cannot be null in Hashtable
504             mavenProperties.setProperty( key, entry.getValue().toString() );
505         }
506     }
507 
508     /**
509      * @param antProject {@link Project}
510      */
511     public void initMavenTasks( Project antProject )
512     {
513         getLog().debug( "Initialize Maven Ant Tasks" );
514         Typedef typedef = new Typedef();
515         typedef.setProject( antProject );
516         typedef.setResource( ANTLIB );
517         if ( !customTaskPrefix.isEmpty() )
518         {
519             typedef.setURI( TASK_URI );
520         }
521         typedef.execute();
522     }
523 
524     /**
525      * Write the Ant target and surrounding tags to a temporary file
526      *
527      * @throws IOException problem with write to file
528      */
529     private File writeTargetToProjectFile( String targetName )
530         throws IOException
531     {
532         // The fileName should probably use the plugin executionId instead of the targetName
533         File buildFile = new File( mavenProject.getBuild().getDirectory(), "antrun/build-" + targetName + ".xml" );
534         // noinspection ResultOfMethodCallIgnored
535         buildFile.getParentFile().mkdirs();
536 
537         AntrunXmlPlexusConfigurationWriter xmlWriter = new AntrunXmlPlexusConfigurationWriter();
538         xmlWriter.write( target, buildFile, customTaskPrefix, targetName );
539 
540         return buildFile;
541     }
542 
543     /**
544      * @param buildException not null
545      * @return the fragment XML part where the buildException occurs.
546      * @since 1.7
547      */
548     private String findFragment( BuildException buildException )
549     {
550         if ( buildException == null || buildException.getLocation() == null
551             || buildException.getLocation().getFileName() == null )
552         {
553             return null;
554         }
555 
556         File antFile = new File( buildException.getLocation().getFileName() );
557         if ( !antFile.exists() )
558         {
559             return null;
560         }
561 
562         try ( LineNumberReader reader = new LineNumberReader( ReaderFactory.newXmlReader( antFile ) ) )
563         {
564             for ( String line = reader.readLine(); line != null; line = reader.readLine() )
565             {
566                 if ( reader.getLineNumber() == buildException.getLocation().getLineNumber() )
567                 {
568                     return "around Ant part ..." + line.trim() + "... @ " + buildException.getLocation().getLineNumber()
569                         + ":" + buildException.getLocation().getColumnNumber() + " in " + antFile.getAbsolutePath();
570 
571                 }
572             }
573         }
574         catch ( Exception e )
575         {
576             getLog().debug( e.getMessage(), e );
577         }
578 
579         return null;
580     }
581 }