Skip to content

Commit 4156490

Browse files
authored
Merge pull request #266 from clairemcginty/allow-list-param
Add target* params for conflict targeting
2 parents 2b948f5 + 6ec6a21 commit 4156490

File tree

12 files changed

+363
-27
lines changed

12 files changed

+363
-27
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 0.2.6
2+
- Added `targetSourcePackages` and `targetDestinationPackages` configuration options
3+
- Renamed `ignoreSubpackages` configuration option to `filterSubpackages`
4+
15
### 0.2.5
26

37
- Added `java.lang.invoke.VarHandle` to the list of classes with

README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,44 @@ packages on the destination side can be ignored with
205205

206206
By default, all subpackages of the specified packages are also ignored, but
207207
this can be disabled on an individual basis by adding
208-
`<ignoreSubpackages>false</ignoreSubpackages>` to the `<ignoreSourcePackage>`
208+
`<filterSubpackages>false</filterSubpackages>` to the `<ignoreSourcePackage>`
209+
or `<ignoreDestinationPackage>` element. **Note**: In previous releases (<=0.2.5), this setting was named
210+
`ignoreSubpackages`. Setting `ignoreSubpackages` in your pom.xml is still supported; the plugin will
211+
translate it to the new key value.
212+
213+
### Target only conflicts from certain packages
214+
215+
Conversely, the plugin can be configured to _only_ report on conflicts in specific packages, based on the name of the class that has the
216+
conflict. There are separate configuration options for targeting conflicts on the "source" side of the conflict
217+
and the "destination" side of the conflict.
218+
219+
Packages on the source side can be targeted with `<targetSourcePackages>` and
220+
packages on the destination side can be targeted with `<targetDestinationPackages>`:
221+
222+
```xml
223+
<configuration>
224+
<!-- Only target conflicts coming from groovy.lang source package -->
225+
<targetSourcePackages>
226+
<targetSourcePackage>
227+
<package>groovy.lang</package>
228+
</targetSourcePackage>
229+
</targetSourcePackages>
230+
<!-- Only target conflicts coming from com.foo package on the callee side -->
231+
<targetDestinationPackages>
232+
<targetDestinationPackage>
233+
<package>com.foo</package>
234+
</targetDestinationPackage>
235+
</targetDestinationPackages>
236+
</configuration>
237+
```
238+
239+
By default, all subpackages of the specified packages are also ignored, but
240+
this can be disabled on an individual basis by adding
241+
`<filterSubpackages>false</filterSubpackages>` to the `<ignoreSourcePackage>`
209242
or `<ignoreDestinationPackage>` element.
210243

244+
**Note** that `target*` options CANNOT be used in conjunction with `ignore*` options. You can only specify one or the other.
245+
211246
# Caveats and Limitations
212247

