Skip to content

Commit

Permalink
Split failIfUnusedDependency intro failIfUnusedDirect, failIfUnusedTr…
Browse files Browse the repository at this point in the history
…ansitive, and FailIfUnusedInherited
  • Loading branch information
cesarsotovalero committed Dec 21, 2020
1 parent 7d6b4ce commit 47faca7
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 151 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ The Maven plugin can be configured with the following additional parameters.
| `<ignoreScopes>` | `Set<String>` | Add a list of scopes, to be ignored by DepClean during the analysis. Useful to not analyze dependencies with scopes that are not needed at runtime. **Valid scopes are:** `compile`, `provided`, `test`, `runtime`, `system`, `import`. An Empty string indicates no scopes (default).|
| `<createPomDebloated>` | `boolean` | If this is true, DepClean creates a debloated version of the pom without unused dependencies called `debloated-pom.xml`, in the root of the project. **Default value is:** `false`.|
| `<createResultJson>` | `boolean` | If this is true, DepClean creates a JSON file of the dependency tree along with metadata of each dependency. The file is called `depclean-results.json`, and is located in the root of the project. **Default value is:** `false`.|
| `<failIfUnusedDependency>` | `boolean` | If this is true, and DepClean reported any unused dependency in the dependency tree, the build fails immediately after running DepClean. **Default value is:** `false`.|
| `<failIfUnusedDirect>` | `boolean` | If this is true, and DepClean reported any unused direct dependency in the dependency tree, the build fails immediately after running DepClean. **Default value is:** `false`.|
| `<failIfUnusedTransitive>` | `boolean` | If this is true, and DepClean reported any unused transitive dependency in the dependency tree, the build fails immediately after running DepClean. **Default value is:** `false`.|
| `<failIfUnusedInherited>` | `boolean` | If this is true, and DepClean reported any unused inherited dependency in the dependency tree, the build fails immediately after running DepClean. **Default value is:** `false`.|
| `<skipDepClean>` | `boolean` | Skip plugin execution completely. **Default value is:** `false`.|


Expand Down
4 changes: 2 additions & 2 deletions depclean-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
<parent>
<groupId>se.kth.castor</groupId>
<artifactId>depclean-parent-pom</artifactId>
<version>1.1.0</version>
<version>1.1.1</version>
</parent>

<!-- Coordinates -->
<artifactId>depclean-core</artifactId>
<version>1.1.0</version>
<version>1.1.1</version>

<name>depclean-core</name>
<packaging>jar</packaging>
Expand Down
6 changes: 3 additions & 3 deletions depclean-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
<parent>
<groupId>se.kth.castor</groupId>
<artifactId>depclean-parent-pom</artifactId>
<version>1.1.0</version>
<version>1.1.1</version>
</parent>

<!-- Coordinates -->
<artifactId>depclean-maven-plugin</artifactId>
<version>1.1.0</version>
<version>1.1.1</version>

<packaging>maven-plugin</packaging>
<name>depclean-maven-plugin</name>
Expand Down Expand Up @@ -78,7 +78,7 @@
<dependency>
<groupId>se.kth.castor</groupId>
<artifactId>depclean-core</artifactId>
<version>1.1.0</version>
<version>1.1.1</version>
</dependency>

