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.DefaultDependencyNode;
56  import org.eclipse.aether.graph.Dependency;
57  import org.eclipse.aether.graph.DependencyFilter;
58  import org.eclipse.aether.installation.InstallRequest;
59  import org.eclipse.aether.installation.InstallationException;
60  import org.eclipse.aether.repository.LocalRepository;
61  import org.eclipse.aether.repository.LocalRepositoryManager;
62  import org.eclipse.aether.repository.RemoteRepository;
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 | InstallationException | ArtifactResolutionException e) {
176             throw new MojoExecutionException(e.getMessage(), e);
177         }
178     }
179 
180     private void resolveProjectArtifacts(Map<String, Artifact> resolvedArtifacts) {
181 
182         // pom packaging doesn't have a main artifact
183         if (project.getArtifact() != null && project.getArtifact().getFile() != null) {
184             Artifact artifact = RepositoryUtils.toArtifact(project.getArtifact());
185             resolvedArtifacts.put(ArtifactIdUtils.toId(artifact), artifact);
186         }
187 
188         project.getAttachedArtifacts().stream()
189                 .map(RepositoryUtils::toArtifact)
190                 .forEach(a -> resolvedArtifacts.put(ArtifactIdUtils.toId(a), a));
191     }
192 
193     private void resolveProjectPoms(MavenProject project, Map<String, 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.put(projectPom.toString(), projectPom);
203         } else {
204             Artifact artifact = resolveArtifact(projectPom, project.getRemoteProjectRepositories());
205             resolvedArtifacts.put(ArtifactIdUtils.toId(artifact), artifact);
206         }
207         resolveProjectPoms(project.getParent(), resolvedArtifacts);
208     }
209 
210     private void resolveProjectDependencies(Map<String, 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                 .filter(d -> classpathFilter.accept(new DefaultDependencyNode(d), null))
228                 .collect(Collectors.toList());
229 
230         CollectRequest collectRequest = new CollectRequest();
231         collectRequest.setRootArtifact(RepositoryUtils.toArtifact(project.getArtifact()));
232         collectRequest.setDependencies(dependencies);
233         collectRequest.setManagedDependencies(managedDependencies);
234 
235         collectRequest.setRepositories(project.getRemoteProjectRepositories());
236 
237         DependencyRequest request = new DependencyRequest(collectRequest, classpathFilter);
238 
239         DependencyResult dependencyResult =
240                 repositorySystem.resolveDependencies(session.getRepositorySession(), request);
241 
242         List<Artifact> artifacts = dependencyResult.getArtifactResults().stream()
243                 .map(ArtifactResult::getArtifact)
244                 .collect(Collectors.toList());
245 
246         artifacts.forEach(a -> resolvedArtifacts.put(ArtifactIdUtils.toId(a), a));
247         resolvePomsForArtifacts(artifacts, resolvedArtifacts, collectRequest.getRepositories());
248     }
249 
250     /**
251      * Resolve extra artifacts.
252      */
253     private void resolveExtraArtifacts(Map<String, Artifact> resolvedArtifacts)
254             throws MojoExecutionException, DependencyResolutionException, ArtifactResolutionException {
255 
256         if (extraArtifacts == null) {
257             return;
258         }
259 
260         DependencyFilter classpathFilter = DependencyFilterUtils.classpathFilter(JavaScopes.RUNTIME);
261 
262         for (String extraArtifact : extraArtifacts) {
263             String[] gav = extraArtifact.split(":");
264             if (gav.length < 3 || gav.length > 5) {
265                 throw new MojoExecutionException("Invalid artifact " + extraArtifact);
266             }
267 
268             String groupId = gav[0];
269             String artifactId = gav[1];
270             String version = gav[2];
271 
272             String type = "jar";
273             if (gav.length > 3) {
274                 type = gav[3];
275             }
276 
277             String classifier = null;
278             if (gav.length == 5) {
279                 classifier = gav[4];
280             }
281 
282             ArtifactType artifactType =
283                     session.getRepositorySession().getArtifactTypeRegistry().get(type);
284 
285             List<RemoteRepository> remoteRepositories =
286                     artifactType != null && "maven-plugin".equals(artifactType.getId())
287                             ? project.getRemotePluginRepositories()
288                             : project.getRemoteProjectRepositories();
289 
290             Artifact artifact = new DefaultArtifact(groupId, artifactId, classifier, null, version, artifactType);
291 
292             resolvePomsForArtifacts(Collections.singletonList(artifact), resolvedArtifacts, remoteRepositories);
293 
294             CollectRequest collectRequest = new CollectRequest();
295             Dependency root = new Dependency(artifact, JavaScopes.COMPILE);
296             collectRequest.setRoot(root);
297             collectRequest.setRepositories(remoteRepositories);
298 
299             DependencyRequest request = new DependencyRequest(collectRequest, classpathFilter);
300             DependencyResult dependencyResult =
301                     repositorySystem.resolveDependencies(session.getRepositorySession(), request);
302 
303             List<Artifact> artifacts = dependencyResult.getArtifactResults().stream()
304                     .map(ArtifactResult::getArtifact)
305                     .collect(Collectors.toList());
306 
307             artifacts.forEach(a -> resolvedArtifacts.put(ArtifactIdUtils.toId(a), a));
308             resolvePomsForArtifacts(artifacts, resolvedArtifacts, collectRequest.getRepositories());
309         }
310     }
311 
312     private void resolvePomsForArtifacts(
313             List<Artifact> artifacts,
314             Map<String, Artifact> resolvedArtifacts,
315             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, Map<String, Artifact> resolvedArtifacts, List<RemoteRepository> remoteRepositories)
326             throws MojoExecutionException, ArtifactResolutionException {
327 
328         if (resolvedArtifacts.containsKey(ArtifactIdUtils.toId(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.put(ArtifactIdUtils.toId(artifact), 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(Map<String, Artifact> resolvedArtifacts) throws InstallationException {
358 
359         RepositorySystemSession systemSessionForLocalRepo = createSystemSessionForLocalRepo();
360 
361         // we can have on dependency two artifacts with the same groupId:artifactId
362         // with different version, in such case when we install both in one request
363         // metadata will contain only one version
364 
365         Map<String, List<Artifact>> collect = resolvedArtifacts.values().stream()
366                 .filter(a -> !hasTheSamePathAsTarget(a, systemSessionForLocalRepo))
367                 .collect(Collectors.groupingBy(
368                         a -> String.format("%s:%s:%s", a.getGroupId(), a.getArtifactId(), a.getVersion()),
369                         LinkedHashMap::new,
370                         Collectors.toList()));
371 
372         for (List<Artifact> artifacts : collect.values()) {
373             InstallRequest request = new InstallRequest();
374             request.setArtifacts(artifacts);
375             repositorySystem.install(systemSessionForLocalRepo, request);
376         }
377     }
378 
379     private boolean hasTheSamePathAsTarget(Artifact artifact, RepositorySystemSession systemSession) {
380         try {
381             LocalRepositoryManager lrm = systemSession.getLocalRepositoryManager();
382             File targetBasedir = lrm.getRepository().getBasedir();
383             if (targetBasedir == null) {
384                 return false;
385             }
386             File targetFile = new File(targetBasedir, lrm.getPathForLocalArtifact(artifact)).getCanonicalFile();
387             File sourceFile = artifact.getFile().getCanonicalFile();
388             if (Objects.equals(targetFile, sourceFile)) {
389                 getLog().debug("Skip install the same target " + sourceFile);
390                 return true;
391             }
392             return false;
393         } catch (IOException e) {
394             throw new UncheckedIOException(e);
395         }
396     }
397 
398     /**
399      * Create a new {@link  RepositorySystemSession} connected with local repo.
400      */
401     private RepositorySystemSession createSystemSessionForLocalRepo() {
402         RepositorySystemSession repositorySystemSession = session.getRepositorySession();
403         if (localRepositoryPath != null) {
404             // "clone" repository session and replace localRepository
405             DefaultRepositorySystemSession newSession =
406                     new DefaultRepositorySystemSession(session.getRepositorySession());
407             // Clear cache, since we're using a new local repository
408             newSession.setCache(new DefaultRepositoryCache());
409             // keep same repositoryType
410             String contentType = newSession.getLocalRepository().getContentType();
411             if ("enhanced".equals(contentType)) {
412                 contentType = "default";
413             }
414             LocalRepositoryManager localRepositoryManager = repositorySystem.newLocalRepositoryManager(
415                     newSession, new LocalRepository(localRepositoryPath, contentType));
416 
417             newSession.setLocalRepositoryManager(localRepositoryManager);
418             repositorySystemSession = newSession;
419             getLog().debug("localRepoPath: "
420                     + localRepositoryManager.getRepository().getBasedir());
421         }
422 
423         return repositorySystemSession;
424     }
425 }