View Javadoc
1   package org.apache.maven.plugins.dependency.resolvers;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.artifact.Artifact;
23  
24  import org.apache.maven.plugin.MojoExecutionException;
25  import org.apache.maven.plugins.dependency.utils.DependencyStatusSets;
26  import org.apache.maven.plugins.dependency.utils.DependencyUtil;
27  import org.apache.maven.plugins.dependency.utils.filters.ResolveFileFilter;
28  import org.apache.maven.plugins.dependency.utils.markers.SourcesFileMarkerHandler;
29  import org.apache.maven.plugins.annotations.LifecyclePhase;
30  import org.apache.maven.plugins.annotations.Mojo;
31  import org.apache.maven.plugins.annotations.Parameter;
32  import org.apache.maven.plugins.annotations.ResolutionScope;
33  import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
34  import org.apache.maven.shared.utils.logging.MessageBuilder;
35  import org.apache.maven.shared.utils.logging.MessageUtils;
36  
37  import java.io.File;
38  import java.io.IOException;
39  import java.lang.reflect.InvocationTargetException;
40  import java.lang.reflect.Method;
41  import java.util.ArrayList;
42  import java.util.Collections;
43  import java.util.LinkedHashSet;
44  import java.util.List;
45  import java.util.Objects;
46  import java.util.Set;
47  import java.util.jar.JarFile;
48  import java.util.jar.Manifest;
49  
50  /**
51   * Goal that resolves the project dependencies from the repository. When using this goal while running on Java 9 the
52   * module names will be visible as well.
53   *
54   * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
55   * @since 2.0
56   */
57  //CHECKSTYLE_OFF: LineLength
58  @Mojo( name = "resolve", requiresDependencyResolution = ResolutionScope.TEST, defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true )
59  //CHECKSTYLE_ON: LineLength
60  public class ResolveDependenciesMojo
61      extends AbstractResolveMojo
62  {
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()
103         throws MojoExecutionException
104     {
105         // get sets of dependencies
106         results = this.getDependencySets( false, includeParents );
107 
108         String output = getOutput( outputAbsoluteArtifactFilename, outputScope, sort );
109         try
110         {
111             if ( outputFile == null )
112             {
113                 DependencyUtil.log( output, getLog() );
114             }
115             else
116             {    
117                 String encoding = Objects.toString( outputEncoding, "UTF-8" );
118                 DependencyUtil.write( output, outputFile, appendOutput, encoding );
119             }
120         }
121         catch ( IOException e )
122         {
123             throw new MojoExecutionException( e.getMessage(), e );
124         }
125     }
126 
127     /**
128      * @return returns the results
129      */
130     public DependencyStatusSets getResults()
131     {
132         return this.results;
133     }
134 
135     @Override
136     protected ArtifactsFilter getMarkedArtifactFilter()
137     {
138         return new ResolveFileFilter( new SourcesFileMarkerHandler( this.markersDirectory ) );
139     }
140 
141     /**
142      * @param outputAbsoluteArtifactFilename absolute artifact filename
143      * @param theOutputScope the output scope
144      * @param theSort sort yes/no
145      * @return the output
146      */
147     public String getOutput( boolean outputAbsoluteArtifactFilename, boolean theOutputScope, boolean theSort )
148     {
149         StringBuilder sb = new StringBuilder();
150         sb.append( System.lineSeparator() );
151         sb.append( "The following files have been resolved:" );
152         sb.append( System.lineSeparator() );
153         if ( results.getResolvedDependencies() == null || results.getResolvedDependencies().isEmpty() )
154         {
155             sb.append( "   none" );
156             sb.append( System.lineSeparator() );
157         }
158         else
159         {
160             sb.append( buildArtifactListOutput( results.getResolvedDependencies(), outputAbsoluteArtifactFilename,
161                                                 theOutputScope, theSort ) );
162         }
163 
164         if ( results.getSkippedDependencies() != null && !results.getSkippedDependencies().isEmpty() )
165         {
166             sb.append( System.lineSeparator() );
167             sb.append( "The following files were skipped:" );
168             sb.append( System.lineSeparator() );
169             Set<Artifact> skippedDependencies = new LinkedHashSet<>();
170             skippedDependencies.addAll( results.getSkippedDependencies() );
171             sb.append( buildArtifactListOutput( skippedDependencies, outputAbsoluteArtifactFilename, theOutputScope,
172                                                 theSort ) );
173         }
174 
175         if ( results.getUnResolvedDependencies() != null && !results.getUnResolvedDependencies().isEmpty() )
176         {
177             sb.append( System.lineSeparator() );
178             sb.append( "The following files have NOT been resolved:" );
179             sb.append( System.lineSeparator() );
180             Set<Artifact> unResolvedDependencies = new LinkedHashSet<>();
181             unResolvedDependencies.addAll( results.getUnResolvedDependencies() );
182             sb.append( buildArtifactListOutput( unResolvedDependencies, outputAbsoluteArtifactFilename, theOutputScope,
183                                                 theSort ) );
184         }
185         sb.append( System.lineSeparator() );
186 
187         return sb.toString();
188     }
189 
190     private StringBuilder buildArtifactListOutput( Set<Artifact> artifacts, boolean outputAbsoluteArtifactFilename,
191                                                    boolean theOutputScope, boolean theSort )
192     {
193         StringBuilder sb = new StringBuilder();
194         List<String> artifactStringList = new ArrayList<>();
195         for ( Artifact artifact : artifacts )
196         {
197             MessageBuilder messageBuilder = MessageUtils.buffer();
198 
199             messageBuilder.a( "   " );
200 
201             if ( theOutputScope )
202             {
203                 messageBuilder.a( artifact.toString() );
204             }
205             else
206             {
207                 messageBuilder.a( artifact.getId() );
208             }
209 
210             if ( outputAbsoluteArtifactFilename )
211             {
212                 try
213                 {
214                     // we want to print the absolute file name here
215                     String artifactFilename = artifact.getFile().getAbsoluteFile().getPath();
216 
217                     messageBuilder.a( ':' ).a( artifactFilename );
218                 }
219                 catch ( NullPointerException e )
220                 {
221                     // ignore the null pointer, we'll output a null string
222                 }
223             }
224 
225             if ( theOutputScope && artifact.isOptional() )
226             {
227                 messageBuilder.a( " (optional)" );
228             }
229 
230             // dependencies:collect won't download jars
231             if ( artifact.getFile() != null )
232             {
233                 ModuleDescriptor moduleDescriptor = getModuleDescriptor( artifact.getFile() );
234                 if ( moduleDescriptor != null )
235                 {
236                     messageBuilder.project( " -- module " + moduleDescriptor.name );
237 
238                     if ( moduleDescriptor.automatic )
239                     {
240                         if ( "MANIFEST".equals( moduleDescriptor.moduleNameSource ) )
241                         {
242                             messageBuilder.strong( " [auto]" );
243                         }
244                         else
245                         {
246                             messageBuilder.warning( " (auto)" );
247                         }
248                     }
249                 }
250             }
251             artifactStringList.add( messageBuilder.toString() + System.lineSeparator() );
252         }
253         if ( theSort )
254         {
255             Collections.sort( artifactStringList );
256         }
257         for ( String artifactString : artifactStringList )
258         {
259             sb.append( artifactString );
260         }
261         return sb;
262     }
263 
264     private ModuleDescriptor getModuleDescriptor( File artifactFile )
265     {
266         ModuleDescriptor moduleDescriptor = null;
267         try
268         {
269             // Use Java9 code to get moduleName, don't try to do it better with own implementation
270             Class<?> moduleFinderClass = Class.forName( "java.lang.module.ModuleFinder" );
271 
272             java.nio.file.Path path = artifactFile.toPath();
273 
274             Method ofMethod = moduleFinderClass.getMethod( "of", java.nio.file.Path[].class );
275             Object moduleFinderInstance = ofMethod.invoke( null, new Object[] { new java.nio.file.Path[] { path } } );
276 
277             Method findAllMethod = moduleFinderClass.getMethod( "findAll" );
278             Set<Object> moduleReferences = (Set<Object>) findAllMethod.invoke( moduleFinderInstance );
279 
280             // moduleReferences can be empty when referring to target/classes without module-info.class
281             if ( !moduleReferences.isEmpty() )
282             {
283                 Object moduleReference = moduleReferences.iterator().next();
284                 Method descriptorMethod = moduleReference.getClass().getMethod( "descriptor" );
285                 Object moduleDescriptorInstance = descriptorMethod.invoke( moduleReference );
286 
287                 Method nameMethod = moduleDescriptorInstance.getClass().getMethod( "name" );
288                 String name = (String) nameMethod.invoke( moduleDescriptorInstance );
289 
290                 moduleDescriptor = new ModuleDescriptor();
291                 moduleDescriptor.name = name;
292 
293                 Method isAutomaticMethod = moduleDescriptorInstance.getClass().getMethod( "isAutomatic" );
294                 moduleDescriptor.automatic = (Boolean) isAutomaticMethod.invoke( moduleDescriptorInstance );
295 
296                 if ( moduleDescriptor.automatic )
297                 {
298                     if ( artifactFile.isFile() )
299                     {
300                         JarFile jarFile = null;
301                         try
302                         {
303                             jarFile = new JarFile( artifactFile );
304 
305                             Manifest manifest = jarFile.getManifest();
306 
307                             if ( manifest != null
308                                 && manifest.getMainAttributes().getValue( "Automatic-Module-Name" ) != null )
309                             {
310                                 moduleDescriptor.moduleNameSource = "MANIFEST";
311                             }
312                             else
313                             {
314                                 moduleDescriptor.moduleNameSource = "FILENAME";
315                             }
316                         }
317                         catch ( IOException e )
318                         {
319                             // noop
320                         }
321                         finally
322                         {
323                             if ( jarFile != null )
324                             {
325                                 try
326                                 {
327                                     jarFile.close();
328                                 }
329                                 catch ( IOException e )
330                                 {
331                                     // noop
332                                 }
333                             }
334                         }
335                     }
336                 }
337             }
338         }
339         catch ( ClassNotFoundException | SecurityException | IllegalAccessException | IllegalArgumentException e )
340         {
341             // do nothing
342         }
343         catch ( NoSuchMethodException e )
344         {
345             e.printStackTrace();
346         }
347         catch ( InvocationTargetException e )
348         {
349             Throwable cause = e.getCause();
350             while ( cause.getCause() != null )
351             {
352                 cause = cause.getCause();
353             }
354             getLog().info( "Can't extract module name from " + artifactFile.getName() + ": " + cause.getMessage() );
355         }
356         return moduleDescriptor;
357     }
358 
359     private class ModuleDescriptor
360     {
361         String name;
362 
363         boolean automatic = true;
364 
365         String moduleNameSource;
366     }
367 }