<!-- Utils -->
Expand Down
316 changes: 172 additions & 144 deletions depclean-maven-plugin/src/main/java/se/kth/depclean/DepCleanMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,25 @@ public class DepCleanMojo extends AbstractMojo {
private Set<String> ignoreScopes;

/**
* If this is true, and DepClean reported any unused dependency in the dependency tree,
* the build fails immediately after running DepClean.
* If this is true, and DepClean reported any unused direct dependency in the dependency tree,
* then the project's build fails immediately after running DepClean.
*/
@Parameter(defaultValue = "false")
private boolean failIfUnusedDependency;
@Parameter(property = "fail.if.unused.direct", defaultValue = "false")
private boolean failIfUnusedDirect;

/**
* If this is true, and DepClean reported any unused transitive dependency in the dependency tree,
* then the project's build fails immediately after running DepClean.
*/
@Parameter(property = "fail.if.unused.transitive", defaultValue = "false")
private boolean failIfUnusedTransitive;

/**
* If this is true, and DepClean reported any unused inherited dependency in the dependency tree,
* then the project's build fails immediately after running DepClean.
*/
@Parameter(property = "fail.if.unused.inherited", defaultValue = "false")
private boolean failIfUnusedInherited;

/**
* Skip plugin execution completely.
Expand Down Expand Up @@ -158,6 +172,142 @@ private static void writePom(final Path pomFile, final Model model) throws IOExc
writer.write(Files.newBufferedWriter(pomFile), model);
}

/**
* Print the status of the depenencies to the standard output.
* The format is: "[coordinates][scope] [(size)]"
*
* @param sizeOfDependencies A map with the size of the dependencies.
* @param dependencies The set dependencies to print.
*/
private void printDependencies(Map<String, Long> sizeOfDependencies, Set<String> dependencies) {
dependencies
.stream()
.sorted(Comparator.comparing(o -> getSizeOfDependency(sizeOfDependencies, o)))
.collect(Collectors.toCollection(LinkedList::new))
.descendingIterator()
.forEachRemaining(s -> printString("\t" + s + " (" + getSize(s, sizeOfDependencies) + ")"));
}

/**
* Utility method to obtain the size of a dependency from a map of dependency -> size. If the size of the dependency
* cannot be obtained form the map (no key with the name of the dependency exists), then it returns 0.
*
* @param sizeOfDependencies A map of dependency -> size.
* @param dependency The coordinates of a dependency.
* @return The size of the dependency if its name is a key in the map, otherwise it returns 0.
*/
private Long getSizeOfDependency(Map<String, Long> sizeOfDependencies, String dependency) {
Long size = sizeOfDependencies.get(dependency.split(":")[1] + "-" + dependency.split(":")[2] + ".jar");
if (size != null) {
return size;
} else {
// The name of the dependency does not match with the name of the download jar, so we keep assume the size
// cannot be obtained and return 0.
return Long.valueOf(0);
}
}

/**
* Get the size of the dependency in human readable format.
*
* @param dependency The dependency.
* @param sizeOfDependencies A map with the size of the dependencies, keys are stored as the downloaded jar file
* i.e., [artifactId]-[version].jar
* @return The human readable representation of the dependency size.
*/
private String getSize(String dependency, Map<String, Long> sizeOfDependencies) {
String dep = dependency.split(":")[1] + "-" + dependency.split(":")[2] + ".jar";
if (sizeOfDependencies.containsKey(dep)) {
return FileUtils.byteCountToDisplaySize(sizeOfDependencies.get(dep));
} else {
// The size cannot be obtained.
return "size unknown";
}
}

/**
* Exclude artifacts with specific scopes from the analysis.
*
* @param artifacts The set of artifacts to analyze.
* @return The set of artifacts for which the scope has not been excluded.
*/
private Set<Artifact> excludeScope(Set<Artifact> artifacts) {
Set<Artifact> nonExcludedArtifacts = new HashSet<>();
for (Artifact artifact : artifacts) {
if (!ignoreScopes.contains(artifact.getScope())) {
nonExcludedArtifacts.add(artifact);
}
}
return nonExcludedArtifacts;
}

/**
* Determine if an artifact is a direct or transitive child of a dependency.
*
* @param artifact The artifact.
* @param dependency The dependency
* @return true if the artifact is a child of a dependency in the dependency tree.
* @throws DependencyGraphBuilderException If the graph cannot be constructed.
*/
private boolean isChildren(Artifact artifact, Dependency dependency) throws DependencyGraphBuilderException {
List<DependencyNode> dependencyNodes = getDependencyNodes();
for (DependencyNode node : dependencyNodes) {
Dependency dependencyNode = createDependency(node.getArtifact());
if (dependency.getGroupId().equals(dependencyNode.getGroupId()) &&
dependency.getArtifactId().equals(dependencyNode.getArtifactId())) {
// now we are in the target dependency
for (DependencyNode child : node.getChildren()) {
if (child.getArtifact().equals(artifact)) {
// the dependency contains the artifact as a child node
return true;
}

}
}
}
return false;
}

/**
* This method returns a list of dependency nodes from a graph of dependency tree.
*
* @return The nodes in the dependency graph.
* @throws DependencyGraphBuilderException If the graph cannot be built.
*/
private List<DependencyNode> getDependencyNodes() throws DependencyGraphBuilderException {
ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
buildingRequest.setProject(project);
DependencyNode rootNode = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, null);
CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor();
rootNode.accept(visitor);
return visitor.getNodes();
}

