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.dependency.fromDependencies;
20  
21  import javax.inject.Inject;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import org.apache.maven.RepositoryUtils;
31  import org.apache.maven.artifact.Artifact;
32  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
33  import org.apache.maven.execution.MavenSession;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugins.annotations.LifecyclePhase;
36  import org.apache.maven.plugins.annotations.Mojo;
37  import org.apache.maven.plugins.annotations.Parameter;
38  import org.apache.maven.plugins.annotations.ResolutionScope;
39  import org.apache.maven.plugins.dependency.utils.CopyUtil;
40  import org.apache.maven.plugins.dependency.utils.DependencyStatusSets;
41  import org.apache.maven.plugins.dependency.utils.DependencyUtil;
42  import org.apache.maven.plugins.dependency.utils.ResolverUtil;
43  import org.apache.maven.plugins.dependency.utils.filters.DestFileFilter;
44  import org.apache.maven.project.MavenProject;
45  import org.apache.maven.project.ProjectBuilder;
46  import org.apache.maven.project.ProjectBuildingRequest;
47  import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
48  import org.apache.maven.shared.transfer.artifact.install.ArtifactInstaller;
49  import org.apache.maven.shared.transfer.artifact.install.ArtifactInstallerException;
50  import org.apache.maven.shared.transfer.repository.RepositoryManager;
51  import org.eclipse.aether.resolution.ArtifactResolutionException;
52  import org.eclipse.aether.util.artifact.SubArtifact;
53  import org.sonatype.plexus.build.incremental.BuildContext;
54  
55  /**
56   * Goal that copies the files for a project's dependencies from the repository to a directory.
57   * The default location to copy to is target/dependencies.
58   * Since all files are copied to the same directory by default, a dependency that
59   * has the same file name as another dependency will be overwritten.
60   *
61   * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
62   * @since 1.0
63   */
64  @Mojo(
65          name = "copy-dependencies",
66          requiresDependencyResolution = ResolutionScope.TEST,
67          defaultPhase = LifecyclePhase.PROCESS_SOURCES,
68          threadSafe = true)
69  public class CopyDependenciesMojo extends AbstractFromDependenciesMojo {
70      /**
71       * Also copy the pom of each artifact.
72       *
73       * @since 2.0
74       */
75      @Parameter(property = "mdep.copyPom", defaultValue = "false")
76      protected boolean copyPom;
77  
78      private final CopyUtil copyUtil;
79  
80      private final ArtifactInstaller installer;
81  
82      private final RepositoryManager repositoryManager;
83  
84      /**
85       * Either append the artifact's baseVersion or uniqueVersion to the filename. Will only be used if
86       * {@link #isStripVersion()} is {@code false}.
87       *
88       * @since 2.6
89       */
90      @Parameter(property = "mdep.useBaseVersion", defaultValue = "true")
91      protected boolean useBaseVersion = true;
92  
93      /**
94       * Add parent poms to the list of copied dependencies (both current project pom parents and dependencies parents).
95       *
96       * @since 2.8
97       */
98      @Parameter(property = "mdep.addParentPoms", defaultValue = "false")
99      protected boolean addParentPoms;
100 
101     /**
102      * Also copy the signature files (.asc) of each artifact.
103      *
104      * @since 3.2.0
105      */
106     @Parameter(property = "mdep.copySignatures", defaultValue = "false")
107     protected boolean copySignatures;
108 
109     @Inject
110     @SuppressWarnings("checkstyle:ParameterNumber")
111     public CopyDependenciesMojo(
112             MavenSession session,
113             BuildContext buildContext,
114             MavenProject project,
115             ResolverUtil resolverUtil,
116             RepositoryManager repositoryManager,
117             ProjectBuilder projectBuilder,
118             ArtifactHandlerManager artifactHandlerManager,
119             CopyUtil copyUtil,
120             ArtifactInstaller installer) {
121         super(session, buildContext, project, resolverUtil, projectBuilder, artifactHandlerManager);
122         this.copyUtil = copyUtil;
123         this.installer = installer;
124         this.repositoryManager = repositoryManager;
125     }
126 
127     /**
128      * Main entry into mojo. Gets the list of dependencies and iterates through calling copyArtifact.
129      *
130      * @throws MojoExecutionException with a message if an error occurs
131      * @see #getDependencySets(boolean, boolean)
132      * @see #copyArtifact(Artifact, boolean, boolean, boolean, boolean)
133      */
134     @Override
135     protected void doExecute() throws MojoExecutionException {
136         DependencyStatusSets dss = getDependencySets(this.failOnMissingClassifierArtifact, addParentPoms);
137         Set<Artifact> artifacts = dss.getResolvedDependencies();
138 
139         if (!useRepositoryLayout) {
140             Map<String, Integer> copies = new HashMap<>();
141             for (Artifact artifactItem : artifacts) {
142                 String destFileName = DependencyUtil.getFormattedFileName(
143                         artifactItem, stripVersion, prependGroupId, useBaseVersion, stripClassifier);
144                 int numCopies = copies.getOrDefault(destFileName, 0);
145                 copies.put(destFileName, numCopies + 1);
146             }
147             for (Map.Entry<String, Integer> entry : copies.entrySet()) {
148                 if (entry.getValue() > 1) {
149                     getLog().warn("Multiple files with the name " + entry.getKey() + " in the dependency tree.");
150                     getLog().warn(
151                                     "Not all JARs will be available. Consider using prependGroupId, useSubDirectoryPerArtifact, or useRepositoryLayout.");
152                 }
153             }
154 
155             for (Artifact artifact : artifacts) {
156                 copyArtifact(
157                         artifact, isStripVersion(), this.prependGroupId, this.useBaseVersion, this.stripClassifier);
158             }
159         } else {
160             ProjectBuildingRequest buildingRequest =
161                     repositoryManager.setLocalRepositoryBasedir(session.getProjectBuildingRequest(), outputDirectory);
162 
163             artifacts.forEach(artifact -> installArtifact(artifact, buildingRequest));
164         }
165 
166         Set<Artifact> skippedArtifacts = dss.getSkippedDependencies();
167         for (Artifact artifact : skippedArtifacts) {
168             getLog().info(artifact.getId() + " already exists in destination.");
169         }
170 
171         if (isCopyPom() && !useRepositoryLayout) {
172             copyPoms(getOutputDirectory(), artifacts, this.stripVersion);
173             copyPoms(getOutputDirectory(), skippedArtifacts, this.stripVersion, this.stripClassifier);
174             // Artifacts that already exist may not yet have poms
175         }
176     }
177 
178     /**
179      * Install the artifact and the corresponding pom if copyPoms=true.
180      */
181     private void installArtifact(Artifact artifact, ProjectBuildingRequest buildingRequest) {
182         try {
183             installer.install(buildingRequest, Collections.singletonList(artifact));
184             installBaseSnapshot(artifact, buildingRequest);
185 
186             if (!"pom".equals(artifact.getType()) && isCopyPom()) {
187                 Artifact pomArtifact = getResolvedPomArtifact(artifact);
188                 if (pomArtifact != null
189                         && pomArtifact.getFile() != null
190                         && pomArtifact.getFile().exists()) {
191                     installer.install(buildingRequest, Collections.singletonList(pomArtifact));
192                     installBaseSnapshot(pomArtifact, buildingRequest);
193                 }
194             }
195         } catch (ArtifactInstallerException e) {
196             getLog().warn("unable to install " + artifact, e);
197         }
198     }
199 
200     private void installBaseSnapshot(Artifact artifact, ProjectBuildingRequest buildingRequest)
201             throws ArtifactInstallerException {
202         if (artifact.isSnapshot() && !artifact.getBaseVersion().equals(artifact.getVersion())) {
203             String version = artifact.getVersion();
204             try {
205                 artifact.setVersion(artifact.getBaseVersion());
206                 installer.install(buildingRequest, Collections.singletonList(artifact));
207             } finally {
208                 artifact.setVersion(version);
209             }
210         }
211     }
212 
213     /**
214      * Copies the Artifact after building the destination file name if overridden. This method also checks if the
215      * classifier is set and adds it to the destination file name if needed.
216      *
217      * @param artifact the object to be copied
218      * @param removeVersion specifies if the version should be removed from the file name when copying
219      * @param prependGroupId specifies if the group ID should be prefixed to the file while copying
220      * @param theUseBaseVersion specifies if the baseVersion of the artifact should be used instead of the version
221      * @throws MojoExecutionException with a message if an error occurs
222      * @see #copyArtifact(Artifact, boolean, boolean, boolean, boolean)
223      */
224     protected void copyArtifact(
225             Artifact artifact, boolean removeVersion, boolean prependGroupId, boolean theUseBaseVersion)
226             throws MojoExecutionException {
227         copyArtifact(artifact, removeVersion, prependGroupId, theUseBaseVersion, false);
228     }
229 
230     /**
231      * Copies the Artifact after building the destination file name if overridden. This method also checks if the
232      * classifier is set and adds it to the destination file name if needed.
233      *
234      * @param artifact the object to be copied
235      * @param removeVersion specifies if the version should be removed from the file name when copying
236      * @param prependGroupId specifies if the groupId should be prefixed to the file while copying
237      * @param useBaseVersion specifies if the baseVersion of the artifact should be used instead of the version
238      * @param removeClassifier specifies if the classifier should be removed from the file name when copying
239      * @throws MojoExecutionException with a message if an error occurs
240      * @see CopyUtil#copyArtifactFile(Artifact, File)
241      * @see DependencyUtil#getFormattedOutputDirectory(boolean, boolean, boolean, boolean, boolean, boolean, File, Artifact)
242      */
243     private static final String SIGNATURE_EXTENSION = ".asc";
244 
245     protected void copyArtifact(
246             Artifact artifact,
247             boolean removeVersion,
248             boolean prependGroupId,
249             boolean useBaseVersion,
250             boolean removeClassifier)
251             throws MojoExecutionException {
252 
253         String destFileName = DependencyUtil.getFormattedFileName(
254                 artifact, removeVersion, prependGroupId, useBaseVersion, removeClassifier);
255 
256         File destDir = DependencyUtil.getFormattedOutputDirectory(
257                 useSubDirectoryPerScope,
258                 useSubDirectoryPerType,
259                 useSubDirectoryPerArtifact,
260                 useRepositoryLayout,
261                 stripVersion,
262                 stripType,
263                 outputDirectory,
264                 artifact);
265         File destFile = new File(destDir, destFileName);
266         if (destFile.exists()) {
267             getLog().warn("Overwriting " + destFile);
268         }
269         try {
270             copyUtil.copyArtifactFile(artifact, destFile);
271 
272             // Copy the signature file if the copySignatures flag is true
273             if (copySignatures) {
274                 copySignatureFile(artifact, destDir, destFileName);
275             }
276 
277         } catch (IOException e) {
278             throw new MojoExecutionException(
279                     "Failed to copy artifact '" + artifact + "' (" + artifact.getFile() + ") to " + destFile, e);
280         }
281     }
282 
283     /**
284      * Copies the signature file of the artifact to the destination directory, if it exists or can be resolved.
285      * If the signature file does not exist and cannot be resolved, a warning is logged.
286      *
287      * @param artifact the artifact whose signature file should be copied
288      * @param destDir the destination directory
289      * @param destFileName the destination file name without the extension
290      */
291     private void copySignatureFile(Artifact artifact, File destDir, String destFileName) {
292         File signatureFile = new File(artifact.getFile().getAbsolutePath() + SIGNATURE_EXTENSION);
293 
294         if (!signatureFile.exists()) {
295             try {
296                 org.eclipse.aether.artifact.Artifact aArtifact = RepositoryUtils.toArtifact(artifact);
297                 org.eclipse.aether.artifact.Artifact aSignatureArtifact =
298                         new SubArtifact(aArtifact, null, "jar" + SIGNATURE_EXTENSION);
299                 org.eclipse.aether.artifact.Artifact resolvedSignature = getResolverUtil()
300                         .resolveArtifact(aSignatureArtifact, getProject().getRemoteProjectRepositories());
301                 signatureFile = resolvedSignature.getFile();
302             } catch (ArtifactResolutionException e) {
303                 getLog().warn("Failed to resolve signature file for artifact: " + artifact, e);
304             }
305         }
306 
307         if (signatureFile != null && signatureFile.exists()) {
308             File signatureDestFile = new File(destDir, destFileName + SIGNATURE_EXTENSION);
309             try {
310                 copyUtil.copyFile(signatureFile, signatureDestFile);
311             } catch (IOException e) {
312                 getLog().warn("Failed to copy signature file: " + signatureFile, e);
313             }
314         } else {
315             getLog().warn("Signature file for artifact " + artifact + " not found and could not be resolved.");
316         }
317     }
318 
319     /**
320      * Copy the pom files associated with the artifacts.
321      *
322      * @param destDir the destination directory {@link File}
323      * @param artifacts the artifacts {@link Artifact}
324      * @param removeVersion remove version or not
325      * @throws MojoExecutionException in case of errors
326      */
327     public void copyPoms(File destDir, Set<Artifact> artifacts, boolean removeVersion) throws MojoExecutionException {
328 
329         copyPoms(destDir, artifacts, removeVersion, false);
330     }
331 
332     /**
333      * Copy the pom files associated with the artifacts.
334      *
335      * @param destDir the destination directory {@link File}
336      * @param artifacts the artifacts {@link Artifact}
337      * @param removeVersion remove version or not
338      * @param removeClassifier remove the classifier or not
339      * @throws MojoExecutionException in case of errors
340      */
341     public void copyPoms(File destDir, Set<Artifact> artifacts, boolean removeVersion, boolean removeClassifier)
342             throws MojoExecutionException {
343 
344         for (Artifact artifact : artifacts) {
345             Artifact pomArtifact = getResolvedPomArtifact(artifact);
346 
347             // Copy the pom
348             if (pomArtifact != null
349                     && pomArtifact.getFile() != null
350                     && pomArtifact.getFile().exists()) {
351                 File pomDestFile = new File(
352                         destDir,
353                         DependencyUtil.getFormattedFileName(
354                                 pomArtifact, removeVersion, prependGroupId, useBaseVersion, removeClassifier));
355                 if (!pomDestFile.exists()) {
356                     try {
357                         copyUtil.copyArtifactFile(pomArtifact, pomDestFile);
358                     } catch (IOException e) {
359                         throw new MojoExecutionException(
360                                 "Failed to copy artifact '" + pomArtifact + "' (" + pomArtifact.getFile() + ") to "
361                                         + pomDestFile,
362                                 e);
363                     }
364                 }
365             }
366         }
367     }
368 
369     /**
370      * @param artifact {@link Artifact}
371      * @return {@link Artifact}
372      */
373     protected Artifact getResolvedPomArtifact(Artifact artifact) {
374 
375         Artifact pomArtifact = null;
376         // Resolve the pom artifact using repos
377         try {
378             org.eclipse.aether.artifact.Artifact aArtifact = RepositoryUtils.toArtifact(artifact);
379             org.eclipse.aether.artifact.Artifact aPomArtifact = new SubArtifact(aArtifact, null, "pom");
380             org.eclipse.aether.artifact.Artifact resolvedPom =
381                     getResolverUtil().resolveArtifact(aPomArtifact, getProject().getRemoteProjectRepositories());
382             pomArtifact = RepositoryUtils.toArtifact(resolvedPom);
383         } catch (ArtifactResolutionException e) {
384             getLog().info(e.getMessage());
385         }
386         return pomArtifact;
387     }
388 
389     @Override
390     protected ArtifactsFilter getMarkedArtifactFilter() {
391         return new DestFileFilter(
392                 this.overWriteReleases,
393                 this.overWriteSnapshots,
394                 this.overWriteIfNewer,
395                 this.useSubDirectoryPerArtifact,
396                 this.useSubDirectoryPerType,
397                 this.useSubDirectoryPerScope,
398                 this.useRepositoryLayout,
399                 this.stripVersion,
400                 this.prependGroupId,
401                 this.useBaseVersion,
402                 this.outputDirectory);
403     }
404 
405     /**
406      * @return true, if the pom of each artifact must be copied
407      */
408     public boolean isCopyPom() {
409         return this.copyPom;
410     }
411 
412     /**
413      * @param copyPom true if the pom of each artifact must be copied
414      */
415     public void setCopyPom(boolean copyPom) {
416         this.copyPom = copyPom;
417     }
418 }