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