From 727cc5aff9170081212543765c339ff2666b98d3 Mon Sep 17 00:00:00 2001 From: daz Date: Sat, 27 Jan 2024 07:30:45 -0700 Subject: [PATCH 1/7] Add a new 'simple' report to assist tracking down dependency origin --- .../simple/SimpleDependencyGraphRenderer.kt | 66 ++++++++++++++++--- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt index 858de70d..fa0268c7 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt @@ -21,18 +21,64 @@ class SimpleDependencyGraphRenderer : DependencyGraphRenderer { resolvedConfigurations: List, outputDirectory: File ) { - val graphOutputFile = File(outputDirectory, "dependency-graph.json") - val graphJson = JacksonJsonSerializer.serializeToJson(resolvedConfigurations) - graphOutputFile.writeText(graphJson) - - val listOutputFile = File(outputDirectory, "dependency-list.txt") - val dependencyList = resolvedConfigurations.flatMap { it -> - it.allDependencies.map { - "${it.coordinates.group}:${it.coordinates.module}:${it.coordinates.version}" + outputDependencyGraph(outputDirectory, resolvedConfigurations) + outputDependencyScopes(outputDirectory, resolvedConfigurations) + outputDependencyList(outputDirectory, resolvedConfigurations) + } + + private fun outputDependencyGraph( + outputDirectory: File, + resolvedConfigurations: List + ) { + val outputFile = File(outputDirectory, "dependency-graph.json") + val jsonContent = JacksonJsonSerializer.serializeToJson(resolvedConfigurations) + outputFile.writeText(jsonContent) + } + + private fun outputDependencyScopes( + outputDirectory: File, + resolvedConfigurations: List + ) { + val outputFile = File(outputDirectory, "dependency-scopes.json") + val dependencyList: MutableMap> = mutableMapOf() + for (config in resolvedConfigurations) { + for (dependency in config.allDependencies) { + val dependencyOrigins = dependencyList.getOrPut(dependency.id) { mutableSetOf() } + dependencyOrigins.add( + SimpleDependencyOrigin( + config.rootOrigin.path, + config.configurationName + ) + ) + } + } + + val simpleDependencies = dependencyList.map { (id, origins) -> + SimpleDependency(id, origins.toList()) + } + val jsonContent = JacksonJsonSerializer.serializeToJson(simpleDependencies) + outputFile.writeText(jsonContent) + } + + private fun outputDependencyList( + outputDirectory: File, + resolvedConfigurations: List + ) { + val outputFile = File(outputDirectory, "dependency-list.txt") + val dependencyList = resolvedConfigurations.flatMap { config -> + config.allDependencies.map { + "${it.coordinates.group}:${it.coordinates.module}:${it.coordinates.version} ---- ${config.rootOrigin.id} ${config.rootOrigin.path} ${config.configurationName}" } }.distinct().sorted() val listTxt = dependencyList.joinToString(separator = "\n") - listOutputFile.writeText(listTxt) + outputFile.writeText(listTxt) } -} \ No newline at end of file +} + +data class SimpleDependency( + val dependency: String, + val resolvedBy: List +) + +data class SimpleDependencyOrigin(val path: String, val configuration: String) From 244b61cd6359cb68dfd084ad6bf2e9d4dee8b8dd Mon Sep 17 00:00:00 2001 From: daz Date: Sat, 27 Jan 2024 12:31:30 -0700 Subject: [PATCH 2/7] Assign dependency scope based on regex filters Add new filter variables that can be used to categorize a dependency as 'runtime'. If not specified or not matched, a 'development' scope is assumed. --- .../extractor/DependencyExtractor.kt | 23 +++++++++++++--- .../extractor/ResolvedConfigurationFilter.kt | 8 +++--- .../dependencygraph/model/DependencyScope.kt | 17 ++++++++++++ .../model/ResolvedConfiguration.kt | 1 + .../simple/SimpleDependencyGraphRenderer.kt | 25 +++++++++++------- .../GitHubRepositorySnapshotBuilder.kt | 26 +++++++++++++++---- .../dependencygraph/model/GitHubDependency.kt | 4 +++ .../internal/ResolvedConfigurationTest.groovy | 14 ++++++++++ 8 files changed, 95 insertions(+), 23 deletions(-) create mode 100644 plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt index 94779807..f4ef9391 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt @@ -9,6 +9,8 @@ import org.gradle.api.internal.artifacts.configurations.ResolveConfigurationDepe import org.gradle.api.logging.Logging import org.gradle.dependencygraph.DependencyGraphRenderer import org.gradle.dependencygraph.model.* +import org.gradle.dependencygraph.model.DependencyScope.RUNTIME +import org.gradle.dependencygraph.model.DependencyScope.DEVELOPMENT import org.gradle.dependencygraph.util.* import org.gradle.initialization.EvaluateSettingsBuildOperationType import org.gradle.initialization.LoadProjectsBuildOperationType @@ -21,6 +23,9 @@ import java.util.* const val PARAM_INCLUDE_PROJECTS = "DEPENDENCY_GRAPH_INCLUDE_PROJECTS" const val PARAM_INCLUDE_CONFIGURATIONS = "DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS" +const val PARAM_RUNTIME_PROJECTS = "DEPENDENCY_GRAPH_RUNTIME_PROJECTS" +const val PARAM_RUNTIME_CONFIGURATIONS = "DEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS" + const val PARAM_REPORT_DIR = "DEPENDENCY_GRAPH_REPORT_DIR" @@ -42,13 +47,20 @@ abstract class DependencyExtractor : // Properties are lazily initialized so that System Properties are initialized by the time // the values are used. This is required due to a bug in older Gradle versions. (https://github.com/gradle/gradle/issues/6825) - private val configurationFilter by lazy { + private val includeFilter by lazy { ResolvedConfigurationFilter( pluginParameters.loadOptional(PARAM_INCLUDE_PROJECTS), pluginParameters.loadOptional(PARAM_INCLUDE_CONFIGURATIONS) ) } + private val runtimeFilter by lazy { + ResolvedConfigurationFilter( + pluginParameters.loadOptional(PARAM_RUNTIME_PROJECTS), + pluginParameters.loadOptional(PARAM_RUNTIME_CONFIGURATIONS) + ) + } + private val dependencyGraphReportDir by lazy { pluginParameters.loadOptional(PARAM_REPORT_DIR) } @@ -157,15 +169,18 @@ abstract class DependencyExtractor : // It is possible to do better. By tracking the current build operation context, we can assign more precisely. // See the Gradle Enterprise Build Scan Plugin: `ConfigurationResolutionCapturer_5_0` val rootPath = projectIdentityPath ?: details.buildPath + val configurationName = details.configurationName - if (!configurationFilter.include(rootPath, details.configurationName)) { - LOGGER.debug("Ignoring resolved configuration: $rootPath - ${details.configurationName}") + if (!includeFilter.include(rootPath, configurationName)) { + LOGGER.debug("Ignoring resolved configuration: $rootPath - $configurationName") return } + val scope = if (runtimeFilter.include(rootPath, configurationName)) RUNTIME else DEVELOPMENT + val rootId = if (projectIdentityPath == null) "build $rootPath" else componentId(rootComponent) val rootOrigin = DependencyOrigin(rootId, rootPath) - val resolvedConfiguration = ResolvedConfiguration(rootOrigin, details.configurationName) + val resolvedConfiguration = ResolvedConfiguration(rootOrigin, configurationName, scope) for (dependencyComponent in getResolvedDependencies(rootComponent)) { val directDep = createComponentNode( diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt index 3a9f0606..0e84b78c 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt @@ -1,14 +1,14 @@ package org.gradle.dependencygraph.extractor class ResolvedConfigurationFilter(projectFilter: String?, configurationFilter: String?) { - private val projectRegex = projectFilter?.toRegex() - private val configurationRegex = configurationFilter?.toRegex() + private val projectFilter = projectFilter?.toRegex() + private val configurationFilter = configurationFilter?.toRegex() fun include(projectPath: String, configurationName: String): Boolean { - if (projectRegex != null && !projectRegex.matches(projectPath)) { + if (projectFilter != null && !projectFilter.matches(projectPath)) { return false } - if (configurationRegex != null && !configurationRegex.matches(configurationName)) { + if (configurationFilter != null && !configurationFilter.matches(configurationName)) { return false } return true diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt new file mode 100644 index 00000000..563836f4 --- /dev/null +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt @@ -0,0 +1,17 @@ +package org.gradle.dependencygraph.model + +/** + * Represents the scope of a resolved dependency. + * At this point, the scopes are limited to those exposed in the GitHub DependencySubmission API. + * Later development may extend this to a richer set of scopes. + */ +enum class DependencyScope { + DEVELOPMENT, RUNTIME; + + companion object { + fun getEffectiveScope(scopes: List): DependencyScope { + if (scopes.contains(RUNTIME)) return RUNTIME + return DEVELOPMENT + } + } +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/model/ResolvedConfiguration.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/model/ResolvedConfiguration.kt index 9bfcc7f2..22612398 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/model/ResolvedConfiguration.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/model/ResolvedConfiguration.kt @@ -3,6 +3,7 @@ package org.gradle.dependencygraph.model data class ResolvedConfiguration( val rootOrigin: DependencyOrigin, val configurationName: String, + val scope: DependencyScope, val allDependencies: MutableList = mutableListOf() ) { fun addDependency(component: ResolvedDependency) { diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt index fa0268c7..40979f5e 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt @@ -3,6 +3,7 @@ package org.gradle.dependencygraph.simple import org.gradle.dependencygraph.DependencyGraphRenderer import org.gradle.dependencygraph.model.BuildLayout import org.gradle.dependencygraph.model.ResolvedConfiguration +import org.gradle.dependencygraph.model.DependencyScope import org.gradle.dependencygraph.util.JacksonJsonSerializer import org.gradle.dependencygraph.util.PluginParameters import java.io.File @@ -40,21 +41,24 @@ class SimpleDependencyGraphRenderer : DependencyGraphRenderer { resolvedConfigurations: List ) { val outputFile = File(outputDirectory, "dependency-scopes.json") - val dependencyList: MutableMap> = mutableMapOf() + val dependencyList: MutableMap> = mutableMapOf() for (config in resolvedConfigurations) { for (dependency in config.allDependencies) { - val dependencyOrigins = dependencyList.getOrPut(dependency.id) { mutableSetOf() } - dependencyOrigins.add( - SimpleDependencyOrigin( + if (dependency.isProject) continue + + val dependencyScopes = dependencyList.getOrPut(dependency.id) { mutableSetOf() } + dependencyScopes.add( + SimpleDependencyResolution( config.rootOrigin.path, - config.configurationName + config.configurationName, + config.scope ) ) } } - val simpleDependencies = dependencyList.map { (id, origins) -> - SimpleDependency(id, origins.toList()) + val simpleDependencies = dependencyList.map { (id, resolutions) -> + SimpleDependency(id, DependencyScope.getEffectiveScope(resolutions.map {it.scope}), resolutions.toList()) } val jsonContent = JacksonJsonSerializer.serializeToJson(simpleDependencies) outputFile.writeText(jsonContent) @@ -67,7 +71,7 @@ class SimpleDependencyGraphRenderer : DependencyGraphRenderer { val outputFile = File(outputDirectory, "dependency-list.txt") val dependencyList = resolvedConfigurations.flatMap { config -> config.allDependencies.map { - "${it.coordinates.group}:${it.coordinates.module}:${it.coordinates.version} ---- ${config.rootOrigin.id} ${config.rootOrigin.path} ${config.configurationName}" + "${it.coordinates.group}:${it.coordinates.module}:${it.coordinates.version}" } }.distinct().sorted() @@ -78,7 +82,8 @@ class SimpleDependencyGraphRenderer : DependencyGraphRenderer { data class SimpleDependency( val dependency: String, - val resolvedBy: List + val effectiveScope: DependencyScope, + val resolvedBy: List ) -data class SimpleDependencyOrigin(val path: String, val configuration: String) +data class SimpleDependencyResolution(val path: String, val configuration: String, val scope: DependencyScope) diff --git a/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt b/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt index ceffc06f..25dc4763 100644 --- a/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt +++ b/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt @@ -3,6 +3,7 @@ package org.gradle.github.dependencygraph import org.gradle.dependencygraph.model.ResolvedDependency import org.gradle.dependencygraph.model.ResolvedConfiguration import org.gradle.dependencygraph.model.BuildLayout +import org.gradle.dependencygraph.model.DependencyScope import org.gradle.github.dependencygraph.model.* class GitHubRepositorySnapshotBuilder( @@ -21,12 +22,12 @@ class GitHubRepositorySnapshotBuilder( fun buildManifest(manifestName: String, resolvedConfigurations: List, buildLayout: BuildLayout): GitHubManifest { val dependencyCollector = DependencyCollector() - for (resolutionRoot in resolvedConfigurations) { - for (dependency in resolutionRoot.allDependencies) { + for (configuration in resolvedConfigurations) { + for (dependency in configuration.allDependencies) { // Ignore project dependencies (transitive deps of projects will be reported with project) if (dependency.isProject) continue - dependencyCollector.addResolved(dependency) + dependencyCollector.addResolved(dependency, determineGitHubScope(configuration)) } } @@ -37,6 +38,13 @@ class GitHubRepositorySnapshotBuilder( ) } + private fun determineGitHubScope(configuration: ResolvedConfiguration): GitHubDependency.Scope { + return when(configuration.scope) { + DependencyScope.DEVELOPMENT -> GitHubDependency.Scope.development + DependencyScope.RUNTIME -> GitHubDependency.Scope.runtime + } + } + /** * Manifest file is the root build settings file if it exists, or the root build file if not. */ @@ -67,11 +75,12 @@ class GitHubRepositorySnapshotBuilder( /** * Merge each resolved component with the same ID into a single GitHubDependency. */ - fun addResolved(component: ResolvedDependency) { + fun addResolved(component: ResolvedDependency, scope: GitHubDependency.Scope) { val dep = dependencyBuilders.getOrPut(component.id) { GitHubDependencyBuilder(component.packageUrl()) } dep.addRelationship(relationship(component)) + dep.addScope(scope) dep.addDependencies(component.dependencies) } @@ -89,6 +98,7 @@ class GitHubRepositorySnapshotBuilder( private class GitHubDependencyBuilder(val package_url: String) { var relationship: GitHubDependency.Relationship = GitHubDependency.Relationship.indirect + var scope: GitHubDependency.Scope = GitHubDependency.Scope.development val dependencies = mutableListOf() fun addRelationship(newRelationship: GitHubDependency.Relationship) { @@ -98,6 +108,12 @@ class GitHubRepositorySnapshotBuilder( } } + fun addScope(newScope: GitHubDependency.Scope) { + if (scope == GitHubDependency.Scope.development) { + scope = newScope + } + } + fun addDependencies(newDependencies: List) { // Add any dependencies that are not in the existing set for (newDependency in newDependencies.subtract(dependencies.toSet())) { @@ -106,7 +122,7 @@ class GitHubRepositorySnapshotBuilder( } fun build(): GitHubDependency { - return GitHubDependency(package_url, relationship, dependencies) + return GitHubDependency(package_url, relationship, scope, dependencies) } } } diff --git a/plugin/src/main/kotlin/org/gradle/github/dependencygraph/model/GitHubDependency.kt b/plugin/src/main/kotlin/org/gradle/github/dependencygraph/model/GitHubDependency.kt index 419de316..5b697494 100644 --- a/plugin/src/main/kotlin/org/gradle/github/dependencygraph/model/GitHubDependency.kt +++ b/plugin/src/main/kotlin/org/gradle/github/dependencygraph/model/GitHubDependency.kt @@ -3,9 +3,13 @@ package org.gradle.github.dependencygraph.model data class GitHubDependency( val package_url: String, val relationship: Relationship, + val scope: Scope, val dependencies: List ) { enum class Relationship { indirect, direct } + enum class Scope { + runtime, development + } } diff --git a/plugin/src/test/groovy/org/gradle/github/dependencygraph/internal/ResolvedConfigurationTest.groovy b/plugin/src/test/groovy/org/gradle/github/dependencygraph/internal/ResolvedConfigurationTest.groovy index a2db614e..a7659c72 100644 --- a/plugin/src/test/groovy/org/gradle/github/dependencygraph/internal/ResolvedConfigurationTest.groovy +++ b/plugin/src/test/groovy/org/gradle/github/dependencygraph/internal/ResolvedConfigurationTest.groovy @@ -95,4 +95,18 @@ class ResolvedConfigurationTest extends Specification { !filter.include(":proj-a:proj-b:", "") !filter.include("parent-proj:proj-a", "") } + + def "filters on excluded project path"() { + when: + def filter = new ResolvedConfigurationFilter(/^:(?!buildSrc).*/, null) + + then: + filter.include(":proj-a", "") + filter.include(":proj-a:proj-b", "") + filter.include(":proj-a:buildSrc", "") + + !filter.include(":buildSrc", "") + !filter.include(":buildSrc:", "") + !filter.include(":buildSrc:proj-b:", "") + } } From b5fdd60c7ac1055d2171a59ff90d4fc6fb8ac4d0 Mon Sep 17 00:00:00 2001 From: daz Date: Sat, 27 Jan 2024 13:57:52 -0700 Subject: [PATCH 3/7] Add test coverage for runtime scope --- .../dependencygraph/BaseExtractorTest.groovy | 1 + ...rationFilterDependencyExtractorTest.groovy | 242 ++++++++++++++++++ ...MultiProjectDependencyExtractorTest.groovy | 67 ----- 3 files changed, 243 insertions(+), 67 deletions(-) create mode 100644 plugin-test/src/test/groovy/org/gradle/github/dependencygraph/ConfigurationFilterDependencyExtractorTest.groovy diff --git a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy index ea1041fb..daa6eeb6 100644 --- a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy +++ b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy @@ -208,6 +208,7 @@ abstract class BaseExtractorTest extends Specification { } assert actual.relationship == (expected.relationship ?: "direct") assert actual.dependencies == (expected.dependencies ?: []) + assert actual.scope == (expected.scope ?: "runtime") } return true diff --git a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/ConfigurationFilterDependencyExtractorTest.groovy b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/ConfigurationFilterDependencyExtractorTest.groovy new file mode 100644 index 00000000..da6234ef --- /dev/null +++ b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/ConfigurationFilterDependencyExtractorTest.groovy @@ -0,0 +1,242 @@ +package org.gradle.github.dependencygraph + + +import org.gradle.test.fixtures.maven.MavenModule + +class ConfigurationFilterDependencyExtractorTest extends BaseExtractorTest { + private MavenModule foo + private MavenModule bar + private MavenModule baz + + private File settingsFile + private File buildFile + + def setup() { + applyDependencyGraphPlugin() + establishEnvironmentVariables() + + foo = mavenRepo.module("org.test", "foo", "1.0").publish() + bar = mavenRepo.module("org.test", "bar", "1.0").publish() + baz = mavenRepo.module("org.test", "baz", "1.0").dependsOn(bar).publish() + + settingsFile = file("settings.gradle") << """ + rootProject.name = 'parent' + """ + + buildFile = file("build.gradle") << """ + allprojects { + group "org.test" + version "1.0" + + repositories { + maven { url "${mavenRepo.uri}" } + } + } + """ + } + + def "can filter projects to extract dependencies"() { + given: + settingsFile << "include 'a', 'b'" + + buildFile << """ + project(':a') { + apply plugin: 'java-library' + dependencies { + api 'org.test:foo:1.0' + } + } + project(':b') { + apply plugin: 'java-library' + dependencies { + api 'org.test:bar:1.0' + } + } + """ + + when: + executer.withArgument("-DDEPENDENCY_GRAPH_INCLUDE_PROJECTS=:b") + run() + + then: + def manifest = gitHubManifest() + manifest.sourceFile == "settings.gradle" + manifest.assertResolved([ + "org.test:bar:1.0": [package_url: purlFor(bar)] + ]) + } + + def "can filter configurations to extract dependencies"() { + given: + settingsFile << "include 'a', 'b'" + + buildFile << """ + project(':a') { + apply plugin: 'java-library' + dependencies { + api 'org.test:foo:1.0' + testImplementation 'org.test:baz:1.0' + } + } + project(':b') { + apply plugin: 'java-library' + dependencies { + implementation 'org.test:bar:1.0' + testImplementation 'org.test:baz:1.0' + } + } + """ + + when: + executer.withArgument("-DDEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS=compileClasspath") + run() + + then: + def manifest = gitHubManifest() + manifest.sourceFile == "settings.gradle" + manifest.assertResolved([ + "org.test:foo:1.0": [package_url: purlFor(foo)], + "org.test:bar:1.0": [package_url: purlFor(bar)] + ]) + } + + def "can filter runtime projects to determine scope"() { + given: + settingsFile << "include 'a', 'b'" + + buildFile << """ + project(':a') { + apply plugin: 'java-library' + dependencies { + api 'org.test:foo:1.0' + } + } + project(':b') { + apply plugin: 'java-library' + dependencies { + implementation 'org.test:bar:1.0' + } + } + """ + + when: + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_PROJECTS=:a") + run() + + then: + def manifest = gitHubManifest() + manifest.sourceFile == "settings.gradle" + manifest.assertResolved([ + "org.test:foo:1.0": [package_url: purlFor(foo), scope: "runtime"], + "org.test:bar:1.0": [package_url: purlFor(bar), scope: "development"], + ]) + } + + def "can filter runtime configurations to determine scope"() { + given: + settingsFile << "include 'a', 'b'" + + buildFile << """ + project(':a') { + apply plugin: 'java-library' + dependencies { + api 'org.test:foo:1.0' + testImplementation 'org.test:baz:1.0' + } + } + project(':b') { + apply plugin: 'java-library' + dependencies { + implementation 'org.test:bar:1.0' + testImplementation 'org.test:baz:1.0' + } + } + """ + + when: + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS=compileClasspath") + run() + + then: + def manifest = gitHubManifest() + manifest.sourceFile == "settings.gradle" + manifest.assertResolved([ + "org.test:foo:1.0": [package_url: purlFor(foo), scope: "runtime"], + "org.test:bar:1.0": [package_url: purlFor(bar), scope: "runtime"], + "org.test:baz:1.0": [package_url: purlFor(baz), scope: "development", dependencies: ["org.test:bar:1.0"]] + ]) + } + + def "can filter runtime configurations to determine scope"() { + given: + settingsFile << "include 'a', 'b'" + + buildFile << """ + project(':a') { + apply plugin: 'java-library' + dependencies { + api 'org.test:foo:1.0' + testImplementation 'org.test:baz:1.0' + } + } + project(':b') { + apply plugin: 'java-library' + dependencies { + implementation 'org.test:bar:1.0' + testImplementation 'org.test:baz:1.0' + } + } + """ + + when: + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS=compileClasspath") + run() + + then: + def manifest = gitHubManifest() + manifest.sourceFile == "settings.gradle" + manifest.assertResolved([ + "org.test:foo:1.0": [package_url: purlFor(foo), scope: "runtime"], + "org.test:bar:1.0": [package_url: purlFor(bar), scope: "runtime"], + "org.test:baz:1.0": [package_url: purlFor(baz), scope: "development", dependencies: ["org.test:bar:1.0"]] + ]) + } + + def "can filter runtime projects and configurations to determine scope"() { + given: + settingsFile << "include 'a', 'b'" + + buildFile << """ + project(':a') { + apply plugin: 'java-library' + dependencies { + api 'org.test:foo:1.0' + testImplementation 'org.test:baz:1.0' + } + } + project(':b') { + apply plugin: 'java-library' + dependencies { + api 'org.test:bar:1.0' + testImplementation 'org.test:baz:1.0' + } + } + """ + + when: + executer + .withArgument("-DDEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS=compileClasspath") + .withArgument("-DDEPENDENCY_GRAPH_RUNTIME_PROJECTS=:a") + run() + + then: + def manifest = gitHubManifest() + manifest.sourceFile == "settings.gradle" + manifest.assertResolved([ + "org.test:foo:1.0": [package_url: purlFor(foo), scope: "runtime"], + "org.test:bar:1.0": [package_url: purlFor(bar), scope: "development"], + "org.test:baz:1.0": [package_url: purlFor(baz), scope: "development", dependencies: ["org.test:bar:1.0"]] + ]) + } + +} diff --git a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/MultiProjectDependencyExtractorTest.groovy b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/MultiProjectDependencyExtractorTest.groovy index 2f6f3b2d..a9d70f7d 100644 --- a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/MultiProjectDependencyExtractorTest.groovy +++ b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/MultiProjectDependencyExtractorTest.groovy @@ -250,71 +250,4 @@ class MultiProjectDependencyExtractorTest extends BaseExtractorTest { "org.test:foo:1.0": [package_url: purlFor(foo)] ]) } - - def "can filter projects to extract dependencies"() { - given: - settingsFile << "include 'a', 'b'" - - buildFile << """ - project(':a') { - apply plugin: 'java-library' - dependencies { - api 'org.test:foo:1.0' - } - } - project(':b') { - apply plugin: 'java-library' - dependencies { - api 'org.test:bar:1.0' - } - } - """ - - when: - executer.withArgument("-DDEPENDENCY_GRAPH_INCLUDE_PROJECTS=:b") - run() - - then: - def manifest = gitHubManifest() - manifest.sourceFile == "settings.gradle" - manifest.assertResolved([ - "org.test:bar:1.0": [package_url: purlFor(bar)] - ]) - } - - def "can filter configurations to extract dependencies"() { - given: - settingsFile << "include 'a', 'b'" - - buildFile << """ - project(':a') { - apply plugin: 'java-library' - dependencies { - api 'org.test:foo:1.0' - testImplementation 'org.test:baz:1.0' - } - } - project(':b') { - apply plugin: 'java-library' - dependencies { - implementation 'org.test:bar:1.0' - testImplementation 'org.test:baz:1.0' - } - } - """ - - when: - executer.withArgument("-DDEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS=compileClasspath") - run() - - then: - def manifest = gitHubManifest() - manifest.sourceFile == "settings.gradle" - manifest.assertResolved([ - "org.test:foo:1.0": [package_url: purlFor(foo)], - "org.test:bar:1.0": [package_url: purlFor(bar)] - ]) - } - - } From 7b2d3929b0f8d494aadb2311a6dc4793e7043cc0 Mon Sep 17 00:00:00 2001 From: daz Date: Sat, 27 Jan 2024 15:25:10 -0700 Subject: [PATCH 4/7] Only add dependency scope if configured To avoid assigned all dependencies to either 'runtime' or 'development' scope, we leave the current behaviour of assigning no scopes unless the relevant input parameters are configured. --- README.md | 19 +++++++++++++++++++ .../dependencygraph/BaseExtractorTest.groovy | 2 +- .../extractor/DependencyExtractor.kt | 15 ++++++++++++--- .../extractor/ResolvedConfigurationFilter.kt | 4 ++++ .../dependencygraph/model/DependencyScope.kt | 5 +++-- .../GitHubRepositorySnapshotBuilder.kt | 12 +++++++----- .../dependencygraph/model/GitHubDependency.kt | 2 +- 7 files changed, 47 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9e485cb4..4fed1338 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,25 @@ You can provide this value via the `DEPENDENCY_GRAPH_INCLUDE_PROJECTS` environme To restrict which Gradle configurations contribute to the report, you can filter configurations by name using a regular expression. You can provide this value via the `DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS` environment variable or system property. +### Controlling the scope of dependencies in the dependency graph + +The GitHub dependency graph allows a scope to be assigned to each reported dependency. +The only permissible values for scope are 'runtime' and 'development'. + +By default, no scope is assigned to dependencies in the graph. To enable scopes in the generated dependency graph, +at least one of `DEPENDENCY_GRAPH_RUNTIME_PROJECTS` or `DEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS` must be configured. + +To restrict which Gradle subprojects contribute 'runtime' dependencies to the report, specify which projects to include via a regular expression. +You can provide this value via the `DEPENDENCY_GRAPH_RUNTIME_PROJECTS` environment variable or system property. +For a project not matching this filter, all dependencies will be scoped 'development'. + +To restrict which Gradle configurations contribute 'runtime' dependencies to the report, you can filter configurations by name using a regular expression. +You can provide this value via the `DEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS` environment variable or system property. +Dependencies resolved by a matching configuration will be scoped 'runtime': all other dependencies will be scoped 'development'. + +For dependencies that are resolved in multiple projects and/or multiple configurations, only a single 'runtime' scoped resolution +is required for that dependency to be scoped 'runtime'. + ### Gradle compatibility The plugin should be compatible with most versions of Gradle >= 5.2, and has been tested against diff --git a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy index daa6eeb6..0ab65746 100644 --- a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy +++ b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy @@ -208,7 +208,7 @@ abstract class BaseExtractorTest extends Specification { } assert actual.relationship == (expected.relationship ?: "direct") assert actual.dependencies == (expected.dependencies ?: []) - assert actual.scope == (expected.scope ?: "runtime") + assert actual.scope == expected.scope } return true diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt index f4ef9391..2ebc97e3 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt @@ -9,8 +9,7 @@ import org.gradle.api.internal.artifacts.configurations.ResolveConfigurationDepe import org.gradle.api.logging.Logging import org.gradle.dependencygraph.DependencyGraphRenderer import org.gradle.dependencygraph.model.* -import org.gradle.dependencygraph.model.DependencyScope.RUNTIME -import org.gradle.dependencygraph.model.DependencyScope.DEVELOPMENT +import org.gradle.dependencygraph.model.DependencyScope.* import org.gradle.dependencygraph.util.* import org.gradle.initialization.EvaluateSettingsBuildOperationType import org.gradle.initialization.LoadProjectsBuildOperationType @@ -176,7 +175,7 @@ abstract class DependencyExtractor : return } - val scope = if (runtimeFilter.include(rootPath, configurationName)) RUNTIME else DEVELOPMENT + val scope = dependencyScope(rootPath, configurationName) val rootId = if (projectIdentityPath == null) "build $rootPath" else componentId(rootComponent) val rootOrigin = DependencyOrigin(rootId, rootPath) @@ -198,6 +197,16 @@ abstract class DependencyExtractor : resolvedConfigurations.add(resolvedConfiguration) } + private fun dependencyScope( + rootPath: String, + configurationName: String + ): DependencyScope { + if (runtimeFilter.isConfigured()) { + return if (runtimeFilter.include(rootPath, configurationName)) RUNTIME else DEVELOPMENT + } + return UNKNOWN + } + private fun walkComponentDependencies( component: ResolvedComponentResult, parentOrigin: DependencyOrigin, diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt index 0e84b78c..6d5de2f8 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt @@ -13,4 +13,8 @@ class ResolvedConfigurationFilter(projectFilter: String?, configurationFilter: S } return true } + + fun isConfigured(): Boolean { + return projectFilter != null || configurationFilter != null + } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt index 563836f4..cb0f4e32 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt @@ -6,12 +6,13 @@ package org.gradle.dependencygraph.model * Later development may extend this to a richer set of scopes. */ enum class DependencyScope { - DEVELOPMENT, RUNTIME; + UNKNOWN, DEVELOPMENT, RUNTIME; companion object { fun getEffectiveScope(scopes: List): DependencyScope { if (scopes.contains(RUNTIME)) return RUNTIME - return DEVELOPMENT + if (scopes.contains(DEVELOPMENT)) return DEVELOPMENT + return UNKNOWN } } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt b/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt index 25dc4763..43af25bc 100644 --- a/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt +++ b/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt @@ -38,10 +38,11 @@ class GitHubRepositorySnapshotBuilder( ) } - private fun determineGitHubScope(configuration: ResolvedConfiguration): GitHubDependency.Scope { + private fun determineGitHubScope(configuration: ResolvedConfiguration): GitHubDependency.Scope? { return when(configuration.scope) { DependencyScope.DEVELOPMENT -> GitHubDependency.Scope.development DependencyScope.RUNTIME -> GitHubDependency.Scope.runtime + DependencyScope.UNKNOWN -> null } } @@ -75,7 +76,7 @@ class GitHubRepositorySnapshotBuilder( /** * Merge each resolved component with the same ID into a single GitHubDependency. */ - fun addResolved(component: ResolvedDependency, scope: GitHubDependency.Scope) { + fun addResolved(component: ResolvedDependency, scope: GitHubDependency.Scope?) { val dep = dependencyBuilders.getOrPut(component.id) { GitHubDependencyBuilder(component.packageUrl()) } @@ -98,7 +99,7 @@ class GitHubRepositorySnapshotBuilder( private class GitHubDependencyBuilder(val package_url: String) { var relationship: GitHubDependency.Relationship = GitHubDependency.Relationship.indirect - var scope: GitHubDependency.Scope = GitHubDependency.Scope.development + var scope: GitHubDependency.Scope? = null val dependencies = mutableListOf() fun addRelationship(newRelationship: GitHubDependency.Relationship) { @@ -108,8 +109,9 @@ class GitHubRepositorySnapshotBuilder( } } - fun addScope(newScope: GitHubDependency.Scope) { - if (scope == GitHubDependency.Scope.development) { + fun addScope(newScope: GitHubDependency.Scope?) { + if (newScope == null) return + if (scope == null || scope == GitHubDependency.Scope.development) { scope = newScope } } diff --git a/plugin/src/main/kotlin/org/gradle/github/dependencygraph/model/GitHubDependency.kt b/plugin/src/main/kotlin/org/gradle/github/dependencygraph/model/GitHubDependency.kt index 5b697494..bf2deb17 100644 --- a/plugin/src/main/kotlin/org/gradle/github/dependencygraph/model/GitHubDependency.kt +++ b/plugin/src/main/kotlin/org/gradle/github/dependencygraph/model/GitHubDependency.kt @@ -3,7 +3,7 @@ package org.gradle.github.dependencygraph.model data class GitHubDependency( val package_url: String, val relationship: Relationship, - val scope: Scope, + val scope: Scope?, val dependencies: List ) { enum class Relationship { From 9945b32b09ffdc1bb7dd0eaa1fceff5c63af9c8e Mon Sep 17 00:00:00 2001 From: daz Date: Sun, 28 Jan 2024 03:42:10 -0700 Subject: [PATCH 5/7] Filter by exclusion as well as inclusion Using a Regex include filter can be problematic when trying to _exclude_ a matching entry (or entries). Adding specific exclude filters matching the current include filters makes configuration more straightforward. --- .../dependencygraph/BaseExtractorTest.groovy | 10 ++ ...rationFilterDependencyExtractorTest.groovy | 167 +++++++++++------- .../extractor/DependencyExtractor.kt | 34 ++-- .../extractor/ResolvedConfigurationFilter.kt | 48 +++-- .../internal/ResolvedConfigurationTest.groovy | 112 ------------ 5 files changed, 165 insertions(+), 206 deletions(-) delete mode 100644 plugin/src/test/groovy/org/gradle/github/dependencygraph/internal/ResolvedConfigurationTest.groovy diff --git a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy index 0ab65746..d3ba3cf1 100644 --- a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy +++ b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/BaseExtractorTest.groovy @@ -113,6 +113,10 @@ abstract class BaseExtractorTest extends Specification { } apply plugin: GitHubDependencyGraphPlugin """.stripMargin() + resetArguments() + } + + protected SimpleGradleExecuter resetArguments() { getExecuter().withArguments("--init-script", "init.gradle") } @@ -194,6 +198,12 @@ abstract class BaseExtractorTest extends Specification { return (manifestData.file as Map).source_location } + def assertResolved(List expectedResolved) { + def resolved = manifestData.resolved as Map + assert resolved.keySet() == expectedResolved as Set + return true + } + def assertResolved(Map expectedResolved = [:]) { def resolved = manifestData.resolved as Map diff --git a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/ConfigurationFilterDependencyExtractorTest.groovy b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/ConfigurationFilterDependencyExtractorTest.groovy index da6234ef..b4da125e 100644 --- a/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/ConfigurationFilterDependencyExtractorTest.groovy +++ b/plugin-test/src/test/groovy/org/gradle/github/dependencygraph/ConfigurationFilterDependencyExtractorTest.groovy @@ -37,7 +37,7 @@ class ConfigurationFilterDependencyExtractorTest extends BaseExtractorTest { def "can filter projects to extract dependencies"() { given: - settingsFile << "include 'a', 'b'" + settingsFile << "include 'a', 'b', 'c'" buildFile << """ project(':a') { @@ -52,6 +52,12 @@ class ConfigurationFilterDependencyExtractorTest extends BaseExtractorTest { api 'org.test:bar:1.0' } } + project(':c') { + apply plugin: 'java-library' + dependencies { + api 'org.test:baz:1.0' + } + } """ when: @@ -59,11 +65,32 @@ class ConfigurationFilterDependencyExtractorTest extends BaseExtractorTest { run() then: - def manifest = gitHubManifest() - manifest.sourceFile == "settings.gradle" - manifest.assertResolved([ - "org.test:bar:1.0": [package_url: purlFor(bar)] - ]) + gitHubManifest().assertResolved(["org.test:bar:1.0"]) + + when: + resetArguments() + executer.withArgument("-DDEPENDENCY_GRAPH_INCLUDE_PROJECTS=:[ab]") + run() + + then: + gitHubManifest().assertResolved(["org.test:foo:1.0", "org.test:bar:1.0"]) + + when: + resetArguments() + executer.withArgument("-DDEPENDENCY_GRAPH_EXCLUDE_PROJECTS=:[bc]") + run() + + then: + gitHubManifest().assertResolved(["org.test:foo:1.0"]) + + when: + resetArguments() + executer.withArgument("-DDEPENDENCY_GRAPH_INCLUDE_PROJECTS=:[ab]") + executer.withArgument("-DDEPENDENCY_GRAPH_EXCLUDE_PROJECTS=:b") + run() + + then: + gitHubManifest().assertResolved(["org.test:foo:1.0"]) } def "can filter configurations to extract dependencies"() { @@ -92,12 +119,15 @@ class ConfigurationFilterDependencyExtractorTest extends BaseExtractorTest { run() then: - def manifest = gitHubManifest() - manifest.sourceFile == "settings.gradle" - manifest.assertResolved([ - "org.test:foo:1.0": [package_url: purlFor(foo)], - "org.test:bar:1.0": [package_url: purlFor(bar)] - ]) + gitHubManifest().assertResolved(["org.test:foo:1.0", "org.test:bar:1.0"]) + + when: + resetArguments() + executer.withArgument("-DDEPENDENCY_GRAPH_EXCLUDE_CONFIGURATIONS=test(Compile|Runtime)Classpath") + run() + + then: + gitHubManifest().assertResolved(["org.test:foo:1.0", "org.test:bar:1.0"]) } def "can filter runtime projects to determine scope"() { @@ -120,50 +150,36 @@ class ConfigurationFilterDependencyExtractorTest extends BaseExtractorTest { """ when: - executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_PROJECTS=:a") + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_INCLUDE_PROJECTS=:a") run() then: - def manifest = gitHubManifest() - manifest.sourceFile == "settings.gradle" - manifest.assertResolved([ - "org.test:foo:1.0": [package_url: purlFor(foo), scope: "runtime"], - "org.test:bar:1.0": [package_url: purlFor(bar), scope: "development"], + gitHubManifest().assertResolved([ + "org.test:foo:1.0": [scope: "runtime"], + "org.test:bar:1.0": [scope: "development"] ]) - } - def "can filter runtime configurations to determine scope"() { - given: - settingsFile << "include 'a', 'b'" + when: + resetArguments() + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_EXCLUDE_PROJECTS=:b") + run() - buildFile << """ - project(':a') { - apply plugin: 'java-library' - dependencies { - api 'org.test:foo:1.0' - testImplementation 'org.test:baz:1.0' - } - } - project(':b') { - apply plugin: 'java-library' - dependencies { - implementation 'org.test:bar:1.0' - testImplementation 'org.test:baz:1.0' - } - } - """ + then: + gitHubManifest().assertResolved([ + "org.test:foo:1.0": [scope: "runtime"], + "org.test:bar:1.0": [scope: "development"] + ]) when: - executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS=compileClasspath") + resetArguments() + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_INCLUDE_PROJECTS=:[ab]") + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_EXCLUDE_PROJECTS=:b") run() then: - def manifest = gitHubManifest() - manifest.sourceFile == "settings.gradle" - manifest.assertResolved([ - "org.test:foo:1.0": [package_url: purlFor(foo), scope: "runtime"], - "org.test:bar:1.0": [package_url: purlFor(bar), scope: "runtime"], - "org.test:baz:1.0": [package_url: purlFor(baz), scope: "development", dependencies: ["org.test:bar:1.0"]] + gitHubManifest().assertResolved([ + "org.test:foo:1.0": [scope: "runtime"], + "org.test:bar:1.0": [scope: "development"] ]) } @@ -189,22 +205,45 @@ class ConfigurationFilterDependencyExtractorTest extends BaseExtractorTest { """ when: - executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS=compileClasspath") + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_INCLUDE_CONFIGURATIONS=compileClasspath") + run() + + then: + gitHubManifest().assertResolved([ + "org.test:foo:1.0": [scope: "runtime"], + "org.test:bar:1.0": [scope: "runtime"], + "org.test:baz:1.0": [scope: "development", dependencies: ["org.test:bar:1.0"]] + ]) + + when: + resetArguments() + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_INCLUDE_CONFIGURATIONS=.*Classpath") run() then: - def manifest = gitHubManifest() - manifest.sourceFile == "settings.gradle" - manifest.assertResolved([ - "org.test:foo:1.0": [package_url: purlFor(foo), scope: "runtime"], - "org.test:bar:1.0": [package_url: purlFor(bar), scope: "runtime"], - "org.test:baz:1.0": [package_url: purlFor(baz), scope: "development", dependencies: ["org.test:bar:1.0"]] + gitHubManifest().assertResolved([ + "org.test:foo:1.0": [scope: "runtime"], + "org.test:bar:1.0": [scope: "runtime"], + "org.test:baz:1.0": [scope: "runtime", dependencies: ["org.test:bar:1.0"]] + ]) + + when: + resetArguments() + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_INCLUDE_CONFIGURATIONS=.*Classpath") + executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_EXCLUDE_CONFIGURATIONS=test(Compile|Runtime)Classpath") + run() + + then: + gitHubManifest().assertResolved([ + "org.test:foo:1.0": [scope: "runtime"], + "org.test:bar:1.0": [scope: "runtime"], + "org.test:baz:1.0": [scope: "development", dependencies: ["org.test:bar:1.0"]] ]) } def "can filter runtime projects and configurations to determine scope"() { given: - settingsFile << "include 'a', 'b'" + settingsFile << "include 'a', 'b', 'c'" buildFile << """ project(':a') { @@ -221,21 +260,27 @@ class ConfigurationFilterDependencyExtractorTest extends BaseExtractorTest { testImplementation 'org.test:baz:1.0' } } + project(':b') { + apply plugin: 'java-library' + dependencies { + api 'org.test:baz:1.0' + } + } """ when: executer - .withArgument("-DDEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS=compileClasspath") - .withArgument("-DDEPENDENCY_GRAPH_RUNTIME_PROJECTS=:a") + .withArgument("-DDEPENDENCY_GRAPH_RUNTIME_INCLUDE_PROJECTS=:[ab]") + .withArgument("-DDEPENDENCY_GRAPH_RUNTIME_INCLUDE_CONFIGURATIONS=.*Classpath") + .withArgument("-DDEPENDENCY_GRAPH_RUNTIME_EXCLUDE_PROJECTS=:b") + .withArgument("-DDEPENDENCY_GRAPH_RUNTIME_EXCLUDE_CONFIGURATIONS=test(Compile|Runtime)Classpath") run() then: - def manifest = gitHubManifest() - manifest.sourceFile == "settings.gradle" - manifest.assertResolved([ - "org.test:foo:1.0": [package_url: purlFor(foo), scope: "runtime"], - "org.test:bar:1.0": [package_url: purlFor(bar), scope: "development"], - "org.test:baz:1.0": [package_url: purlFor(baz), scope: "development", dependencies: ["org.test:bar:1.0"]] + gitHubManifest().assertResolved([ + "org.test:foo:1.0": [scope: "runtime"], + "org.test:bar:1.0": [scope: "development"], + "org.test:baz:1.0": [scope: "development", dependencies: ["org.test:bar:1.0"]] ]) } diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt index 2ebc97e3..4cfe3d7f 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt @@ -1,6 +1,5 @@ package org.gradle.dependencygraph.extractor -import org.gradle.api.GradleException import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.artifacts.result.ResolvedDependencyResult @@ -13,7 +12,6 @@ import org.gradle.dependencygraph.model.DependencyScope.* import org.gradle.dependencygraph.util.* import org.gradle.initialization.EvaluateSettingsBuildOperationType import org.gradle.initialization.LoadProjectsBuildOperationType -import org.gradle.internal.exceptions.Contextual import org.gradle.internal.exceptions.DefaultMultiCauseException import org.gradle.internal.operations.* import java.io.File @@ -22,8 +20,12 @@ import java.util.* const val PARAM_INCLUDE_PROJECTS = "DEPENDENCY_GRAPH_INCLUDE_PROJECTS" const val PARAM_INCLUDE_CONFIGURATIONS = "DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS" -const val PARAM_RUNTIME_PROJECTS = "DEPENDENCY_GRAPH_RUNTIME_PROJECTS" -const val PARAM_RUNTIME_CONFIGURATIONS = "DEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS" +const val PARAM_EXCLUDE_PROJECTS = "DEPENDENCY_GRAPH_EXCLUDE_PROJECTS" +const val PARAM_EXCLUDE_CONFIGURATIONS = "DEPENDENCY_GRAPH_EXCLUDE_CONFIGURATIONS" +const val PARAM_RUNTIME_INCLUDE_PROJECTS = "DEPENDENCY_GRAPH_RUNTIME_INCLUDE_PROJECTS" +const val PARAM_RUNTIME_INCLUDE_CONFIGURATIONS = "DEPENDENCY_GRAPH_RUNTIME_INCLUDE_CONFIGURATIONS" +const val PARAM_RUNTIME_EXCLUDE_PROJECTS = "DEPENDENCY_GRAPH_RUNTIME_EXCLUDE_PROJECTS" +const val PARAM_RUNTIME_EXCLUDE_CONFIGURATIONS = "DEPENDENCY_GRAPH_RUNTIME_EXCLUDE_CONFIGURATIONS" const val PARAM_REPORT_DIR = "DEPENDENCY_GRAPH_REPORT_DIR" @@ -46,18 +48,8 @@ abstract class DependencyExtractor : // Properties are lazily initialized so that System Properties are initialized by the time // the values are used. This is required due to a bug in older Gradle versions. (https://github.com/gradle/gradle/issues/6825) - private val includeFilter by lazy { - ResolvedConfigurationFilter( - pluginParameters.loadOptional(PARAM_INCLUDE_PROJECTS), - pluginParameters.loadOptional(PARAM_INCLUDE_CONFIGURATIONS) - ) - } - - private val runtimeFilter by lazy { - ResolvedConfigurationFilter( - pluginParameters.loadOptional(PARAM_RUNTIME_PROJECTS), - pluginParameters.loadOptional(PARAM_RUNTIME_CONFIGURATIONS) - ) + private val configurationFilter by lazy { + ResolvedConfigurationFilter(pluginParameters) } private val dependencyGraphReportDir by lazy { @@ -67,12 +59,12 @@ abstract class DependencyExtractor : abstract fun getRendererClassName(): String override fun started(buildOperation: BuildOperationDescriptor, startEvent: OperationStartEvent) { - // This method will never be called when registered in a `BuildServiceRegistry` (ie. Gradle 6.1 & higher) + // This method will never be called when registered in a `BuildServiceRegistry` (i.e. Gradle 6.1 & higher) // No-op } override fun progress(operationIdentifier: OperationIdentifier, progressEvent: OperationProgressEvent) { - // This method will never be called when registered in a `BuildServiceRegistry` (ie. Gradle 6.1 & higher) + // This method will never be called when registered in a `BuildServiceRegistry` (i.e. Gradle 6.1 & higher) // No-op } @@ -170,7 +162,7 @@ abstract class DependencyExtractor : val rootPath = projectIdentityPath ?: details.buildPath val configurationName = details.configurationName - if (!includeFilter.include(rootPath, configurationName)) { + if (!configurationFilter.include(rootPath, configurationName)) { LOGGER.debug("Ignoring resolved configuration: $rootPath - $configurationName") return } @@ -201,8 +193,8 @@ abstract class DependencyExtractor : rootPath: String, configurationName: String ): DependencyScope { - if (runtimeFilter.isConfigured()) { - return if (runtimeFilter.include(rootPath, configurationName)) RUNTIME else DEVELOPMENT + if (configurationFilter.scopesAreConfigured()) { + return if (configurationFilter.isRuntime(rootPath, configurationName)) RUNTIME else DEVELOPMENT } return UNKNOWN } diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt index 6d5de2f8..421bea15 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/ResolvedConfigurationFilter.kt @@ -1,20 +1,44 @@ package org.gradle.dependencygraph.extractor -class ResolvedConfigurationFilter(projectFilter: String?, configurationFilter: String?) { - private val projectFilter = projectFilter?.toRegex() - private val configurationFilter = configurationFilter?.toRegex() +import org.gradle.dependencygraph.util.PluginParameters + +class ResolvedConfigurationFilter(pluginParameters: PluginParameters) { + private val includeProjects = pluginParameters.loadOptional(PARAM_INCLUDE_PROJECTS)?.toRegex() + private val includeConfigurations = pluginParameters.loadOptional(PARAM_INCLUDE_CONFIGURATIONS)?.toRegex() + private val excludeProjects = pluginParameters.loadOptional(PARAM_EXCLUDE_PROJECTS)?.toRegex() + private val excludeConfigurations = pluginParameters.loadOptional(PARAM_EXCLUDE_CONFIGURATIONS)?.toRegex() + + private val runtimeIncludeProjects = pluginParameters.loadOptional(PARAM_RUNTIME_INCLUDE_PROJECTS)?.toRegex() + private val runtimeIncludeConfigurations = pluginParameters.loadOptional(PARAM_RUNTIME_INCLUDE_CONFIGURATIONS)?.toRegex() + private val runtimeExcludeProjects = pluginParameters.loadOptional(PARAM_RUNTIME_EXCLUDE_PROJECTS)?.toRegex() + private val runtimeExcludeConfigurations = pluginParameters.loadOptional(PARAM_RUNTIME_EXCLUDE_CONFIGURATIONS)?.toRegex() fun include(projectPath: String, configurationName: String): Boolean { - if (projectFilter != null && !projectFilter.matches(projectPath)) { - return false - } - if (configurationFilter != null && !configurationFilter.matches(configurationName)) { - return false - } - return true + return includes(includeProjects, projectPath) + && notExcludes(excludeProjects, projectPath) + && includes(includeConfigurations, configurationName) + && notExcludes(excludeConfigurations, configurationName) + } + + fun scopesAreConfigured(): Boolean { + return runtimeIncludeProjects != null + || runtimeIncludeConfigurations != null + || runtimeExcludeProjects != null + || runtimeExcludeConfigurations != null + } + + fun isRuntime(projectPath: String, configurationName: String): Boolean { + return includes(runtimeIncludeProjects, projectPath) + && notExcludes(runtimeExcludeProjects, projectPath) + && includes(runtimeIncludeConfigurations, configurationName) + && notExcludes(runtimeExcludeConfigurations, configurationName) + } + + private fun includes(regex: Regex?, value: String): Boolean { + return regex == null || regex.matches(value) } - fun isConfigured(): Boolean { - return projectFilter != null || configurationFilter != null + private fun notExcludes(regex: Regex?, value: String): Boolean { + return regex == null || !regex.matches(value) } } \ No newline at end of file diff --git a/plugin/src/test/groovy/org/gradle/github/dependencygraph/internal/ResolvedConfigurationTest.groovy b/plugin/src/test/groovy/org/gradle/github/dependencygraph/internal/ResolvedConfigurationTest.groovy deleted file mode 100644 index a7659c72..00000000 --- a/plugin/src/test/groovy/org/gradle/github/dependencygraph/internal/ResolvedConfigurationTest.groovy +++ /dev/null @@ -1,112 +0,0 @@ -package org.gradle.github.dependencygraph.internal - -import org.gradle.dependencygraph.extractor.ResolvedConfigurationFilter -import spock.lang.Specification - -class ResolvedConfigurationTest extends Specification { - def "null filter includes everything"() { - when: - def filter = new ResolvedConfigurationFilter(null, null) - - then: - filter.include("foo", "bar") - filter.include(":foo:bar:baz", "not a real name") - filter.include("", "") - } - - def "filters on exact configuration name"() { - when: - def filter = new ResolvedConfigurationFilter(null, "compileClasspath") - - then: - filter.include("", "compileClasspath") - - !filter.include("", "classpath") - !filter.include("", "runtimeClasspath") - !filter.include("", "testCompileClasspath") - } - - def "filters on exact configuration names"() { - when: - def filter = new ResolvedConfigurationFilter(null, "compileClasspath|runtimeClasspath") - - then: - filter.include("", "compileClasspath") - filter.include("", "runtimeClasspath") - - !filter.include("", "classpath") - !filter.include("", "testCompileClasspath") - !filter.include("", "runtimeElements") - } - - def "filters on configuration name match"() { - when: - def filter = new ResolvedConfigurationFilter(null, ".*[cC]lasspath") - - then: - filter.include("", "compileClasspath") - filter.include("", "runtimeClasspath") - filter.include("", "testCompileClasspath") - filter.include("", "classpath") - - !filter.include("", "runtimeElements") - !filter.include("", "compileClasspathOnly") - } - - def "filters on exact project path"() { - when: - def filter = new ResolvedConfigurationFilter(":parent-proj:proj", null) - - then: - filter.include(":parent-proj:proj", "") - - !filter.include(":parent-proj", "") - !filter.include(":parent-proj:", "") - !filter.include(":proj", "") - !filter.include(":", "") - } - - def "filters on exact project paths"() { - when: - def filter = new ResolvedConfigurationFilter(":proj-a|:proj-b", null) - - then: - filter.include(":proj-a", "") - filter.include(":proj-b", "") - - !filter.include(":parent-proj:proj-a", "") - !filter.include(":proj", "") - !filter.include(":", "") - } - - def "filters on project path match"() { - when: - def filter = new ResolvedConfigurationFilter(/(:[\w-]+)*:proj-a(:[\w-]+)*/, null) - - then: - filter.include(":proj-a", "") - filter.include(":proj-a:proj-b", "") - filter.include(":proj-a:proj-b:proj-c", "") - filter.include(":parent-proj:proj-a", "") - filter.include(":parent-proj:proj-a:proj-b", "") - - !filter.include(":proj-another", "") - !filter.include(":proj-a:", "") - !filter.include(":proj-a:proj-b:", "") - !filter.include("parent-proj:proj-a", "") - } - - def "filters on excluded project path"() { - when: - def filter = new ResolvedConfigurationFilter(/^:(?!buildSrc).*/, null) - - then: - filter.include(":proj-a", "") - filter.include(":proj-a:proj-b", "") - filter.include(":proj-a:buildSrc", "") - - !filter.include(":buildSrc", "") - !filter.include(":buildSrc:", "") - !filter.include(":buildSrc:proj-b:", "") - } -} From 94c5357f222c5d591b56c722e3cd442ca791a684 Mon Sep 17 00:00:00 2001 From: daz Date: Sun, 28 Jan 2024 03:45:48 -0700 Subject: [PATCH 6/7] Cosmetic improvements to Simple resolution report --- .../dependencygraph/extractor/DependencyExtractor.kt | 4 ++-- .../org/gradle/dependencygraph/model/DependencyScope.kt | 8 ++++---- .../simple/SimpleDependencyGraphRenderer.kt | 6 +++--- .../dependencygraph/GitHubRepositorySnapshotBuilder.kt | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt index 4cfe3d7f..dcf1eae1 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/extractor/DependencyExtractor.kt @@ -194,9 +194,9 @@ abstract class DependencyExtractor : configurationName: String ): DependencyScope { if (configurationFilter.scopesAreConfigured()) { - return if (configurationFilter.isRuntime(rootPath, configurationName)) RUNTIME else DEVELOPMENT + return if (configurationFilter.isRuntime(rootPath, configurationName)) Runtime else Development } - return UNKNOWN + return Unknown } private fun walkComponentDependencies( diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt index cb0f4e32..65b8fee7 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/model/DependencyScope.kt @@ -6,13 +6,13 @@ package org.gradle.dependencygraph.model * Later development may extend this to a richer set of scopes. */ enum class DependencyScope { - UNKNOWN, DEVELOPMENT, RUNTIME; + Unknown, Development, Runtime; companion object { fun getEffectiveScope(scopes: List): DependencyScope { - if (scopes.contains(RUNTIME)) return RUNTIME - if (scopes.contains(DEVELOPMENT)) return DEVELOPMENT - return UNKNOWN + if (scopes.contains(Runtime)) return Runtime + if (scopes.contains(Development)) return Development + return Unknown } } } \ No newline at end of file diff --git a/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt b/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt index 40979f5e..099e403d 100644 --- a/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt +++ b/plugin/src/main/kotlin/org/gradle/dependencygraph/simple/SimpleDependencyGraphRenderer.kt @@ -40,14 +40,14 @@ class SimpleDependencyGraphRenderer : DependencyGraphRenderer { outputDirectory: File, resolvedConfigurations: List ) { - val outputFile = File(outputDirectory, "dependency-scopes.json") + val outputFile = File(outputDirectory, "dependency-resolution.json") val dependencyList: MutableMap> = mutableMapOf() for (config in resolvedConfigurations) { for (dependency in config.allDependencies) { if (dependency.isProject) continue - val dependencyScopes = dependencyList.getOrPut(dependency.id) { mutableSetOf() } - dependencyScopes.add( + val dependencyResolutions = dependencyList.getOrPut(dependency.id) { mutableSetOf() } + dependencyResolutions.add( SimpleDependencyResolution( config.rootOrigin.path, config.configurationName, diff --git a/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt b/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt index 43af25bc..3fc3a444 100644 --- a/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt +++ b/plugin/src/main/kotlin/org/gradle/github/dependencygraph/GitHubRepositorySnapshotBuilder.kt @@ -40,9 +40,9 @@ class GitHubRepositorySnapshotBuilder( private fun determineGitHubScope(configuration: ResolvedConfiguration): GitHubDependency.Scope? { return when(configuration.scope) { - DependencyScope.DEVELOPMENT -> GitHubDependency.Scope.development - DependencyScope.RUNTIME -> GitHubDependency.Scope.runtime - DependencyScope.UNKNOWN -> null + DependencyScope.Development -> GitHubDependency.Scope.development + DependencyScope.Runtime -> GitHubDependency.Scope.runtime + DependencyScope.Unknown -> null } } From bf014629c5d309ea53f20b294d88a6f737937431 Mon Sep 17 00:00:00 2001 From: daz Date: Sun, 28 Jan 2024 09:03:27 -0700 Subject: [PATCH 7/7] Improve documentation for configuration filtering --- README.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 4fed1338..6a30f696 100644 --- a/README.md +++ b/README.md @@ -45,27 +45,35 @@ eg: Env var `DEPENDENCY_GRAPH_REPORT_DIR` can be set with `-DDEPENDENCY_GRAPH_RE If you do not want to include every dependency configuration in every project in your build, you can limit the dependency extraction to a subset of these. -To restrict which Gradle subprojects contribute to the report, specify which projects to include via a regular expression. -You can provide this value via the `DEPENDENCY_GRAPH_INCLUDE_PROJECTS` environment variable or system property. +The following parameters control the set of projects and configurations that contribute dependencies. +Each of these is a regular expression value, and can set either as an environment variable or as a system property on the command line. -To restrict which Gradle configurations contribute to the report, you can filter configurations by name using a regular expression. -You can provide this value via the `DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS` environment variable or system property. +| Property | Description | Default | +|-----------------------------------------|---------------------------|---------------------------------| +| DEPENDENCY_GRAPH_INCLUDE_PROJECTS | Projects to include | All projects are included | +| DEPENDENCY_GRAPH_EXCLUDE_PROJECTS | Projects to exclude | No projects are included | +| DEPENDENCY_GRAPH_INCLUDE_CONFIGURATIONS | Configurations to include | All configurations are included | +| DEPENDENCY_GRAPH_EXCLUDE_CONFIGURATIONS | Configurations to exclude | No configurations are included | ### Controlling the scope of dependencies in the dependency graph The GitHub dependency graph allows a scope to be assigned to each reported dependency. The only permissible values for scope are 'runtime' and 'development'. -By default, no scope is assigned to dependencies in the graph. To enable scopes in the generated dependency graph, -at least one of `DEPENDENCY_GRAPH_RUNTIME_PROJECTS` or `DEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS` must be configured. +The following parameters control the set of projects and configurations that provide 'runtime' scoped dependencies. +Any dependency resolution that does not match these parameters will be scoped 'development'. -To restrict which Gradle subprojects contribute 'runtime' dependencies to the report, specify which projects to include via a regular expression. -You can provide this value via the `DEPENDENCY_GRAPH_RUNTIME_PROJECTS` environment variable or system property. -For a project not matching this filter, all dependencies will be scoped 'development'. +Each of these parameters is a regular expression value, and can set either as an environment variable or as a system property on the command line. -To restrict which Gradle configurations contribute 'runtime' dependencies to the report, you can filter configurations by name using a regular expression. -You can provide this value via the `DEPENDENCY_GRAPH_RUNTIME_CONFIGURATIONS` environment variable or system property. -Dependencies resolved by a matching configuration will be scoped 'runtime': all other dependencies will be scoped 'development'. +| Property | Description | Default | +|-------------------------------------------------|-----------------------------------------------------------|---------------------------------| +| DEPENDENCY_GRAPH_RUNTIME_INCLUDE_PROJECTS | Projects that can provide 'runtime' dependencies | All projects are included | +| DEPENDENCY_GRAPH_RUNTIME_EXCLUDE_PROJECTS | Projects that do not provide 'runtime' dependencies | No projects are included | +| DEPENDENCY_GRAPH_RUNTIME_INCLUDE_CONFIGURATIONS | Configurations that contain 'runtime' dependencies | All configurations are included | +| DEPENDENCY_GRAPH_RUNTIME_EXCLUDE_CONFIGURATIONS | Configurations that do not contain 'runtime' dependencies | No configurations are included | + +By default, no scope is assigned to dependencies in the graph. To enable scopes in the generated dependency graph, +at least one of these parameters must be configured. For dependencies that are resolved in multiple projects and/or multiple configurations, only a single 'runtime' scoped resolution is required for that dependency to be scoped 'runtime'.