View Javadoc
1   package org.apache.maven.plugins.shade.mojo;
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  import org.apache.maven.artifact.repository.ArtifactRepository;
24  import org.apache.maven.execution.MavenSession;
25  import org.apache.maven.model.Dependency;
26  import org.apache.maven.model.Exclusion;
27  import org.apache.maven.model.Model;
28  import org.apache.maven.plugin.AbstractMojo;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugins.annotations.Component;
31  import org.apache.maven.plugins.annotations.LifecyclePhase;
32  import org.apache.maven.plugins.annotations.Mojo;
33  import org.apache.maven.plugins.annotations.Parameter;
34  import org.apache.maven.plugins.annotations.ResolutionScope;
35  import org.apache.maven.plugins.shade.ShadeRequest;
36  import org.apache.maven.plugins.shade.Shader;
37  import org.apache.maven.plugins.shade.filter.Filter;
38  import org.apache.maven.plugins.shade.filter.MinijarFilter;
39  import org.apache.maven.plugins.shade.filter.SimpleFilter;
40  import org.apache.maven.plugins.shade.pom.PomWriter;
41  import org.apache.maven.plugins.shade.relocation.Relocator;
42  import org.apache.maven.plugins.shade.relocation.SimpleRelocator;
43  import org.apache.maven.plugins.shade.resource.ResourceTransformer;
44  import org.apache.maven.project.DefaultProjectBuildingRequest;
45  import org.apache.maven.project.MavenProject;
46  import org.apache.maven.project.MavenProjectHelper;
47  import org.apache.maven.project.ProjectBuilder;
48  import org.apache.maven.project.ProjectBuildingException;
49  import org.apache.maven.project.ProjectBuildingRequest;
50  import org.apache.maven.project.ProjectBuildingResult;
51  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
52  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
53  import org.apache.maven.shared.dependency.graph.DependencyNode;
54  import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;
55  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;
56  import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
57  import org.codehaus.plexus.PlexusConstants;
58  import org.codehaus.plexus.PlexusContainer;
59  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
60  import org.codehaus.plexus.context.Context;
61  import org.codehaus.plexus.context.ContextException;
62  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
63  import org.codehaus.plexus.util.IOUtil;
64  import org.codehaus.plexus.util.WriterFactory;
65  
66  import java.io.File;
67  import java.io.FileInputStream;
68  import java.io.FileOutputStream;
69  import java.io.IOException;
70  import java.io.InputStream;
71  import java.io.OutputStream;
72  import java.io.Writer;
73  import java.util.ArrayList;
74  import java.util.Arrays;
75  import java.util.Collections;
76  import java.util.HashMap;
77  import java.util.HashSet;
78  import java.util.LinkedHashSet;
79  import java.util.List;
80  import java.util.Map;
81  import java.util.Set;
82  
83  /**
84   * Mojo that performs shading delegating to the Shader component.
85   *
86   * @author Jason van Zyl
87   * @author Mauro Talevi
88   * @author David Blevins
89   * @author Hiram Chirino
90   */
91  // CHECKSTYLE_OFF: LineLength
92  @Mojo( name = "shade", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true, requiresDependencyResolution = ResolutionScope.RUNTIME )
93  // CHECKSTYLE_ON: LineLength
94  public class ShadeMojo
95      extends AbstractMojo
96      implements Contextualizable
97  {
98      /**
99       * The current Maven session.
100      */
101     @Parameter( defaultValue = "${session}", readonly = true, required = true )
102     private MavenSession session;
103 
104     /**
105      * The current Maven project.
106      */
107     @Parameter( defaultValue = "${project}", readonly = true, required = true )
108     private MavenProject project;
109 
110     @Component
111     private MavenProjectHelper projectHelper;
112 
113     @Component( hint = "default", role = org.apache.maven.plugins.shade.Shader.class )
114     private Shader shader;
115 
116     /**
117      * The dependency graph builder to use.
118      */
119     @Component
120     private DependencyGraphBuilder dependencyGraphBuilder;
121 
122     /**
123      * ProjectBuilder, needed to create projects from the artifacts.
124      */
125     @Component
126     private ProjectBuilder projectBuilder;
127 
128     /**
129      * Remote repositories which will be searched for source attachments.
130      */
131     @Parameter( readonly = true, required = true, defaultValue = "${project.remoteArtifactRepositories}" )
132     protected List<ArtifactRepository> remoteArtifactRepositories;
133 
134     /**
135      * Local maven repository.
136      */
137     @Parameter( readonly = true, required = true, defaultValue = "${localRepository}" )
138     protected ArtifactRepository localRepository;
139 
140     /**
141      * Artifact resolver, needed to download source jars for inclusion in classpath.
142      */
143     @Component
144     protected ArtifactResolver artifactResolver;
145 
146     /**
147      * Artifacts to include/exclude from the final artifact. Artifacts are denoted by composite identifiers of the
148      * general form <code>groupId:artifactId:type:classifier</code>. Since version 1.3, the wildcard characters '*' and
149      * '?' can be used within the sub parts of those composite identifiers to do pattern matching. For convenience, the
150      * syntax <code>groupId</code> is equivalent to <code>groupId:*:*:*</code>, <code>groupId:artifactId</code> is
151      * equivalent to <code>groupId:artifactId:*:*</code> and <code>groupId:artifactId:classifier</code> is equivalent to
152      * <code>groupId:artifactId:*:classifier</code>. For example:
153      * 
154      * <pre>
155      * &lt;artifactSet&gt;
156      *   &lt;includes&gt;
157      *     &lt;include&gt;org.apache.maven:*&lt;/include&gt;
158      *   &lt;/includes&gt;
159      *   &lt;excludes&gt;
160      *     &lt;exclude&gt;*:maven-core&lt;/exclude&gt;
161      *   &lt;/excludes&gt;
162      * &lt;/artifactSet&gt;
163      * </pre>
164      */
165     @Parameter
166     private ArtifactSet artifactSet;
167 
168     /**
169      * Packages to be relocated. For example:
170      * 
171      * <pre>
172      * &lt;relocations&gt;
173      *   &lt;relocation&gt;
174      *     &lt;pattern&gt;org.apache&lt;/pattern&gt;
175      *     &lt;shadedPattern&gt;hidden.org.apache&lt;/shadedPattern&gt;
176      *     &lt;includes&gt;
177      *       &lt;include&gt;org.apache.maven.*&lt;/include&gt;
178      *     &lt;/includes&gt;
179      *     &lt;excludes&gt;
180      *       &lt;exclude&gt;org.apache.maven.Public*&lt;/exclude&gt;
181      *     &lt;/excludes&gt;
182      *   &lt;/relocation&gt;
183      * &lt;/relocations&gt;
184      * </pre>
185      * 
186      * <em>Note:</em> Support for includes exists only since version 1.4.
187      */
188     @SuppressWarnings( "MismatchedReadAndWriteOfArray" )
189     @Parameter
190     private PackageRelocation[] relocations;
191 
192     /**
193      * Resource transformers to be used. Please see the "Examples" section for more information on available
194      * transformers and their configuration.
195      */
196     @Parameter
197     private ResourceTransformer[] transformers;
198 
199     /**
200      * Archive Filters to be used. Allows you to specify an artifact in the form of a composite identifier as used by
201      * {@link #artifactSet} and a set of include/exclude file patterns for filtering which contents of the archive are
202      * added to the shaded jar. From a logical perspective, includes are processed before excludes, thus it's possible
203      * to use an include to collect a set of files from the archive then use excludes to further reduce the set. By
204      * default, all files are included and no files are excluded. If multiple filters apply to an artifact, the
205      * intersection of the matched files will be included in the final JAR. For example:
206      * 
207      * <pre>
208      * &lt;filters&gt;
209      *   &lt;filter&gt;
210      *     &lt;artifact&gt;junit:junit&lt;/artifact&gt;
211      *     &lt;includes&gt;
212      *       &lt;include&gt;org/junit/**&lt;/include&gt;
213      *     &lt;/includes&gt;
214      *     &lt;excludes&gt;
215      *       &lt;exclude&gt;org/junit/experimental/**&lt;/exclude&gt;
216      *     &lt;/excludes&gt;
217      *   &lt;/filter&gt;
218      * &lt;/filters&gt;
219      * </pre>
220      */
221     @SuppressWarnings( "MismatchedReadAndWriteOfArray" )
222     @Parameter
223     private ArchiveFilter[] filters;
224 
225     /**
226      * The destination directory for the shaded artifact.
227      */
228     @Parameter( defaultValue = "${project.build.directory}" )
229     private File outputDirectory;
230 
231     /**
232      * The name of the shaded artifactId.
233      * <p/>
234      * If you like to change the name of the native artifact, you may use the &lt;build>&lt;finalName> setting. If this
235      * is set to something different than &lt;build>&lt;finalName>, no file replacement will be performed, even if
236      * shadedArtifactAttached is being used.
237      */
238     @Parameter
239     private String finalName;
240 
241     /**
242      * The name of the shaded artifactId. So you may want to use a different artifactId and keep the standard version.
243      * If the original artifactId was "foo" then the final artifact would be something like foo-1.0.jar. So if you
244      * change the artifactId you might have something like foo-special-1.0.jar.
245      */
246     @Parameter( defaultValue = "${project.artifactId}" )
247     private String shadedArtifactId;
248 
249     /**
250      * If specified, this will include only artifacts which have groupIds which start with this.
251      */
252     @Parameter
253     private String shadedGroupFilter;
254 
255     /**
256      * Defines whether the shaded artifact should be attached as classifier to the original artifact. If false, the
257      * shaded jar will be the main artifact of the project
258      */
259     @Parameter
260     private boolean shadedArtifactAttached;
261 
262     /**
263      * Flag whether to generate a simplified POM for the shaded artifact. If set to <code>true</code>, dependencies that
264      * have been included into the uber JAR will be removed from the <code>&lt;dependencies&gt;</code> section of the
265      * generated POM. The reduced POM will be named <code>dependency-reduced-pom.xml</code> and is stored into the same
266      * directory as the shaded artifact. Unless you also specify dependencyReducedPomLocation, the plugin will create a
267      * temporary file named <code>dependency-reduced-pom.xml</code> in the project basedir.
268      */
269     @Parameter( defaultValue = "true" )
270     private boolean createDependencyReducedPom;
271 
272     /**
273      * Where to put the dependency reduced pom. Note: setting a value for this parameter with a directory other than
274      * ${basedir} will change the value of ${basedir} for all executions that come after the shade execution. This is
275      * often not what you want. This is considered an open issue with this plugin.
276      *
277      * @since 1.7
278      */
279     @Parameter( defaultValue = "${basedir}/dependency-reduced-pom.xml" )
280     private File dependencyReducedPomLocation;
281 
282     /**
283      * Create a dependency-reduced POM in ${basedir}/drp-UNIQUE.pom. This avoids build collisions of parallel builds
284      * without moving the dependency-reduced POM to a different directory. The property
285      * maven.shade.dependency-reduced-pom is set to the generated filename.
286      *
287      * @since 1.7.2
288      */
289     @Parameter( defaultValue = "false" )
290     private boolean generateUniqueDependencyReducedPom;
291 
292     /**
293      * When true, dependencies are kept in the pom but with scope 'provided'; when false, the dependency is removed.
294      */
295     @Parameter
296     private boolean keepDependenciesWithProvidedScope;
297 
298     /**
299      * When true, transitive deps of removed dependencies are promoted to direct dependencies. This should allow the
300      * drop in replacement of the removed deps with the new shaded jar and everything should still work.
301      */
302     @Parameter
303     private boolean promoteTransitiveDependencies;
304 
305     /**
306      * The name of the classifier used in case the shaded artifact is attached.
307      */
308     @Parameter( defaultValue = "shaded" )
309     private String shadedClassifierName;
310 
311     /**
312      * When true, it will attempt to create a sources jar as well
313      */
314     @Parameter
315     private boolean createSourcesJar;
316 
317     /**
318      * When true, it will attempt to create a test sources jar.
319      */
320     @Parameter
321     private boolean createTestSourcesJar;
322 
323     /**
324      * When true, it will attempt to shade the contents of the java source files when creating the sources jar. When
325      * false, it will just relocate the java source files to the shaded paths, but will not modify the actual contents
326      * of the java source files.
327      */
328     @Parameter( property = "shadeSourcesContent", defaultValue = "false" )
329     private boolean shadeSourcesContent;
330 
331     /**
332      * When true, dependencies will be stripped down on the class level to only the transitive hull required for the
333      * artifact. <em>Note:</em> Usage of this feature requires Java 1.5 or higher.
334      *
335      * @since 1.4
336      */
337     @Parameter
338     private boolean minimizeJar;
339 
340     /**
341      * The path to the output file for the shaded artifact. When this parameter is set, the created archive will neither
342      * replace the project's main artifact nor will it be attached. Hence, this parameter causes the parameters
343      * {@link #finalName}, {@link #shadedArtifactAttached}, {@link #shadedClassifierName} and
344      * {@link #createDependencyReducedPom} to be ignored when used.
345      *
346      * @since 1.3
347      */
348     @Parameter
349     private File outputFile;
350 
351     /**
352      * You can pass here the roleHint about your own Shader implementation plexus component.
353      *
354      * @since 1.6
355      */
356     @Parameter
357     private String shaderHint;
358 
359     /**
360      * When true, the version of each dependency of the reduced pom will be based on the baseVersion of the original
361      * dependency instead of its resolved version. For example, if the original pom (transitively) depends on
362      * a:a:2.7-SNAPSHOT, if useBaseVersion is set to false, the reduced pom will depend on a:a:2.7-20130312.222222-12
363      * whereas if useBaseVersion is set to true, the reduced pom will depend on a:a:2.7-SNAPSHOT
364      *
365      * @since 3.0
366      */
367     @Parameter( defaultValue = "false" )
368     private boolean useBaseVersion;
369 
370     /**
371      * When true, creates a shaded test-jar artifact as well.
372      */
373     @Parameter( defaultValue = "false" )
374     private boolean shadeTestJar;
375 
376     /**
377      * @since 1.6
378      */
379     private PlexusContainer plexusContainer;
380 
381     public void contextualize( Context context )
382         throws ContextException
383     {
384         plexusContainer = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
385     }
386 
387     /**
388      * @throws MojoExecutionException in case of an error.
389      */
390     public void execute()
391         throws MojoExecutionException
392     {
393 
394         setupHintedShader();
395 
396         Set<File> artifacts = new LinkedHashSet<>();
397         Set<String> artifactIds = new LinkedHashSet<>();
398         Set<File> sourceArtifacts = new LinkedHashSet<>();
399         Set<File> testArtifacts = new LinkedHashSet<>();
400         Set<File> testSourceArtifacts = new LinkedHashSet<>();
401 
402         ArtifactSelector artifactSelector =
403             new ArtifactSelector( project.getArtifact(), artifactSet, shadedGroupFilter );
404 
405         if ( artifactSelector.isSelected( project.getArtifact() ) && !"pom".equals( project.getArtifact().getType() ) )
406         {
407             if ( invalidMainArtifact() )
408             {
409                 createErrorOutput();
410                 throw new MojoExecutionException( "Failed to create shaded artifact, "
411                     + "project main artifact does not exist." );
412             }
413 
414             artifacts.add( project.getArtifact().getFile() );
415 
416             if ( createSourcesJar )
417             {
418                 File file = shadedSourcesArtifactFile();
419                 if ( file.isFile() )
420                 {
421                     sourceArtifacts.add( file );
422                 }
423             }
424 
425             if ( shadeTestJar )
426             {
427                 File file = shadedTestArtifactFile();
428                 if ( file.isFile() )
429                 {
430                     testArtifacts.add( file );
431                 }
432             }
433 
434             if ( createTestSourcesJar )
435             {
436                 File file = shadedTestSourcesArtifactFile();
437                 if ( file.isFile() )
438                 {
439                     testSourceArtifacts.add( file );
440                 }
441             }
442         }
443 
444         processArtifactSelectors( artifacts, artifactIds, sourceArtifacts, testArtifacts, testSourceArtifacts,
445             artifactSelector );
446 
447         File outputJar = ( outputFile != null ) ? outputFile : shadedArtifactFileWithClassifier();
448         File sourcesJar = shadedSourceArtifactFileWithClassifier();
449         File testJar = shadedTestArtifactFileWithClassifier();
450         File testSourcesJar = shadedTestSourceArtifactFileWithClassifier();
451 
452         // Now add our extra resources
453         try
454         {
455             List<Filter> filters = getFilters();
456 
457             List<Relocator> relocators = getRelocators();
458 
459             List<ResourceTransformer> resourceTransformers = getResourceTransformers();
460 
461             ShadeRequest shadeRequest = shadeRequest( artifacts, outputJar, filters, relocators, resourceTransformers );
462 
463             shader.shade( shadeRequest );
464 
465             if ( createSourcesJar )
466             {
467                 ShadeRequest shadeSourcesRequest =
468                     createShadeSourcesRequest( sourceArtifacts, sourcesJar, filters, relocators, resourceTransformers );
469 
470                 shader.shade( shadeSourcesRequest );
471             }
472 
473             if ( shadeTestJar )
474             {
475                 ShadeRequest shadeTestRequest =
476                     shadeRequest( testArtifacts, testJar, filters, relocators, resourceTransformers );
477 
478                 shader.shade( shadeTestRequest );
479             }
480 
481             if ( createTestSourcesJar )
482             {
483                 ShadeRequest shadeTestSourcesRequest =
484                     createShadeSourcesRequest( testSourceArtifacts, testSourcesJar, filters, relocators,
485                         resourceTransformers );
486 
487                 shader.shade( shadeTestSourcesRequest );
488             }
489 
490             if ( outputFile == null )
491             {
492                 boolean renamed = false;
493 
494                 // rename the output file if a specific finalName is set
495                 // but don't rename if the finalName is the <build><finalName>
496                 // because this will be handled implicitly later
497                 if ( finalName != null && finalName.length() > 0 //
498                     && !finalName.equals( project.getBuild().getFinalName() ) )
499                 {
500                     String finalFileName = finalName + "." + project.getArtifact().getArtifactHandler().getExtension();
501                     File finalFile = new File( outputDirectory, finalFileName );
502                     replaceFile( finalFile, outputJar );
503                     outputJar = finalFile;
504 
505                     // Also support the sources JAR
506                     if ( createSourcesJar )
507                     {
508                         finalFileName = finalName + "-sources.jar";
509                         finalFile = new File( outputDirectory, finalFileName );
510                         replaceFile( finalFile, sourcesJar );
511                         sourcesJar = finalFile;
512                     }
513 
514                     // Also support the test JAR
515                     if ( shadeTestJar )
516                     {
517                         finalFileName = finalName + "-tests.jar";
518                         finalFile = new File( outputDirectory, finalFileName );
519                         replaceFile( finalFile, testJar );
520                         testJar = finalFile;
521                     }
522 
523                     if ( createTestSourcesJar )
524                     {
525                         finalFileName = finalName + "-test-sources.jar";
526                         finalFile = new File( outputDirectory, finalFileName );
527                         replaceFile( finalFile, testSourcesJar );
528                         testSourcesJar = finalFile;
529                     }
530                 
531                     renamed = true;
532                 }
533 
534                 if ( shadedArtifactAttached )
535                 {
536                     getLog().info( "Attaching shaded artifact." );
537                     projectHelper.attachArtifact( project, project.getArtifact().getType(), shadedClassifierName,
538                                                   outputJar );
539                     if ( createSourcesJar )
540                     {
541                         projectHelper.attachArtifact( project, "java-source", shadedClassifierName + "-sources",
542                                                       sourcesJar );
543                     }
544 
545                     if ( shadeTestJar )
546                     {
547                         projectHelper.attachArtifact( project, "test-jar", shadedClassifierName + "-tests",
548                                                       testJar );
549                     }
550 
551                     if ( createTestSourcesJar )
552                     {
553                         projectHelper.attachArtifact( project, "java-source",
554                                 shadedClassifierName + "-test-sources", testSourcesJar );
555                     }
556                 }
557                 else if ( !renamed )
558                 {
559                     getLog().info( "Replacing original artifact with shaded artifact." );
560                     File originalArtifact = project.getArtifact().getFile();
561                     if ( originalArtifact != null )
562                     {
563                         replaceFile( originalArtifact, outputJar );
564 
565                         if ( createSourcesJar )
566                         {
567                             getLog().info( "Replacing original source artifact with shaded source artifact." );
568                             File shadedSources = shadedSourcesArtifactFile();
569 
570                             replaceFile( shadedSources, sourcesJar );
571 
572                             projectHelper.attachArtifact( project, "java-source", "sources", shadedSources );
573                         }
574 
575                         if ( shadeTestJar )
576                         {
577                             getLog().info( "Replacing original test artifact with shaded test artifact." );
578                             File shadedTests = shadedTestArtifactFile();
579 
580                             replaceFile( shadedTests, testJar );
581 
582                             projectHelper.attachArtifact( project, "test-jar", shadedTests );
583                         }
584 
585                         if ( createTestSourcesJar )
586                         {
587                             getLog().info( "Replacing original test source artifact "
588                                 + "with shaded test source artifact." );
589                             File shadedTestSources = shadedTestSourcesArtifactFile();
590 
591                             replaceFile( shadedTestSources, testSourcesJar );
592 
593                             projectHelper.attachArtifact( project, "java-source", "test-sources",
594                                 shadedTestSources );
595                         }
596 
597                         if ( createDependencyReducedPom )
598                         {
599                             createDependencyReducedPom( artifactIds );
600                         }
601                     }
602                 }
603             }
604         }
605         catch ( Exception e )
606         {
607             throw new MojoExecutionException( "Error creating shaded jar: " + e.getMessage(), e );
608         }
609     }
610 
611     private void createErrorOutput()
612     {
613         getLog().error( "The project main artifact does not exist. This could have the following" );
614         getLog().error( "reasons:" );
615         getLog().error( "- You have invoked the goal directly from the command line. This is not" );
616         getLog().error( "  supported. Please add the goal to the default lifecycle via an" );
617         getLog().error( "  <execution> element in your POM and use \"mvn package\" to have it run." );
618         getLog().error( "- You have bound the goal to a lifecycle phase before \"package\". Please" );
619         getLog().error( "  remove this binding from your POM such that the goal will be run in" );
620         getLog().error( "  the proper phase." );
621         getLog().error( "- You removed the configuration of the maven-jar-plugin that produces the main artifact." );
622     }
623 
624     private ShadeRequest shadeRequest( Set<File> artifacts, File outputJar, List<Filter> filters,
625                                        List<Relocator> relocators, List<ResourceTransformer> resourceTransformers )
626     {
627         ShadeRequest shadeRequest = new ShadeRequest();
628         shadeRequest.setJars( artifacts );
629         shadeRequest.setUberJar( outputJar );
630         shadeRequest.setFilters( filters );
631         shadeRequest.setRelocators( relocators );
632         shadeRequest.setResourceTransformers( resourceTransformers );
633         return shadeRequest;
634     }
635 
636     private ShadeRequest createShadeSourcesRequest( Set<File> testArtifacts, File testJar, List<Filter> filters,
637                                                     List<Relocator> relocators,
638                                                     List<ResourceTransformer> resourceTransformers )
639     {
640         ShadeRequest shadeSourcesRequest =
641             shadeRequest( testArtifacts, testJar, filters, relocators, resourceTransformers );
642         shadeSourcesRequest.setShadeSourcesContent( shadeSourcesContent );
643         return shadeSourcesRequest;
644     }
645 
646     private void setupHintedShader()
647         throws MojoExecutionException
648     {
649         if ( shaderHint != null )
650         {
651             try
652             {
653                 shader = (Shader) plexusContainer.lookup( Shader.ROLE, shaderHint );
654             }
655             catch ( ComponentLookupException e )
656             {
657                 throw new MojoExecutionException( "unable to lookup own Shader implementation with hint:'" + shaderHint
658                     + "'", e );
659             }
660         }
661     }
662 
663     private void processArtifactSelectors( Set<File> artifacts, Set<String> artifactIds, Set<File> sourceArtifacts,
664                                            Set<File> testArtifacts, Set<File> testSourceArtifacts,
665                                            ArtifactSelector artifactSelector )
666     {
667 
668         List<String> excludedArtifacts = new ArrayList<>();
669         List<String> pomArtifacts = new ArrayList<>();
670         List<String> emptySourceArtifacts = new ArrayList<>();
671         List<String> emptyTestArtifacts = new ArrayList<>();
672         List<String> emptyTestSourceArtifacts = new ArrayList<>();
673 
674         for ( Artifact artifact : project.getArtifacts() )
675         {
676             if ( !artifactSelector.isSelected( artifact ) )
677             {
678                 excludedArtifacts.add( artifact.getId() );
679 
680                 continue;
681             }
682 
683             if ( "pom".equals( artifact.getType() ) )
684             {
685                 pomArtifacts.add( artifact.getId() );
686                 continue;
687             }
688 
689             getLog().info( "Including " + artifact.getId() + " in the shaded jar." );
690 
691             artifacts.add( artifact.getFile() );
692             artifactIds.add( getId( artifact ) );
693 
694             if ( createSourcesJar )
695             {
696                 File file = resolveArtifactForClassifier( artifact, "sources" );
697                 if ( file != null )
698                 {
699                     if ( file.length() > 0 )
700                     {
701                         sourceArtifacts.add( file );
702                     }
703                     else
704                     {
705                         emptySourceArtifacts.add( artifact.getArtifactId() );
706                     }
707                 }
708             }
709 
710             if ( shadeTestJar )
711             {
712                 File file = resolveArtifactForClassifier( artifact, "tests" );
713                 if ( file != null )
714                 {
715                     if ( file.length() > 0 )
716                     {
717                         testArtifacts.add( file );
718                     }
719                     else
720                     {
721                         emptyTestArtifacts.add( artifact.getId() );
722                     }
723                 }
724             }
725 
726             if ( createTestSourcesJar )
727             {
728                 File file = resolveArtifactForClassifier( artifact, "test-sources" );
729                 if ( file != null )
730                 {
731                     testSourceArtifacts.add( file );
732                 }
733                 else
734                 {
735                     emptyTestSourceArtifacts.add( artifact.getId() );
736                 }
737             }
738         }
739 
740         for ( String artifactId : excludedArtifacts )
741         {
742             getLog().info( "Excluding " + artifactId + " from the shaded jar." );
743         }
744         for ( String artifactId : pomArtifacts )
745         {
746             getLog().info( "Skipping pom dependency " + artifactId + " in the shaded jar." );
747         }
748         for ( String artifactId : emptySourceArtifacts )
749         {
750             getLog().warn( "Skipping empty source jar " + artifactId + "." );
751         }
752         for ( String artifactId : emptyTestArtifacts )
753         {
754             getLog().warn( "Skipping empty test jar " + artifactId + "." );
755         }
756         for ( String artifactId : emptyTestArtifacts )
757         {
758             getLog().warn( "Skipping empty test source jar " + artifactId + "." );
759         }
760     }
761 
762     private boolean invalidMainArtifact()
763     {
764         return project.getArtifact().getFile() == null || !project.getArtifact().getFile().isFile();
765     }
766 
767     private void replaceFile( File oldFile, File newFile )
768         throws MojoExecutionException
769     {
770         getLog().info( "Replacing " + oldFile + " with " + newFile );
771 
772         File origFile = new File( outputDirectory, "original-" + oldFile.getName() );
773         if ( oldFile.exists() && !oldFile.renameTo( origFile ) )
774         {
775             // try a gc to see if an unclosed stream needs garbage collecting
776             System.gc();
777             System.gc();
778 
779             if ( !oldFile.renameTo( origFile ) )
780             {
781                 // Still didn't work. We'll do a copy
782                 try
783                 {
784                     copyFiles( oldFile, origFile );
785                 }
786                 catch ( IOException ex )
787                 {
788                     // kind of ignorable here. We're just trying to save the original
789                     getLog().warn( ex );
790                 }
791             }
792         }
793         if ( !newFile.renameTo( oldFile ) )
794         {
795             // try a gc to see if an unclosed stream needs garbage collecting
796             System.gc();
797             System.gc();
798 
799             if ( !newFile.renameTo( oldFile ) )
800             {
801                 // Still didn't work. We'll do a copy
802                 try
803                 {
804                     copyFiles( newFile, oldFile );
805                 }
806                 catch ( IOException ex )
807                 {
808                     throw new MojoExecutionException( "Could not replace original artifact with shaded artifact!", ex );
809                 }
810             }
811         }
812     }
813 
814     private void copyFiles( File source, File target )
815         throws IOException
816     {
817         try ( InputStream in = new FileInputStream( source );
818               OutputStream out = new FileOutputStream( target ) )
819         {
820             IOUtil.copy( in, out );
821         }
822     }
823 
824     private File resolveArtifactForClassifier( Artifact artifact, String classifier )
825     {
826         DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
827         coordinate.setGroupId( artifact.getGroupId() );
828         coordinate.setArtifactId( artifact.getArtifactId() );
829         coordinate.setVersion( artifact.getVersion() );
830         coordinate.setExtension( "jar" );
831         coordinate.setClassifier( classifier );
832 
833         Artifact resolvedArtifact;
834         try
835         {
836             resolvedArtifact =
837                 artifactResolver.resolveArtifact( session.getProjectBuildingRequest(), coordinate ).getArtifact();
838         }
839         catch ( ArtifactResolverException e )
840         {
841             getLog().warn( "Could not get " + classifier + " for " + artifact );
842             return null;
843         }
844 
845         if ( resolvedArtifact.isResolved() )
846         {
847             return resolvedArtifact.getFile();
848         }
849         return null;
850     }
851 
852     private List<Relocator> getRelocators()
853     {
854         List<Relocator> relocators = new ArrayList<>();
855 
856         if ( relocations == null )
857         {
858             return relocators;
859         }
860 
861         for ( PackageRelocation r : relocations )
862         {
863             relocators.add( new SimpleRelocator( r.getPattern(), r.getShadedPattern(), r.getIncludes(), r.getExcludes(),
864                                                  r.isRawString() ) );
865         }
866 
867         return relocators;
868     }
869 
870     private List<ResourceTransformer> getResourceTransformers()
871     {
872         if ( transformers == null )
873         {
874             return Collections.emptyList();
875         }
876 
877         return Arrays.asList( transformers );
878     }
879 
880     private List<Filter> getFilters()
881         throws MojoExecutionException
882     {
883         List<Filter> filters = new ArrayList<>();
884         List<SimpleFilter> simpleFilters = new ArrayList<>();
885 
886         if ( this.filters != null && this.filters.length > 0 )
887         {
888             Map<Artifact, ArtifactId> artifacts = new HashMap<>();
889 
890             artifacts.put( project.getArtifact(), new ArtifactId( project.getArtifact() ) );
891 
892             for ( Artifact artifact : project.getArtifacts() )
893             {
894                 artifacts.put( artifact, new ArtifactId( artifact ) );
895             }
896 
897             for ( ArchiveFilter filter : this.filters )
898             {
899                 ArtifactId pattern = new ArtifactId( filter.getArtifact() );
900 
901                 Set<File> jars = new HashSet<>();
902 
903                 for ( Map.Entry<Artifact, ArtifactId> entry : artifacts.entrySet() )
904                 {
905                     if ( entry.getValue().matches( pattern ) )
906                     {
907                         Artifact artifact = entry.getKey();
908 
909                         jars.add( artifact.getFile() );
910 
911                         if ( createSourcesJar )
912                         {
913                             File file = resolveArtifactForClassifier( artifact, "sources" );
914                             if ( file != null )
915                             {
916                                 jars.add( file );
917                             }
918                         }
919 
920                         if ( shadeTestJar )
921                         {
922                             File file = resolveArtifactForClassifier( artifact, "tests" );
923                             if ( file != null )
924                             {
925                                 jars.add( file );
926                             }
927                         }
928                     }
929                 }
930 
931                 if ( jars.isEmpty() )
932                 {
933                     getLog().info( "No artifact matching filter " + filter.getArtifact() );
934 
935                     continue;
936                 }
937 
938                 simpleFilters.add( new SimpleFilter( jars, filter ) );
939             }
940         }
941 
942         filters.addAll( simpleFilters );
943 
944         if ( minimizeJar )
945         {
946             getLog().info( "Minimizing jar " + project.getArtifact() );
947 
948             try
949             {
950                 filters.add( new MinijarFilter( project, getLog(), simpleFilters ) );
951             }
952             catch ( IOException e )
953             {
954                 throw new MojoExecutionException( "Failed to analyze class dependencies", e );
955             }
956         }
957 
958         return filters;
959     }
960 
961     private File shadedArtifactFileWithClassifier()
962     {
963         Artifact artifact = project.getArtifact();
964         final String shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "."
965             + artifact.getArtifactHandler().getExtension();
966         return new File( outputDirectory, shadedName );
967     }
968 
969     private File shadedSourceArtifactFileWithClassifier()
970     {
971         return shadedArtifactFileWithClassifier( "sources" );
972     }
973 
974     private File shadedTestSourceArtifactFileWithClassifier()
975     {
976         return shadedArtifactFileWithClassifier( "test-sources" );
977     }
978 
979     private File shadedArtifactFileWithClassifier( String classifier )
980     {
981         Artifact artifact = project.getArtifact();
982         final String shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName
983             + "-" + classifier + "." + artifact.getArtifactHandler().getExtension();
984         return new File( outputDirectory, shadedName );
985     }
986 
987     private File shadedTestArtifactFileWithClassifier()
988     {
989         return shadedArtifactFileWithClassifier( "tests" );
990     }
991 
992     private File shadedSourcesArtifactFile()
993     {
994         return shadedArtifactFile( "sources" );
995     }
996 
997     private File shadedTestSourcesArtifactFile()
998     {
999         return shadedArtifactFile( "test-sources" );
1000     }
1001 
1002     private File shadedArtifactFile( String classifier )
1003     {
1004         Artifact artifact = project.getArtifact();
1005 
1006         String shadedName;
1007 
1008         if ( project.getBuild().getFinalName() != null )
1009         {
1010             shadedName = project.getBuild().getFinalName() + "-" + classifier + "."
1011                 + artifact.getArtifactHandler().getExtension();
1012         }
1013         else
1014         {
1015             shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + classifier + "."
1016                 + artifact.getArtifactHandler().getExtension();
1017         }
1018 
1019         return new File( outputDirectory, shadedName );
1020     }
1021 
1022     private File shadedTestArtifactFile()
1023     {
1024         return shadedArtifactFile( "tests" );
1025     }
1026 
1027     // We need to find the direct dependencies that have been included in the uber JAR so that we can modify the
1028     // POM accordingly.
1029     private void createDependencyReducedPom( Set<String> artifactsToRemove )
1030         throws IOException, DependencyGraphBuilderException, ProjectBuildingException
1031     {
1032         List<Dependency> dependencies = new ArrayList<>();
1033 
1034         boolean modified = false;
1035 
1036         List<Dependency> transitiveDeps = new ArrayList<>();
1037 
1038         // NOTE: By using the getArtifacts() we get the completely evaluated artifacts
1039         // including the system scoped artifacts with expanded values of properties used.
1040         for ( Artifact artifact : project.getArtifacts() )
1041         {
1042             if ( "pom".equals( artifact.getType() ) )
1043             {
1044                 // don't include pom type dependencies in dependency reduced pom
1045                 continue;
1046             }
1047 
1048             // promote
1049             Dependency dep = createDependency( artifact );
1050 
1051             // we'll figure out the exclusions in a bit.
1052             transitiveDeps.add( dep );
1053         }
1054         List<Dependency> origDeps = project.getDependencies();
1055 
1056         if ( promoteTransitiveDependencies )
1057         {
1058             origDeps = transitiveDeps;
1059         }
1060 
1061         Model model = project.getOriginalModel();
1062         // MSHADE-185: We will remove all system scoped dependencies which usually
1063         // have some kind of property usage. At this time the properties within
1064         // such things are already evaluated.
1065         List<Dependency> originalDependencies = model.getDependencies();
1066         removeSystemScopedDependencies( artifactsToRemove, originalDependencies );
1067 
1068         for ( Dependency d : origDeps )
1069         {
1070             dependencies.add( d );
1071 
1072             String id = getId( d );
1073 
1074             if ( artifactsToRemove.contains( id ) )
1075             {
1076                 modified = true;
1077 
1078                 if ( keepDependenciesWithProvidedScope )
1079                 {
1080                     d.setScope( "provided" );
1081                 }
1082                 else
1083                 {
1084                     dependencies.remove( d );
1085                 }
1086             }
1087         }
1088 
1089         // MSHADE-155
1090         model.setArtifactId( shadedArtifactId );
1091 
1092         // MSHADE-185: We will add those system scoped dependencies
1093         // from the non interpolated original pom file. So we keep
1094         // things like this: <systemPath>${tools.jar}</systemPath> intact.
1095         addSystemScopedDependencyFromNonInterpolatedPom( dependencies, originalDependencies );
1096 
1097         // Check to see if we have a reduction and if so rewrite the POM.
1098         rewriteDependencyReducedPomIfWeHaveReduction( dependencies, modified, transitiveDeps, model );
1099     }
1100 
1101     private void rewriteDependencyReducedPomIfWeHaveReduction( List<Dependency> dependencies, boolean modified,
1102                                                                List<Dependency> transitiveDeps, Model model )
1103                                                                    throws IOException, ProjectBuildingException,
1104                                                                    DependencyGraphBuilderException
1105     {
1106         if ( modified )
1107         {
1108             for ( int loopCounter = 0; modified; loopCounter++ )
1109             {
1110 
1111                 model.setDependencies( dependencies );
1112 
1113                 if ( generateUniqueDependencyReducedPom )
1114                 {
1115                     dependencyReducedPomLocation =
1116                         File.createTempFile( "dependency-reduced-pom-", ".xml", project.getBasedir() );
1117                     project.getProperties().setProperty( "maven.shade.dependency-reduced-pom",
1118                                                          dependencyReducedPomLocation.getAbsolutePath() );
1119                 }
1120                 else
1121                 {
1122                     if ( dependencyReducedPomLocation == null )
1123                     {
1124                         // MSHADE-123: We can't default to 'target' because it messes up uses of ${project.basedir}
1125                         dependencyReducedPomLocation = new File( project.getBasedir(), "dependency-reduced-pom.xml" );
1126                     }
1127                 }
1128 
1129                 File f = dependencyReducedPomLocation;
1130                 // MSHADE-225 
1131                 // Works for now, maybe there's a better algorithm where no for-loop is required
1132                 if ( loopCounter == 0 )
1133                 {
1134                     getLog().info( "Dependency-reduced POM written at: " + f.getAbsolutePath() );
1135                 }
1136 
1137                 if ( f.exists() )
1138                 {
1139                     // noinspection ResultOfMethodCallIgnored
1140                     f.delete();
1141                 }
1142 
1143                 Writer w = WriterFactory.newXmlWriter( f );
1144 
1145                 String replaceRelativePath = null;
1146                 if ( model.getParent() != null )
1147                 {
1148                     replaceRelativePath = model.getParent().getRelativePath();
1149 
1150                 }
1151 
1152                 if ( model.getParent() != null )
1153                 {
1154                     File parentFile =
1155                         new File( project.getBasedir(), model.getParent().getRelativePath() ).getCanonicalFile();
1156                     if ( !parentFile.isFile() )
1157                     {
1158                         parentFile = new File( parentFile, "pom.xml" );
1159                     }
1160 
1161                     parentFile = parentFile.getCanonicalFile();
1162 
1163                     String relPath = RelativizePath.convertToRelativePath( parentFile, f );
1164                     model.getParent().setRelativePath( relPath );
1165                 }
1166 
1167                 try
1168                 {
1169                     PomWriter.write( w, model, true );
1170                 }
1171                 finally
1172                 {
1173                     if ( model.getParent() != null )
1174                     {
1175                         model.getParent().setRelativePath( replaceRelativePath );
1176                     }
1177                     w.close();
1178                 }
1179 
1180                 ProjectBuildingRequest projectBuildingRequest =
1181                     new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
1182                 projectBuildingRequest.setLocalRepository( localRepository );
1183                 projectBuildingRequest.setRemoteRepositories( remoteArtifactRepositories );
1184 
1185                 ProjectBuildingResult result = projectBuilder.build( f, projectBuildingRequest );
1186 
1187                 getLog().debug( "updateExcludesInDeps()" );
1188                 modified = updateExcludesInDeps( result.getProject(), dependencies, transitiveDeps );
1189             }
1190 
1191             project.setFile( dependencyReducedPomLocation );
1192         }
1193     }
1194 
1195     private void removeSystemScopedDependencies( Set<String> artifactsToRemove, List<Dependency> originalDependencies )
1196     {
1197         for ( Dependency dependency : originalDependencies )
1198         {
1199             if ( dependency.getScope() != null && dependency.getScope().equalsIgnoreCase( "system" ) )
1200             {
1201                 artifactsToRemove.add( getId( dependency ) );
1202             }
1203         }
1204     }
1205 
1206     private void addSystemScopedDependencyFromNonInterpolatedPom( List<Dependency> dependencies,
1207                                                                   List<Dependency> originalDependencies )
1208     {
1209         for ( Dependency dependency : originalDependencies )
1210         {
1211             if ( dependency.getScope() != null && dependency.getScope().equalsIgnoreCase( "system" ) )
1212             {
1213                 dependencies.add( dependency );
1214             }
1215         }
1216     }
1217 
1218     private Dependency createDependency( Artifact artifact )
1219     {
1220         Dependency dep = new Dependency();
1221         dep.setArtifactId( artifact.getArtifactId() );
1222         if ( artifact.hasClassifier() )
1223         {
1224             dep.setClassifier( artifact.getClassifier() );
1225         }
1226         dep.setGroupId( artifact.getGroupId() );
1227         dep.setOptional( artifact.isOptional() );
1228         dep.setScope( artifact.getScope() );
1229         dep.setType( artifact.getType() );
1230         if ( useBaseVersion )
1231         {
1232             dep.setVersion( artifact.getBaseVersion() );
1233         }
1234         else
1235         {
1236             dep.setVersion( artifact.getVersion() );
1237         }
1238         return dep;
1239     }
1240 
1241     private String getId( Artifact artifact )
1242     {
1243         return getId( artifact.getGroupId(), artifact.getArtifactId(), artifact.getType(), artifact.getClassifier() );
1244     }
1245 
1246     private String getId( Dependency dependency )
1247     {
1248         return getId( dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(),
1249                       dependency.getClassifier() );
1250     }
1251 
1252     private String getId( String groupId, String artifactId, String type, String classifier )
1253     {
1254         return groupId + ":" + artifactId + ":" + type + ":" + ( ( classifier != null ) ? classifier : "" );
1255     }
1256 
1257     public boolean updateExcludesInDeps( MavenProject project, List<Dependency> dependencies,
1258                                          List<Dependency> transitiveDeps )
1259                                              throws DependencyGraphBuilderException
1260     {
1261         MavenProject original = session.getProjectBuildingRequest().getProject();
1262         try
1263         {
1264             session.getProjectBuildingRequest().setProject( project );
1265             DependencyNode node = dependencyGraphBuilder
1266                 .buildDependencyGraph( session.getProjectBuildingRequest(), null );
1267             boolean modified = false;
1268             for ( DependencyNode n2 : node.getChildren() )
1269             {
1270                 for ( DependencyNode n3 : n2.getChildren() )
1271                 {
1272                     // check if it really isn't in the list of original dependencies. Maven
1273                     // prior to 2.0.8 may grab versions from transients instead of
1274                     // from the direct deps in which case they would be marked included
1275                     // instead of OMITTED_FOR_DUPLICATE
1276 
1277                     // also, if not promoting the transitives, level 2's would be included
1278                     boolean found = false;
1279                     for ( Dependency dep : transitiveDeps )
1280                     {
1281                         if ( getId( dep ).equals( getId( n3.getArtifact() ) ) )
1282                         {
1283                             found = true;
1284                             break;
1285                         }
1286                     }
1287 
1288                     // MSHADE-311: do not add exclusion for provided transitive dep
1289                     //       note: MSHADE-31 introduced the exclusion logic for promoteTransitiveDependencies=true,
1290                     //             but as of 3.2.1 promoteTransitiveDependencies has no effect for provided deps,
1291                     //             which makes this fix even possible (see also MSHADE-181)
1292                     if ( !found && !"provided".equals( n3.getArtifact().getScope() ) )
1293                     {
1294                         for ( Dependency dep : dependencies )
1295                         {
1296                             if ( getId( dep ).equals( getId( n2.getArtifact() ) ) )
1297                             {
1298                                 Exclusion exclusion = new Exclusion();
1299                                 exclusion.setArtifactId( n3.getArtifact().getArtifactId() );
1300                                 exclusion.setGroupId( n3.getArtifact().getGroupId() );
1301                                 dep.addExclusion( exclusion );
1302                                 modified = true;
1303                                 break;
1304                             }
1305                         }
1306                     }
1307                 }
1308             }
1309             return modified;
1310         }
1311         finally
1312         {
1313             // restore it
1314             session.getProjectBuildingRequest().setProject( original );
1315         }
1316     }
1317 }