Skip to content

Commit 8d2de4c

Browse files
committed
[MDEP-317] - add mojo to analyze invalid exclusions
This mojo reports if exclusions are defined on a dependency, but that dependency does not pull in said artifacts.
1 parent bc33485 commit 8d2de4c

File tree

12 files changed

+1000
-0
lines changed

12 files changed

+1000
-0
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,13 @@ under the License.
326326
<scope>test</scope>
327327
</dependency>
328328

329+
<dependency>
330+
<groupId>org.assertj</groupId>
331+
<artifactId>assertj-core</artifactId>
332+
<version>3.24.2</version>
333+
<scope>test</scope>
334+
</dependency>
335+
329336
<!-- Remove once deprecated code has been replaced/removed -->
330337
<dependency>
331338
<groupId>org.apache.maven</groupId>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
invoker.goals = clean ${project.groupId}:${project.artifactId}:${project.version}:analyze-exclusions
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Licensed to the Apache Software Foundation (ASF) under one
4+
~ or more contributor license agreements. See the NOTICE file
5+
~ distributed with this work for additional information
6+
~ regarding copyright ownership. The ASF licenses this file
7+
~ to you under the Apache License, Version 2.0 (the
8+
~ "License"); you may not use this file except in compliance
9+
~ with the License. You may obtain a copy of the License at
10+
~
11+
~ http://www.apache.org/licenses/LICENSE-2.0
12+
~
13+
~ Unless required by applicable law or agreed to in writing,
14+
~ software distributed under the License is distributed on an
15+
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
~ KIND, either express or implied. See the License for the
17+
~ specific language governing permissions and limitations
18+
~ under the License.
19+
-->
20+
21+
<project xmlns="http://maven.apache.org/POM/4.0.0"
22+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
23+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
24+
<modelVersion>4.0.0</modelVersion>
25+
26+
<groupId>org.apache.maven.its.dependency</groupId>
27+
<artifactId>test</artifactId>
28+
<version>1.0-SNAPSHOT</version>
29+
30+
<name>Test</name>
31+
<description>
32+
Test dependency:analyze-exclusion
33+
</description>
34+
35+
<properties>
36+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
37+
</properties>
38+
39+
<dependencies>
40+
<dependency>
41+
<groupId>org.apache.maven</groupId>
42+
<artifactId>maven-project</artifactId>
43+
<version>2.0.6</version>
44+
</dependency>
45+
<dependency>
46+
<groupId>org.apache.maven</groupId>
47+
<artifactId>maven-artifact</artifactId>
48+
<version>2.0.6</version>
49+
<exclusions>
50+
<exclusion>
51+
<groupId>javax.annotation</groupId>
52+
<artifactId>javax.annotation-api</artifactId>
53+
</exclusion>
54+
<exclusion>
55+
<groupId>javax.activation</groupId>
56+
<artifactId>javax.activation-api</artifactId>
57+
</exclusion>
58+
</exclusions>
59+
</dependency>
60+
<dependency>
61+
<groupId>org.apache.maven</groupId>
62+
<artifactId>maven-model</artifactId>
63+
<version>2.0.6</version>
64+
</dependency>
65+
</dependencies>
66+
67+
<dependencyManagement>
68+
<dependencies>
69+
<dependency>
70+
<groupId>org.apache.maven</groupId>
71+
<artifactId>maven-project</artifactId>
72+
<version>2.0.6</version>
73+
<exclusions>
74+
<exclusion>
75+
<groupId>javax.inject</groupId>
76+
<artifactId>javax.inject</artifactId>
77+
</exclusion>
78+
</exclusions>
79+
</dependency>
80+
</dependencies>
81+
</dependencyManagement>
82+
83+
<build>
84+
<pluginManagement>
85+
<plugins>
86+
<plugin>
87+
<artifactId>maven-dependency-plugin</artifactId>
88+
<configuration>
89+
<failOnWarning>false</failOnWarning>
90+
</configuration>
91+
</plugin>
92+
</plugins>
93+
</pluginManagement>
94+
</build>
95+
</project>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import org.apache.maven.artifact.Artifact;
21+
import org.apache.maven.artifact.repository.metadata.Metadata;
22+
import org.apache.maven.model.Model;
23+
24+
public class Main
25+
{
26+
public static final String SCOPE_COMPILE = Artifact.SCOPE_COMPILE;
27+
28+
public Model model = null;
29+
30+
public Metadata metadata = null;
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
File file = new File( basedir, "build.log" );
21+
assert file.exists();
22+
23+
String buildLog = file.getText( "UTF-8" );
24+
assert buildLog.contains( '[WARNING] The following dependencies defines unnecessary excludes');
25+
assert buildLog.contains( '[WARNING] org.apache.maven:maven-artifact:');
26+
assert buildLog.contains( '[WARNING] - javax.annotation:javax.annotation-api');
27+
assert buildLog.contains( '[WARNING] - javax.activation:javax.activation-api');
28+
assert buildLog.contains( '[WARNING] org.apache.maven:maven-project:');
29+
assert buildLog.contains( '[WARNING] - javax.inject:javax.inject');
30+
31+
return true;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.plugins.dependency.exclusion;
20+
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Objects;
24+
import java.util.Set;
25+
import java.util.function.Consumer;
26+
27+
import org.apache.maven.artifact.Artifact;
28+
import org.apache.maven.execution.MavenSession;
29+
import org.apache.maven.model.Dependency;
30+
import org.apache.maven.plugin.AbstractMojo;
31+
import org.apache.maven.plugin.MojoExecutionException;
32+
import org.apache.maven.plugin.MojoFailureException;
33+
import org.apache.maven.plugins.annotations.Component;
34+
import org.apache.maven.plugins.annotations.Execute;
35+
import org.apache.maven.plugins.annotations.LifecyclePhase;
36+
import org.apache.maven.plugins.annotations.Mojo;
37+
import org.apache.maven.plugins.annotations.Parameter;
38+
import org.apache.maven.plugins.annotations.ResolutionScope;
39+
import org.apache.maven.project.DefaultProjectBuildingRequest;
40+
import org.apache.maven.project.MavenProject;
41+
import org.apache.maven.project.ProjectBuilder;
42+
import org.apache.maven.project.ProjectBuildingException;
43+
import org.apache.maven.project.ProjectBuildingRequest;
44+
import org.apache.maven.project.ProjectBuildingResult;
45+
46+
import static java.lang.String.format;
47+
import static java.util.stream.Collectors.toList;
48+
import static java.util.stream.Collectors.toSet;
49+
import static org.apache.commons.lang3.StringUtils.stripToEmpty;
50+
import static org.apache.maven.plugins.dependency.exclusion.Coordinates.coordinates;
51+
52+
/**
53+
* Analyzes the exclusions defined on dependencies in this project and reports if any of them are invalid.
54+
* <p>
55+
* Relevant use case is when an artifact in a later version has removed usage of a dependency, making the exclusion no
56+
* longer valid.
57+
* </p>
58+
* @since 3.6.2
59+
*/
60+
@Mojo(name = "analyze-exclusions", requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
61+
@Execute(phase = LifecyclePhase.TEST_COMPILE)
62+
public class AnalyzeExclusionsMojo extends AbstractMojo {
63+
64+
@Component
65+
private MavenProject project;
66+
67+
@Component
68+
private ProjectBuilder projectBuilder;
69+
70+
@Component
71+
private MavenSession session;
72+
73+
/**
74+
* Whether to fail the build if invalid exclusions is found.
75+
*/
76+
@Parameter(property = "failOnWarning", defaultValue = "false")
77+
private boolean failOnWarning;
78+
79+
/**
80+
* Skip plugin execution completely.
81+
*/
82+
@Parameter(property = "mdep.skip", defaultValue = "false")
83+
private boolean skip;
84+
85+
@Override
86+
public void execute() throws MojoExecutionException, MojoFailureException {
87+
if (skip) {
88+
getLog().debug("Skipping execution");
89+
return;
90+
}
91+
List<Dependency> dependenciesWithExclusions = project.getDependencies().stream()
92+
.filter(dep -> !dep.getExclusions().isEmpty())
93+
.collect(toList());
94+
95+
if (dependenciesWithExclusions.isEmpty()) {
96+
getLog().debug("No dependencies defined with exclusions - exiting");
97+
return;
98+
}
99+
100+
ExclusionChecker checker = new ExclusionChecker();
101+
102+
for (Dependency dependency : dependenciesWithExclusions) {
103+
Coordinates currentCoordinates = coordinates(dependency.getGroupId(), dependency.getArtifactId());
104+
Artifact matchingArtifact = project.getArtifacts().stream()
105+
.filter(artifact -> matchesDependency(artifact, dependency))
106+
.findFirst()
107+
.orElseThrow(() -> new MojoExecutionException("TODO Bug in code?"));
108+
109+
ProjectBuildingResult result = buildProject(matchingArtifact);
110+
111+
Set<Coordinates> actualDependencies = result.getProject().getArtifacts().stream()
112+
.map(a -> coordinates(a.getGroupId(), a.getArtifactId()))
113+
.collect(toSet());
114+
115+
Set<Coordinates> exclusions = dependency.getExclusions().stream()
116+
.map(e -> coordinates(e.getGroupId(), e.getArtifactId()))
117+
.collect(toSet());
118+
119+
checker.check(currentCoordinates, exclusions, actualDependencies);
120+
}
121+
122+
if (!checker.getViolations().isEmpty()) {
123+
if (failOnWarning) {
124+
logWarnings(checker.getViolations(), (value) -> getLog().error(value));
125+
throw new MojoExecutionException("Invalid exclusions found");
126+
} else {
127+
logWarnings(checker.getViolations(), (value) -> getLog().warn(value));
128+
}
129+
}
130+
}
131+
132+
private boolean matchesDependency(Artifact artifact, Dependency dependency) {
133+
return Objects.equals(artifact.getGroupId(), dependency.getGroupId())
134+
&& Objects.equals(artifact.getArtifactId(), dependency.getArtifactId())
135+
&& Objects.equals(artifact.getType(), dependency.getType())
136+
&& Objects.equals(stripToEmpty(artifact.getClassifier()), stripToEmpty(dependency.getClassifier()));
137+
}
138+
139+
private void logWarnings(Map<Coordinates, List<Coordinates>> violations, final Consumer<String> logger) {
140+
logger.accept("The following dependencies defines unnecessary excludes");
141+
violations.forEach((dependency, invalidExclusions) -> {
142+
logger.accept(" " + dependency + ":");
143+
invalidExclusions.forEach(invalidExclusion -> {
144+
logger.accept(" - " + invalidExclusion);
145+
});
146+
});
147+
}
148+
149+
private ProjectBuildingResult buildProject(Artifact artifact) throws MojoExecutionException {
150+
try {
151+
ProjectBuildingRequest projectBuildingRequest =
152+
new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
153+
projectBuildingRequest.setResolveDependencies(true);
154+
return projectBuilder.build(artifact, true, projectBuildingRequest);
155+
} catch (ProjectBuildingException e) {
156+
throw new MojoExecutionException(
157+
format("Failed to build project for %s:%s", artifact.getGroupId(), artifact.getArtifactId()), e);
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)