-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Detect and allow failing for unused/undeclared dependencies #62
base: master
Are you sure you want to change the base?
Changes from all commits
fff3d98
bd27e63
b1da3b5
95b36b3
81593db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ | |
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.stream.Collectors; | ||
|
||
import javax.inject.Inject; | ||
import javax.inject.Named; | ||
|
@@ -144,6 +145,8 @@ public class CompilerJdt extends AbstractCompiler implements ICompilerRequestor | |
|
||
private final Map<File, ResourceMetadata<File>> sources = new LinkedHashMap<>(); | ||
|
||
private Set<Path> referencedClasspathEntries = new LinkedHashSet<>(); | ||
|
||
/** | ||
* Set of ICompilationUnit to be compiled. | ||
*/ | ||
|
@@ -719,6 +722,7 @@ protected synchronized void addCompilationUnit(ICompilationUnit sourceUnit, Comp | |
compiler.options.storeAnnotations = true; | ||
} | ||
|
||
referencedClasspathEntries = namingEnvironment.getReferencedEntries(); | ||
return strategy.compile(namingEnvironment, compiler); | ||
} finally { | ||
if (fileManager != null) { | ||
|
@@ -987,4 +991,18 @@ public void skipCompile() { | |
strategy.skipCompile(); | ||
super.skipCompile(); | ||
} | ||
|
||
@Override | ||
public Set<File> getReferencedClasspathEntries() { | ||
return referencedClasspathEntries.stream().map(this::pathToFile).filter(file -> file != null).collect(Collectors.toSet()); | ||
} | ||
|
||
private File pathToFile(Path path) { | ||
try { | ||
return path.toFile(); | ||
} catch (UnsupportedOperationException e) { | ||
// Java 9 support | ||
return null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not feel right, to be honest. I think it will be cleaner to remember classpath dependencies as |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -374,4 +374,74 @@ public void testInnerTypeDependency_sourceDependencies() throws Exception { | |
mojos.compile(project, newParameter("dependencySourceTypes", "prefer")); | ||
mojos.assertBuildOutputs(new File(basedir, "target/classes"), "innertyperef/InnerTypeRef.class"); | ||
} | ||
|
||
@Test | ||
public void testReferencedEntries() throws Exception { | ||
final String javacMessage = "Compiler javac does not support unusedDeclaredDependency=error, use compilerId=jdt"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
File parent = resources.getBasedir("compile/referenced-entries"); | ||
Xpp3Dom unused = new Xpp3Dom("unusedDeclaredDependency"); | ||
unused.setValue("error"); | ||
|
||
mojos.flushClasspathCaches(); | ||
|
||
File b = new File(parent, "b"); | ||
|
||
try { | ||
mojos.compile(b, unused); | ||
} catch (UnsupportedOperationException e) { | ||
ErrorMessage.isMatch(e.getMessage(), javacMessage); | ||
} | ||
|
||
MavenProject a = mojos.readMavenProject(new File(parent, "a")); | ||
addDependency(a, "b", new File(b, "target/classes")); | ||
|
||
try { | ||
mojos.compile(a, unused); | ||
mojos.assertBuildOutputs(parent, "a/target/classes/a/A1.class", "a/target/classes/a/A2.class"); | ||
} catch (UnsupportedOperationException e) { | ||
ErrorMessage.isMatch(e.getMessage(), javacMessage); | ||
} | ||
|
||
cp(new File(parent, "a/src/main/java/a"), "A2.java-method", "A2.java"); | ||
|
||
try { | ||
mojos.compile(a, unused); | ||
// only A2 should have new output | ||
mojos.assertBuildOutputs(parent, "a/target/classes/a/A2.class"); | ||
} catch (UnsupportedOperationException e) { | ||
ErrorMessage.isMatch(e.getMessage(), javacMessage); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for completeness sake this test should also remove |
||
} | ||
|
||
@Test | ||
public void testReactorUnused() throws Exception { | ||
final String javacMessage = "Compiler javac does not support unusedDeclaredDependency=error, use compilerId=jdt"; | ||
ErrorMessage expected = new ErrorMessage(compilerId); | ||
expected.setSnippets("jdt", "ERROR pom.xml [0:0] The following dependencies are declared but are not used: [test:module-b:jar:1.0:compile]"); | ||
File parent = resources.getBasedir("compile/reactor-unused"); | ||
Xpp3Dom unused = new Xpp3Dom("unusedDeclaredDependency"); | ||
unused.setValue("error"); | ||
|
||
mojos.flushClasspathCaches(); | ||
|
||
File moduleB = new File(parent, "module-b"); | ||
|
||
try { | ||
mojos.compile(moduleB, unused); | ||
} catch (UnsupportedOperationException e) { | ||
ErrorMessage.isMatch(e.getMessage(), javacMessage); | ||
} | ||
|
||
MavenProject moduleA = mojos.readMavenProject(new File(parent, "module-a")); | ||
addDependency(moduleA, "module-b", new File(moduleB, "target/classes")); | ||
|
||
try { | ||
mojos.compile(moduleA, unused); | ||
Assert.fail(); | ||
} catch (MojoExecutionException e) { | ||
mojos.assertMessage(parent, "module-a/pom.xml", expected); | ||
} catch (UnsupportedOperationException e) { | ||
ErrorMessage.isMatch(e.getMessage(), javacMessage); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<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"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>reactor</groupId> | ||
<artifactId>module-a</artifactId> | ||
<version>1.0.0-SNAPSHOT</version> | ||
<packaging>jar</packaging> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>reactor</groupId> | ||
<artifactId>module-b</artifactId> | ||
<version>1.0.0-SNAPSHOT</version> | ||
</dependency> | ||
</dependencies> | ||
</project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package reactor.modulea; | ||
|
||
public class ModuleA { | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1
&& context.isEscalated()
here will result in errors reported during full build "magically" disappear during incremental build, only to reappear during the next full build or when modifying some sources but not others.We need to find a way to make
unusedDeclaredDependency=error
work during incremental build. I think the only way to do this is to track dependencies used by each source, record this information in build context and use it during subsequent incremental builds. I don't know how easy this is to implement.Do you see any other way to correctly support incremental build?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. I did not consider that scenario, and I agree with what you said. I'll remove this change.
I do not see another way from what you detailed. Since there is a need of persistence of the set of dependencies referenced between builds, I believe to keep that set of dependencies accurate, each source's list of dependencies would need to be tracked.
Along those lines, I'm wondering if something like a
Map<Path, Set<Artifact>>
where at the end of each build, any sources that were built would update the map would work (or the reverse with each dependency listing each source that references it where any that are still empty by the end of the build are considered unused).