Skip to content

Commit

Permalink
Avoid resolving configurations that are deprecated for resolving
Browse files Browse the repository at this point in the history
Certain configurations can be reported as "Deprecated for resolving", 
which is detectable via the`DeprecatableConfiguration.canSafelyBeResolved` internal API.

Resolving these configurations during dependency graph generationg can
be problematic, particularly in the case of strict Dependency Locking,
since no lockfile is likely to be generated for these configurations.

With this fix, reflection is used to attempt to determine if a
Configuration can be safely resolved, avoiding resolution of deprecated
configurations.

Fixes #128
  • Loading branch information
bigdaz authored Apr 6, 2024
1 parent dc07592 commit 68164b8
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
matrix:
# Test earliest and latest supported version of 5.x, 6.x and 7.x, as well as all patched minor versions of 8.x
# Latest 8.x is tested in 'quick-check' job using the wrapper
gradle-version: [ "5.2.1", "5.6.4", "6.0.1", "6.9.4", "7.1.1", "7.6.3", "8.0.2", "8.1.1", "8.2.1", "8.3", "8.4", "8.5"]
gradle-version: [ "5.2.1", "5.6.4", "6.0.1", "6.9.4", "7.1.1", "7.6.4", "8.0.2", "8.1.1", "8.2.1", "8.3", "8.4", "8.5", "8.6"]
runs-on: ubuntu-latest
env:
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.gradle.github.dependencygraph

import org.gradle.test.fixtures.PluginPublisher
import org.gradle.test.fixtures.maven.MavenModule
import org.gradle.util.GradleVersion
import spock.lang.IgnoreIf

class DependencyLockingDependencyExtractorTest extends BaseExtractorTest {
private MavenModule foo
private MavenModule bar
private MavenModule baz
private File settingsFile
private File buildFile

def setup() {
establishEnvironmentVariables()

foo = mavenRepo.module("org.test", "foo", "1.0").publish()

settingsFile = file("settings.gradle") << """
rootProject.name = 'a'
"""

buildFile = file("build.gradle") << """
apply plugin: 'java'
repositories {
maven { url "${mavenRepo.uri}" }
}
"""
}

def "extracts dependencies when dependency locking is enabled"() {
given:
buildFile << """
dependencies {
implementation "org.test:foo:+"
}
dependencyLocking {
lockAllConfigurations()
}
"""

// Write dependency lock file
run("dependencies", "--write-locks")
mavenRepo.module("org.test", "foo", "1.1").publish()

when:
applyDependencyGraphPlugin()
run()

then:
def manifest = gitHubManifest()
manifest.sourceFile == "settings.gradle"

manifest.assertResolved([
"org.test:foo:1.0": [
package_url: purlFor(foo)
]
])
}

@IgnoreIf({
// `LockMode.STRICT` was introduced in Gradle 6.1
GradleVersion.version(testGradleVersion) < GradleVersion.version("6.1")
})
def "extracts dependencies when Strict dependency locking is enabled"() {
given:
buildFile << """
dependencies {
implementation "org.test:foo:+"
}
dependencyLocking {
lockAllConfigurations()
lockMode = LockMode.STRICT
}
"""

// Write dependency lock file
run("dependencies", "--write-locks")
mavenRepo.module("org.test", "foo", "1.1").publish()

when:
applyDependencyGraphPlugin()
run()

then:
def manifest = gitHubManifest()
manifest.sourceFile == "settings.gradle"

manifest.assertResolved([
"org.test:foo:1.0": [
package_url: purlFor(foo)
]
])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.gradle.forceresolve

import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.Internal
import org.gradle.dependencygraph.extractor.ResolvedConfigurationFilter
import org.gradle.work.DisableCachingByDefault
import java.lang.reflect.Method

@DisableCachingByDefault(because = "Not worth caching")
abstract class AbstractResolveProjectDependenciesTask : DefaultTask() {
private val canSafelyBeResolvedMethod: Method? = getCanSafelyBeResolvedMethod()

@Internal
var configurationFilter: ResolvedConfigurationFilter? = null

@Internal
protected fun getReportableConfigurations(): List<Configuration> {
return project.configurations.filter {
canSafelyBeResolved(it) && configurationFilter!!.include(project.path, it.name)
}
}

/**
* If `DeprecatableConfiguration.canSafelyBeResolve()` is available, use it.
* Else fall back to `Configuration.canBeResolved`.
*/
private fun canSafelyBeResolved(configuration: Configuration): Boolean {
if (canSafelyBeResolvedMethod != null) {
return canSafelyBeResolvedMethod.invoke(configuration) as Boolean
}
return configuration.isCanBeResolved
}

private fun getCanSafelyBeResolvedMethod(): Method? {
return try {
val dc = Class.forName("org.gradle.internal.deprecation.DeprecatableConfiguration")
dc.getMethod("canSafelyBeResolved")
} catch (e: ReflectiveOperationException) {
null
}
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
package org.gradle.forceresolve

import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.dependencygraph.extractor.ResolvedConfigurationFilter
import org.gradle.work.DisableCachingByDefault

@DisableCachingByDefault(because = "Not worth caching")
abstract class LegacyResolveProjectDependenciesTask: DefaultTask() {
@Internal
var configurationFilter: ResolvedConfigurationFilter? = null

private fun getReportableConfigurations(): List<Configuration> {
return project.configurations.filter {
it.isCanBeResolved && configurationFilter!!.include(project.path, it.name)
}
}
abstract class LegacyResolveProjectDependenciesTask: AbstractResolveProjectDependenciesTask() {

@TaskAction
fun action() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,21 @@
package org.gradle.forceresolve

import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.dependencygraph.extractor.ResolvedConfigurationFilter
import org.gradle.internal.serialization.Cached
import org.gradle.work.DisableCachingByDefault

@DisableCachingByDefault(because = "Not worth caching")
abstract class ResolveProjectDependenciesTask: DefaultTask() {
abstract class ResolveProjectDependenciesTask: AbstractResolveProjectDependenciesTask() {
private val configurationResolvers = Cached.of { createConfigurationResolvers() }

@Internal
var configurationFilter: ResolvedConfigurationFilter? = null

private fun createConfigurationResolvers(): List<Provider<ResolvedComponentResult>> {
return getReportableConfigurations().map {
it.incoming.resolutionResult.rootComponent
}
}

private fun getReportableConfigurations(): List<Configuration> {
return project.configurations.filter {
it.isCanBeResolved && configurationFilter!!.include(project.path, it.name)
}
}

@TaskAction
fun action() {
for (configuration in configurationResolvers.get()) {
Expand Down

0 comments on commit 68164b8

Please sign in to comment.