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.resolvers;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.LinkedHashSet;
28  import java.util.List;
29  import java.util.Objects;
30  import java.util.Set;
31  import java.util.jar.JarFile;
32  import java.util.jar.Manifest;
33  
34  import org.apache.maven.artifact.Artifact;
35  import org.apache.maven.plugin.MojoExecutionException;
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.plugins.dependency.utils.DependencyStatusSets;
41  import org.apache.maven.plugins.dependency.utils.DependencyUtil;
42  import org.apache.maven.plugins.dependency.utils.filters.ResolveFileFilter;
43  import org.apache.maven.plugins.dependency.utils.markers.SourcesFileMarkerHandler;
44  import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
45  import org.apache.maven.shared.utils.logging.MessageBuilder;
46  import org.apache.maven.shared.utils.logging.MessageUtils;
47  
48  /**
49   * Goal that resolves the project dependencies from the repository. When using this goal while running on Java 9 the
50   * module names will be visible as well.
51   *
52   * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
53   * @since 2.0
54   */
55  // CHECKSTYLE_OFF: LineLength
56  @Mojo(
57          name = "resolve",
58          requiresDependencyResolution = ResolutionScope.TEST,
59          defaultPhase = LifecyclePhase.GENERATE_SOURCES,
60          threadSafe = true)
61  // CHECKSTYLE_ON: LineLength
62  public class ResolveDependenciesMojo extends AbstractResolveMojo {
63  
64      @Parameter(property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}")
65      private String outputEncoding;
66  
67      /**
68       * If we should display the scope when resolving
69       *
70       * @since 2.0-alpha-2
71       */
72      @Parameter(property = "mdep.outputScope", defaultValue = "true")
73      protected boolean outputScope;
74  
75      /**
76       * Only used to store results for integration test validation
77       */
78      DependencyStatusSets results;
79  
80      /**
81       * Sort the output list of resolved artifacts alphabetically. The default ordering matches the classpath order.
82       *
83       * @since 2.8
84       */
85      @Parameter(property = "sort", defaultValue = "false")
86      boolean sort;
87  
88      /**
89       * Include parent poms in the dependency resolution list.
90       *
91       * @since 2.8
92       */
93      @Parameter(property = "includeParents", defaultValue = "false")
94      boolean includeParents;
95  
96      /**
97       * Main entry into mojo. Gets the list of dependencies and iterates through displaying the resolved version.
98       *
99       * @throws MojoExecutionException with a message if an error occurs
100      */
101     @Override
102     protected void doExecute() throws MojoExecutionException {
103         // get sets of dependencies
104         results = this.getDependencySets(false, includeParents);
105 
106         String output = getOutput(outputAbsoluteArtifactFilename, outputScope, sort);
107         try {
108             if (outputFile == null) {
109                 DependencyUtil.log(output, getLog());
110             } else {
111                 String encoding = Objects.toString(outputEncoding, "UTF-8");
112                 DependencyUtil.write(output, outputFile, appendOutput, encoding);
113             }
114         } catch (IOException e) {
115             throw new MojoExecutionException(e.getMessage(), e);
116         }
117     }
118 
119     /**
120      * @return returns the results
121      */
122     public DependencyStatusSets getResults() {
123         return this.results;
124     }
125 
126     @Override
127     protected ArtifactsFilter getMarkedArtifactFilter() {
128         return new ResolveFileFilter(new SourcesFileMarkerHandler(this.markersDirectory));
129     }
130 
131     /**
132      * @param outputAbsoluteArtifactFilename absolute artifact filename
133      * @param theOutputScope the output scope
134      * @param theSort sort yes/no
135      * @return the output
136      */
137     public String getOutput(boolean outputAbsoluteArtifactFilename, boolean theOutputScope, boolean theSort) {
138         StringBuilder sb = new StringBuilder();
139         sb.append(System.lineSeparator());
140         sb.append("The following files have been resolved:");
141         sb.append(System.lineSeparator());
142         if (results.getResolvedDependencies() == null
143                 || results.getResolvedDependencies().isEmpty()) {
144             sb.append("   none");
145             sb.append(System.lineSeparator());
146         } else {
147             sb.append(buildArtifactListOutput(
148                     results.getResolvedDependencies(), outputAbsoluteArtifactFilename, theOutputScope, theSort));
149         }
150 
151         if (results.getSkippedDependencies() != null
152                 && !results.getSkippedDependencies().isEmpty()) {
153             sb.append(System.lineSeparator());
154             sb.append("The following files were skipped:");
155             sb.append(System.lineSeparator());
156             Set<Artifact> skippedDependencies = new LinkedHashSet<>(results.getSkippedDependencies());
157             sb.append(buildArtifactListOutput(
158                     skippedDependencies, outputAbsoluteArtifactFilename, theOutputScope, theSort));
159         }
160 
161         if (results.getUnResolvedDependencies() != null
162                 && !results.getUnResolvedDependencies().isEmpty()) {
163             sb.append(System.lineSeparator());
164             sb.append("The following files have NOT been resolved:");
165             sb.append(System.lineSeparator());
166             Set<Artifact> unResolvedDependencies = new LinkedHashSet<>(results.getUnResolvedDependencies());
167             sb.append(buildArtifactListOutput(
168                     unResolvedDependencies, outputAbsoluteArtifactFilename, theOutputScope, theSort));
169         }
170         sb.append(System.lineSeparator());
171 
172         return sb.toString();
173     }
174 
175     private StringBuilder buildArtifactListOutput(
176             Set<Artifact> artifacts, boolean outputAbsoluteArtifactFilename, boolean theOutputScope, boolean theSort) {
177         StringBuilder sb = new StringBuilder();
178         List<String> artifactStringList = new ArrayList<>();
179         for (Artifact artifact : artifacts) {
180             MessageBuilder messageBuilder = MessageUtils.buffer();
181 
182             messageBuilder.a("   ");
183 
184             if (theOutputScope) {
185                 messageBuilder.a(artifact.toString());
186             } else {
187                 messageBuilder.a(artifact.getId());
188             }
189 
190             if (outputAbsoluteArtifactFilename) {
191                 try {
192                     // we want to print the absolute file name here
193                     String artifactFilename =
194                             artifact.getFile().getAbsoluteFile().getPath();
195 
196                     messageBuilder.a(':').a(artifactFilename);
197                 } catch (NullPointerException e) {
198                     // ignore the null pointer, we'll output a null string
199                 }
200             }
201 
202             if (theOutputScope && artifact.isOptional()) {
203                 messageBuilder.a(" (optional)");
204             }
205 
206             // dependencies:collect won't download jars
207             if (artifact.getFile() != null) {
208                 ModuleDescriptor moduleDescriptor = getModuleDescriptor(artifact.getFile());
209                 if (moduleDescriptor != null) {
210                     messageBuilder.project(" -- module " + moduleDescriptor.name);
211 
212                     if (moduleDescriptor.automatic) {
213                         if ("MANIFEST".equals(moduleDescriptor.moduleNameSource)) {
214                             messageBuilder.strong(" [auto]");
215                         } else {
216                             messageBuilder.warning(" (auto)");
217                         }
218                     }
219                 }
220             }
221             artifactStringList.add(messageBuilder + System.lineSeparator());
222         }
223         if (theSort) {
224             Collections.sort(artifactStringList);
225         }
226         for (String artifactString : artifactStringList) {
227             sb.append(artifactString);
228         }
229         return sb;
230     }
231 
232     private ModuleDescriptor getModuleDescriptor(File artifactFile) {
233         ModuleDescriptor moduleDescriptor = null;
234         try {
235             // Use Java9 code to get moduleName, don't try to do it better with own implementation
236             Class<?> moduleFinderClass = Class.forName("java.lang.module.ModuleFinder");
237 
238             java.nio.file.Path path = artifactFile.toPath();
239 
240             Method ofMethod = moduleFinderClass.getMethod("of", java.nio.file.Path[].class);
241             Object moduleFinderInstance = ofMethod.invoke(null, new Object[] {new java.nio.file.Path[] {path}});
242 
243             Method findAllMethod = moduleFinderClass.getMethod("findAll");
244             Set<Object> moduleReferences = (Set<Object>) findAllMethod.invoke(moduleFinderInstance);
245 
246             // moduleReferences can be empty when referring to target/classes without module-info.class
247             if (!moduleReferences.isEmpty()) {
248                 Object moduleReference = moduleReferences.iterator().next();
249                 Method descriptorMethod = moduleReference.getClass().getMethod("descriptor");
250                 Object moduleDescriptorInstance = descriptorMethod.invoke(moduleReference);
251 
252                 Method nameMethod = moduleDescriptorInstance.getClass().getMethod("name");
253                 String name = (String) nameMethod.invoke(moduleDescriptorInstance);
254 
255                 moduleDescriptor = new ModuleDescriptor();
256                 moduleDescriptor.name = name;
257 
258                 Method isAutomaticMethod = moduleDescriptorInstance.getClass().getMethod("isAutomatic");
259                 moduleDescriptor.automatic = (Boolean) isAutomaticMethod.invoke(moduleDescriptorInstance);
260 
261                 if (moduleDescriptor.automatic) {
262                     if (artifactFile.isFile()) {
263                         try (JarFile jarFile = new JarFile(artifactFile)) {
264                             Manifest manifest = jarFile.getManifest();
265 
266                             if (manifest != null
267                                     && manifest.getMainAttributes().getValue("Automatic-Module-Name") != null) {
268                                 moduleDescriptor.moduleNameSource = "MANIFEST";
269                             } else {
270                                 moduleDescriptor.moduleNameSource = "FILENAME";
271                             }
272                         } catch (IOException e) {
273                             // noop
274                         }
275                     }
276                 }
277             }
278         } catch (ClassNotFoundException | SecurityException | IllegalAccessException | IllegalArgumentException e) {
279             // do nothing
280         } catch (NoSuchMethodException e) {
281             e.printStackTrace();
282         } catch (InvocationTargetException e) {
283             Throwable cause = e.getCause();
284             while (cause.getCause() != null) {
285                 cause = cause.getCause();
286             }
287             getLog().info("Can't extract module name from " + artifactFile.getName() + ": " + cause.getMessage());
288         }
289         return moduleDescriptor;
290     }
291 
292     private class ModuleDescriptor {
293         String name;
294 
295         boolean automatic = true;
296 
297         String moduleNameSource;
298     }
299 }