All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugins.shade.mojo.ShadeMojo Maven / Gradle / Ivy

Go to download

Repackages the project classes together with their dependencies into a single uber-jar, optionally renaming classes or removing unused classes.

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.plugins.shade.mojo;

import javax.inject.Inject;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.Model;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.plugins.shade.ShadeRequest;
import org.apache.maven.plugins.shade.Shader;
import org.apache.maven.plugins.shade.filter.Filter;
import org.apache.maven.plugins.shade.filter.MinijarFilter;
import org.apache.maven.plugins.shade.filter.SimpleFilter;
import org.apache.maven.plugins.shade.pom.PomWriter;
import org.apache.maven.plugins.shade.relocation.Relocator;
import org.apache.maven.plugins.shade.relocation.SimpleRelocator;
import org.apache.maven.plugins.shade.resource.ManifestResourceTransformer;
import org.apache.maven.plugins.shade.resource.ResourceTransformer;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingResult;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.WriterFactory;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.CollectResult;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;

import static org.apache.maven.plugins.shade.resource.UseDependencyReducedPom.createPomReplaceTransformers;

/**
 * Mojo that performs shading delegating to the Shader component.
 *
 * @author Jason van Zyl
 * @author Mauro Talevi
 * @author David Blevins
 * @author Hiram Chirino
 */
// CHECKSTYLE_OFF: LineLength
@Mojo(
        name = "shade",
        defaultPhase = LifecyclePhase.PACKAGE,
        threadSafe = true,
        requiresDependencyResolution = ResolutionScope.RUNTIME)
// CHECKSTYLE_ON: LineLength
public class ShadeMojo extends AbstractMojo {
    /**
     * The current Maven session.
     */
    @Parameter(defaultValue = "${session}", readonly = true, required = true)
    private MavenSession session;

    /**
     * The current Maven project.
     */
    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;

