Skip to content

Commit

Permalink
GenerateBuildHealthTask is compatible with the configuration cache. (#…
Browse files Browse the repository at this point in the history
…1066)

* GenerateBuildHealthTask is compatible with the configuration cache.

* ComputeDuplicateDependenciesTask is compatible with the configuration cache.

* Cleanup

* Cleanup test.
  • Loading branch information
autonomousapps authored Dec 11, 2023
1 parent 46c4927 commit b529902
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.autonomousapps.android

import com.autonomousapps.android.projects.AndroidAssetsProject
import com.autonomousapps.internal.android.AgpVersion
import org.gradle.util.GradleVersion

import static com.autonomousapps.advice.truth.BuildHealthSubject.buildHealth
Expand Down Expand Up @@ -31,10 +32,6 @@ final class ConfigurationCacheSpec extends AbstractAndroidSpec {
and: 'generateBuildHealth succeeded'
assertAbout(buildTasks()).that(result.task(':generateBuildHealth')).succeeded()
and: 'This plugin is not yet compatible with the configuration cache'
assertThat(result.output).contains('0 problems were found storing the configuration cache.')
assertThat(result.output).contains('Configuration cache entry discarded.')
when: 'We build again'
result = build(
gradleVersion as GradleVersion,
Expand All @@ -50,11 +47,13 @@ final class ConfigurationCacheSpec extends AbstractAndroidSpec {
and: 'generateBuildHealth was up-to-date'
assertAbout(buildTasks()).that(result.task(':generateBuildHealth')).upToDate()
and: 'This plugin is not yet compatible with the configuration cache'
assertThat(result.output).contains('0 problems were found storing the configuration cache.')
assertThat(result.output).contains('Configuration cache entry discarded.')
and: 'This plugin is compatible with the configuration cache'
if (AgpVersion.version(agpVersion as String) > AgpVersion.version('8.0')) {
// AGP < 8 has a bug that prevents use of CC
assertThat(result.output).contains('Configuration cache entry reused.')
}
where: 'Min support for this is Gradle 7.5'
[gradleVersion, agpVersion] << gradleAgpMatrix([GRADLE_7_5])
[gradleVersion, agpVersion] << gradleAgpMatrix()
}
}
2 changes: 2 additions & 0 deletions src/main/kotlin/com/autonomousapps/internal/GradleVersions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ internal object GradleVersions {
private val gradle74: GradleVersion = GradleVersion.version("7.4")
private val gradle75: GradleVersion = GradleVersion.version("7.5")
private val gradle82: GradleVersion = GradleVersion.version("8.2")
private val gradle84: GradleVersion = GradleVersion.version("8.4")

/** Minimum supported version of Gradle. */
@JvmField val minGradleVersion: GradleVersion = gradle74
Expand All @@ -18,4 +19,5 @@ internal object GradleVersions {
val isAtLeastGradle74: Boolean = current >= gradle74
val isAtLeastGradle75: Boolean = current >= gradle75
val isAtLeastGradle82: Boolean = current >= gradle82
val isAtLeastGradle84: Boolean = current >= gradle84
}
10 changes: 10 additions & 0 deletions src/main/kotlin/com/autonomousapps/internal/artifacts/Attr.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.autonomousapps.internal.artifacts

import org.gradle.api.Named
import org.gradle.api.attributes.Attribute

/** Teach Gradle how custom configurations relate to each other, and the artifacts they provide and consume. */
internal class Attr<T : Named>(
val attribute: Attribute<T>,
val attributeName: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.autonomousapps.internal.artifacts

import org.gradle.api.Named
import org.gradle.api.attributes.Attribute

internal interface DagpArtifacts : Named {
companion object {
@JvmField val DAGP_ARTIFACTS_ATTRIBUTE: Attribute<DagpArtifacts> = Attribute.of(
"dagp.internal.artifacts", DagpArtifacts::class.java
)
}

enum class Kind(
val declarableName: String,
val artifactName: String,
) {
PROJECT_HEALTH("projectHealth", "project-health"),
RESOLVED_DEPS("resolvedDeps", "resolved-deps"),
}
}
71 changes: 71 additions & 0 deletions src/main/kotlin/com/autonomousapps/internal/artifacts/Publisher.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.autonomousapps.internal.artifacts

import org.gradle.api.Named
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider

/**
* Used for publishing custom artifacts from a subproject to an aggregating project (often the "root" project). Only
* for inter-project publishing (e.g., _not_ for publishing to Artifactory). See also [Resolver].
*
* Represents a set of tightly coupled [Configuration]s:
* * A "dependency scope" configuration ([Resolver.declarable]).
* * A "resolvable" configuration ([Resolver.internal]).
* * A "consumable" configuration ([external]).
*
* Dependencies are _declared_ on [Resolver.declarable] in the aggregating project. Custom artifacts (e.g., not jars),
* generated by tasks, are published via [publish], which should be used on dependency (artifact-producing) projects.
*
* Gradle uses [attributes][Attr] to wire the consumer project's [Resolver.internal] (resolvable) configuration to the
* producer project's [external] (consumable) configuration, which is itself configured via [publish].
*
* @see <a href="https://docs.gradle.org/current/userguide/cross_project_publications.html#sec:variant-aware-sharing">Variant-aware sharing of artifacts between projects</a>
* @see <a href="https://dev.to/autonomousapps/configuration-roles-and-the-blogging-industrial-complex-21mn">Gradle configuration roles</a>
*/
internal class Publisher<T : Named>(
project: Project,
// TODO: ultimately this should not need to be exposed
val declarableName: String,
attr: Attr<T>,
) {

companion object {
/** Convenience function for creating a [Publisher] for inter-project publishing of [DagpArtifacts]. */
fun interProjectPublisher(
project: Project,
artifact: DagpArtifacts.Kind,
): Publisher<DagpArtifacts> {
return Publisher(
project,
artifact.declarableName,
Attr(DagpArtifacts.DAGP_ARTIFACTS_ATTRIBUTE, artifact.artifactName)
)
}
}

// Following the naming pattern established by the Java Library plugin.
// See https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_configurations_graph
private val externalName = "${declarableName}Elements"

/** The plugin will expose dependencies on this configuration, which extends from the declared dependencies. */
private val external: NamedDomainObjectProvider<out Configuration> =
project.consumableConfiguration(externalName) {
// This attribute is identical to what is set on the internal/resolvable configuration
attributes {
attribute(
attr.attribute,
project.objects.named(attr.attribute.type, attr.attributeName)
)
}
}

/** Teach Gradle which thing produces the artifact associated with the external/consumable configuration. */
fun publish(output: Provider<RegularFile>) {
external.configure {
outgoing.artifact(output)
}
}
}
69 changes: 69 additions & 0 deletions src/main/kotlin/com/autonomousapps/internal/artifacts/Resolver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.autonomousapps.internal.artifacts

import org.gradle.api.Named
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration

/**
* Used for resolving custom artifacts in an aggregating project (often the "root" project), from producing projects
* (often all or a subset of the subprojects ina build). Only for inter-project publishing and resolving (e.g., _not_
* for publishing to Artifactory). See also [Publisher].
*
* Represents a set of tightly coupled [Configuration]s:
* * A "dependency scope" configuration ([declarable]).
* * A "resolvable" configuration ([internal]).
* * A "consumable" configuration ([Publisher.external]).
*
* Dependencies are _declared_ on [declarable], and resolved within a project via [internal]. Custom artifacts (e.g.,
* not jars), generated by tasks, are published via [Publisher.publish], which should be used on dependency
* (artifact-producing) projects.
*
* Gradle uses [attributes][Attr] to wire the consumer project's [internal] (resolvable) configuration to the producer
* project's [Publisher.external] (consumable) configuration, which is itself configured via [Publisher.publish].
*
* @see <a href="https://docs.gradle.org/current/userguide/cross_project_publications.html#sec:variant-aware-sharing">Variant-aware sharing of artifacts between projects</a>
* @see <a href="https://dev.to/autonomousapps/configuration-roles-and-the-blogging-industrial-complex-21mn">Gradle configuration roles</a>
*/
internal class Resolver<T : Named>(
project: Project,
declarableName: String,
attr: Attr<T>,
) {

internal companion object {
/** Convenience function for creating a [Resolver] for inter-project resolving of [DagpArtifacts]. */
fun interProjectResolver(
project: Project,
artifact: DagpArtifacts.Kind,
): Resolver<DagpArtifacts> {
return Resolver(
project,
artifact.declarableName,
Attr(DagpArtifacts.DAGP_ARTIFACTS_ATTRIBUTE, artifact.artifactName)
)
}
}

// Following the naming pattern established by the Java Library plugin. See
// https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_configurations_graph
private val internalName = "${declarableName}Classpath"

/** Dependencies are declared on this configuration */
val declarable: Configuration = project.dependencyScopeConfiguration(declarableName).get()

/**
* The plugin will resolve dependencies against this internal configuration, which extends from
* the declared dependencies.
*/
val internal: NamedDomainObjectProvider<out Configuration> =
project.resolvableConfiguration(internalName, declarable) {
// This attribute is identical to what is set on the external/consumable configuration
attributes {
attribute(
attr.attribute,
project.objects.named(attr.attribute.type, attr.attributeName)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
@file:Suppress("UnstableApiUsage")

package com.autonomousapps.internal.artifacts

import com.autonomousapps.internal.GradleVersions
import org.gradle.api.Action
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration

/**
* Creates a "dependency scope"-type configuration, which we can think of as a _bucket_ for declaring dependencies. See
* also [resolvableConfiguration] and [consumableConfiguration].
*/
internal fun Project.dependencyScopeConfiguration(configurationName: String): NamedDomainObjectProvider<out Configuration> {
return if (GradleVersions.isAtLeastGradle84) {
configurations.dependencyScope(configurationName)
} else {
configurations.register(configurationName) {
isCanBeResolved = false
isCanBeConsumed = true
isVisible = false
}
}
}

/**
* Creates a "resolvable"-type configuration, which can be thought of as the method by which projects "resolve" the
* dependencies that they declare on the [dependencyScopeConfiguration] configurations.
*/
internal fun Project.resolvableConfiguration(
configurationName: String,
dependencyScopeConfiguration: Configuration,
configureAction: Action<in Configuration>,
): NamedDomainObjectProvider<out Configuration> {
return if (GradleVersions.isAtLeastGradle84) {
configurations.resolvable(configurationName) {
extendsFrom(dependencyScopeConfiguration)
configureAction.execute(this)
}
} else {
configurations.register(configurationName) {
isCanBeResolved = true
isCanBeConsumed = false
isVisible = false

extendsFrom(dependencyScopeConfiguration)

configureAction.execute(this)
}
}
}

/**
* Creates a "consumable"-type configuration, which can be thought of as the method by which projects export artifacts
* to consumer projects, which have declared a dependency on _this_ project using the [dependencyScopeConfiguration]
* configuration (which may be `null` for this project).
*/
internal fun Project.consumableConfiguration(
configurationName: String,
dependencyScopeConfiguration: Configuration? = null,
configureAction: Action<in Configuration>,
): NamedDomainObjectProvider<out Configuration> {
return if (GradleVersions.isAtLeastGradle84) {
configurations.consumable(configurationName) {
dependencyScopeConfiguration?.let { extendsFrom(it) }
configureAction.execute(this)
}
} else {
configurations.register(configurationName) {
isCanBeConsumed = true
isCanBeResolved = false
isVisible = false

dependencyScopeConfiguration?.let { extendsFrom(it) }

configureAction.execute(this)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ import org.gradle.api.artifacts.Configuration

internal object Configurations {

internal const val CONF_ADVICE_ALL_CONSUMER = "adviceAllConsumer"
internal const val CONF_ADVICE_ALL_PRODUCER = "adviceAllProducer"
internal const val CONF_RESOLVED_DEPS_CONSUMER = "resolvedDepsConsumer"
internal const val CONF_RESOLVED_DEPS_PRODUCER = "resolvedDepsProducer"

private val COMPILE_ONLY_SUFFIXES = listOf("compileOnly", "compileOnlyApi", "providedCompile")
private val MAIN_SUFFIXES = COMPILE_ONLY_SUFFIXES + listOf("api", "implementation", "runtimeOnly")

Expand Down
Loading

0 comments on commit b529902

Please sign in to comment.