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