213248
Because this plugin analyzes the bytecode of the `.class` files of your code
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.spotify.missinglink.d;
2+
3+
/**
4+
* TODO: document!
5+
*/
6+
public class WillGoAway {
7+
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.spotify.missinglink.e;
2+
3+
import com.spotify.missinglink.d.WillGoAway;
4+
5+
/**
6+
* TODO: document!
7+
*/
8+
public class E {
9+
public static void classShouldBeMissing() {
10+
System.out.println(new WillGoAway());
11+
}
12+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<groupId>com.spotify.missinglink.tests</groupId>
6+
<artifactId>class-missing-target-destination</artifactId>
7+
<version>1-SNAPSHOT</version>
8+
9+
<dependencies>
10+
<dependency>
11+
<groupId>com.spotify.missinglink.tests</groupId>
12+
<artifactId>a</artifactId>
13+
<version>2-SNAPSHOT</version>
14+
</dependency>
15+
<dependency>
16+
<groupId>com.spotify.missinglink.tests</groupId>
17+
<artifactId>b</artifactId>
18+
<version>1-SNAPSHOT</version>
19+
</dependency>
20+
<dependency>
21+
<groupId>junit</groupId>
22+
<artifactId>junit</artifactId>
23+
<version>4.13.1</version>
24+
<scope>test</scope>
25+
</dependency>
26+
</dependencies>
27+
28+
<build>
29+
<plugins>
30+
<plugin>
31+
<groupId>@project.groupId@</groupId>
32+
<artifactId>@project.artifactId@</artifactId>
33+
<version>@project.version@</version>
34+
<configuration>
35+
<targetDestinationPackages>
36+
<targetDestinationPackage>
37+
<package>com.spotify.missinglink.d</package>
38+
</targetDestinationPackage>
39+
</targetDestinationPackages>
40+
</configuration>
41+
<executions>
42+
<execution>
43+
<goals><goal>check</goal></goals>
44+
<phase>process-classes</phase>
45+
</execution>
46+
</executions>
47+
</plugin>
48+
<plugin>
49+
<groupId>org.apache.maven.plugins</groupId>
50+
<artifactId>maven-compiler-plugin</artifactId>
51+
<version>3.5</version>
52+
<configuration>
53+
<source>8</source>
54+
<target>8</target>
55+
<useIncrementalCompilation>false</useIncrementalCompilation>
56+
</configuration>
57+
</plugin>
58+
</plugins>
59+
</build>
60+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.spotify.missinglink;
2+
3+
import com.spotify.missinglink.e.E;
4+
5+
/**
6+
* This is expected to generate a runtime error because WillGoAway(), as invoked by E.classShouldBeMissing(),
7+
* doesn't exist in a conflicting package.
8+
*/
9+
public class ClassMissingAllowDestination {
10+
11+
public static void main(String[] args) {
12+
E.classShouldBeMissing();
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.spotify.missinglink;
2+
3+
import org.junit.Assert;
4+
import org.junit.Test;
5+
6+
public class ClassMissingAllowDestinationTest {
7+
8+
@Test
9+
public void shouldThrowError() throws Exception {
10+
Throwable ex = Assert.assertThrows(NoClassDefFoundError.class, () -> ClassMissingAllowDestination.main(new String[0]));
11+
Assert.assertTrue(ex.getMessage().contains("WillGoAway"));
12+
}
13+
}

maven-plugin/src/main/java/com/spotify/missinglink/maven/CheckMojo.java

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,15 @@ public class CheckMojo extends AbstractMojo {
140140
* #excludeDependencies} but operates at a package name level instead of a groupId/artifactId
141141
* level.
142142
*/
143-
@Parameter protected List<IgnoredPackage> ignoreSourcePackages = new ArrayList<>();
143+
@Parameter protected List<PackageFilter> ignoreSourcePackages = new ArrayList<>();
144+
145+
/**
146+
* Optional list of source packages to target in conflict searches. Any conflicts originating in
147+
* packages outside of this list will be ignored.
148+
*
149+
* <p>This parameter CANNOT be used in conjunction with ignoreSourcePackages. Only one can be set.
150+
*/
151+
@Parameter protected List<PackageFilter> targetSourcePackages = new ArrayList<>();
144152

145153
/**
146154
* Optional list of packages to ignore conflicts in where the destination/called-side of the
@@ -152,8 +160,20 @@ public class CheckMojo extends AbstractMojo {
152160
*
153161
* <p>For example, if the package "javax.bar" is in ignoreDestinationPackages, then any conflict
154162
* found having to do with calling a method in a class in javax.bar is ignored.
163+
*
164+
* <p>This parameter CANNOT be used in conjunction with targetDestinationPackages. Only one can be
165+
* set.
155166
*/
156-
@Parameter protected List<IgnoredPackage> ignoreDestinationPackages = new ArrayList<>();
167+
@Parameter protected List<PackageFilter> ignoreDestinationPackages = new ArrayList<>();
168+
169+
/**
170+
* Optional list of destination packages to target in conflict searches. Any conflicts originating
171+
* in packages outside of this list will be ignored.
172+
*
173+
* <p>This parameter CANNOT be used in conjunction with ignoreDestinationPackages. Only one can be
174+
* set.
175+
*/
176+
@Parameter protected List<PackageFilter> targetDestinationPackages = new ArrayList<>();
157177

158178
/**
159179
* Optional: can be set to explicitly define the path to use for the bootclasspath containing the
@@ -197,6 +217,17 @@ public void execute() throws MojoExecutionException, MojoFailureException {
197217
+ Joiner.on(", ").join(ConflictCategory.values()));
198218
}
199219

220+
if (!ignoreDestinationPackages.isEmpty() && !targetDestinationPackages.isEmpty()) {
221+
throw new MojoExecutionException(
222+
"Either ignoreDestinationPackages or targetDestinationPackages can be set, "
223+
+ "but not both.");
224+
}
225+
226+
if (!ignoreSourcePackages.isEmpty() && !targetSourcePackages.isEmpty()) {
227+
throw new MojoExecutionException(
228+
"Either ignoreSourcePackages or targetSourcePackages can be set, " + "but not both.");
229+
}
230+
200231
Collection<Conflict> conflicts = loadArtifactsAndCheckConflicts();
201232
final int initialCount = conflicts.size();
202233

@@ -260,7 +291,7 @@ private Collection<Conflict> filterConflicts(
260291
getLog().debug("Ignoring source packages: " + Joiner.on(", ").join(ignoreSourcePackages));
261292

262293
final Predicate<Conflict> predicate =
263-
conflict -> !packageIsIgnored(ignoreSourcePackages, conflict.dependency().fromClass());
294+
conflict -> !packageIsFiltered(ignoreSourcePackages, conflict.dependency().fromClass());
264295

265296
conflicts =
266297
filterConflictsBy(
@@ -271,6 +302,24 @@ private Collection<Conflict> filterConflicts(
271302
+ " conflicts found in ignored source packages. "
272303
+ "Run plugin again without the 'ignoreSourcePackages' parameter to see "
273304
+ "all conflicts that were found.");
305+
} else if (!targetSourcePackages.isEmpty()) {
306+
getLog()
307+
.debug(
308+
"Checking for conflicts in source packages: "
309+
+ Joiner.on(", ").join(targetSourcePackages));
310+
311+
final Predicate<Conflict> predicate =
312+
conflict -> packageIsFiltered(targetSourcePackages, conflict.dependency().fromClass());
313+
314+
conflicts =
315+
filterConflictsBy(
316+
conflicts,
317+
predicate,
318+
num ->
319+
num
320+
+ " conflicts found in targeted source packages. "
321+
+ "Run plugin again without the 'targetSourcePackages' parameter to see "
322+
+ "all conflicts that were found.");
274323
}
275324

276325
if (!ignoreDestinationPackages.isEmpty()) {
@@ -280,7 +329,7 @@ private Collection<Conflict> filterConflicts(
280329

281330
final Predicate<Conflict> predicate =
282331
conflict ->
283-
!packageIsIgnored(ignoreDestinationPackages, conflict.dependency().targetClass());
332+
!packageIsFiltered(ignoreDestinationPackages, conflict.dependency().targetClass());
284333

285334
conflicts =
286335
filterConflictsBy(
@@ -291,6 +340,25 @@ private Collection<Conflict> filterConflicts(
291340
+ " conflicts found in ignored destination packages. "
292341
+ "Run plugin again without the 'ignoreDestinationPackages' parameter to see "
293342
+ "all conflicts that were found.");
343+
} else if (!targetDestinationPackages.isEmpty()) {
344+
getLog()
345+
.debug(
346+
"Checking for conflicts in destination packages: "
347+
+ Joiner.on(", ").join(targetDestinationPackages));
348+
349+
final Predicate<Conflict> predicate =
350+
conflict ->
351+
packageIsFiltered(targetDestinationPackages, conflict.dependency().targetClass());
352+
353+
conflicts =
354+
filterConflictsBy(
355+
conflicts,
356+
predicate,
357+
num ->
358+
num
359+
+ " conflicts found in allowed targeted packages. "
360+
+ "Run plugin again without the 'targetDestinationPackages' parameter to see "
361+
+ "all conflicts that were found.");
294362
}
295363

296364
return conflicts;
@@ -327,19 +395,18 @@ private Collection<Conflict> filterConflictsBy(
327395
* destination-side) is ignored based on the collection of IgnoredPackages. Reusable logic between
328396
* ignoring source/destination packages.
329397
*/
330-
private boolean packageIsIgnored(
331-
Collection<IgnoredPackage> ignoredPackages, ClassTypeDescriptor classTypeDescriptor) {
332-
398+
private boolean packageIsFiltered(
399+
Collection<PackageFilter> packageFilters, ClassTypeDescriptor classTypeDescriptor) {
333400
final String className = classTypeDescriptor.getClassName().replace('/', '.');
334401
// this might be missing some corner-cases on naming rules:
335402
final String conflictPackageName = className.substring(0, className.lastIndexOf('.'));
336403

337-
return ignoredPackages.stream()
404+
return packageFilters.stream()
338405
.anyMatch(
339406
p -> {
340407
final String ignoredPackageName = p.getPackage();
341408
return conflictPackageName.equals(ignoredPackageName)
342-
|| (p.isIgnoreSubpackages()
409+
|| (p.isFilterSubpackages()
343410
&& conflictPackageName.startsWith(ignoredPackageName + "."));
344411
});
345412
}

maven-plugin/src/main/java/com/spotify/missinglink/maven/IgnoredPackage.java renamed to maven-plugin/src/main/java/com/spotify/missinglink/maven/PackageFilter.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,22 @@
4242
/**
4343
* Package names to be ignored when reporting conflicts. A package name is a String like
4444
* "javax.servlet" (not a regular expression). By default, subpackages of the specified package are
45-
* also ignored - to disable this behavior, set ignoreSubpackages to false.
45+
* also ignored - to disable this behavior, set filterSubpackages to false.
4646
*/
47-
public class IgnoredPackage {
47+
public class PackageFilter {
4848

4949
// Plexus seems to require classes to be referenced in the Maven project model to have no-arg
5050
// constructors and getters/setters.
5151

5252
private String name;
53-
private boolean ignoreSubpackages = true;
53+
private boolean filterSubpackages = true;
5454

55-
public IgnoredPackage() {}
55+
public PackageFilter() {}
5656

57-
public IgnoredPackage(String name, boolean ignoreSubpackages) {
57+
public PackageFilter(String name, boolean filterSubpackages) {
5858
this.name = checkNotNull(name);
5959
Preconditions.checkArgument(!name.isEmpty(), "name cannot be empty");
60-
this.ignoreSubpackages = ignoreSubpackages;
60+
this.filterSubpackages = filterSubpackages;
6161
}
6262

6363
public String getPackage() {
@@ -69,12 +69,16 @@ public void setPackage(String name) {
6969
this.name = name;
7070
}
7171

72-
public boolean isIgnoreSubpackages() {
73-
return ignoreSubpackages;
72+
public boolean isFilterSubpackages() {
73+
return filterSubpackages;
74+
}
75+
76+
public void setFilterSubpackages(boolean filterSubpackages) {
77+
this.filterSubpackages = filterSubpackages;
7478
}
7579

7680
public void setIgnoreSubpackages(boolean ignoreSubpackages) {
77-
this.ignoreSubpackages = ignoreSubpackages;
81+
this.filterSubpackages = ignoreSubpackages;
7882
}
7983

8084
@Override
@@ -86,9 +90,9 @@ public boolean equals(Object o) {
8690
return false;
8791
}
8892

89-
IgnoredPackage that = (IgnoredPackage) o;
93+
PackageFilter that = (PackageFilter) o;
9094

91-
if (ignoreSubpackages != that.ignoreSubpackages) {
95+
if (filterSubpackages != that.filterSubpackages) {
9296
return false;
9397
}
9498
return name.equals(that.name);
@@ -97,7 +101,7 @@ public boolean equals(Object o) {
97101
@Override
98102
public int hashCode() {
99103
int result = name.hashCode();
100-
result = 31 * result + (ignoreSubpackages ? 1 : 0);
104+
result = 31 * result + (filterSubpackages ? 1 : 0);
101105
return result;
102106
}
103107

@@ -107,8 +111,8 @@ public String toString() {
107111
+ "name='"
108112
+ name
109113
+ '\''
110-
+ ", ignoreSubpackages="
111-
+ ignoreSubpackages
114+
+ ", filterSubpackages="
115+
+ filterSubpackages
112116
+ '}';
113117
}
114118
}

0 commit comments

Comments
 (0)