Skip to content
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

Differentiating direct transitive and inherited transitive dependencies #148

Merged
merged 12 commits into from
Dec 24, 2022
Merged
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Configure the `pom.xml` file of your Maven project to use DepClean as part of th
<plugin>
<groupId>se.kth.castor</groupId>
<artifactId>depclean-maven-plugin</artifactId>
<version>2.0.3</version>
<version>{DEPCLEAN_LATEST_VERSION}</version>
<executions>
<execution>
<goals>
Expand All @@ -47,10 +47,10 @@ Configure the `pom.xml` file of your Maven project to use DepClean as part of th
Or you can run DepClean directly from the command line.

```bash
cd PATH_TO_MAVEN_PROJECT
mvn compile
cd {PATH_TO_MAVEN_PROJECT}
mvn compile
mvn compiler:testCompile
mvn se.kth.castor:depclean-maven-plugin:2.0.3:depclean
mvn se.kth.castor:depclean-maven-plugin:{DEPCLEAN_LATEST_VERSION}:depclean
```

Let's see an example of running DepClean version 2.0.1 in the project [Apache Commons Numbers](https://github.com/apache/commons-numbers/tree/master/commons-numbers-examples/examples-jmh)!
Expand Down Expand Up @@ -84,7 +84,7 @@ For example, if you want to fail the build in the presence of unused direct depe
<plugin>
<groupId>se.kth.castor</groupId>
<artifactId>depclean-maven-plugin</artifactId>
<version>2.0.3</version>
<version>{DEPCLEAN_LATEST_VERSION}</version>
<executions>
<execution>
<goals>
Expand All @@ -102,7 +102,7 @@ For example, if you want to fail the build in the presence of unused direct depe
Of course, it is also possible to execute DepClean with parameters directly from the command line. The previous example can be executed directly as follows:

```bash
mvn se.kth.castor:depclean-maven-plugin:2.0.3:depclean -DfailIfUnusedDirect=true -DignoreScopes=provided,test,runtime,system,import
mvn se.kth.castor:depclean-maven-plugin:{DEPCLEAN_LATEST_VERSION}:depclean -DfailIfUnusedDirect=true -DignoreScopes=provided,test,runtime,system,import
```

## How does DepClean works?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public class DepCleanManager {
private final Set<String> ignoreDependencies;
private final boolean failIfUnusedDirect;
private final boolean failIfUnusedTransitive;
private final boolean failIfUnusedInherited;
private final boolean failIfUnusedInheritedDirect;
private final boolean failIfUnusedInheritedTransitive;
private final boolean createPomDebloated;
private final boolean createResultJson;
private final boolean createCallGraphCsv;
Expand All @@ -65,7 +66,7 @@ public ProjectDependencyAnalysis execute() throws AnalysisFailureException {
return null;
}

extractLibClasses();
extractClassesFromDependencies();

final DefaultProjectDependencyAnalyzer projectDependencyAnalyzer = new DefaultProjectDependencyAnalyzer();
final ProjectDependencyAnalysis analysis = projectDependencyAnalyzer.analyze(buildProjectContext());
Expand All @@ -83,10 +84,16 @@ public ProjectDependencyAnalysis execute() throws AnalysisFailureException {
"Build failed due to unused transitive dependencies in the dependency tree of the project.");
}

/* Fail the build if there are unused inherited dependencies */
if (failIfUnusedInherited && analysis.hasUnusedInheritedDependencies()) {
/* Fail the build if there are unused inherited direct dependencies */
if (failIfUnusedInheritedDirect && analysis.hasUnusedInheritedDirectDependencies()) {
throw new AnalysisFailureException(
"Build failed due to unused inherited dependencies in the dependency tree of the project.");
"Build failed due to unused inherited direct dependencies in the dependency tree of the project.");
}

/* Fail the build if there are unused inherited direct dependencies */
if (failIfUnusedInheritedTransitive && analysis.hasUnusedInheritedTransitiveDependencies()) {
throw new AnalysisFailureException(
"Build failed due to unused inherited transitive dependencies in the dependency tree of the project.");
}

/* Writing the debloated version of the pom */
Expand All @@ -106,23 +113,21 @@ public ProjectDependencyAnalysis execute() throws AnalysisFailureException {
}

