diff --git a/src/it/mrm/repository/dep-reduced-pom-with-transitive-dependencies-with-exclusions/dep-reduced-pom-with-transitive-dependencies-with-exclusions-1.0.pom b/src/it/mrm/repository/dep-reduced-pom-with-transitive-dependencies-with-exclusions/dep-reduced-pom-with-transitive-dependencies-with-exclusions-1.0.pom new file mode 100644 index 00000000..874c7b5a --- /dev/null +++ b/src/it/mrm/repository/dep-reduced-pom-with-transitive-dependencies-with-exclusions/dep-reduced-pom-with-transitive-dependencies-with-exclusions-1.0.pom @@ -0,0 +1,75 @@ + + + + + + 4.0.0 + + org.apache.maven.its.shade.drpwtdwe + dep-reduced-pom-with-transitive-dependencies-with-exclusions + 1.0 + + + Test that creation of the dependency reduced POM properly handles transitive dependencies with exclusions. + + + + + org.apache.maven.its.shade.drp + a + 0.1 + + + com.opencsv + opencsv + 5.9 + + + commons-beanutils + commons-beanutils + + + org.apache.commons + commons-text + + + org.apache.commons + commons-collections4 + + + + + org.apache.commons + commons-collections4 + 4.5.0 + + + junit + junit + + + + + org.apache.commons + commons-text + 1.14.0 + + + diff --git a/src/it/projects/MSHADE-467_parallel-dependency-reduced-pom/shadeMT1/expected-dependency-reduced-pom.xml b/src/it/projects/MSHADE-467_parallel-dependency-reduced-pom/shadeMT1/expected-dependency-reduced-pom.xml new file mode 100644 index 00000000..384acab2 --- /dev/null +++ b/src/it/projects/MSHADE-467_parallel-dependency-reduced-pom/shadeMT1/expected-dependency-reduced-pom.xml @@ -0,0 +1,600 @@ + + + + MSHADE-467 + org.apache.maven.its.shade.parallel + 1.0.0-SNAPSHOT + + 4.0.0 + shadeMT1 + + + + maven-shade-plugin + + + + + + org.apache.commons + commons-vfs2 + 2.9.0 + compile + + + hadoop-hdfs-client + org.apache.hadoop + + + + + org.apache.commons + commons-lang3 + 3.12.0 + compile + + + com.vividsolutions + jts + 1.13 + compile + + + com.itextpdf + itextpdf + 5.5.13.3 + compile + + + org.apache.xmlgraphics + batik-swing + 1.17 + compile + + + org.apache.xmlgraphics + batik-anim + 1.17 + compile + + + org.apache.xmlgraphics + batik-parser + 1.17 + compile + + + org.apache.xmlgraphics + batik-svg-dom + 1.17 + compile + + + org.apache.xmlgraphics + batik-awt-util + 1.17 + compile + + + org.apache.xmlgraphics + xmlgraphics-commons + 2.9 + compile + + + commons-io + commons-io + 2.11.0 + compile + + + org.apache.xmlgraphics + batik-bridge + 1.17 + compile + + + org.apache.xmlgraphics + batik-css + 1.17 + compile + + + org.apache.xmlgraphics + batik-gui-util + 1.17 + compile + + + org.apache.xmlgraphics + batik-gvt + 1.17 + compile + + + org.apache.xmlgraphics + batik-script + 1.17 + compile + + + org.apache.xmlgraphics + batik-shared-resources + 1.17 + compile + + + org.apache.xmlgraphics + batik-util + 1.17 + compile + + + org.apache.xmlgraphics + batik-constants + 1.17 + compile + + + org.apache.xmlgraphics + batik-i18n + 1.17 + compile + + + xml-apis + xml-apis-ext + 1.3.04 + compile + + + org.apache.xmlgraphics + batik-dom + 1.17 + compile + + + org.apache.xmlgraphics + batik-ext + 1.17 + compile + + + org.apache.xmlgraphics + batik-xml + 1.17 + compile + + + xml-apis + xml-apis + 1.4.01 + compile + + + org.apache.xmlgraphics + batik-transcoder + 1.17 + compile + + + org.apache.xmlgraphics + batik-svggen + 1.17 + compile + + + org.springframework + spring-core + 5.3.31 + compile + + + org.springframework + spring-jcl + 5.3.31 + compile + + + io.grpc + grpc-core + 1.58.0 + compile + + + io.grpc + grpc-api + 1.58.0 + compile + + + com.google.code.findbugs + jsr305 + 3.0.2 + compile + + + com.google.code.gson + gson + 2.10.1 + compile + + + com.google.android + annotations + 4.1.1.4 + runtime + + + org.codehaus.mojo + animal-sniffer-annotations + 1.23 + runtime + + + com.google.errorprone + error_prone_annotations + 2.20.0 + compile + + + com.google.guava + guava + 32.0.1-android + compile + + + com.google.guava + failureaccess + 1.0.1 + compile + + + com.google.guava + listenablefuture + 9999.0-empty-to-avoid-conflict-with-guava + compile + + + org.checkerframework + checker-qual + 3.33.0 + compile + + + com.google.j2objc + j2objc-annotations + 2.8 + compile + + + io.perfmark + perfmark-api + 0.26.0 + runtime + + + io.grpc + grpc-context + 1.58.0 + compile + + + io.grpc + grpc-util + 1.58.0 + runtime + + + io.micrometer + micrometer-registry-stackdriver + 1.9.16 + compile + + + io.micrometer + micrometer-core + 1.9.16 + compile + + + org.hdrhistogram + HdrHistogram + 2.1.12 + compile + + + org.latencyutils + LatencyUtils + 2.0.3 + runtime + + + com.google.cloud + google-cloud-monitoring + 3.2.10 + compile + + + javax.annotation-api + javax.annotation + + + + + io.grpc + grpc-stub + 1.45.2 + compile + + + io.grpc + grpc-protobuf + 1.45.2 + compile + + + io.grpc + grpc-protobuf-lite + 1.45.2 + compile + + + protobuf-javalite + com.google.protobuf + + + + + com.google.api + api-common + 2.1.5 + compile + + + javax.annotation-api + javax.annotation + + + + + com.google.protobuf + protobuf-java + 3.19.6 + compile + + + com.google.api.grpc + proto-google-common-protos + 2.8.4 + compile + + + com.google.api.grpc + proto-google-cloud-monitoring-v3 + 3.2.10 + compile + + + javax.annotation-api + javax.annotation + + + + + com.google.api + gax + 2.16.0 + compile + + + com.google.auth + google-auth-library-credentials + 1.6.0 + compile + + + com.google.api + gax-grpc + 2.16.0 + compile + + + io.grpc + grpc-alts + 1.45.2 + runtime + + + io.grpc + grpc-grpclb + 1.45.2 + runtime + + + com.google.protobuf + protobuf-java-util + 3.19.6 + runtime + + + org.conscrypt + conscrypt-openjdk-uber + 2.5.1 + runtime + + + io.grpc + grpc-auth + 1.45.2 + runtime + + + io.grpc + grpc-netty-shaded + 1.45.2 + runtime + + + io.grpc + grpc-googleapis + 1.45.2 + runtime + + + io.grpc + grpc-xds + 1.45.2 + runtime + + + io.grpc + grpc-services + 1.45.2 + runtime + + + com.google.re2j + re2j + 1.5 + runtime + + + org.bouncycastle + bcpkix-jdk15on + 1.67 + runtime + + + org.bouncycastle + bcprov-jdk15on + 1.67 + runtime + + + io.opencensus + opencensus-proto + 0.2.0 + runtime + + + org.threeten + threetenbp + 1.6.0 + compile + + + com.google.auth + google-auth-library-oauth2-http + 1.6.0 + compile + + + com.google.auto.value + auto-value-annotations + 1.9 + compile + + + com.google.http-client + google-http-client + 1.41.7 + compile + + + org.apache.httpcomponents + httpclient + 4.5.13 + compile + + + commons-logging + commons-logging + 1.2 + compile + + + commons-codec + commons-codec + 1.15 + compile + + + org.apache.httpcomponents + httpcore + 4.4.15 + compile + + + io.opencensus + opencensus-api + 0.31.0 + compile + + + io.opencensus + opencensus-contrib-http-util + 0.31.0 + compile + + + com.google.http-client + google-http-client-gson + 1.41.7 + compile + + + org.slf4j + slf4j-api + 1.7.36 + compile + + + org.springframework + spring-context + 5.3.31 + compile + + + org.springframework + spring-aop + 5.3.31 + compile + + + org.springframework + spring-beans + 5.3.31 + compile + + + org.springframework + spring-expression + 5.3.31 + compile + + + org.slf4j + slf4j-simple + 1.7.36 + compile + + + diff --git a/src/it/projects/MSHADE-467_parallel-dependency-reduced-pom/verify.groovy b/src/it/projects/MSHADE-467_parallel-dependency-reduced-pom/verify.groovy index 71ba38cb..cea4acab 100644 --- a/src/it/projects/MSHADE-467_parallel-dependency-reduced-pom/verify.groovy +++ b/src/it/projects/MSHADE-467_parallel-dependency-reduced-pom/verify.groovy @@ -21,5 +21,5 @@ .readLines() .findAll { it.contains '' } .size() - assert exclusionCount == 5 + assert exclusionCount == 15 } || true diff --git a/src/it/projects/dep-reduced-pom-with-transitive-dependencies-with-exclusions/pom.xml b/src/it/projects/dep-reduced-pom-with-transitive-dependencies-with-exclusions/pom.xml new file mode 100644 index 00000000..3b85e69d --- /dev/null +++ b/src/it/projects/dep-reduced-pom-with-transitive-dependencies-with-exclusions/pom.xml @@ -0,0 +1,76 @@ + + + + + + 4.0.0 + + org.apache.maven.its.shade.drpwtdwe + test + 1.0 + + + Test that creation of the dependency reduced POM properly handles transitive dependencies with exclusions. + + + + + org.apache.maven.its.shade.drpwtdwe + dep-reduced-pom-with-transitive-dependencies-with-exclusions + 1.0 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + @project.version@ + + + + org.apache.maven.its.shade.drpwtdwe:dep-reduced-pom-with-transitive-dependencies-with-exclusions + + + + + *:* + + META-INF/maven/** + + + + target/dependency-reduced-pom.xml + true + true + + + + package + + shade + + + + + + + diff --git a/src/it/projects/dep-reduced-pom-with-transitive-dependencies-with-exclusions/verify.groovy b/src/it/projects/dep-reduced-pom-with-transitive-dependencies-with-exclusions/verify.groovy new file mode 100644 index 00000000..fd761e8f --- /dev/null +++ b/src/it/projects/dep-reduced-pom-with-transitive-dependencies-with-exclusions/verify.groovy @@ -0,0 +1,32 @@ +/* + * 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. + */ +import groovy.xml.XmlParser + +File pomFile = new File( basedir, "target/dependency-reduced-pom.xml" ); +assert pomFile.isFile() + +def ns = new groovy.xml.Namespace("http://maven.apache.org/POM/4.0.0") +def pom = new XmlParser().parse( pomFile ) + +assert pom[ns.modelVersion].size() == 1 +assert pom[ns.dependencies][ns.dependency].size() == 5 +assert pom[ns.dependencies][ns.dependency][1][ns.exclusions][ns.exclusion].size() == 3 +assert pom[ns.dependencies][ns.dependency][3][ns.exclusions][ns.exclusion].size() == 1 + + diff --git a/src/main/java/org/apache/maven/plugins/shade/mojo/DependenciesExclusionResolver.java b/src/main/java/org/apache/maven/plugins/shade/mojo/DependenciesExclusionResolver.java new file mode 100644 index 00000000..a73c00c5 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/shade/mojo/DependenciesExclusionResolver.java @@ -0,0 +1,371 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.maven.RepositoryUtils; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Exclusion; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +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.ArtifactDescriptorException; +import org.eclipse.aether.resolution.ArtifactDescriptorRequest; +import org.eclipse.aether.resolution.ArtifactDescriptorResult; + +/** + * 1 CASE - direct exclusion with promotion + * If our pom.xml has a dependency, called A (and A is defined to include in artifactSet). + * And this dependency has defined exclusions (in our pom.xml), + * So in reduced pom, we need to add exclusions for these transitive dependencies. + *

+ * 2 CASE - direct exclusion without promotion + * If our pom.xml has a dependency called A (and A is NOT defined to include in artifactSet). + * And this dependency has defined exclusions (in out pom.xml), + * So in reduced pom, we need to add original exclusions to A dependency + *

+ * 3 CASE - transitive exclusion + * If our pom.xml has a dependency, called A (and A is defined to include in artifactSet). + * Inside A pom.xml there can be a dependency (B) with exclusions, and we promote this transitive dependency (B), + * So in reduced pom, if we have B dependency, we need to keep original exclusions for B. + */ +public class DependenciesExclusionResolver { + + private final MavenSession session; + private final MavenProject originalProject; + + private final MavenProject shadedProject; + private final RepositorySystem repositorySystem; + private final Log log; + private final CollectResult shadedProjectStructure; + + private boolean pomModified; + + public DependenciesExclusionResolver( + MavenSession session, + MavenProject originalProject, + MavenProject shadedProject, + RepositorySystem repositorySystem, + Log log) + throws DependencyCollectionException { + this.session = session; + this.originalProject = originalProject; + this.shadedProject = shadedProject; + this.repositorySystem = repositorySystem; + this.log = log; + this.shadedProjectStructure = getCollectResult(); + this.pomModified = false; + } + + private CollectResult getCollectResult() throws DependencyCollectionException { + CollectRequest collectRequest = new CollectRequest(); + collectRequest.setRootArtifact(RepositoryUtils.toArtifact(shadedProject.getArtifact())); + collectRequest.setRepositories(shadedProject.getRemoteProjectRepositories()); + collectRequest.setDependencies(shadedProject.getDependencies().stream() + .map(d -> RepositoryUtils.toDependency( + d, session.getRepositorySession().getArtifactTypeRegistry())) + .collect(Collectors.toList())); + if (shadedProject.getDependencyManagement() != null) { + collectRequest.setManagedDependencies(shadedProject.getDependencyManagement().getDependencies().stream() + .map(d -> RepositoryUtils.toDependency( + d, session.getRepositorySession().getArtifactTypeRegistry())) + .collect(Collectors.toList())); + } + + return repositorySystem.collectDependencies(session.getRepositorySession(), collectRequest); + } + + public boolean resolve(DependencyList transitiveDependencies, List finalDependencies) + throws ArtifactDescriptorException, DependencyCollectionException { + + calculateExclusionsBasedOnMissingDependenciesInFinal(transitiveDependencies, finalDependencies); + addDirectAndTransitiveExclusions(finalDependencies); + + return pomModified; + } + + private void calculateExclusionsBasedOnMissingDependenciesInFinal( + DependencyList transitiveDependencies, List finalDependencies) { + if (shadedProjectStructure.getRoot() == null) { + return; + } + + for (DependencyNode n2 : shadedProjectStructure.getRoot().getChildren()) { + String artifactId2 = ShadeMojo.getId(RepositoryUtils.toArtifact(n2.getArtifact())); + + for (DependencyNode n3 : n2.getChildren()) { + // stupid m-a Artifact that has no idea what it is: dependency or artifact? + org.apache.maven.artifact.Artifact artifact3 = RepositoryUtils.toArtifact(n3.getArtifact()); + artifact3.setScope(n3.getDependency().getScope()); + String artifactId3 = ShadeMojo.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 : transitiveDependencies) { + if (ShadeMojo.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())) { + log.debug(String.format( + "dependency %s (scope %s) not found in transitive dependencies", + artifactId3, artifact3.getScope())); + for (Dependency dep : finalDependencies) { + if (ShadeMojo.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 (!hasExclusion(dep, artifact3)) { + log.debug(String.format( + "Adding exclusion for dependency %s (scope %s) " + "to %s (scope %s)", + artifactId3, artifact3.getScope(), ShadeMojo.getId(dep), dep.getScope())); + dep.addExclusion(toExclusion(artifact3)); + pomModified = true; + break; + } + } + } + } + } + } + } + + private void addDirectAndTransitiveExclusions(List finalDependencies) + throws ArtifactDescriptorException { + for (Dependency originalDirectDep : originalProject.getDependencies()) { + List directExclusions = getDirectExclusions(originalDirectDep); + log.debug( + "Found " + directExclusions.size() + " direct exclusions for " + originalDirectDep.getArtifactId()); + + RepositorySystemSession repoSession = session.getRepositorySession(); + + ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); + descriptorRequest.setArtifact(toArtifact(originalDirectDep)); + descriptorRequest.setRepositories(originalProject.getRemoteProjectRepositories()); + + ArtifactDescriptorResult descriptorResult = + repositorySystem.readArtifactDescriptor(repoSession, descriptorRequest); + + List dependenciesWithExclusions = + getDependenciesWithExclusions(descriptorResult); + + for (Dependency dependencyWithMissingExclusions : finalDependencies) { + // 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 (!dependencyWithMissingExclusions.getScope().equals("provided")) { + // move original exclusions from pom.xml to reduced pom.xml + if (isEqual(dependencyWithMissingExclusions, originalDirectDep)) { + addDirectExclusions(dependencyWithMissingExclusions, directExclusions); + } + + // move exclusions defined inside dependencies + // Our pom.xml has dependency A. + // Dependency A has defined dependency B with exclusions + // If we add B to reduced pom, we need to keep original exclusions for B + addMissingTransitiveExclusions(dependencyWithMissingExclusions, dependenciesWithExclusions); + } + } + } + } + + private void addDirectExclusions(Dependency dependencyWithMissingExclusions, List directExclusions) { + for (Exclusion directExclusion : directExclusions) { + if (!hasExclusion(dependencyWithMissingExclusions, directExclusion)) { + String msg = String.format( + "Adding direct exclusion for %s:%s to dependency %s (scope %s)", + directExclusion.getGroupId(), + directExclusion.getArtifactId(), + dependencyWithMissingExclusions.getArtifactId(), + dependencyWithMissingExclusions.getScope()); + log.debug(msg); + pomModified = true; + dependencyWithMissingExclusions.addExclusion(clone(directExclusion)); + } + } + } + + private List getDependenciesWithExclusions( + ArtifactDescriptorResult descriptorResult) { + + List toCopyList = descriptorResult.getDependencies(); + + ArrayList copy = new ArrayList<>(toCopyList.size()); + + for (org.eclipse.aether.graph.Dependency toCopy : toCopyList) { + if (toCopy.getScope() != null && toCopy.getScope().equals("test")) { + // test scope dependencies are not included in the resolved descriptor + continue; + } + copy.add(new org.eclipse.aether.graph.Dependency( + toCopy.getArtifact(), toCopy.getScope(), toCopy.isOptional(), toCopy.getExclusions())); + } + return copy; + } + + private void addMissingTransitiveExclusions( + Dependency depWithMissingExclusions, List dependenciesWithExclusions) { + + for (org.eclipse.aether.graph.Dependency depWithExclusions : dependenciesWithExclusions) { + if (isEqual(depWithMissingExclusions, depWithExclusions)) { + List exclusions = getExclusions(depWithExclusions); + for (Exclusion exclusion : exclusions) { + if (!hasExclusion(depWithMissingExclusions, exclusion)) { + String msg = String.format( + "Adding exclusion for %s:%s to dependency %s (scope %s)", + exclusion.getGroupId(), + exclusion.getArtifactId(), + depWithMissingExclusions.getArtifactId(), + depWithMissingExclusions.getScope()); + log.debug(msg); + pomModified = true; + depWithMissingExclusions.addExclusion(clone(exclusion)); + } + } + } + } + } + + private List getExclusions(org.eclipse.aether.graph.Dependency depWithExclusions) { + if (depWithExclusions.getExclusions() == null + || depWithExclusions.getExclusions().isEmpty()) { + return Collections.emptyList(); + } + + List exclusions = + new ArrayList<>(depWithExclusions.getExclusions().size()); + + log.debug("Found " + depWithExclusions.getExclusions().size() + " transitive exclusions for " + + depWithExclusions.getArtifact().getArtifactId()); + + for (org.eclipse.aether.graph.Exclusion aetherExclusion : depWithExclusions.getExclusions()) { + exclusions.add(toExclusion(aetherExclusion)); + } + + return exclusions; + } + + private List getDirectExclusions(Dependency dependency) { + for (Dependency originalDependency : originalProject.getDependencies()) { + if (isEqual(dependency, originalDependency)) { + return copy(originalDependency.getExclusions()); + } + } + + return Collections.emptyList(); + } + + private List copy(List toCopyList) { + ArrayList copy = new ArrayList<>(toCopyList.size()); + for (Exclusion toCopy : toCopyList) { + Exclusion exclusion = new Exclusion(); + exclusion.setArtifactId(toCopy.getArtifactId()); + exclusion.setGroupId(toCopy.getGroupId()); + copy.add(exclusion); + } + + return copy; + } + + private boolean isEqual(Dependency d1, Dependency d2) { + return d1.getGroupId().equals(d2.getGroupId()) && d1.getArtifactId().equals(d2.getArtifactId()); + } + + private boolean isEqual(Dependency d1, org.eclipse.aether.graph.Dependency d2) { + return d1.getGroupId().equals(d2.getArtifact().getGroupId()) + && d1.getArtifactId().equals(d2.getArtifact().getArtifactId()); + } + + private boolean isEqual(Exclusion e1, Exclusion e2) { + return e1.getGroupId().equals(e2.getGroupId()) && e1.getArtifactId().equals(e2.getArtifactId()); + } + + private Artifact toArtifact(Dependency dependency) { + org.eclipse.aether.graph.Dependency aetherDep = RepositoryUtils.toDependency( + dependency, session.getRepositorySession().getArtifactTypeRegistry()); + + return aetherDep.getArtifact(); + } + + private boolean hasExclusion(Dependency dep, Exclusion exclusion) { + for (Exclusion existingExclusion : dep.getExclusions()) { + if (isEqual(existingExclusion, exclusion)) { + return true; + } + } + + return false; + } + + private boolean hasExclusion(Dependency dep, org.apache.maven.artifact.Artifact exclusionToCheck) { + for (Exclusion existingExclusion : dep.getExclusions()) { + if (existingExclusion.getGroupId().equals(exclusionToCheck.getGroupId()) + && existingExclusion.getArtifactId().equals(exclusionToCheck.getArtifactId())) { + return true; + } + } + + return false; + } + + private Exclusion toExclusion(org.apache.maven.artifact.Artifact artifact) { + Exclusion exclusion = new Exclusion(); + exclusion.setArtifactId(artifact.getArtifactId()); + exclusion.setGroupId(artifact.getGroupId()); + return exclusion; + } + + private Exclusion toExclusion(org.eclipse.aether.graph.Exclusion aetherExclusion) { + Exclusion exclusion = new Exclusion(); + exclusion.setGroupId(aetherExclusion.getGroupId()); + exclusion.setArtifactId(aetherExclusion.getArtifactId()); + return exclusion; + } + + private Exclusion clone(Exclusion toClone) { + Exclusion exclusion = new Exclusion(); + exclusion.setGroupId(toClone.getGroupId()); + exclusion.setArtifactId(toClone.getArtifactId()); + return exclusion; + } +} diff --git a/src/main/java/org/apache/maven/plugins/shade/mojo/DependencyList.java b/src/main/java/org/apache/maven/plugins/shade/mojo/DependencyList.java new file mode 100644 index 00000000..ad59e856 --- /dev/null +++ b/src/main/java/org/apache/maven/plugins/shade/mojo/DependencyList.java @@ -0,0 +1,103 @@ +/* + * 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 java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.function.Consumer; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Dependency; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.project.DefaultProjectBuildingRequest; +import org.apache.maven.project.MavenProject; +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.eclipse.aether.RepositorySystem; +import org.eclipse.aether.collection.DependencyCollectionException; +import org.eclipse.aether.resolution.ArtifactDescriptorException; + +public class DependencyList implements Iterable { + + private final List dependencies; + + public DependencyList(List dependencies) { + this.dependencies = new ArrayList<>(); + copyDependencies(dependencies); + } + + public boolean resolveTransitiveDependenciesExclusions( + MavenSession session, + MavenProject originalProject, + ProjectBuilder projectBuilder, + File reducedPomFile, + RepositorySystem repositorySystem, + List finalDependencies, + Log log) { + try { + + synchronized (session.getProjectBuildingRequest()) { // Lock critical section to fix MSHADE-467 + ProjectBuildingRequest request = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest()); + request.setLocalRepository(session.getLocalRepository()); + request.setRemoteRepositories(originalProject.getRemoteArtifactRepositories()); + + ProjectBuildingResult shaded = projectBuilder.build(reducedPomFile, request); + + DependenciesExclusionResolver resolver = new DependenciesExclusionResolver( + session, originalProject, shaded.getProject(), repositorySystem, log); + + return resolver.resolve(this, finalDependencies); + } + } catch (ArtifactDescriptorException | DependencyCollectionException | ProjectBuildingException e) { + log.error("Failed to resolve exclusions for " + originalProject.getArtifact(), e); + throw new RuntimeException(e); + } + } + + // 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. + private void copyDependencies(List dependencies) { + for (Dependency d : dependencies) { + Dependency cloned = d.clone(); + this.dependencies.add(cloned); + } + } + + @Override + public Iterator iterator() { + return Collections.unmodifiableList(dependencies).iterator(); + } + + @Override + public void forEach(Consumer action) { + Iterable.super.forEach(action); + } + + @Override + public Spliterator spliterator() { + return Iterable.super.spliterator(); + } +} diff --git a/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java b/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java index 312c76ae..e5b89a41 100644 --- a/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java +++ b/src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java @@ -61,13 +61,10 @@ 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; @@ -1078,11 +1075,8 @@ private void createDependencyReducedPom(Set artifactsToRemove) // 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()); - } + DependencyList origDeps = + new DependencyList(promoteTransitiveDependencies ? transitiveDeps : project.getDependencies()); model = model.clone(); // MSHADE-185: We will remove all system scoped dependencies which usually @@ -1118,12 +1112,11 @@ private void createDependencyReducedPom(Set artifactsToRemove) addSystemScopedDependencyFromNonInterpolatedPom(dependencies, originalDependencies); // Check to see if we have a reduction and if so rewrite the POM. - rewriteDependencyReducedPomIfWeHaveReduction(dependencies, modified, transitiveDeps, model); + rewriteDependencyReducedPomIfWeHaveReduction(dependencies, modified, model, origDeps); } private void rewriteDependencyReducedPomIfWeHaveReduction( - List dependencies, boolean modified, List transitiveDeps, Model model) - throws IOException, ProjectBuildingException, DependencyCollectionException { + List dependencies, boolean modified, Model model, DependencyList origDeps) throws IOException { if (modified) { for (int loopCounter = 0; modified; loopCounter++) { @@ -1185,17 +1178,8 @@ private void rewriteDependencyReducedPomIfWeHaveReduction( 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); - } + modified = origDeps.resolveTransitiveDependenciesExclusions( + session, project, projectBuilder, f, repositorySystem, dependencies, getLog()); } project.setFile(dependencyReducedPomLocation); @@ -1237,107 +1221,19 @@ private Dependency createDependency(Artifact artifact) { return dep; } - private String getId(Artifact artifact) { + static String getId(Artifact artifact) { return getId(artifact.getGroupId(), artifact.getArtifactId(), artifact.getType(), artifact.getClassifier()); } - private String getId(Dependency dependency) { + static 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) { + static 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<>();