Skip to content
This repository has been archived by the owner on Apr 1, 2022. It is now read-only.

Commit

Permalink
refactor: Clean up analyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
elldritch committed Jul 28, 2021
1 parent 85fd648 commit 188743c
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 75 deletions.
116 changes: 93 additions & 23 deletions scripts/jsondeps.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@

// TODO: finish
// You can manually debug this by running `gradle -I etc.`

// A gradle init script to add a `jsonDeps` task.
// This task outputs project dependency trees as JSON.
// This Gradle init script adds a `jsonDeps` task that outputs the dependencies
// of each subproject as JSON.
//
// If you're debugging this script, you can directly run this on a Gradle project
// by running `gradle -I/path/to/script $TASK` e.g.
// `gradle -I/tmp/jsondeps.gradle :jsonDeps`. This lets you see the output
// directly.
//
// Useful documentation:
// - Gradle init scripts: https://docs.gradle.org/current/userguide/init_scripts.html
// - Gradle subprojects: https://docs.gradle.org/current/userguide/multi_project_builds.html
// - Gradle configurations: https://docs.gradle.org/current/userguide/declaring_dependencies.html
// - Gradle build script primer: https://docs.gradle.org/current/userguide/groovy_build_script_primer.html
// - Gradle init script API reference:
// - https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:allprojects(groovy.lang.Closure)
// - https://docs.gradle.org/current/javadoc/index.html
//
// ----
//
// The resulting JSON output is a map of configuration names to an array of
// top-level dependencies.
Expand All @@ -25,35 +37,69 @@
// Project Name -- first-party (sub)projects
// | Package Name Version [Dependency]


// TODO: Only print debug logging when running in debug mode?
allprojects {
task jsonDeps {
doLast {
def depToJSON
// resolvedDep: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ResolvedDependency.html
depToJSON = { resolvedDep ->
println "RESOLVED DEP"
println "DEBUG: Resolved dep"
println resolvedDep
println "MODULE ARTIFACTS"

println "DEBUG: Module artifacts"
println resolvedDep.moduleArtifacts
println resolvedDep.moduleArtifacts.size()
def artifact = resolvedDep.moduleArtifacts.iterator().next() // moduleArtifacts never returns null; can it return empty set? it seems to only return a single module or project
println "ARTIFACT"

// moduleArtifacts never returns null, but sometimes this
// iterator can be empty (for dependencies with no artifacts
// e.g. `jackson-bom`).
//
// artifact: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ResolvedArtifact.html
def artifact = resolvedDep.moduleArtifacts.iterator().next()

println "DEBUG: Artifact"
println artifact

// artifact.id: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/component/ComponentArtifactIdentifier.html
// artifact.id.componentIdentifier: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/component/ComponentIdentifier.html
def id = artifact.id.componentIdentifier
def json = "{"

// A "project component identifier" is a dependency that refers
// to another Gradle subproject (as opposed to a third-party
// dependency). We don't care about these for now.
if (id instanceof ProjectComponentIdentifier) {
// Minor problem here: we don't get the specific configuration used for the subproject.
// The default is the configuration named "default"
json += "\"type\":\"project\",\"name\":\"${id.projectPath}\""
} else if (id instanceof ModuleComponentIdentifier) {
// A "module" is a third-party dependency. Almost all
// modules have "artifacts", which is the actual dependency
// code that gets downloaded.
json += "\"type\":\"package\",\"name\":\"${id.group}:${id.module}\",\"version\":\"${id.version}\","
def childResults = []
if (!resolvedDep.children.isEmpty()) {
// childResolvedDep: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ResolvedDependency.html
resolvedDep.children.each { childResolvedDep ->
// If we don't do this check, then calling
// `resolvedDep.moduleArtifacts.iterator().next()`
// above explodes because the `moduleArtifacts`
// iterator is empty.
//
// This seems to happen for at least `jackson-bom`
// and and `junit-bom`. I'm not sure how this is
// possible.
//
// See also:
// - https://docs.gradle.org/current/userguide/declaring_dependencies.html#sub:module_dependencies
// - https://stackoverflow.com/questions/67328406/what-is-junit-bom-and-junit-platform-for-and-should-i-include-them-in-gradle-de
if (childResolvedDep.moduleArtifacts.size() > 0) {
def result = depToJSON childResolvedDep
childResults << result
} else {
println "DEP WITH NO MODULES"
println "DEBUG: Found dep with no module artifacts"
println childResolvedDep
}
}
Expand All @@ -62,6 +108,8 @@ allprojects {
json += childResults.join(",")
json += "]"
} else {
// The other possibility here is "LibraryBinaryIdentifier".
// See: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/component/package-summary.html
return null; // FUTURE: binary dependencies in the filetree
}

Expand All @@ -70,13 +118,14 @@ allprojects {
return json
}

// config: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/Configuration.html
def configToKeyValue = { config ->
def jsonDeps = []
println "TRYING CONFIG"
println "DEBUG: Trying to resolve configuration"
println config
// https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/Configuration.html
// config.resolvedConfiguration: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/ResolvedConfiguration.html
config.resolvedConfiguration.firstLevelModuleDependencies.each { dep ->
println "DEP"
println "DEBUG: Found direct dependency in configuration"
println dep
def result = depToJSON dep
if (result != null) {
Expand All @@ -87,36 +136,57 @@ allprojects {
return "\"${config.name}\":[${combined}]"
}

// project: https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
def projectToJSON = { project ->
def jsonConfigs = []
project.configurations.each { config ->
println "CONFIG"
println "DEBUG: Trying to resolve configuration"
println config
try {
def result = configToKeyValue config
// println "RESULT"
// println result
println "DEBUG: Configuration resolved"
println result
jsonConfigs << result
} catch (IllegalStateException e) {
// This particular exception is fairly common, so we
// handle it specially. There's nothing to do in this
// case. Some configurations aren't meant to be
// resolved, because they're just meant to be containers
// of dependency constraints.
//
// See also:
// - https://discuss.gradle.org/t/what-is-a-configuration-which-cant-be-directly-resolved/30721
// - https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:resolvable-consumable-configs
if (e.getMessage().contains("canBeResolved=false")) {
println "CANNOT BE RESOLVED"
println "DEBUG: Could not resolve configuration"
} else {
throw e
}
} catch (Exception ignored) {
println "EXCEPTION"
// If more common exceptions occur, add a catch case to
// handle them. Groovy supports multiple catch clauses
// (the documentation calls this "multi-catch"), and
// will try to pattern match exceptions to handlers in
// order.
//
// See also:
// - https://www.tutorialspoint.com/groovy/groovy_exception_handling.htm
// - https://groovy-lang.org/semantics.html#_exception_handling
println "DEBUG: Some other exception occurred"
println ignored
// ignored.printStackTrace()
ignored.printStackTrace()
}
}
// println "JSON CONFIGS"
// println jsonConfigs
def combined = jsonConfigs.join(",")
return "{${combined}}"
}

def result = projectToJSON project
println "DONE"

// We use the "JSONDEPS_" prefix to print output. This is why it's
// safe for us to print a bunch of other debugging messages
// everywhere else - the parser in Spectrometer ignores those
// messages.
println "JSONDEPS_${project.path}_${result}"
}
}
Expand Down
Loading

0 comments on commit 188743c

Please sign in to comment.