    /**
     * Artifacts to include/exclude from the final artifact. Artifacts are denoted by composite identifiers of the
     * general form groupId:artifactId:type:classifier. Since version 1.3, the wildcard characters '*' and
     * '?' can be used within the sub parts of those composite identifiers to do pattern matching. For convenience, the
     * syntax groupId is equivalent to groupId:*:*:*, groupId:artifactId is
     * equivalent to groupId:artifactId:*:* and groupId:artifactId:classifier is equivalent to
     * groupId:artifactId:*:classifier. For example:
     *
     * 
     * <artifactSet>
     *   <includes>
     *     <include>org.apache.maven:*</include>
     *   </includes>
     *   <excludes>
     *     <exclude>*:maven-core</exclude>
     *   </excludes>
     * </artifactSet>
     * 
*/ @Parameter private ArtifactSet artifactSet; /** * Packages to be relocated. For example: * *
     * <relocations>
     *   <relocation>
     *     <pattern>org.apache</pattern>
     *     <shadedPattern>hidden.org.apache</shadedPattern>
     *     <includes>
     *       <include>org.apache.maven.*</include>
     *     </includes>
     *     <excludes>
     *       <exclude>org.apache.maven.Public*</exclude>
     *     </excludes>
     *   </relocation>
     * </relocations>
     * 
* * Note: Support for includes exists only since version 1.4. */ @SuppressWarnings("MismatchedReadAndWriteOfArray") @Parameter private PackageRelocation[] relocations; /** * Resource transformers to be used. Please see the "Examples" section for more information on available * transformers and their configuration. */ @Parameter private ResourceTransformer[] transformers; /** * Archive Filters to be used. Allows you to specify an artifact in the form of a composite identifier as used by * {@link #artifactSet} and a set of include/exclude file patterns for filtering which contents of the archive are * added to the shaded jar. From a logical perspective, includes are processed before excludes, thus it's possible * to use an include to collect a set of files from the archive then use excludes to further reduce the set. By * default, all files are included and no files are excluded. If multiple filters apply to an artifact, the * intersection of the matched files will be included in the final JAR. For example: * *
     * <filters>
     *   <filter>
     *     <artifact>junit:junit</artifact>
     *     <includes>
     *       <include>org/junit/**</include>
     *     </includes>
     *     <excludes>
     *       <exclude>org/junit/experimental/**</exclude>
     *     </excludes>
     *   </filter>
     * </filters>
     * 
*/ @SuppressWarnings("MismatchedReadAndWriteOfArray") @Parameter private ArchiveFilter[] filters; /** * The destination directory for the shaded artifact. */ @Parameter(defaultValue = "${project.build.directory}") private File outputDirectory; /** * The name of the shaded artifactId. *

* If you like to change the name of the native artifact, you may use the <build><finalName> setting. If this * is set to something different than <build><finalName>, no file replacement will be performed, even if * shadedArtifactAttached is being used. */ @Parameter private String finalName; /** * The name of the shaded artifactId. So you may want to use a different artifactId and keep the standard version. * If the original artifactId was "foo" then the final artifact would be something like foo-1.0.jar. So if you * change the artifactId you might have something like foo-special-1.0.jar. */ @Parameter(defaultValue = "${project.artifactId}") private String shadedArtifactId; /** * If specified, this will include only artifacts which have groupIds which start with this. */ @Parameter private String shadedGroupFilter; /** * Defines whether the shaded artifact should be attached as classifier to the original artifact. If false, the * shaded jar will be the main artifact of the project */ @Parameter private boolean shadedArtifactAttached; /** * Flag whether to generate a simplified POM for the shaded artifact. If set to true, dependencies that * have been included into the uber JAR will be removed from the <dependencies> section of the * generated POM. The reduced POM will be named dependency-reduced-pom.xml and is stored into the same * directory as the shaded artifact. Unless you also specify dependencyReducedPomLocation, the plugin will create a * temporary file named dependency-reduced-pom.xml in the project basedir. */ @Parameter(defaultValue = "true") private boolean createDependencyReducedPom; /** * Where to put the dependency reduced pom. Note: setting a value for this parameter with a directory other than * ${basedir} will change the value of ${basedir} for all executions that come after the shade execution. This is * often not what you want. This is considered an open issue with this plugin. * * @since 1.7 */ @Parameter(defaultValue = "${basedir}/dependency-reduced-pom.xml") private File dependencyReducedPomLocation; /** * Create a dependency-reduced POM in ${basedir}/drp-UNIQUE.pom. This avoids build collisions of parallel builds * without moving the dependency-reduced POM to a different directory. The property * maven.shade.dependency-reduced-pom is set to the generated filename. * * @since 1.7.2 */ @Parameter(defaultValue = "false") private boolean generateUniqueDependencyReducedPom; /** * Add dependency reduced POM to the JAR instead of the original one provided by the project. * If {@code createDependencyReducedPom} is {@code false} this parameter will be ignored. * * @since 3.3.0 */ @Parameter(defaultValue = "false") private boolean useDependencyReducedPomInJar; /** * When true, dependencies are kept in the pom but with scope 'provided'; when false, the dependency is removed. */ @Parameter private boolean keepDependenciesWithProvidedScope; /** * When true, transitive deps of removed dependencies are promoted to direct dependencies. This should allow the * drop in replacement of the removed deps with the new shaded jar and everything should still work. */ @Parameter private boolean promoteTransitiveDependencies; /** * The name of the classifier used in case the shaded artifact is attached. */ @Parameter(defaultValue = "shaded") private String shadedClassifierName; /** * When true, it will attempt to create a sources jar as well */ @Parameter private boolean createSourcesJar; /** * When true, it will attempt to create a test sources jar. */ @Parameter private boolean createTestSourcesJar; /** * When true, it will attempt to shade the contents of Java source files when creating the sources JAR. When false, * it will just relocate the Java source files to the shaded paths, but will not modify the actual source file * contents. *

* Please note: This feature uses a heuristic search & replace approach which covers many, but definitely not * all possible cases of source code shading and its excludes. There is no full Java parser behind this * functionality, which would be the only way to get this right for Java language elements. As for matching within * Java string constants, this is next to impossible to get 100% right, trying to guess if they are used in * reflection or not. *

* Please understand that the source shading feature is not meant as a source code generator anyway, merely as a * tool creating reasonably plausible source code when navigating to a relocated library class from an IDE, * hopefully displaying source code which makes 95% sense - no more, no less. */ @Parameter(property = "shadeSourcesContent", defaultValue = "false") private boolean shadeSourcesContent; /** * When true, dependencies will be stripped down on the class level to only the transitive hull required for the * artifact. See also {@link #entryPoints}, if you wish to further optimize JAR minimization. *

* Note: This feature requires Java 1.8 or higher due to its use of * jdependency. Its accuracy therefore also depends on * jdependency's limitations. * * @since 1.4 */ @Parameter private boolean minimizeJar; /** * Use this option in order to fine-tune {@link #minimizeJar}: By default, all of the target module's classes are * kept and used as entry points for JAR minimization. By explicitly limiting the set of entry points, you can * further minimize the set of classes kept in the shaded JAR. This affects both classes in the module itself and * dependency classes. If {@link #minimizeJar} is inactive, this option has no effect either. *

* Note: This feature requires Java 1.8 or higher due to its use of * jdependency. Its accuracy therefore also depends on * jdependency's limitations. *

* Configuration example: *

{@code
     * true
     * 
     *   org.acme.Application
     *   org.acme.OtherEntryPoint
     * 
     * }
* * @since 3.5.0 */ @Parameter private Set entryPoints; /** * The path to the output file for the shaded artifact. When this parameter is set, the created archive will neither * replace the project's main artifact nor will it be attached. Hence, this parameter causes the parameters * {@link #finalName}, {@link #shadedArtifactAttached}, {@link #shadedClassifierName} and * {@link #createDependencyReducedPom} to be ignored when used. * * @since 1.3 */ @Parameter private File outputFile; /** * You can pass here the roleHint about your own Shader implementation plexus component. * * @since 1.6 */ @Parameter private String shaderHint; /** * When true, the version of each dependency of the reduced pom will be based on the baseVersion of the original * dependency instead of its resolved version. For example, if the original pom (transitively) depends on * a:a:2.7-SNAPSHOT, if useBaseVersion is set to false, the reduced pom will depend on a:a:2.7-20130312.222222-12 * whereas if useBaseVersion is set to true, the reduced pom will depend on a:a:2.7-SNAPSHOT * * @since 3.0 */ @Parameter(defaultValue = "false") private boolean useBaseVersion; /** * When true, creates a shaded test-jar artifact as well. */ @Parameter(defaultValue = "false") private boolean shadeTestJar; /** * When true, skips the execution of this MOJO. * @since 3.3.0 */ @Parameter(defaultValue = "false") private boolean skip; /** * Extra JAR files to infuse into shaded result. Accepts list of files that must exists. If any of specified * files does not exist (or is not a file), Mojo will fail. *

* Extra JARs will be processed in same way as main JAR (if any) is: applied relocation, resource transformers * but not filtering. *

* Note: this feature should be used lightly, is not meant as ability to replace dependency hull! It is more * just a feature to be able to slightly "differentiate" shaded JAR from main only. * * @since 3.6.0 */ @Parameter private List extraJars; /** * Extra Artifacts to infuse into shaded result. Accepts list of GAVs in form of * {@code :[:[:]]:} that will be resolved. If any of them * cannot be resolved, Mojo will fail. *

* The artifacts will be resolved (not transitively), and will be processed in same way as dependency JARs * are (if any): applied relocation, resource transformers and filtering. *

* Note: this feature should be used lightly, is not meant as ability to replace dependency hull! It is more * just a feature to be able to slightly "differentiate" shaded JAR from main only. * * @since 3.6.0 */ @Parameter private List extraArtifacts; @Inject private MavenProjectHelper projectHelper; @Inject private Shader shader; @Inject private RepositorySystem repositorySystem; /** * ProjectBuilder, needed to create projects from the artifacts. */ @Inject private ProjectBuilder projectBuilder; /** * All the present Shaders. */ @Inject private Map shaders; /** * @throws MojoExecutionException in case of an error. */ @Override public void execute() throws MojoExecutionException { if (skip) { getLog().info("Shading has been skipped."); return; } setupHintedShader(); Set artifacts = new LinkedHashSet<>(); Set artifactIds = new LinkedHashSet<>(); Set sourceArtifacts = new LinkedHashSet<>(); Set testArtifacts = new LinkedHashSet<>(); Set testSourceArtifacts = new LinkedHashSet<>(); ArtifactSelector artifactSelector = new ArtifactSelector(project.getArtifact(), artifactSet, shadedGroupFilter); if (artifactSelector.isSelected(project.getArtifact()) && !"pom".equals(project.getArtifact().getType())) { if (invalidMainArtifact()) { createErrorOutput(); throw new MojoExecutionException( "Failed to create shaded artifact, " + "project main artifact does not exist."); } artifacts.add(project.getArtifact().getFile()); if (extraJars != null && !extraJars.isEmpty()) { for (File extraJar : extraJars) { if (!Files.isRegularFile(extraJar.toPath())) { throw new MojoExecutionException( "Failed to create shaded artifact: parameter extraJars contains path " + extraJar + " that is not a file (does not exist or is not a file)"); } artifacts.add(extraJar); } } if (createSourcesJar) { File file = shadedSourcesArtifactFile(); if (file.isFile()) { sourceArtifacts.add(file); } } if (shadeTestJar) { File file = shadedTestArtifactFile(); if (file.isFile()) { testArtifacts.add(file); } } if (createTestSourcesJar) { File file = shadedTestSourcesArtifactFile(); if (file.isFile()) { testSourceArtifacts.add(file); } } } processArtifactSelectors( artifacts, artifactIds, sourceArtifacts, testArtifacts, testSourceArtifacts, artifactSelector); File outputJar = (outputFile != null) ? outputFile : shadedArtifactFileWithClassifier(); File sourcesJar = shadedSourceArtifactFileWithClassifier(); File testJar = shadedTestArtifactFileWithClassifier(); File testSourcesJar = shadedTestSourceArtifactFileWithClassifier(); // Now add our extra resources try { List filters = getFilters(); List relocators = getRelocators(); List resourceTransformers = getResourceTransformers(); if (createDependencyReducedPom) { createDependencyReducedPom(artifactIds); if (useDependencyReducedPomInJar) { // In some cases the used implementation of the resourceTransformers is immutable. resourceTransformers = new ArrayList<>(resourceTransformers); resourceTransformers.addAll(createPomReplaceTransformers(project, dependencyReducedPomLocation)); } } ShadeRequest shadeRequest = shadeRequest("jar", artifacts, outputJar, filters, relocators, resourceTransformers); shader.shade(shadeRequest); if (createSourcesJar) { ShadeRequest shadeSourcesRequest = createShadeSourcesRequest( "sources-jar", sourceArtifacts, sourcesJar, filters, relocators, resourceTransformers); shader.shade(shadeSourcesRequest); } if (shadeTestJar) { ShadeRequest shadeTestRequest = shadeRequest("test-jar", testArtifacts, testJar, filters, relocators, resourceTransformers); shader.shade(shadeTestRequest); } if (createTestSourcesJar) { ShadeRequest shadeTestSourcesRequest = createShadeSourcesRequest( "test-sources-jar", testSourceArtifacts, testSourcesJar, filters, relocators, resourceTransformers); shader.shade(shadeTestSourcesRequest); } if (outputFile == null) { boolean renamed = false; // rename the output file if a specific finalName is set // but don't rename if the finalName is the // because this will be handled implicitly later if (finalName != null && finalName.length() > 0 // && !finalName.equals(project.getBuild().getFinalName())) { String finalFileName = finalName + "." + project.getArtifact().getArtifactHandler().getExtension(); File finalFile = new File(outputDirectory, finalFileName); replaceFile(finalFile, outputJar); outputJar = finalFile; // Also support the sources JAR if (createSourcesJar) { finalFileName = finalName + "-sources.jar"; finalFile = new File(outputDirectory, finalFileName); replaceFile(finalFile, sourcesJar); sourcesJar = finalFile; } // Also support the test JAR if (shadeTestJar) { finalFileName = finalName + "-tests.jar"; finalFile = new File(outputDirectory, finalFileName); replaceFile(finalFile, testJar); testJar = finalFile; } if (createTestSourcesJar) { finalFileName = finalName + "-test-sources.jar"; finalFile = new File(outputDirectory, finalFileName); replaceFile(finalFile, testSourcesJar); testSourcesJar = finalFile; } renamed = true; } if (shadedArtifactAttached) { getLog().info("Attaching shaded artifact."); projectHelper.attachArtifact( project, project.getArtifact().getType(), shadedClassifierName, outputJar); if (createSourcesJar) { projectHelper.attachArtifact( project, "java-source", shadedClassifierName + "-sources", sourcesJar); } if (shadeTestJar) { projectHelper.attachArtifact(project, "test-jar", shadedClassifierName + "-tests", testJar); } if (createTestSourcesJar) { projectHelper.attachArtifact( project, "java-source", shadedClassifierName + "-test-sources", testSourcesJar); } } else if (!renamed) { getLog().info("Replacing original artifact with shaded artifact."); File originalArtifact = project.getArtifact().getFile(); if (originalArtifact != null) { replaceFile(originalArtifact, outputJar); if (createSourcesJar) { getLog().info("Replacing original source artifact with shaded source artifact."); File shadedSources = shadedSourcesArtifactFile(); replaceFile(shadedSources, sourcesJar); projectHelper.attachArtifact(project, "java-source", "sources", shadedSources); } if (shadeTestJar) { getLog().info("Replacing original test artifact with shaded test artifact."); File shadedTests = shadedTestArtifactFile(); replaceFile(shadedTests, testJar); projectHelper.attachArtifact(project, "test-jar", shadedTests); } if (createTestSourcesJar) { getLog().info("Replacing original test source artifact " + "with shaded test source artifact."); File shadedTestSources = shadedTestSourcesArtifactFile(); replaceFile(shadedTestSources, testSourcesJar); projectHelper.attachArtifact(project, "java-source", "test-sources", shadedTestSources); } } } } } catch (Exception e) { throw new MojoExecutionException("Error creating shaded jar: " + e.getMessage(), e); } } private void createErrorOutput() { getLog().error("The project main artifact does not exist. This could have the following"); getLog().error("reasons:"); getLog().error("- You have invoked the goal directly from the command line. This is not"); getLog().error(" supported. Please add the goal to the default lifecycle via an"); getLog().error(" element in your POM and use \"mvn package\" to have it run."); getLog().error("- You have bound the goal to a lifecycle phase before \"package\". Please"); getLog().error(" remove this binding from your POM such that the goal will be run in"); getLog().error(" the proper phase."); getLog().error("- You removed the configuration of the maven-jar-plugin that produces the main artifact."); } private ShadeRequest shadeRequest( String shade, Set artifacts, File outputJar, List filters, List relocators, List resourceTransformers) { ShadeRequest shadeRequest = new ShadeRequest(); shadeRequest.setJars(artifacts); shadeRequest.setUberJar(outputJar); shadeRequest.setFilters(filters); shadeRequest.setRelocators(relocators); shadeRequest.setResourceTransformers(toResourceTransformers(shade, resourceTransformers)); return shadeRequest; } private ShadeRequest createShadeSourcesRequest( String shade, Set testArtifacts, File testJar, List filters, List relocators, List resourceTransformers) { ShadeRequest shadeSourcesRequest = shadeRequest(shade, testArtifacts, testJar, filters, relocators, resourceTransformers); shadeSourcesRequest.setShadeSourcesContent(shadeSourcesContent); return shadeSourcesRequest; } private void setupHintedShader() throws MojoExecutionException { if (shaderHint != null) { shader = shaders.get(shaderHint); if (shader == null) { throw new MojoExecutionException( "unable to lookup own Shader implementation with hint: '" + shaderHint + "'"); } } } private void processArtifactSelectors( Set artifacts, Set artifactIds, Set sourceArtifacts, Set testArtifacts, Set testSourceArtifacts, ArtifactSelector artifactSelector) throws MojoExecutionException { List excludedArtifacts = new ArrayList<>(); List pomArtifacts = new ArrayList<>(); List emptySourceArtifacts = new ArrayList<>(); List emptyTestArtifacts = new ArrayList<>(); List emptyTestSourceArtifacts = new ArrayList<>(); ArrayList processedArtifacts = new ArrayList<>(); if (extraArtifacts != null && !extraArtifacts.isEmpty()) { processedArtifacts.addAll(extraArtifacts.stream() .map(org.eclipse.aether.artifact.DefaultArtifact::new) .map(RepositoryUtils::toArtifact) .collect(Collectors.toList())); for (Artifact artifact : processedArtifacts) { try { org.eclipse.aether.artifact.Artifact resolved = resolveArtifact(RepositoryUtils.toArtifact(artifact)); if (resolved.getFile() != null) { artifact.setFile(resolved.getFile()); } } catch (ArtifactResolutionException e) { throw new MojoExecutionException( "Failed to create shaded artifact: parameter extraArtifacts contains artifact " + artifact.getId() + " that is not resolvable", e); } } } processedArtifacts.addAll(project.getArtifacts()); for (Artifact artifact : processedArtifacts) { if (!artifactSelector.isSelected(artifact)) { excludedArtifacts.add(artifact.getId()); continue; } if ("pom".equals(artifact.getType())) { pomArtifacts.add(artifact.getId()); continue; } getLog().info("Including " + artifact.getId() + " in the shaded jar."); artifacts.add(artifact.getFile()); artifactIds.add(getId(artifact)); if (createSourcesJar) { File file = resolveArtifactForClassifier(artifact, "sources"); if (file != null) { if (file.length() > 0) { sourceArtifacts.add(file); } else { emptySourceArtifacts.add(artifact.getArtifactId()); } } } if (shadeTestJar) { File file = resolveArtifactForClassifier(artifact, "tests"); if (file != null) { if (file.length() > 0) { testArtifacts.add(file); } else { emptyTestArtifacts.add(artifact.getId()); } } } if (createTestSourcesJar) { File file = resolveArtifactForClassifier(artifact, "test-sources"); if (file != null) { testSourceArtifacts.add(file); } else { emptyTestSourceArtifacts.add(artifact.getId()); } } } for (String artifactId : excludedArtifacts) { getLog().info("Excluding " + artifactId + " from the shaded jar."); } for (String artifactId : pomArtifacts) { getLog().info("Skipping pom dependency " + artifactId + " in the shaded jar."); } for (String artifactId : emptySourceArtifacts) { getLog().warn("Skipping empty source jar " + artifactId + "."); } for (String artifactId : emptyTestArtifacts) { getLog().warn("Skipping empty test jar " + artifactId + "."); } for (String artifactId : emptyTestSourceArtifacts) { getLog().warn("Skipping empty test source jar " + artifactId + "."); } } private boolean invalidMainArtifact() { return project.getArtifact().getFile() == null || !project.getArtifact().getFile().isFile(); } private void replaceFile(File oldFile, File newFile) throws MojoExecutionException { getLog().info("Replacing " + oldFile + " with " + newFile); File origFile = new File(outputDirectory, "original-" + oldFile.getName()); if (oldFile.exists() && !oldFile.renameTo(origFile)) { // try a gc to see if an unclosed stream needs garbage collecting System.gc(); System.gc(); if (!oldFile.renameTo(origFile)) { // Still didn't work. We'll do a copy try { copyFiles(oldFile, origFile); } catch (IOException ex) { // kind of ignorable here. We're just trying to save the original getLog().warn(ex); } } } if (!newFile.renameTo(oldFile)) { // try a gc to see if an unclosed stream needs garbage collecting System.gc(); System.gc(); if (!newFile.renameTo(oldFile)) { // Still didn't work. We'll do a copy try { copyFiles(newFile, oldFile); } catch (IOException ex) { throw new MojoExecutionException("Could not replace original artifact with shaded artifact!", ex); } } } } private void copyFiles(File source, File target) throws IOException { try (InputStream in = Files.newInputStream(source.toPath()); OutputStream out = Files.newOutputStream(target.toPath())) { IOUtil.copy(in, out); } } private File resolveArtifactForClassifier(Artifact artifact, String classifier) { Artifact toResolve = new DefaultArtifact( artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersionRange() == null ? VersionRange.createFromVersion(artifact.getVersion()) : artifact.getVersionRange(), artifact.getScope(), artifact.getType(), classifier, artifact.getArtifactHandler(), artifact.isOptional()); try { org.eclipse.aether.artifact.Artifact resolved = resolveArtifact(RepositoryUtils.toArtifact(toResolve)); if (resolved.getFile() != null) { return resolved.getFile(); } return null; } catch (ArtifactResolutionException e) { getLog().warn("Could not get " + classifier + " for " + artifact); return null; } } private org.eclipse.aether.artifact.Artifact resolveArtifact(org.eclipse.aether.artifact.Artifact artifact) throws ArtifactResolutionException { return repositorySystem .resolveArtifact( session.getRepositorySession(), new ArtifactRequest(artifact, project.getRemoteProjectRepositories(), "shade")) .getArtifact(); } private List getRelocators() { List relocators = new ArrayList<>(); if (relocations == null) { return relocators; } for (PackageRelocation r : relocations) { relocators.add(new SimpleRelocator( r.getPattern(), r.getShadedPattern(), r.getIncludes(), r.getExcludes(), r.isRawString())); } return relocators; } private List getResourceTransformers() throws MojoExecutionException { if (transformers == null) { return Collections.emptyList(); } for (ResourceTransformer transformer : transformers) { if (transformer == null) { throw new MojoExecutionException( "Failed to create shaded artifact: parameter transformers contains null (double-check XML attribute)"); } } return Arrays.asList(transformers); } private List getFilters() throws MojoExecutionException { List filters = new ArrayList<>(); List simpleFilters = new ArrayList<>(); if (this.filters != null && this.filters.length > 0) { Map artifacts = new HashMap<>(); artifacts.put(project.getArtifact(), new ArtifactId(project.getArtifact())); for (Artifact artifact : project.getArtifacts()) { artifacts.put(artifact, new ArtifactId(artifact)); } for (ArchiveFilter filter : this.filters) { ArtifactId pattern = new ArtifactId(filter.getArtifact()); Set jars = new HashSet<>(); for (Map.Entry entry : artifacts.entrySet()) { if (entry.getValue().matches(pattern)) { Artifact artifact = entry.getKey(); jars.add(artifact.getFile()); if (createSourcesJar) { File file = resolveArtifactForClassifier(artifact, "sources"); if (file != null) { jars.add(file); } } if (shadeTestJar) { File file = resolveArtifactForClassifier(artifact, "tests"); if (file != null) { jars.add(file); } } } } if (jars.isEmpty()) { getLog().info("No artifact matching filter " + filter.getArtifact()); continue; } simpleFilters.add(new SimpleFilter(jars, filter)); } } filters.addAll(simpleFilters); if (minimizeJar) { if (entryPoints == null) { entryPoints = new HashSet<>(); } getLog().info("Minimizing jar " + project.getArtifact() + (entryPoints.isEmpty() ? "" : " with entry points")); try { filters.add(new MinijarFilter(project, getLog(), simpleFilters, entryPoints)); } catch (IOException e) { throw new MojoExecutionException("Failed to analyze class dependencies", e); } } return filters; } private File shadedArtifactFileWithClassifier() { Artifact artifact = project.getArtifact(); final String shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "." + artifact.getArtifactHandler().getExtension(); return new File(outputDirectory, shadedName); } private File shadedSourceArtifactFileWithClassifier() { return shadedArtifactFileWithClassifier("sources"); } private File shadedTestSourceArtifactFileWithClassifier() { return shadedArtifactFileWithClassifier("test-sources"); } private File shadedArtifactFileWithClassifier(String classifier) { Artifact artifact = project.getArtifact(); final String shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + shadedClassifierName + "-" + classifier + "." + artifact.getArtifactHandler().getExtension(); return new File(outputDirectory, shadedName); } private File shadedTestArtifactFileWithClassifier() { return shadedArtifactFileWithClassifier("tests"); } private File shadedSourcesArtifactFile() { return shadedArtifactFile("sources"); } private File shadedTestSourcesArtifactFile() { return shadedArtifactFile("test-sources"); } private File shadedArtifactFile(String classifier) { Artifact artifact = project.getArtifact(); String shadedName; if (project.getBuild().getFinalName() != null) { shadedName = project.getBuild().getFinalName() + "-" + classifier + "." + artifact.getArtifactHandler().getExtension(); } else { shadedName = shadedArtifactId + "-" + artifact.getVersion() + "-" + classifier + "." + artifact.getArtifactHandler().getExtension(); } return new File(outputDirectory, shadedName); } private File shadedTestArtifactFile() { return shadedArtifactFile("tests"); } // We need to find the direct dependencies that have been included in the uber JAR so that we can modify the // POM accordingly. private void createDependencyReducedPom(Set artifactsToRemove) throws IOException, ProjectBuildingException, DependencyCollectionException { List transitiveDeps = new ArrayList<>(); // NOTE: By using the getArtifacts() we get the completely evaluated artifacts // including the system scoped artifacts with expanded values of properties used. for (Artifact artifact : project.getArtifacts()) { if ("pom".equals(artifact.getType())) { // don't include pom type dependencies in dependency reduced pom continue; } // promote Dependency dep = createDependency(artifact); // we'll figure out the exclusions in a bit. transitiveDeps.add(dep); } Model model = project.getOriginalModel(); // MSHADE-413: Must not use objects (for example `Model` or `Dependency`) that are "owned // by Maven" and being used by other projects/plugins. Modifying those will break the // correctness of the build - or cause an endless loop. List origDeps = new ArrayList<>(); List source = promoteTransitiveDependencies ? transitiveDeps : project.getDependencies(); for (Dependency d : source) { origDeps.add(d.clone()); } model = model.clone(); // MSHADE-185: We will remove all system scoped dependencies which usually // have some kind of property usage. At this time the properties within // such things are already evaluated. List originalDependencies = model.getDependencies(); removeSystemScopedDependencies(artifactsToRemove, originalDependencies); List dependencies = new ArrayList<>(); boolean modified = false; for (Dependency d : origDeps) { if (artifactsToRemove.contains(getId(d))) { if (keepDependenciesWithProvidedScope) { if (!"provided".equals(d.getScope())) { modified = true; d.setScope("provided"); } } else { modified = true; continue; } } dependencies.add(d); } // MSHADE-155 model.setArtifactId(shadedArtifactId); // MSHADE-185: We will add those system scoped dependencies // from the non interpolated original pom file. So we keep // things like this: ${tools.jar} intact. addSystemScopedDependencyFromNonInterpolatedPom(dependencies, originalDependencies); // Check to see if we have a reduction and if so rewrite the POM. rewriteDependencyReducedPomIfWeHaveReduction(dependencies, modified, transitiveDeps, model); } private void rewriteDependencyReducedPomIfWeHaveReduction( List dependencies, boolean modified, List transitiveDeps, Model model) throws IOException, ProjectBuildingException, DependencyCollectionException { if (modified) { for (int loopCounter = 0; modified; loopCounter++) { model.setDependencies(dependencies); if (generateUniqueDependencyReducedPom) { dependencyReducedPomLocation = Files.createTempFile( project.getBasedir().toPath(), "dependency-reduced-pom-", ".xml") .toFile(); project.getProperties() .setProperty( "maven.shade.dependency-reduced-pom", dependencyReducedPomLocation.getAbsolutePath()); } else { if (dependencyReducedPomLocation == null) { // MSHADE-123: We can't default to 'target' because it messes up uses of ${project.basedir} dependencyReducedPomLocation = new File(project.getBasedir(), "dependency-reduced-pom.xml"); } } File f = dependencyReducedPomLocation; // MSHADE-225 // Works for now, maybe there's a better algorithm where no for-loop is required if (loopCounter == 0) { getLog().info("Dependency-reduced POM written at: " + f.getAbsolutePath()); } if (f.exists()) { // noinspection ResultOfMethodCallIgnored f.delete(); } Writer w = WriterFactory.newXmlWriter(f); String replaceRelativePath = null; if (model.getParent() != null) { replaceRelativePath = model.getParent().getRelativePath(); } if (model.getParent() != null) { File parentFile = new File(project.getBasedir(), model.getParent().getRelativePath()).getCanonicalFile(); if (!parentFile.isFile()) { parentFile = new File(parentFile, "pom.xml"); } parentFile = parentFile.getCanonicalFile(); String relPath = RelativizePath.convertToRelativePath(parentFile, f); model.getParent().setRelativePath(relPath); } try { PomWriter.write(w, model, true); } finally { if (model.getParent() != null) { model.getParent().setRelativePath(replaceRelativePath); } w.close(); } synchronized (session.getProjectBuildingRequest()) { // Lock critical section to fix MSHADE-467 ProjectBuildingRequest projectBuildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest()); projectBuildingRequest.setLocalRepository(session.getLocalRepository()); projectBuildingRequest.setRemoteRepositories(project.getRemoteArtifactRepositories()); ProjectBuildingResult result = projectBuilder.build(f, projectBuildingRequest); getLog().debug("updateExcludesInDeps()"); modified = updateExcludesInDeps(result.getProject(), dependencies, transitiveDeps); } } project.setFile(dependencyReducedPomLocation); } } private void removeSystemScopedDependencies(Set artifactsToRemove, List originalDependencies) { for (Dependency dependency : originalDependencies) { if (dependency.getScope() != null && dependency.getScope().equalsIgnoreCase("system")) { artifactsToRemove.add(getId(dependency)); } } } private void addSystemScopedDependencyFromNonInterpolatedPom( List dependencies, List originalDependencies) { for (Dependency dependency : originalDependencies) { if (dependency.getScope() != null && dependency.getScope().equalsIgnoreCase("system")) { dependencies.add(dependency); } } } private Dependency createDependency(Artifact artifact) { Dependency dep = new Dependency(); dep.setArtifactId(artifact.getArtifactId()); if (artifact.hasClassifier()) { dep.setClassifier(artifact.getClassifier()); } dep.setGroupId(artifact.getGroupId()); dep.setOptional(artifact.isOptional()); dep.setScope(artifact.getScope()); dep.setType(artifact.getType()); if (useBaseVersion) { dep.setVersion(artifact.getBaseVersion()); } else { dep.setVersion(artifact.getVersion()); } return dep; } private String getId(Artifact artifact) { return getId(artifact.getGroupId(), artifact.getArtifactId(), artifact.getType(), artifact.getClassifier()); } private String getId(Dependency dependency) { return getId( dependency.getGroupId(), dependency.getArtifactId(), dependency.getType(), dependency.getClassifier()); } private String getId(String groupId, String artifactId, String type, String classifier) { return groupId + ":" + artifactId + ":" + type + ":" + ((classifier != null) ? classifier : ""); } public boolean updateExcludesInDeps( MavenProject project, List dependencies, List transitiveDeps) throws DependencyCollectionException { CollectRequest collectRequest = new CollectRequest(); collectRequest.setRootArtifact(RepositoryUtils.toArtifact(project.getArtifact())); collectRequest.setRepositories(project.getRemoteProjectRepositories()); collectRequest.setDependencies(project.getDependencies().stream() .map(d -> RepositoryUtils.toDependency( d, session.getRepositorySession().getArtifactTypeRegistry())) .collect(Collectors.toList())); if (project.getDependencyManagement() != null) { collectRequest.setManagedDependencies(project.getDependencyManagement().getDependencies().stream() .map(d -> RepositoryUtils.toDependency( d, session.getRepositorySession().getArtifactTypeRegistry())) .collect(Collectors.toList())); } CollectResult result = repositorySystem.collectDependencies(session.getRepositorySession(), collectRequest); boolean modified = false; if (result.getRoot() != null) { for (DependencyNode n2 : result.getRoot().getChildren()) { String artifactId2 = getId(RepositoryUtils.toArtifact(n2.getArtifact())); for (DependencyNode n3 : n2.getChildren()) { // stupid m-a Artifact that has no idea what it is: dependency or artifact? Artifact artifact3 = RepositoryUtils.toArtifact(n3.getArtifact()); artifact3.setScope(n3.getDependency().getScope()); String artifactId3 = getId(artifact3); // check if it really isn't in the list of original dependencies. Maven // prior to 2.0.8 may grab versions from transients instead of // from the direct deps in which case they would be marked included // instead of OMITTED_FOR_DUPLICATE // also, if not promoting the transitives, level 2's would be included boolean found = false; for (Dependency dep : transitiveDeps) { if (getId(dep).equals(artifactId3)) { found = true; break; } } // MSHADE-311: do not add exclusion for provided transitive dep // note: MSHADE-31 introduced the exclusion logic for promoteTransitiveDependencies=true, // but as of 3.2.1 promoteTransitiveDependencies has no effect for provided deps, // which makes this fix even possible (see also MSHADE-181) if (!found && !"provided".equals(artifact3.getScope())) { getLog().debug(String.format( "dependency %s (scope %s) not found in transitive dependencies", artifactId3, artifact3.getScope())); for (Dependency dep : dependencies) { if (getId(dep).equals(artifactId2)) { // MSHADE-413: First check whether the exclusion has already been added, // because it's meaningless to add it more than once. Certain cases // can end up adding the exclusion "forever" and cause an endless loop // rewriting the whole dependency-reduced-pom.xml file. if (!dependencyHasExclusion(dep, artifact3)) { getLog().debug(String.format( "Adding exclusion for dependency %s (scope %s) " + "to %s (scope %s)", artifactId3, artifact3.getScope(), getId(dep), dep.getScope())); Exclusion exclusion = new Exclusion(); exclusion.setArtifactId(artifact3.getArtifactId()); exclusion.setGroupId(artifact3.getGroupId()); dep.addExclusion(exclusion); modified = true; break; } } } } } } } return modified; } private boolean dependencyHasExclusion(Dependency dep, Artifact exclusionToCheck) { boolean containsExclusion = false; for (Exclusion existingExclusion : dep.getExclusions()) { if (existingExclusion.getGroupId().equals(exclusionToCheck.getGroupId()) && existingExclusion.getArtifactId().equals(exclusionToCheck.getArtifactId())) { containsExclusion = true; break; } } return containsExclusion; } private List toResourceTransformers( String shade, List resourceTransformers) { List forShade = new ArrayList<>(); ManifestResourceTransformer lastMt = null; for (ResourceTransformer transformer : resourceTransformers) { if (!(transformer instanceof ManifestResourceTransformer)) { forShade.add(transformer); } else if (((ManifestResourceTransformer) transformer).isForShade(shade)) { final ManifestResourceTransformer mt = (ManifestResourceTransformer) transformer; if (mt.isUsedForDefaultShading() && lastMt != null && !lastMt.isUsedForDefaultShading()) { continue; // skip, we already have a specific transformer } if (!mt.isUsedForDefaultShading() && lastMt != null && lastMt.isUsedForDefaultShading()) { forShade.remove(lastMt); } else if (!mt.isUsedForDefaultShading() && lastMt != null) { getLog().warn("Ambiguous manifest transformer definition for '" + shade + "': " + mt + " / " + lastMt); } if (lastMt == null || !mt.isUsedForDefaultShading()) { lastMt = mt; } forShade.add(transformer); } } return forShade; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy