Copilot commented on code in PR #770:
URL:
https://github.com/apache/maven-shade-plugin/pull/770#discussion_r3674335547
##########
src/main/java/org/apache/maven/plugins/shade/mojo/ShadeMojo.java:
##########
@@ -1078,11 +1075,8 @@ private void createDependencyReducedPom(Set<String>
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<Dependency> origDeps = new ArrayList<>();
- List<Dependency> source = promoteTransitiveDependencies ?
transitiveDeps : project.getDependencies();
- for (Dependency d : source) {
- origDeps.add(d.clone());
- }
+ DependencyList origDeps =
+ new DependencyList(promoteTransitiveDependencies ?
transitiveDeps : project.getDependencies());
Review Comment:
`origDeps` is later passed as the `transitiveDependencies` input to
exclusion calculation. When `promoteTransitiveDependencies` is `false`, this
passes only direct dependencies rather than the computed transitive dependency
set, which can change the “found in transitive dependencies” check and lead to
incorrect exclusions being added. Consider always passing the actual
`transitiveDeps` list into the exclusion-resolution step, or rename/adjust the
resolver logic so it doesn’t rely on a parameter that is sometimes not
transitive.
##########
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.
+ * <p></p>
+ * 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
+ * <p></p>
+ * 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<Dependency> finalDependencies)
+ throws ArtifactDescriptorException, DependencyCollectionException {
+
+
calculateExclusionsBasedOnMissingDependenciesInFinal(transitiveDependencies,
finalDependencies);
+ addDirectAndTransitiveExclusions(finalDependencies);
+
+ return pomModified;
+ }
+
+ private void calculateExclusionsBasedOnMissingDependenciesInFinal(
+ DependencyList transitiveDependencies, List<Dependency>
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<Dependency>
finalDependencies)
+ throws ArtifactDescriptorException {
+ for (Dependency originalDirectDep : originalProject.getDependencies())
{
+ List<Exclusion> 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<org.eclipse.aether.graph.Dependency>
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);
+ }
Review Comment:
`Dependency#getScope()` can be `null` (defaulting to compile), so calling
`.equals("provided")` can throw `NullPointerException`. Use a null-safe
comparison (e.g., `"provided".equals(scope)` or `!"provided".equals(scope)`)
consistent with the earlier pattern used for `artifact3.getScope()`.
##########
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<Dependency> {
+
+ private final List<Dependency> dependencies;
+
+ public DependencyList(List<Dependency> dependencies) {
+ this.dependencies = new ArrayList<>();
+ copyDependencies(dependencies);
+ }
+
+ public boolean resolveTransitiveDependenciesExclusions(
+ MavenSession session,
+ MavenProject originalProject,
+ ProjectBuilder projectBuilder,
+ File reducedPomFile,
+ RepositorySystem repositorySystem,
+ List<Dependency> 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);
+ }
+ }
Review Comment:
The method swallows checked exceptions and rethrows `RuntimeException`,
which changes the error contract compared to the previous code path and makes
it harder for callers to handle/report Maven-plugin failures consistently.
Prefer propagating a checked exception (or wrapping into a
Maven/Plugin-specific exception type used by the surrounding Mojo) and avoid
double-logging (logging + rethrow) if the caller already logs failures.
##########
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.
+ * <p></p>
+ * 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
+ * <p></p>
+ * 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<Dependency> finalDependencies)
+ throws ArtifactDescriptorException, DependencyCollectionException {
+
+
calculateExclusionsBasedOnMissingDependenciesInFinal(transitiveDependencies,
finalDependencies);
+ addDirectAndTransitiveExclusions(finalDependencies);
+
+ return pomModified;
+ }
+
+ private void calculateExclusionsBasedOnMissingDependenciesInFinal(
+ DependencyList transitiveDependencies, List<Dependency>
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<Dependency>
finalDependencies)
+ throws ArtifactDescriptorException {
+ for (Dependency originalDirectDep : originalProject.getDependencies())
{
+ List<Exclusion> 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<org.eclipse.aether.graph.Dependency>
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<Exclusion> 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<org.eclipse.aether.graph.Dependency>
getDependenciesWithExclusions(
+ ArtifactDescriptorResult descriptorResult) {
+
+ List<org.eclipse.aether.graph.Dependency> toCopyList =
descriptorResult.getDependencies();
+
+ ArrayList<org.eclipse.aether.graph.Dependency> 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<org.eclipse.aether.graph.Dependency> dependenciesWithExclusions) {
+
+ for (org.eclipse.aether.graph.Dependency depWithExclusions :
dependenciesWithExclusions) {
+ if (isEqual(depWithMissingExclusions, depWithExclusions)) {
+ List<Exclusion> 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<Exclusion> getExclusions(org.eclipse.aether.graph.Dependency
depWithExclusions) {
+ if (depWithExclusions.getExclusions() == null
+ || depWithExclusions.getExclusions().isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<Exclusion> 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<Exclusion> getDirectExclusions(Dependency dependency) {
+ for (Dependency originalDependency :
originalProject.getDependencies()) {
+ if (isEqual(dependency, originalDependency)) {
+ return copy(originalDependency.getExclusions());
+ }
+ }
+
+ return Collections.emptyList();
+ }
+
+ private List<Exclusion> copy(List<Exclusion> toCopyList) {
+ ArrayList<Exclusion> 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());
+ }
Review Comment:
Equality checks ignore `type` and `classifier` (and also ignore `version`),
while `ShadeMojo.getId(...)` includes `type` and `classifier`. This mismatch
can apply exclusions to the wrong dependency when the same `groupId:artifactId`
appears with different types/classifiers (e.g., `jar` vs `pom`, attached
artifacts). Consider aligning matching logic with `getId(...)`-style
identifiers (including type/classifier) to avoid mis-associating exclusions.
##########
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.
+ * <p></p>
+ * 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
Review Comment:
Correct the typo “in out pom.xml” to “in our pom.xml”.
##########
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
Review Comment:
The test asserts exclusions by dependency list index (`[1]`, `[3]`), which
is brittle because dependency ordering in generated POMs can change due to
benign reordering. Prefer selecting the dependency node by
`groupId`/`artifactId` (and then assert its exclusions) to make the IT robust
across ordering/formatting changes.
##########
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.
+ * <p></p>
+ * 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
+ * <p></p>
+ * 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<Dependency> finalDependencies)
+ throws ArtifactDescriptorException, DependencyCollectionException {
+
+
calculateExclusionsBasedOnMissingDependenciesInFinal(transitiveDependencies,
finalDependencies);
+ addDirectAndTransitiveExclusions(finalDependencies);
+
+ return pomModified;
+ }
+
+ private void calculateExclusionsBasedOnMissingDependenciesInFinal(
+ DependencyList transitiveDependencies, List<Dependency>
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<Dependency>
finalDependencies)
+ throws ArtifactDescriptorException {
+ for (Dependency originalDirectDep : originalProject.getDependencies())
{
+ List<Exclusion> 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<org.eclipse.aether.graph.Dependency>
dependenciesWithExclusions =
+ getDependenciesWithExclusions(descriptorResult);
+
+ for (Dependency dependencyWithMissingExclusions :
finalDependencies) {
Review Comment:
This performs an artifact-descriptor resolve for every direct dependency and
then scans all `finalDependencies` for each, resulting in N descriptor reads
plus an O(N*M) loop. Consider indexing `finalDependencies` (e.g., by a stable
key) and only processing candidates that can match the current direct
dependency or are present in `dependenciesWithExclusions`; also consider
caching `ArtifactDescriptorResult` per direct dependency coordinate to avoid
repeated remote/IO work.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]