Skip to content

Commit

Permalink
ResolveExternalDependencies is configuration-cache compatible.
Browse files Browse the repository at this point in the history
This is a first step towards making the rest of the plugin compatible.
  • Loading branch information
autonomousapps committed Sep 29, 2023
1 parent 83bd478 commit ea94054
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.autonomousapps

import com.autonomousapps.fixtures.ProjectDirProvider
import com.autonomousapps.internal.GradleVersions
import com.autonomousapps.internal.android.AgpVersion
import com.autonomousapps.kit.GradleProject
import com.autonomousapps.model.ProjectAdvice
Expand All @@ -12,26 +13,19 @@ import static com.autonomousapps.utils.DebugAware.debug

abstract class AbstractFunctionalSpec extends Specification {

protected static final GRADLE_7_2 = GradleVersion.version('7.2')
protected static final GRADLE_7_3 = GradleVersion.version('7.3.3')
protected static final GRADLE_7_4 = GradleVersion.version('7.4.2')
protected static final GRADLE_7_5 = GradleVersion.version('7.5.1')
protected static final GRADLE_7_6 = GradleVersion.version('7.6.2')
protected static final GRADLE_8_0 = GradleVersion.version('8.0.2')
protected static final GRADLE_8_1 = GradleVersion.version('8.1.1')
protected static final GRADLE_8_2 = GradleVersion.version('8.2.1')
protected static final GRADLE_8_3 = GradleVersion.version('8.3')
protected static final GRADLE_8_4 = GradleVersion.version('8.4-rc-2')

// For faster CI times, we only test min + max. Testing all would be preferable, but we don't have till the heat death
// of the universe to wait.
protected static final SUPPORTED_GRADLE_VERSIONS = [
GRADLE_7_2,
// GRADLE_7_3,
// GRADLE_7_4,
// GRADLE_7_5,
// GRADLE_7_6,
// GRADLE_8_0,
// GRADLE_8_1,
GRADLE_8_2,
GradleVersions.minGradleVersion,
GRADLE_8_3,
]

protected GradleProject gradleProject = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ import static com.google.common.truth.Truth.assertThat
// https://github.com/autonomousapps/dependency-analysis-android-gradle-plugin/issues/565
final class IncludedBuildSpec extends AbstractJvmSpec {

private static INCLUDED_BUILD_SUPPORT_GRADLE_VERSIONS = [
GRADLE_7_3, GRADLE_7_4, SUPPORTED_GRADLE_VERSIONS.last()
]

def "doesn't crash in presence of an included build (#gradleVersion)"() {
given:
def project = new IncludedBuildProject()
Expand All @@ -27,10 +23,11 @@ final class IncludedBuildSpec extends AbstractJvmSpec {
assertThat(project.actualBuildHealth()).containsExactlyElementsIn(project.expectedBuildHealth('second-build'))
and: 'the build health of the second build is as expected'
assertThat(project.actualBuildHealthOfSecondBuild()).containsExactlyElementsIn(project.expectedBuildHealthOfIncludedBuild(':'))
assertThat(project.actualBuildHealthOfSecondBuild())
.containsExactlyElementsIn(project.expectedBuildHealthOfIncludedBuild(':'))
where: 'This new feature only works for Gradle 7.3+'
gradleVersion << INCLUDED_BUILD_SUPPORT_GRADLE_VERSIONS
gradleVersion << gradleVersions()
}
def "result of analysis does not change if root build of included build tree changes for projects without subprojects"() {
Expand All @@ -46,10 +43,11 @@ final class IncludedBuildSpec extends AbstractJvmSpec {
assertThat(project.actualBuildHealth()).containsExactlyElementsIn(project.expectedBuildHealth(':'))
then: 'the build health of the second build is the same as when running that build as included build'
assertThat(project.actualBuildHealthOfSecondBuild()).containsExactlyElementsIn(project.expectedBuildHealthOfIncludedBuild('the-project'))
assertThat(project.actualBuildHealthOfSecondBuild())
.containsExactlyElementsIn(project.expectedBuildHealthOfIncludedBuild('the-project'))
where:
gradleVersion << INCLUDED_BUILD_SUPPORT_GRADLE_VERSIONS
gradleVersion << gradleVersions()
}
def "does not confuse identities of included subprojects depended on by another build"() {
Expand All @@ -64,7 +62,7 @@ final class IncludedBuildSpec extends AbstractJvmSpec {
assertThat(project.actualBuildHealth()).containsExactlyElementsIn(project.expectedBuildHealth)
where:
gradleVersion << INCLUDED_BUILD_SUPPORT_GRADLE_VERSIONS
gradleVersion << gradleVersions()
}
def "does not confuse identities of included subprojects depending on each other by GA dependency notation"() {
Expand All @@ -76,10 +74,11 @@ final class IncludedBuildSpec extends AbstractJvmSpec {
build(gradleVersion, gradleProject.rootDir, ':second-build:buildHealth')
then: 'and there is no advice'
assertThat(project.actualIncludedBuildHealth()).containsExactlyElementsIn(project.expectedIncludedBuildHealth('second-build'))
assertThat(project.actualIncludedBuildHealth())
.containsExactlyElementsIn(project.expectedIncludedBuildHealth('second-build'))
where:
gradleVersion << INCLUDED_BUILD_SUPPORT_GRADLE_VERSIONS
gradleVersion << gradleVersions()
}
def "does not confuse identities of included subprojects depending on each other by project dependency notation"() {
Expand All @@ -91,10 +90,11 @@ final class IncludedBuildSpec extends AbstractJvmSpec {
build(gradleVersion, gradleProject.rootDir, ':second-build:buildHealth')
then:
assertThat(project.actualIncludedBuildHealth()).containsExactlyElementsIn(project.expectedIncludedBuildHealth('second-build'))
assertThat(project.actualIncludedBuildHealth())
.containsExactlyElementsIn(project.expectedIncludedBuildHealth('second-build'))
where:
gradleVersion << INCLUDED_BUILD_SUPPORT_GRADLE_VERSIONS
gradleVersion << gradleVersions()
}
def "result of analysis does not change if root build of included build tree changes for projects with subprojects"() {
Expand All @@ -109,7 +109,7 @@ final class IncludedBuildSpec extends AbstractJvmSpec {
assertThat(project.actualIncludedBuildHealth()).containsExactlyElementsIn(project.expectedIncludedBuildHealth(':'))
where:
gradleVersion << INCLUDED_BUILD_SUPPORT_GRADLE_VERSIONS
gradleVersion << gradleVersions()
}
def "up-to-date check is correct when switching root builds"() {
Expand All @@ -121,7 +121,8 @@ final class IncludedBuildSpec extends AbstractJvmSpec {
build(gradleVersion, gradleProject.rootDir, ':second-build:buildHealth')
then:
assertThat(project.actualIncludedBuildHealth()).containsExactlyElementsIn(project.expectedIncludedBuildHealth('second-build'))
assertThat(project.actualIncludedBuildHealth())
.containsExactlyElementsIn(project.expectedIncludedBuildHealth('second-build'))
when: 'The second build is the root - the "buildPath" attribute of ProjectCoordinates changes'
build(gradleVersion, new File(gradleProject.rootDir, 'second-build'), ':buildHealth')
Expand All @@ -130,7 +131,7 @@ final class IncludedBuildSpec extends AbstractJvmSpec {
assertThat(project.actualIncludedBuildHealth()).containsExactlyElementsIn(project.expectedIncludedBuildHealth(':'))
where:
gradleVersion << INCLUDED_BUILD_SUPPORT_GRADLE_VERSIONS
gradleVersion << gradleVersions()
}
def "can handle annotation processors from cache in subsequent builds"() {
Expand All @@ -157,6 +158,6 @@ final class IncludedBuildSpec extends AbstractJvmSpec {
assertThat(project.actualBuildHealth()).containsExactlyElementsIn(project.expectedBuildHealth)
where:
gradleVersion << INCLUDED_BUILD_SUPPORT_GRADLE_VERSIONS
gradleVersion << gradleVersions()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package com.autonomousapps

import com.autonomousapps.Flags.compatibility
import com.autonomousapps.internal.GradleVersions
import com.autonomousapps.internal.android.AgpVersion
import com.autonomousapps.subplugin.ProjectPlugin
import com.autonomousapps.subplugin.RootPlugin
Expand All @@ -25,11 +26,19 @@ class DependencyAnalysisPlugin : Plugin<Project> {
/** If this is the root project, apply configuration necessary for the root. */
private fun Project.applyForRoot() {
if (this == rootProject) {
checkGradleVersion()
checkAgpVersion()
RootPlugin(this).apply()
}
}

private fun checkGradleVersion() {
check(GradleVersions.isAtLeastMinimum) {
"Dependency Analysis Gradle Plugin requires Gradle ${GradleVersions.minGradleVersion.version} or higher. " +
"Was ${GradleVersions.current.version}."
}
}

/** Warn Android users if they're using an untested version of AGP. */
private fun Project.checkAgpVersion() {
val current = try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ import org.gradle.util.GradleVersion

internal object GradleVersions {

private val current: GradleVersion = GradleVersion.current()
/** Minimum supported version of Gradle. */
@JvmField val minGradleVersion: GradleVersion = GradleVersion.version("7.5")

val current: GradleVersion = GradleVersion.current()

private val gradle74: GradleVersion = GradleVersion.version("7.4")
private val gradle82: GradleVersion = GradleVersion.version("8.2")

val isAtLeastMinimum: Boolean = current >= minGradleVersion
val isAtLeastGradle74: Boolean = current >= gradle74
val isAtLeastGradle82: Boolean = current >= gradle82
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.autonomousapps.internal.graph

import com.autonomousapps.internal.isJavaPlatform
import com.autonomousapps.internal.utils.rootCoordinates
import com.autonomousapps.internal.utils.toCoordinates
import com.autonomousapps.model.Coordinates
import com.autonomousapps.model.DependencyGraphView
import com.google.common.graph.Graph
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult

/**
* Walks the resolved dependency graph to create a dependency graph rooted on the current project in a configuration
* cache-compatible way.
*
* Ultimately a replacement for [GraphViewBuilder].
*/
@Suppress("UnstableApiUsage") // Guava Graph
internal class CCGraphViewBuilder(
root: ResolvedComponentResult,
fileCoordinates: Set<Coordinates>,
) {

val graph: Graph<Coordinates>

private val graphBuilder = DependencyGraphView.newGraphBuilder()

private val visited = mutableSetOf<Coordinates>()

init {
val rootId = root.rootCoordinates()

walkFileDeps(fileCoordinates, rootId)
walk(root, rootId)

graph = graphBuilder.build()
}

private fun walkFileDeps(fileCoordinates: Set<Coordinates>, rootId: Coordinates) {
graphBuilder.addNode(rootId)

// the only way to get flat jar file dependencies
fileCoordinates.forEach { id ->
graphBuilder.putEdge(rootId, id)
}
}

private fun walk(root: ResolvedComponentResult, rootId: Coordinates) {
root.dependencies
.filterIsInstance<ResolvedDependencyResult>()
// AGP adds all runtime dependencies as constraints to the compile classpath, and these show
// up in the resolution result. Filter them out.
.filterNot { it.isConstraint }
// For similar reasons as above
.filterNot { it.isJavaPlatform() }
// Sometimes there is a self-dependency?
.filterNot { it.selected == root }
.forEach { dependencyResult ->
// Might be from an included build, in which case the coordinates reflect the _requested_ dependency instead of
// the _resolved_ dependency.
val depId = dependencyResult.toCoordinates()

// add an edge
graphBuilder.putEdge(rootId, depId)

if (!visited.contains(depId)) {
visited.add(depId)
// recursively walk the graph in a depth-first pattern
walk(dependencyResult.selected, depId)
}
}
}
}
22 changes: 18 additions & 4 deletions src/main/kotlin/com/autonomousapps/internal/utils/gradleStrings.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.autonomousapps.internal.utils

import com.autonomousapps.internal.GradleVersions
import com.autonomousapps.model.*
import org.gradle.api.GradleException
import org.gradle.api.artifacts.*
Expand All @@ -11,6 +12,7 @@ import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.artifacts.result.ResolvedVariantResult
import org.gradle.api.capabilities.Capability
Expand Down Expand Up @@ -84,15 +86,27 @@ private fun ComponentIdentifier.wrapInIncludedBuildCoordinates(variant: Resolved
}

/** Returns the [coordinates][Coordinates] of the root of [this][Configuration]. */
internal fun Configuration.rootCoordinates(): Coordinates = incoming.resolutionResult.root.id
// For the root, the 'GradleVariantIdentification' is always empty as there is only one root (which we match later)
.toCoordinates(GradleVariantIdentification(setOf("ROOT"), emptyMap()))
internal fun Configuration.rootCoordinates(): Coordinates = incoming.resolutionResult.root.rootCoordinates()

/** Returns the [coordinates][Coordinates] of the root of [this][ResolvedComponentResult]. */
internal fun ResolvedComponentResult.rootCoordinates(): Coordinates {
return id
// For the root, the 'GradleVariantIdentification' is always empty as there is only one root (which we match later)
.toCoordinates(GradleVariantIdentification(setOf("ROOT"), emptyMap()))
}

/** Converts this [ComponentIdentifier] to group-artifact-version (GAV) coordinates in a tuple of (GA, V?). */
private fun ComponentIdentifier.toCoordinates(gradleVariantIdentification: GradleVariantIdentification): Coordinates {
val identifier = toIdentifier()
return when (this) {
is ProjectComponentIdentifier -> ProjectCoordinates(identifier, gradleVariantIdentification, build.name)
is ProjectComponentIdentifier -> {
ProjectCoordinates(identifier, gradleVariantIdentification, build.name)
// FIXME use 'buildState.buildIdentifier.buildPath' with Gradle 8.2+?
// this breaks IncludedBuildSpec for later versions of Gradle:
// val buildPath = if (GradleVersions.isAtLeastGradle82) build.buildPath else build.name
// ProjectCoordinates(identifier, gradleVariantIdentification, buildPath)
}

is ModuleComponentIdentifier -> {
resolvedVersion()?.let { resolvedVersion ->
ModuleCoordinates(identifier, resolvedVersion, gradleVariantIdentification)
Expand Down
Loading

0 comments on commit ea94054

Please sign in to comment.