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