/**
* This method creates a {@link org.apache.maven.model.Dependency} object from a
* Maven {@link org.apache.maven.artifact.Artifact}.
*
* @param artifact The artifact to create the dependency.
* @return The Dependency object.
*/
private Dependency createDependency(final Artifact artifact) {
Dependency dependency = new Dependency();
dependency.setGroupId(artifact.getGroupId());
dependency.setArtifactId(artifact.getArtifactId());
dependency.setVersion(artifact.getVersion());
if (artifact.hasClassifier()) {
dependency.setClassifier(artifact.getClassifier());
}
dependency.setOptional(artifact.isOptional());
dependency.setScope(artifact.getScope());
dependency.setType(artifact.getType());
return dependency;
}

private void printString(String string) {
System.out.println(string); //NOSONAR avoid a warning of non-used logger
}

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (skipDepClean) {
Expand Down Expand Up @@ -375,9 +525,22 @@ public void execute() throws MojoExecutionException, MojoFailureException {
ignoreDependencies.stream().forEach(s -> printString("\t" + s));
}

/* Fail the build if there are unused dependencies */
if (failIfUnusedDependency && (!unusedDirectArtifactsCoordinates.isEmpty() || !unusedTransitiveArtifactsCoordinates.isEmpty())) {
throw new MojoExecutionException("Build failed due to unused dependencies in the dependency tree.");
/* Fail the build if there are unused direct dependencies */
if (failIfUnusedDirect && !unusedDirectArtifactsCoordinates.isEmpty()) {
throw new MojoExecutionException("Build failed due to unused direct dependencies in the dependency tree " +
"of the project.");
}

/* Fail the build if there are unused direct dependencies */
if (failIfUnusedTransitive && !unusedTransitiveArtifactsCoordinates.isEmpty()) {
throw new MojoExecutionException("Build failed due to unused transitive dependencies in the dependency " +
"tree of the project.");
}

/* Fail the build if there are unused direct dependencies */
if (failIfUnusedInherited && !unusedInheritedArtifactsCoordinates.isEmpty()) {
throw new MojoExecutionException("Build failed due to unused inherited dependencies in the dependency " +
"tree of the project.");
}

/* Writing the debloated version of the pom */
Expand All @@ -387,7 +550,8 @@ public void execute() throws MojoExecutionException, MojoFailureException {
/* Add used transitive as direct dependencies */
try {
if (!usedTransitiveArtifacts.isEmpty()) {
getLog().info("Adding " + unusedTransitiveArtifactsCoordinates.size() + " used transitive dependencies as direct dependencies.");
getLog().info("Adding " + unusedTransitiveArtifactsCoordinates.size() + " used transitive " +
"dependencies as direct dependencies.");
for (Artifact usedUndeclaredArtifact : usedTransitiveArtifacts) {
model.addDependency(createDependency(usedUndeclaredArtifact));
}
Expand Down Expand Up @@ -485,140 +649,4 @@ public void execute() throws MojoExecutionException, MojoFailureException {
}
}
}

/**
* Print the status of the depenencies to the standard output.
* The format is: "[coordinates][scope] [(size)]"
*
* @param sizeOfDependencies A map with the size of the dependencies.
* @param dependencies The set dependencies to print.
*/
private void printDependencies(Map<String, Long> sizeOfDependencies, Set<String> dependencies) {
dependencies
.stream()
.sorted(Comparator.comparing(o -> getSizeOfDependency(sizeOfDependencies, o)))
.collect(Collectors.toCollection(LinkedList::new))
.descendingIterator()
.forEachRemaining(s -> printString("\t" + s + " (" + getSize(s, sizeOfDependencies) + ")"));
}

/**
* Utility method to obtain the size of a dependency from a map of dependency -> size. If the size of the dependency
* cannot be obtained form the map (no key with the name of the dependency exists), then it returns 0.
*
* @param sizeOfDependencies A map of dependency -> size.
* @param dependency The coordinates of a dependency.
* @return The size of the dependency if its name is a key in the map, otherwise it returns 0.
*/
private Long getSizeOfDependency(Map<String, Long> sizeOfDependencies, String dependency) {
Long size = sizeOfDependencies.get(dependency.split(":")[1] + "-" + dependency.split(":")[2] + ".jar");
if (size != null) {
return size;
} else {
// The name of the dependency does not match with the name of the download jar, so we keep assume the size
// cannot be obtained and return 0.
return Long.valueOf(0);
}
}

/**
* Get the size of the dependency in human readable format.
*
* @param dependency The dependency.
* @param sizeOfDependencies A map with the size of the dependencies, keys are stored as the downloaded jar file
* i.e., [artifactId]-[version].jar
* @return The human readable representation of the dependency size.
*/
private String getSize(String dependency, Map<String, Long> sizeOfDependencies) {
String dep = dependency.split(":")[1] + "-" + dependency.split(":")[2] + ".jar";
if (sizeOfDependencies.containsKey(dep)) {
return FileUtils.byteCountToDisplaySize(sizeOfDependencies.get(dep));
} else {
// The size cannot be obtained.
return "size unknown";
}
}

/**
* Exclude artifacts with specific scopes from the analysis.
*
* @param artifacts The set of artifacts to analyze.
* @return The set of artifacts for which the scope has not been excluded.
*/
private Set<Artifact> excludeScope(Set<Artifact> artifacts) {
Set<Artifact> nonExcludedArtifacts = new HashSet<>();
for (Artifact artifact : artifacts) {
if (!ignoreScopes.contains(artifact.getScope())) {
nonExcludedArtifacts.add(artifact);
}
}
return nonExcludedArtifacts;
}

/**
* Determine if an artifact is a direct or transitive child of a dependency.
*
* @param artifact The artifact.
* @param dependency The dependency
* @return true if the artifact is a child of a dependency in the dependency tree.
* @throws DependencyGraphBuilderException If the graph cannot be constructed.
*/
private boolean isChildren(Artifact artifact, Dependency dependency) throws DependencyGraphBuilderException {
List<DependencyNode> dependencyNodes = getDependencyNodes();
for (DependencyNode node : dependencyNodes) {
Dependency dependencyNode = createDependency(node.getArtifact());
if (dependency.getGroupId().equals(dependencyNode.getGroupId()) &&
dependency.getArtifactId().equals(dependencyNode.getArtifactId())) {
// now we are in the target dependency
for (DependencyNode child : node.getChildren()) {
if (child.getArtifact().equals(artifact)) {
// the dependency contains the artifact as a child node
return true;
}

}
}
}
return false;
}

/**
* This method returns a list of dependency nodes from a graph of dependency tree.
*
* @return The nodes in the dependency graph.
* @throws DependencyGraphBuilderException If the graph cannot be built.
*/
private List<DependencyNode> getDependencyNodes() throws DependencyGraphBuilderException {
ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
buildingRequest.setProject(project);
DependencyNode rootNode = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, null);
CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor();
rootNode.accept(visitor);
return visitor.getNodes();
}

/**
* This method creates a {@link org.apache.maven.model.Dependency} object from a
* Maven {@link org.apache.maven.artifact.Artifact}.
*
* @param artifact The artifact to create the dependency.
* @return The Dependency object.
*/
private Dependency createDependency(final Artifact artifact) {
Dependency dependency = new Dependency();
dependency.setGroupId(artifact.getGroupId());
dependency.setArtifactId(artifact.getArtifactId());
dependency.setVersion(artifact.getVersion());
if (artifact.hasClassifier()) {
dependency.setClassifier(artifact.getClassifier());
}
dependency.setOptional(artifact.isOptional());
dependency.setScope(artifact.getScope());
dependency.setType(artifact.getType());
return dependency;
}

private void printString(String string) {
System.out.println(string); //NOSONAR avoid a warning of non-used logger
}
}
Loading

0 comments on commit 47faca7

Please sign in to comment.