Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kellyrob99/4334483 to your computer and use it in GitHub Desktop.
Save kellyrob99/4334483 to your computer and use it in GitHub Desktop.
A simple Gradle wrapper for the maven dependency:analyze goal
import org.apache.maven.shared.dependency.analyzer.ClassAnalyzer
import org.apache.maven.shared.dependency.analyzer.DefaultClassAnalyzer
import org.apache.maven.shared.dependency.analyzer.DependencyAnalyzer
import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis
import org.apache.maven.shared.dependency.analyzer.asm.ASMDependencyAnalyzer
import org.gradle.api.Project
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency
buildscript {
repositories {
maven{url 'http://repo1.maven.org/maven2'}
mavenCentral()
}
dependencies {
classpath 'org.apache.maven.shared:maven-dependency-analyzer:1.3'
}
}
task analyze{
doLast{
StringBuffer buffer = new StringBuffer()
ProjectDependencyAnalysis analysis = new ProjectDependencyResolver().analyzeDependencies(project)
['usedDeclaredArtifacts', 'usedUndeclaredArtifacts','unusedDeclaredArtifacts'].each{ section ->
buffer.append("$section = \n")
analysis."$section".each{
buffer.append("\t$it\n")
}
}
logger.warn (buffer.toString())
}
}
class ProjectDependencyResolver
{
private ClassAnalyzer classAnalyzer = new DefaultClassAnalyzer();
private DependencyAnalyzer dependencyAnalyzer = new ASMDependencyAnalyzer();
/**
*
* @param project
* @return
*/
ProjectDependencyAnalysis analyzeDependencies(Project project)
{
if (!project.plugins.hasPlugin('java'))
{
throw new IllegalStateException("Project does not have the java plugin applied.")
}
Set<ResolvedDependency> firstLevelDeps = getFirstLevelDependencies(project, 'compile')
Set<File> dependencyArtifacts = findModuleArtifactFiles(firstLevelDeps)
Map<File, Set<String>> fileClassMap = buildArtifactClassMap(dependencyArtifacts)
project.logger.info "fileClassMap = $fileClassMap"
Set<String> dependencyClasses = analyzeClassDependencies(project)
project.logger.info "dependencyClasses = $dependencyClasses"
Set<File> usedArtifacts = buildUsedArtifacts(fileClassMap, dependencyClasses)
project.logger.info "usedArtifacts = $usedArtifacts"
Set<File> usedDeclaredArtifacts = new LinkedHashSet<File>(dependencyArtifacts)
usedDeclaredArtifacts.retainAll(usedArtifacts)
project.logger.info "usedDeclaredArtifacts = $usedDeclaredArtifacts"
Set<File> usedUndeclaredArtifacts = new LinkedHashSet<File>(usedArtifacts)
usedUndeclaredArtifacts.removeAll(dependencyArtifacts)
project.logger.info "usedUndeclaredArtifacts = $usedUndeclaredArtifacts"
Set<String> unusedDeclaredArtifacts = new LinkedHashSet<File>(dependencyArtifacts)
unusedDeclaredArtifacts.removeAll(usedArtifacts)
project.logger.info "unusedDeclaredArtifacts = $unusedDeclaredArtifacts"
//Now work back from the files to the artifact information
ConfigurationContainer configurations = project.configurations
def nonTestConfigurations = configurations.findAll {!it.name.contains('test')}
List<ResolvedArtifact> artifacts = nonTestConfigurations*.resolvedConfiguration*.resolvedArtifacts.unique {it.resolvedDependency}.flatten()
return new ProjectDependencyAnalysis(
artifacts.findAll {ResolvedArtifact artifact -> artifact.file in usedDeclaredArtifacts}.unique {it.file} as Set,
artifacts.findAll {ResolvedArtifact artifact -> artifact.file in usedUndeclaredArtifacts}.unique {it.file} as Set,
artifacts.findAll {ResolvedArtifact artifact -> artifact.file in unusedDeclaredArtifacts}.unique {it.file} as Set)
}
private Set<ResolvedDependency> getFirstLevelDependencies(Project project, String configurationName)
{
project.configurations."$configurationName".resolvedConfiguration.firstLevelModuleDependencies
}
/**
* Map each of the files declared on all configurations of the project to a collection of the class names they contain.
* @param project the project we're working on
* @return a Map of files to their classes
* @throws IOException
*/
private Map<File, Set<String>> buildArtifactClassMap(Set<File> dependencyArtifacts) throws IOException
{
Map<File, Set<String>> artifactClassMap = [:]
dependencyArtifacts.each { File file ->
if (file.name.endsWith('jar'))
{
artifactClassMap.put(file, classAnalyzer.analyze(file.toURL()))
}
else
{
project.logger.info "Skipping analysis of file for classes: $file"
}
}
return artifactClassMap
}
private Set<File> findModuleArtifactFiles(Set<ResolvedDependency> dependencies)
{
dependencies*.moduleArtifacts*.collect {it.file}.unique().flatten()
}
/**
* Find and analyze all class files to determine which external classes are used.
* @param project
* @return a Set of class names
*/
private Collection analyzeClassDependencies(Project project)
{
return project.sourceSets*.output.classesDir?.collect {File file ->
dependencyAnalyzer.analyze(file.toURI().toURL())
}?.flatten()?.unique()
}
/**
* Determine which of the project dependencies are used.
*
* @param artifactClassMap a map of Files to the classes they contain
* @param dependencyClasses all classes used directly by the project
* @return a set of project dependencies confirmed to be used by the project
*/
private Set<File> buildUsedArtifacts(Map<File, Set<String>> artifactClassMap, Set<String> dependencyClasses)
{
Set<File> usedArtifacts = new HashSet()
dependencyClasses.each { String className ->
File artifact = artifactClassMap.find {it.value.contains(className)}?.key
if (artifact)
{
usedArtifacts << artifact
}
}
return usedArtifacts
}
}
@wfhartford
Copy link

I've wrapped the basic functionality of this into a plugin at https://github.com/wfhartford/gradle-dependency-anayze

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment