Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Assign dependency scopes based on include/exclude filters #105

Merged
merged 7 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,38 @@ 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'.

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'.

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.

| 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'.

### Gradle compatibility

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ abstract class BaseExtractorTest extends Specification {
}
apply plugin: GitHubDependencyGraphPlugin
""".stripMargin()
resetArguments()
}

protected SimpleGradleExecuter resetArguments() {
getExecuter().withArguments("--init-script", "init.gradle")
}

Expand Down Expand Up @@ -194,6 +198,12 @@ abstract class BaseExtractorTest extends Specification {
return (manifestData.file as Map).source_location
}

def assertResolved(List<String> expectedResolved) {
def resolved = manifestData.resolved as Map<String, Map>
assert resolved.keySet() == expectedResolved as Set
return true
}

def assertResolved(Map<String, Map> expectedResolved = [:]) {
def resolved = manifestData.resolved as Map<String, Map>

Expand All @@ -208,6 +218,7 @@ abstract class BaseExtractorTest extends Specification {
}
assert actual.relationship == (expected.relationship ?: "direct")
assert actual.dependencies == (expected.dependencies ?: [])
assert actual.scope == expected.scope
}

return true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
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', 'c'"

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'
}
}
project(':c') {
apply plugin: 'java-library'
dependencies {
api 'org.test:baz:1.0'
}
}
"""

when:
executer.withArgument("-DDEPENDENCY_GRAPH_INCLUDE_PROJECTS=:b")
run()

then:
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"() {
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:
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"() {
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_INCLUDE_PROJECTS=:a")
run()

then:
gitHubManifest().assertResolved([
"org.test:foo:1.0": [scope: "runtime"],
"org.test:bar:1.0": [scope: "development"]
])

when:
resetArguments()
executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_EXCLUDE_PROJECTS=:b")
run()

then:
gitHubManifest().assertResolved([
"org.test:foo:1.0": [scope: "runtime"],
"org.test:bar:1.0": [scope: "development"]
])

when:
resetArguments()
executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_INCLUDE_PROJECTS=:[ab]")
executer.withArgument("-DDEPENDENCY_GRAPH_RUNTIME_EXCLUDE_PROJECTS=:b")
run()

then:
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'"

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_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:
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', 'c'"

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'
}
}
project(':b') {
apply plugin: 'java-library'
dependencies {
api 'org.test:baz:1.0'
}
}
"""

when:
executer
.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:
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"]]
])
}

}
Loading