@SneakyThrows
private void extractLibClasses() {
final File dependencyDirectory =
dependencyManager.getBuildDirectory().resolve(DIRECTORY_TO_EXTRACT_DEPENDENCIES).toFile();
private void extractClassesFromDependencies() {
File dependencyDirectory = dependencyManager.getBuildDirectory().resolve(DIRECTORY_TO_EXTRACT_DEPENDENCIES).toFile();
FileUtils.deleteDirectory(dependencyDirectory);
dependencyManager.dependencyGraph().allDependencies()
.forEach(jarFile -> copyDependencies(jarFile, dependencyDirectory));

// TODO remove this workaround later
// Workaround for dependencies that are in located in a project's libs directory.
if (dependencyManager.getBuildDirectory().resolve("libs").toFile().exists()) {
try {
FileUtils.copyDirectory(
dependencyManager.getBuildDirectory().resolve("libs").toFile(),
dependencyDirectory
);
} catch (IOException | NullPointerException e) {
getLog().error("Error copying directory libs to dependency");
throw new RuntimeException(e);
getLog().error("Error copying directory libs to" + dependencyDirectory.getAbsolutePath());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ public ActualUsedClasses(ProjectContext context) {
private void registerClass(ClassName className) {
// Do not register class unknown to dependencies
if (context.hasNoDependencyOnClass(className)) {
log.debug("Class {} is not known to any dependency", className);
return;
}
log.trace("## Register class {}", className);
log.debug("## Registered class {}", className);
classes.add(className);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,13 @@ public ProjectDependencyAnalysis analyze(final ProjectContext projectContext) {
.forEach(folder -> actualUsedClasses.registerClasses(getProjectDependencyClasses(folder)));
// analyze project's tests class files
if (!projectContext.ignoreTests()) {
log.trace("Parsing test folder");
projectContext.getTestOutputFolders()
.forEach(folder -> actualUsedClasses.registerClasses(getProjectTestDependencyClasses(folder)));
}
// the set of compiled classes and tests in the project
Set<String> projectClasses = new HashSet<>(DefaultCallGraph.getProjectVertices());
log.debug("Project classes: {}", projectClasses);

// analyze dependencies' class files
actualUsedClasses.registerClasses(getProjectDependencyClasses(projectContext.getDependenciesFolder()));
// analyze extra classes (collected through static analysis of source code)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class ProjectDependencyAnalysisBuilder {
usedDependencies = actualUsedClasses.getRegisteredClasses().stream()
.flatMap(clazz -> context.getDependenciesForClass(clazz).stream())
.collect(Collectors.toSet());

log.debug("Actual used classes: " + actualUsedClasses.getRegisteredClasses());
log.debug("Used dependencies" + usedDependencies);
}

/**
Expand All @@ -38,30 +41,38 @@ public class ProjectDependencyAnalysisBuilder {
* @return the analysis
*/
public ProjectDependencyAnalysis analyse() {
// used dependencies
final Set<Dependency> usedDirectDependencies = getUsedDirectDependencies();
final Set<Dependency> usedTransitiveDependencies = getUsedTransitiveDependencies();
final Set<Dependency> usedInheritedDependencies = getUsedInheritedDependencies();
final Set<Dependency> usedInheritedDirectDependencies = getUsedInheritedDirectDependencies();
final Set<Dependency> usedInheritedTransitiveDependencies = getUsedInheritedTransitiveDependencies();
// unused dependencies
final Set<Dependency> unusedDirectDependencies = getUnusedDirectDependencies(usedDirectDependencies);
final Set<Dependency> unusedTransitiveDependencies = getUnusedTransitiveDependencies(usedTransitiveDependencies);
final Set<Dependency> unusedInheritedDependencies = getUnusedInheritedDependencies(usedInheritedDependencies);
final Set<Dependency> unusedInheritedDirectDependencies = getUnusedInheritedDirectDependencies(usedInheritedDirectDependencies);
final Set<Dependency> unusedInheritedTransitiveDependencies = getUnusedInheritedTransitiveDependencies(usedInheritedTransitiveDependencies);
// classes in each dependency
final Map<Dependency, DependencyTypes> dependencyClassesMap = buildDependencyClassesMap();

// ignore dependencies
context.getIgnoredDependencies().forEach(dependencyToIgnore -> {
ignoreDependency(usedDirectDependencies, unusedDirectDependencies, dependencyToIgnore);
ignoreDependency(usedTransitiveDependencies, unusedTransitiveDependencies, dependencyToIgnore);
ignoreDependency(usedInheritedDependencies, unusedInheritedDependencies, dependencyToIgnore);
ignoreDependency(usedInheritedDirectDependencies, unusedInheritedDirectDependencies, dependencyToIgnore);
ignoreDependency(usedInheritedTransitiveDependencies, unusedInheritedTransitiveDependencies, dependencyToIgnore);
});

return new ProjectDependencyAnalysis(
usedDirectDependencies,
usedTransitiveDependencies,
usedInheritedDependencies,
usedInheritedDirectDependencies,
usedInheritedTransitiveDependencies,
unusedDirectDependencies,
unusedTransitiveDependencies,
unusedInheritedDependencies,
unusedInheritedDirectDependencies,
unusedInheritedTransitiveDependencies,
context.getIgnoredDependencies(),
dependencyClassesMap,
context.getDependencyGraph());
context.getDependencyGraph()
);
}

private Map<Dependency, DependencyTypes> buildDependencyClassesMap() {
Expand All @@ -76,6 +87,13 @@ private Map<Dependency, DependencyTypes> buildDependencyClassesMap() {
return output;
}

private Set<Dependency> getUsedInheritedDirectDependencies() {
return usedDependencies.stream()
.filter(a -> context.getDependencyGraph().inheritedDirectDependencies().contains(a))
.peek(dependency -> log.trace("## Used Inherited Direct dependency {}", dependency))
.collect(Collectors.toSet());
}

private Set<Dependency> getUsedDirectDependencies() {
return usedDependencies.stream()
.filter(a -> context.getDependencyGraph().directDependencies().contains(a))
Expand All @@ -90,9 +108,9 @@ private Set<Dependency> getUsedTransitiveDependencies() {
.collect(Collectors.toSet());
}

private Set<Dependency> getUsedInheritedDependencies() {
private Set<Dependency> getUsedInheritedTransitiveDependencies() {
return usedDependencies.stream()
.filter(a -> context.getDependencyGraph().inheritedDependencies().contains(a))
.filter(a -> context.getDependencyGraph().inheritedTransitiveDependencies().contains(a))
.peek(dependency -> log.trace("## Used Transitive dependency {}", dependency))
.collect(Collectors.toSet());
}
Expand All @@ -101,36 +119,33 @@ private Set<Dependency> getUnusedDirectDependencies(Set<Dependency> usedDirectDe
return getUnusedDependencies(context.getDependencyGraph().directDependencies(), usedDirectDependencies);
}

private Set<Dependency> getUnusedTransitiveDependencies(
Set<Dependency> usedTransitiveDependencies) {
private Set<Dependency> getUnusedTransitiveDependencies(Set<Dependency> usedTransitiveDependencies) {
return getUnusedDependencies(context.getDependencyGraph().transitiveDependencies(), usedTransitiveDependencies);
}

private Set<Dependency> getUnusedInheritedDependencies(
Set<Dependency> usedInheritedDependencies) {
return getUnusedDependencies(context.getDependencyGraph().inheritedDependencies(), usedInheritedDependencies);
private Set<Dependency> getUnusedInheritedDirectDependencies(Set<Dependency> usedInheritedDependencies) {
return getUnusedDependencies(context.getDependencyGraph().inheritedDirectDependencies(), usedInheritedDependencies);
}

private Set<Dependency> getUnusedDependencies(
Set<Dependency> baseDependencies, Set<Dependency> usedDependencies) {
private Set<Dependency> getUnusedInheritedTransitiveDependencies(Set<Dependency> usedInheritedDependencies) {
return getUnusedDependencies(context.getDependencyGraph().inheritedTransitiveDependencies(), usedInheritedDependencies);
}

private Set<Dependency> getUnusedDependencies(Set<Dependency> baseDependencies, Set<Dependency> usedDependencies) {
final Set<Dependency> unusedInheritedDependencies = newHashSet(baseDependencies);
unusedInheritedDependencies.removeAll(usedDependencies);
return unusedInheritedDependencies;
}

/**
* If the dependencyToIgnore is an unused dependency, then add it to the set of
* usedDependencyCoordinates and remove it from the set of unusedDependencyCoordinates.
* If the dependency to ignore is an unused dependency, then add it to the set of usedDependencyCoordinates
* and remove it from the set of unusedDependencyCoordinates.
*
* @param usedDependencies The set of used artifacts where the dependency will be added.
* @param unusedDependencies The set of unused artifacts where the dependency will be removed.
* @param dependencyToIgnore The dependency to ignore.
*/
private void ignoreDependency(
Set<Dependency> usedDependencies,
Set<Dependency> unusedDependencies,
Dependency dependencyToIgnore) {

private void ignoreDependency(Set<Dependency> usedDependencies, Set<Dependency> unusedDependencies, Dependency dependencyToIgnore) {
for (Iterator<Dependency> i = unusedDependencies.iterator(); i.hasNext(); ) {
Dependency unusedDependency = i.next();
if (dependencyToIgnore.equals(unusedDependency)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.jgrapht.graph.AbstractBaseGraph;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
Expand All @@ -30,6 +31,7 @@
/**
* A directed graph G = (V, E) where V is a set of classes and E is a set of edges. Edges represent class member calls between the classes in V.
*/
@Slf4j
public class DefaultCallGraph {

private static final AbstractBaseGraph<String, DefaultEdge> directedGraph = new DefaultDirectedGraph<>(DefaultEdge.class);
Expand Down Expand Up @@ -63,12 +65,12 @@ public static void addEdge(String clazz, Set<String> referencedClassMembers) {
* @return All the referenced classes.
*/
public static Set<String> referencedClassMembers(Set<String> projectClasses) {
//System.out.println("project classes: " + projectClasses);
log.debug("Project classes: " + projectClasses);
Set<String> allReferencedClassMembers = new HashSet<>();
for (String projectClass : projectClasses) {
allReferencedClassMembers.addAll(traverse(projectClass));
}
//System.out.println("All referenced class members: " + allReferencedClassMembers);
log.debug("All referenced class members: " + allReferencedClassMembers);
return allReferencedClassMembers;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ public interface DependencyGraph {

Set<Dependency> directDependencies();

Set<Dependency> inheritedDependencies();

Set<Dependency> transitiveDependencies();

Set<Dependency> inheritedDirectDependencies();

Set<Dependency> inheritedTransitiveDependencies();

Set<Dependency> allDependencies();

Set<Dependency> getDependenciesForParent(Dependency parent);
Expand Down
Loading