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