View Javadoc
1   /*
2    * (C) Copyright 2006-2015 Nuxeo SA (http://nuxeo.com/) and contributors.
3    *
4    * All rights reserved. This program and the accompanying materials
5    * are made available under the terms of the GNU Lesser General Public License
6    * (LGPL) version 2.1 which accompanies this distribution, and is available at
7    * http://www.gnu.org/licenses/lgpl-2.1.html
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12   * Lesser General Public License for more details.
13   *
14   * Contributors:
15   *     bstefanescu, jcarsique, slacoin
16   */
17  
18  package org.nuxeo.build.maven;
19  
20  import java.io.File;
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Hashtable;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.maven.artifact.DependencyResolutionRequiredException;
29  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
30  import org.apache.maven.artifact.repository.ArtifactRepository;
31  import org.apache.maven.model.Profile;
32  import org.apache.maven.plugin.AbstractMojo;
33  import org.apache.maven.plugin.MojoExecutionException;
34  import org.apache.maven.plugin.MojoFailureException;
35  import org.apache.maven.plugins.annotations.Component;
36  import org.apache.maven.plugins.annotations.LifecyclePhase;
37  import org.apache.maven.plugins.annotations.Mojo;
38  import org.apache.maven.plugins.annotations.Parameter;
39  import org.apache.maven.plugins.annotations.ResolutionScope;
40  import org.apache.maven.project.MavenProject;
41  import org.apache.maven.project.MavenProjectHelper;
42  import org.apache.maven.settings.Settings;
43  import org.apache.tools.ant.BuildException;
44  import org.apache.tools.ant.ExitStatusException;
45  import org.apache.tools.ant.Project;
46  import org.apache.tools.ant.types.Path;
47  import org.codehaus.plexus.util.ReaderFactory;
48  import org.codehaus.plexus.util.StringUtils;
49  import org.eclipse.aether.DefaultRepositorySystemSession;
50  import org.eclipse.aether.RepositorySystem;
51  import org.eclipse.aether.RepositorySystemSession;
52  import org.eclipse.aether.collection.DependencySelector;
53  import org.eclipse.aether.repository.RemoteRepository;
54  import org.eclipse.aether.util.artifact.JavaScopes;
55  import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
56  import org.eclipse.aether.util.graph.selector.AndDependencySelector;
57  import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
58  import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
59  import org.eclipse.aether.util.graph.transformer.ConflictResolver;
60  
61  import org.nuxeo.build.ant.AntClient;
62  import org.nuxeo.build.ant.artifact.Expand;
63  import org.nuxeo.build.ant.profile.AntProfileManager;
64  import org.nuxeo.build.maven.filter.TrueFilter;
65  import org.nuxeo.build.maven.graph.Graph;
66  
67  /**
68   * Execute an Ant build manipulating Maven objects (artifacts, dependencies, properties, profiles, repositories,
69   * graph...). Typical usage is the build of a distribution. It is also commonly used to setup and teardown the
70   * integration test environment.
71   */
72  @Mojo(name = "build", threadSafe = true, defaultPhase = LifecyclePhase.PACKAGE, //
73  requiresDependencyCollection = ResolutionScope.TEST, //
74  requiresDependencyResolution = ResolutionScope.TEST)
75  public class AntBuildMojo extends AbstractMojo {
76  
77      private static final ThreadLocal<AntBuildMojo> instance = new ThreadLocal<>();
78  
79      protected Graph graph;
80  
81      protected AntProfileManager antProfileManager;
82  
83      /**
84       * Location of the build file, if unique
85       */
86      @Parameter(property = "buildFile")
87      protected File buildFile;
88  
89      /**
90       * Location of the build files.
91       */
92      @Parameter(property = "buildFiles")
93      protected File[] buildFiles;
94  
95      public File[] getBuildFiles() throws MojoExecutionException {
96          if (buildFile != null && buildFiles != null && buildFiles.length > 0) {
97              throw new MojoExecutionException(
98                      "The configuration parameters 'buildFile' and 'buildFiles' cannot both be used.");
99          }
100         if (buildFiles == null || buildFiles.length == 0) {
101             if (buildFile == null) {
102                 buildFile = new File("build.xml");
103             }
104             buildFiles = new File[] { buildFile };
105         }
106         return buildFiles;
107     }
108 
109     /**
110      * Ant target to call on build file(s).
111      */
112     @Parameter(property = "target")
113     protected String target;
114 
115     /**
116      * Ant targets to call on build file(s).
117      *
118      * @since 1.6
119      */
120     @Parameter(property = "targets")
121     protected String[] targets;
122 
123     /**
124      * How many levels the graph must be expanded before running Ant.
125      */
126     @Parameter(defaultValue = "0", property = "expand")
127     protected String expand;
128 
129     @Component
130     protected RepositorySystem system;
131 
132     public RepositorySystem getSystem() {
133         return system;
134     }
135 
136     @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
137     protected RepositorySystemSession repositorySystemSession;
138 
139     protected DefaultRepositorySystemSession session;
140 
141     public DefaultRepositorySystemSession getSession() {
142         if (session == null) {
143             session = new DefaultRepositorySystemSession(repositorySystemSession);
144             DependencySelector depSelector = session.getDependencySelector();
145             getLog().debug("Replace DependencySelector " + depSelector);
146             DependencySelector depFilter = new AndDependencySelector(
147                     new org.nuxeo.build.maven.graph.ScopeDependencySelector(JavaScopes.PROVIDED, JavaScopes.TEST),
148                     new OptionalDependencySelector(), new ExclusionDependencySelector());
149             session.setDependencySelector(depFilter);
150             session.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, false);
151             session.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true);
152             session.setReadOnly();
153             repositorySystemSession = session;
154         }
155         return session;
156     }
157 
158     @Parameter(defaultValue = "${project}", readonly = true)
159     protected MavenProject project;
160 
161     @Component
162     protected MavenProjectHelper projectHelper;
163 
164     @Component
165     protected ArtifactHandlerManager artifactHandlerManager;
166 
167     public ArtifactHandlerManager getArtifactHandlerManager() {
168         return artifactHandlerManager;
169     }
170 
171     /**
172      * Prefix for property names.
173      *
174      * @since 2.0
175      */
176     @Parameter(defaultValue = "maven.", property = "aamp.propertyPrefix")
177     protected String propertyPrefix;
178 
179     @Parameter(defaultValue = "${localRepository}")
180     protected ArtifactRepository localRepository;
181 
182     /**
183      * If true, Ant properties are propagated to Maven.
184      *
185      * @since 2.0
186      */
187     @Parameter(defaultValue = "false", property = "aamp.exportAntProperties")
188     protected boolean exportAntProperties;
189 
190     /**
191      * If set, only the listed properties will be set back to Maven from Ant.
192      *
193      * @since 2.0
194      */
195     @Parameter(property = "aamp.exportedAntProperties")
196     protected Set<String> exportedAntProperties;
197 
198     @Parameter(defaultValue = "${project.remoteProjectRepositories}")
199     protected List<RemoteRepository> remoteRepositories;
200 
201     public List<RemoteRepository> getRemoteRepositories() {
202         return remoteRepositories;
203     }
204 
205     /**
206      * The character encoding scheme to be applied.
207      */
208     @Parameter(defaultValue = "${project.build.sourceEncoding}", property = "aamp.encoding")
209     protected String encoding;
210 
211     public String getEncoding() {
212         if (StringUtils.isEmpty(encoding)) {
213             getLog().warn(
214                     "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
215                             + ", i.e. build is platform dependent!");
216             encoding = ReaderFactory.FILE_ENCODING;
217         }
218         return encoding;
219     }
220 
221     @Parameter(defaultValue = "${settings}")
222     protected Settings settings;
223 
224     /**
225      * If 'false', the Maven build will proceed even if the Ant build fails. Default is 'true'.
226      *
227      * @since 2.0
228      */
229     @Parameter(defaultValue = "true", property = "failOnError", alias = "aamp.failOnError")
230     protected boolean failOnError;
231 
232     @Override
233     public void execute() throws MojoExecutionException, MojoFailureException {
234         instance.set(this);
235         settings.setInteractiveMode(false);
236         AntClient ant = new AntClient(getLog());
237         ant.getProject().setBaseDir(project.getBasedir());
238         try {
239             setAntReferencesFromMaven(ant.getProject());
240         } catch (BuildException | DependencyResolutionRequiredException e) {
241             throw new MojoExecutionException(e.getMessage(), e);
242         }
243         setAntPropertiesFromMaven(ant.getProject());
244 
245         if (target != null && targets != null && targets.length > 0) {
246             throw new MojoExecutionException("The configuration parameters 'target' and 'targets' cannot both be used.");
247         }
248         if ((targets == null || targets.length == 0) && target != null) {
249             targets = new String[] { target };
250         }
251         for (File file : getBuildFiles()) {
252             try {
253                 if (targets != null && targets.length > 0) {
254                     ant.run(file, Arrays.asList(targets));
255                 } else {
256                     ant.run(file);
257                 }
258                 if (exportAntProperties) {
259                     setMavenPropertiesFromAnt(ant);
260                 }
261             } catch (BuildException e) {
262                 String errMsg = String.format("Error occurred while running %s@%d:%d\n%s", file.getPath(),
263                         e.getLocation().getLineNumber(), e.getLocation().getColumnNumber(), e.getMessage());
264                 if (failOnError) {
265                     if (e instanceof ExitStatusException) {
266                         throw new MojoFailureException(errMsg, e);
267                     } else {
268                         throw new MojoExecutionException(errMsg, e);
269                     }
270                 } else {
271                     getLog().error(errMsg, e);
272                 }
273             }
274         }
275     }
276 
277     /**
278      * @since 1.10.2
279      */
280     public Graph newGraph() {
281         graph = new Graph();
282         graph.addRootNode(project);
283         expandGraph(graph);
284         return graph;
285     }
286 
287     /**
288      * @param key artifact GAV
289      * @return a {@link Graph} which root is artifact resolved from {@code key}
290      * @since 2.0
291      */
292     public Graph newGraph(String key) {
293         graph = new Graph();
294         graph.addRootNode(key);
295         expandGraph(graph);
296         return graph;
297     }
298 
299     protected void expandGraph(Graph newGraph) {
300         int depth = Expand.readExpand(expand);
301         if (depth > 0) {
302             newGraph.resolveDependencies(new TrueFilter(), depth);
303         }
304     }
305 
306     /**
307      * @since 2.0
308      */
309     protected void setMavenPropertiesFromAnt(AntClient ant) {
310         Hashtable<String, Object> antProps = ant.getProject().getUserProperties();
311         Set<String> keySet;
312         if (exportedAntProperties != null && exportedAntProperties.size() > 0) {
313             keySet = exportedAntProperties;
314         } else {
315             keySet = antProps.keySet();
316         }
317         for (String key : keySet) {
318             if (!(antProps.get(key) instanceof String)) {
319                 continue;
320             }
321             project.getProperties().setProperty(key, (String) antProps.get(key));
322         }
323     }
324 
325     /**
326      * @throws DependencyResolutionRequiredException
327      * @throws BuildException
328      * @since 2.0
329      */
330     protected void setAntReferencesFromMaven(Project antProject) throws BuildException,
331             DependencyResolutionRequiredException {
332         Path p = new Path(antProject);
333         p.setPath(StringUtils.join(project.getCompileClasspathElements().iterator(), File.pathSeparator));
334         antProject.addReference(propertyPrefix + "compile.classpath", p);
335 
336         p = new Path(antProject);
337         p.setPath(StringUtils.join(project.getRuntimeClasspathElements().iterator(), File.pathSeparator));
338         antProject.addReference(propertyPrefix + "runtime.classpath", p);
339 
340         p = new Path(antProject);
341         p.setPath(StringUtils.join(project.getTestClasspathElements().iterator(), File.pathSeparator));
342         antProject.addReference(propertyPrefix + "test.classpath", p);
343 
344         antProject.addReference(propertyPrefix + "project", project);
345         antProject.addReference(propertyPrefix + "project.helper", projectHelper);
346         antProject.addReference(propertyPrefix + "local.repository", localRepository);
347     }
348 
349     /**
350      * @since 2.0
351      */
352     protected void setAntPropertiesFromMaven(Project antProject) {
353         for (String key : project.getProperties().stringPropertyNames()) {
354             antProject.setInheritedProperty(key, project.getProperties().getProperty(key));
355         }
356         antProject.setProperty(propertyPrefix + "basedir", project.getBasedir().getPath());
357         antProject.setProperty(propertyPrefix + "project.groupId", project.getGroupId());
358         antProject.setProperty(propertyPrefix + "project.artifactId", project.getArtifactId());
359         antProject.setProperty(propertyPrefix + "project.version", project.getVersion());
360         antProject.setProperty(propertyPrefix + "project.name", project.getName());
361         antProject.setProperty(propertyPrefix + "project.description", project.getDescription());
362         antProject.setProperty(propertyPrefix + "project.packaging", project.getPackaging());
363         antProject.setProperty(propertyPrefix + "project.id", project.getId());
364         antProject.setProperty(propertyPrefix + "project.build.directory", project.getBuild().getDirectory());
365         antProject.setProperty(propertyPrefix + "project.build.outputDirectory", project.getBuild()
366                                                                                         .getOutputDirectory());
367         antProject.setProperty((propertyPrefix + "project.build.testOutputDirectory"), project.getBuild()
368                                                                                               .getTestOutputDirectory());
369         antProject.setProperty((propertyPrefix + "project.build.sourceDirectory"), project.getBuild()
370                                                                                           .getSourceDirectory());
371         antProject.setProperty((propertyPrefix + "project.build.testSourceDirectory"), project.getBuild()
372                                                                                               .getTestSourceDirectory());
373         antProject.setProperty((propertyPrefix + "localRepository"), localRepository.toString());
374         antProject.setProperty((propertyPrefix + "settings.localRepository"), localRepository.getBasedir());
375         antProject.setProperty(propertyPrefix + "project.build.finalName", project.getBuild().getFinalName());
376         antProject.setProperty(propertyPrefix + "offline", settings.isOffline() ? "-o" : "");
377 
378         // add active Maven profiles to Ant
379         antProfileManager = new AntProfileManager();
380         for (String profileId : getInjectedProfileIds()) {
381             antProfileManager.activateProfile(profileId, true);
382             // define a property for each activated profile
383             antProject.setProperty(propertyPrefix + "profile." + profileId, "true");
384         }
385         // Finally add System properties (overriding project and profile ones)
386         for (String key : System.getProperties().stringPropertyNames()) {
387             antProject.setUserProperty(key, System.getProperty(key));
388         }
389     }
390 
391     public List<Profile> getActiveProfiles() {
392         return project.getActiveProfiles();
393     }
394 
395     public List<String> getInjectedProfileIds() {
396         List<String> profileIds = new ArrayList<>();
397         Map<String, List<String>> injectedProfileIds = project.getInjectedProfileIds();
398         if (injectedProfileIds.size() > 0) {
399             StringBuilder sb = new StringBuilder();
400             sb.append("Active Maven profiles:");
401             for (Map.Entry<String, List<String>> entry : injectedProfileIds.entrySet()) {
402                 for (String profileId : entry.getValue()) {
403                     sb.append(String.format("\n%s (source: %s)", profileId, entry.getKey()));
404                     if (!profileIds.contains(profileId)) {
405                         profileIds.add(profileId);
406                     }
407                 }
408             }
409             getLog().info(sb.toString());
410         }
411         return profileIds;
412     }
413 
414     public MavenProject getProject() {
415         return project;
416     }
417 
418     public ArtifactRepository getLocalRepository() {
419         return localRepository;
420     }
421 
422     public MavenProjectHelper getProjectHelper() {
423         return projectHelper;
424     }
425 
426     public Graph getGraph() {
427         if (graph == null) {
428             graph = newGraph();
429         }
430         return graph;
431     }
432 
433     public AntProfileManager getAntProfileManager() {
434         return antProfileManager;
435     }
436 
437     /**
438      * @since 2.0
439      */
440     public static AntBuildMojo getInstance() {
441         return instance.get();
442     }
443 
444 }