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