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.invoker;
20  
21  import java.io.File;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Optional;
29  import java.util.stream.Collectors;
30  
31  import org.apache.maven.RepositoryUtils;
32  import org.apache.maven.execution.MavenSession;
33  import org.apache.maven.model.DependencyManagement;
34  import org.apache.maven.model.Model;
35  import org.apache.maven.model.Parent;
36  import org.apache.maven.plugin.AbstractMojo;
37  import org.apache.maven.plugin.MojoExecutionException;
38  import org.apache.maven.plugins.annotations.Component;
39  import org.apache.maven.plugins.annotations.LifecyclePhase;
40  import org.apache.maven.plugins.annotations.Mojo;
41  import org.apache.maven.plugins.annotations.Parameter;
42  import org.apache.maven.plugins.annotations.ResolutionScope;
43  import org.apache.maven.project.MavenProject;
44  import org.apache.maven.project.artifact.ProjectArtifact;
45  import org.eclipse.aether.DefaultRepositoryCache;
46  import org.eclipse.aether.DefaultRepositorySystemSession;
47  import org.eclipse.aether.RepositorySystem;
48  import org.eclipse.aether.RepositorySystemSession;
49  import org.eclipse.aether.artifact.Artifact;
50  import org.eclipse.aether.artifact.ArtifactType;
51  import org.eclipse.aether.artifact.ArtifactTypeRegistry;
52  import org.eclipse.aether.artifact.DefaultArtifact;
53  import org.eclipse.aether.collection.CollectRequest;
54  import org.eclipse.aether.graph.Dependency;
55  import org.eclipse.aether.graph.DependencyFilter;
56  import org.eclipse.aether.installation.InstallRequest;
57  import org.eclipse.aether.installation.InstallationException;
58  import org.eclipse.aether.repository.LocalRepository;
59  import org.eclipse.aether.repository.LocalRepositoryManager;
60  import org.eclipse.aether.repository.RemoteRepository;
61  import org.eclipse.aether.resolution.ArtifactDescriptorException;
62  import org.eclipse.aether.resolution.ArtifactRequest;
63  import org.eclipse.aether.resolution.ArtifactResolutionException;
64  import org.eclipse.aether.resolution.ArtifactResult;
65  import org.eclipse.aether.resolution.DependencyRequest;
66  import org.eclipse.aether.resolution.DependencyResolutionException;
67  import org.eclipse.aether.resolution.DependencyResult;
68  import org.eclipse.aether.util.artifact.JavaScopes;
69  import org.eclipse.aether.util.artifact.SubArtifact;
70  import org.eclipse.aether.util.filter.DependencyFilterUtils;
71  
72  /**
73   * Installs the project artifacts of the main build into the local repository as a preparation to run the sub projects.
74   * More precisely, all artifacts of the project itself, all its locally reachable parent POMs and all its dependencies
75   * from the reactor will be installed to the local repository.
76   *
77   * @author Paul Gier
78   * @author Benjamin Bentmann
79   * @since 1.2
80   */
81  @Mojo(
82          name = "install",
83          defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST,
84          requiresDependencyResolution = ResolutionScope.TEST,
85          threadSafe = true)
86  public class InstallMojo extends AbstractMojo {
87  
88      // components used in Mojo
89  
90      @Component
91      private RepositorySystem repositorySystem;
92  
93      @Parameter(defaultValue = "${session}", readonly = true, required = true)
94      private MavenSession session;
95  
96      @Parameter(defaultValue = "${project}", readonly = true, required = true)
97      private MavenProject project;
98  
99      /**
100      * The path to the local repository into which the project artifacts should be installed for the integration tests.
101      * If not set, the regular local repository will be used. To prevent soiling of your regular local repository with
102      * possibly broken artifacts, it is strongly recommended to use an isolated repository for the integration tests
103      * (e.g. <code>${project.build.directory}/it-repo</code>).
104      */
105     @Parameter(
106             property = "invoker.localRepositoryPath",
107             defaultValue = "${session.localRepository.basedir}",
108             required = true)
109     private File localRepositoryPath;
110 
111     /**
112      * A flag used to disable the installation procedure. This is primarily intended for usage from the command line to
113      * occasionally adjust the build.
114      *
115      * @since 1.4
116      */
117     @Parameter(property = "invoker.skip", defaultValue = "false")
118     private boolean skipInstallation;
119 
120     /**
121      * Extra dependencies that need to be installed on the local repository.
122      * <p>
123      * Format:
124      * <pre>
125      * groupId:artifactId:version:type:classifier
126      * </pre>
127      * <p>
128      * Examples:
129      * <pre>
130      * org.apache.maven.plugins:maven-clean-plugin:2.4:maven-plugin
131      * org.apache.maven.plugins:maven-clean-plugin:2.4:jar:javadoc
132      * </pre>
133      * <p>
134      * If the type is 'maven-plugin' the plugin will try to resolve the artifact using plugin remote repositories,
135      * instead of using artifact remote repositories.
136      * <p>
137      * <b>NOTICE</b> all dependencies will be resolved with transitive dependencies in <code>runtime</code> scope.
138      *
139      * @since 1.6
140      */
141     @Parameter
142     private String[] extraArtifacts;
143 
144     /**
145      * Scope to resolve project artifacts.
146      *
147      * @since 3.5.0
148      */
149     @Parameter(property = "invoker.install.scope", defaultValue = "runtime")
150     private String scope;
151 
152     /**
153      * Performs this mojo's tasks.
154      *
155      * @throws MojoExecutionException If the artifacts could not be installed.
156      */
157     public void execute() throws MojoExecutionException {
158         if (skipInstallation) {
159             getLog().info("Skipping artifact installation per configuration.");
160             return;
161         }
162 
163         Collection<Artifact> resolvedArtifacts = new ArrayList<>();
164 
165         try {
166 
167             resolveProjectArtifacts(resolvedArtifacts);
168             resolveProjectPoms(project, resolvedArtifacts);
169             resolveProjectDependencies(resolvedArtifacts);
170             resolveExtraArtifacts(resolvedArtifacts);
171             installArtifacts(resolvedArtifacts);
172 
173         } catch (DependencyResolutionException
174                 | InstallationException
175                 | ArtifactDescriptorException
176                 | ArtifactResolutionException e) {
177             throw new MojoExecutionException(e.getMessage(), e);
178         }
179     }
180 
181     private void resolveProjectArtifacts(Collection<Artifact> resolvedArtifacts) {
182 
183         // pom packaging doesn't have a main artifact
184         if (project.getArtifact() != null && project.getArtifact().getFile() != null) {
185             resolvedArtifacts.add(RepositoryUtils.toArtifact(project.getArtifact()));
186         }
187 
188         resolvedArtifacts.addAll(project.getAttachedArtifacts().stream()
189                 .map(RepositoryUtils::toArtifact)
190                 .collect(Collectors.toList()));
191     }
192 
193     private void resolveProjectPoms(MavenProject project, Collection<Artifact> resolvedArtifacts)
194             throws ArtifactResolutionException {
195 
196         if (project == null) {
197             return;
198         }
199 
200         Artifact projectPom = RepositoryUtils.toArtifact(new ProjectArtifact(project));
201         if (projectPom.getFile() != null) {
202             resolvedArtifacts.add(projectPom);
203         } else {
204             Artifact artifact = resolveArtifact(projectPom, project.getRemoteProjectRepositories());
205             resolvedArtifacts.add(artifact);
206         }
207         resolveProjectPoms(project.getParent(), resolvedArtifacts);
208     }
209 
210     private void resolveProjectDependencies(Collection<Artifact> resolvedArtifacts)
211             throws ArtifactResolutionException, MojoExecutionException, DependencyResolutionException {
212 
213         DependencyFilter classpathFilter = DependencyFilterUtils.classpathFilter(scope);
214 
215         ArtifactTypeRegistry artifactTypeRegistry =
216                 session.getRepositorySession().getArtifactTypeRegistry();
217 
218         List<Dependency> managedDependencies = Optional.ofNullable(project.getDependencyManagement())
219                 .map(DependencyManagement::getDependencies)
220                 .orElseGet(Collections::emptyList)
221                 .stream()
222                 .map(d -> RepositoryUtils.toDependency(d, artifactTypeRegistry))
223                 .collect(Collectors.toList());
224 
225         List<Dependency> dependencies = project.getDependencies().stream()
226                 .map(d -> RepositoryUtils.toDependency(d, artifactTypeRegistry))
227                 .collect(Collectors.toList());
228 
229         CollectRequest collectRequest = new CollectRequest();
230         collectRequest.setRootArtifact(RepositoryUtils.toArtifact(project.getArtifact()));
231         collectRequest.setDependencies(dependencies);
232         collectRequest.setManagedDependencies(managedDependencies);
233 
234         collectRequest.setRepositories(project.getRemoteProjectRepositories());
235 
236         DependencyRequest request = new DependencyRequest(collectRequest, classpathFilter);
237 
238         DependencyResult dependencyResult =
239                 repositorySystem.resolveDependencies(session.getRepositorySession(), request);
240 
241         List<Artifact> artifacts = dependencyResult.getArtifactResults().stream()
242                 .map(ArtifactResult::getArtifact)
243                 .collect(Collectors.toList());
244 
245         resolvedArtifacts.addAll(artifacts);
246         resolvePomsForArtifacts(artifacts, resolvedArtifacts, collectRequest.getRepositories());
247     }
248 
249     /**
250      * Resolve extra artifacts.
251      *
252      * @return
253      */
254     private void resolveExtraArtifacts(Collection<Artifact> resolvedArtifacts)
255             throws MojoExecutionException, DependencyResolutionException, ArtifactDescriptorException,
256                     ArtifactResolutionException {
257 
258         if (extraArtifacts == null) {
259             return;
260         }
261 
262         DependencyFilter classpathFilter = DependencyFilterUtils.classpathFilter(JavaScopes.RUNTIME);
263 
264         for (String extraArtifact : extraArtifacts) {
265             String[] gav = extraArtifact.split(":");
266             if (gav.length < 3 || gav.length > 5) {
267                 throw new MojoExecutionException("Invalid artifact " + extraArtifact);
268             }
269 
270             String groupId = gav[0];
271             String artifactId = gav[1];
272             String version = gav[2];
273 
274             String type = "jar";
275             if (gav.length > 3) {
276                 type = gav[3];
277             }
278 
279             String classifier = null;
280             if (gav.length == 5) {
281                 classifier = gav[4];
282             }
283 
284             ArtifactType artifactType =
285                     session.getRepositorySession().getArtifactTypeRegistry().get(type);
286 
287             List<RemoteRepository> remoteRepositories =
288                     artifactType != null && "maven-plugin".equals(artifactType.getId())
289                             ? project.getRemotePluginRepositories()
290                             : project.getRemoteProjectRepositories();
291 
292             Artifact artifact = new DefaultArtifact(groupId, artifactId, classifier, null, version, artifactType);
293 
294             resolvePomsForArtifacts(Collections.singletonList(artifact), resolvedArtifacts, remoteRepositories);
295 
296             CollectRequest collectRequest = new CollectRequest();
297             Dependency root = new Dependency(artifact, JavaScopes.COMPILE);
298             collectRequest.setRoot(root);
299             collectRequest.setRepositories(remoteRepositories);
300 
301             DependencyRequest request = new DependencyRequest(collectRequest, classpathFilter);
302             DependencyResult dependencyResult =
303                     repositorySystem.resolveDependencies(session.getRepositorySession(), request);
304 
305             List<Artifact> artifacts = dependencyResult.getArtifactResults().stream()
306                     .map(ArtifactResult::getArtifact)
307                     .collect(Collectors.toList());
308 
309             resolvedArtifacts.addAll(artifacts);
310             resolvePomsForArtifacts(artifacts, resolvedArtifacts, collectRequest.getRepositories());
311         }
312     }
313 
314     private void resolvePomsForArtifacts(
315             List<Artifact> artifacts, Collection<Artifact> resolvedArtifacts, List<RemoteRepository> remoteRepositories)
316             throws ArtifactResolutionException, MojoExecutionException {
317 
318         for (Artifact a : artifacts) {
319             Artifact artifactResult = resolveArtifact(new SubArtifact(a, "", "pom"), remoteRepositories);
320             resolvePomWithParents(artifactResult, resolvedArtifacts, remoteRepositories);
321         }
322     }
323 
324     private void resolvePomWithParents(
325             Artifact artifact, Collection<Artifact> resolvedArtifacts, List<RemoteRepository> remoteRepositories)
326             throws MojoExecutionException, ArtifactResolutionException {
327 
328         if (resolvedArtifacts.contains(artifact)) {
329             return;
330         }
331 
332         Model model = PomUtils.loadPom(artifact.getFile());
333         Parent parent = model.getParent();
334         if (parent != null) {
335             DefaultArtifact pom =
336                     new DefaultArtifact(parent.getGroupId(), parent.getArtifactId(), "", "pom", parent.getVersion());
337             Artifact resolvedPom = resolveArtifact(pom, remoteRepositories);
338             resolvePomWithParents(resolvedPom, resolvedArtifacts, remoteRepositories);
339         }
340 
341         resolvedArtifacts.add(artifact);
342     }
343 
344     private Artifact resolveArtifact(Artifact artifact, List<RemoteRepository> remoteRepositories)
345             throws ArtifactResolutionException {
346 
347         ArtifactRequest request = new ArtifactRequest();
348         request.setArtifact(artifact);
349         request.setRepositories(remoteRepositories);
350         ArtifactResult artifactResult = repositorySystem.resolveArtifact(session.getRepositorySession(), request);
351         return artifactResult.getArtifact();
352     }
353 
354     /**
355      * Install list of artifacts into local repository.
356      */
357     private void installArtifacts(Collection<Artifact> resolvedArtifacts) throws InstallationException {
358 
359         // we can have on dependency two artifacts with the same groupId:artifactId
360         // with different version, in such case when we install both in one request
361         // metadata will contain only one version
362 
363         Map<String, List<Artifact>> collect = resolvedArtifacts.stream()
364                 .collect(Collectors.groupingBy(
365                         a -> String.format("%s:%s:%s", a.getGroupId(), a.getArtifactId(), a.getVersion()),
366                         LinkedHashMap::new,
367                         Collectors.toList()));
368 
369         RepositorySystemSession systemSessionForLocalRepo = createSystemSessionForLocalRepo();
370 
371         for (List<Artifact> artifacts : collect.values()) {
372             InstallRequest request = new InstallRequest();
373             request.setArtifacts(artifacts);
374             repositorySystem.install(systemSessionForLocalRepo, request);
375         }
376     }
377 
378     /**
379      * Create a new {@link  RepositorySystemSession} connected with local repo.
380      */
381     private RepositorySystemSession createSystemSessionForLocalRepo() {
382         RepositorySystemSession repositorySystemSession = session.getRepositorySession();
383         if (localRepositoryPath != null) {
384             // "clone" repository session and replace localRepository
385             DefaultRepositorySystemSession newSession =
386                     new DefaultRepositorySystemSession(session.getRepositorySession());
387             // Clear cache, since we're using a new local repository
388             newSession.setCache(new DefaultRepositoryCache());
389             // keep same repositoryType
390             String contentType = newSession.getLocalRepository().getContentType();
391             if ("enhanced".equals(contentType)) {
392                 contentType = "default";
393             }
394             LocalRepositoryManager localRepositoryManager = repositorySystem.newLocalRepositoryManager(
395                     newSession, new LocalRepository(localRepositoryPath, contentType));
396 
397             newSession.setLocalRepositoryManager(localRepositoryManager);
398             repositorySystemSession = newSession;
399             getLog().debug("localRepoPath: "
400                     + localRepositoryManager.getRepository().getBasedir());
401         }
402 
403         return repositorySystemSession;
404     }
405 }