Skip to content

Commit

Permalink
feat: migrate to new, non-deprecated AGP APIs. Min AGP version now 8.0.
Browse files Browse the repository at this point in the history
  • Loading branch information
autonomousapps committed Jan 8, 2024
1 parent 9526a5e commit 58d9581
Show file tree
Hide file tree
Showing 19 changed files with 374 additions and 284 deletions.
7 changes: 5 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ moshix = "0.19.0"
okio = "2.10.0"
retrofit = "2.9.0"

agp = "7.4.2"
agp-common = "30.4.2"
agp = "8.0.2"
#agp = "8.1.4
#agp = "8.2.1"
#agp = "8.3.0-beta01" # Provides `Sources.manifests`
agp-common = "31.2.0"

[libraries]
agp = { module = "com.android.tools.build:gradle", version.ref = "agp" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,26 @@ import com.autonomousapps.kit.GradleProject
import com.autonomousapps.kit.gradle.BuildscriptBlock
import com.autonomousapps.kit.gradle.GradleProperties
import com.autonomousapps.kit.gradle.dependencies.Plugins
import com.autonomousapps.utils.DebugAware

@SuppressWarnings('GrMethodMayBeStatic')
abstract class AbstractProject extends AbstractGradleProject {

protected static final PRINT_ADVICE = "dependency.analysis.print.build.health=true"
protected static final String PRINT_ADVICE = "dependency.analysis.print.build.health=true"

@Override
protected GradleProject.Builder newGradleProjectBuilder(GradleProject.DslKind dslKind = GradleProject.DslKind.GROOVY) {
protected GradleProject.Builder newGradleProjectBuilder(
GradleProject.DslKind dslKind = GradleProject.DslKind.GROOVY
) {
def additionalProperties = GradleProperties.of(PRINT_ADVICE)
// There is a Gradle bug that makes tests break when the test uses CC and we're also debugging
if (!DebugAware.debug) {
additionalProperties += GradleProperties.enableConfigurationCache()
}

return super.newGradleProjectBuilder(dslKind)
.withRootProject { r ->
r.gradleProperties += GradleProperties.enableConfigurationCache() + PRINT_ADVICE
r.gradleProperties += additionalProperties
r.withBuildScript { bs ->
bs.plugins(Plugins.dependencyAnalysis, Plugins.kotlinNoApply)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,36 @@ import com.autonomousapps.fixtures.ProjectDirProvider
import com.autonomousapps.internal.android.AgpVersion
import org.gradle.util.GradleVersion

/**
* @see <a href="https://maven.google.com/web/m_index.html?q=com.android.tools.build#com.android.tools.build:gradle">AGP artifacts</a>
*/
abstract class AbstractAndroidSpec extends AbstractFunctionalSpec {

protected ProjectDirProvider androidProject = null

protected static final AGP_7_3 = AgpVersion.version('7.3.1')
protected static final AGP_7_4 = AgpVersion.version('7.4.2')
protected static final AGP_8_0 = AgpVersion.version('8.0.2')
protected static final AGP_8_1 = AgpVersion.version('8.1.0')
protected static final AGP_8_2 = AgpVersion.version('8.2.0-alpha16')
protected static final AGP_8_1 = AgpVersion.version('8.1.4')
protected static final AGP_8_2 = AgpVersion.version('8.2.0')
protected static final AGP_8_3 = AgpVersion.version('8.3.0-beta01')
protected static final AGP_8_4 = AgpVersion.version('8.4.0-alpha01')

protected static final AGP_LATEST = AGP_8_2
protected static final AGP_LATEST = AGP_8_4

/**
* {@code AGP_7_4} represents the minimum stable _tested_ version. {@code AGP_8_1} represents the maximum stable
* TODO(tsr): this doc is perpetually out of date.
*
* {@code AGP_8_0} represents the minimum stable _tested_ version. {@code AGP_8_1} represents the maximum stable
* _tested_ version. We also test against the latest alpha, {@code AGP_8_2} at time of writing. DAGP may work with
* other versions of AGP, but they aren't tested, primarily for CI performance reasons.
*
* @see <a href="https://maven.google.com/web/index.html?q=build#com.android.tools.build:gradle">AGP releases</a>
*/
protected static final SUPPORTED_AGP_VERSIONS = [
AGP_7_4,
// AGP_8_0,
AGP_8_0,
AGP_8_1,
AGP_8_2,
AGP_8_3,
AGP_8_4,
]

protected static List<AgpVersion> agpVersions(AgpVersion minAgpVersion = AgpVersion.AGP_MIN) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ final class DuplicateDependencyVersionsSpec extends AbstractAndroidSpec {
.contains(project.expectedOutput)
where:
[gradleVersion, agpVersion] << multivariableDataPipe([GRADLE_7_5], [AGP_7_3.version])
[gradleVersion, agpVersion] << multivariableDataPipe([GRADLE_8_0], [AGP_8_0.version])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import static com.google.common.truth.Truth.assertAbout
@SuppressWarnings("GroovyAssignabilityCheck")
final class IgnoredVariantSpec extends AbstractAndroidSpec {

def "plugin ignore android variants (#gradleVersion AGP #agpVersion ignored debug)"() {
def "can ignore debug variant (#gradleVersion AGP #agpVersion)"() {
given:
def project = new DebugVariantIgnoredProject(agpVersion)
gradleProject = project.gradleProject
Expand All @@ -30,7 +30,7 @@ final class IgnoredVariantSpec extends AbstractAndroidSpec {
[gradleVersion, agpVersion] << gradleAgpMatrix()
}
def "plugin ignore android variants (#gradleVersion AGP #agpVersion ignored release)"() {
def "can ignore release variant (#gradleVersion AGP #agpVersion)"() {
given:
def project = new ReleaseVariantIgnoredProject(agpVersion)
gradleProject = project.gradleProject
Expand All @@ -47,7 +47,7 @@ final class IgnoredVariantSpec extends AbstractAndroidSpec {
[gradleVersion, agpVersion] << gradleAgpMatrix()
}
def "plugin ignore android variants (#gradleVersion AGP #agpVersion ignored all variants)"() {
def "can ignore all (debug and release) variants (#gradleVersion AGP #agpVersion)"() {
given:
def project = new AllVariantsIgnoredProject(agpVersion)
gradleProject = project.gradleProject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.autonomousapps.kit.gradle.GradleProperties
import com.autonomousapps.kit.gradle.Plugin
import com.autonomousapps.kit.gradle.dependencies.Plugins
import com.autonomousapps.model.ProjectAdvice
import com.autonomousapps.utils.DebugAware

import static com.autonomousapps.AdviceHelper.actualProjectAdvice
import static com.autonomousapps.kit.gradle.dependencies.Dependencies.*
Expand All @@ -37,9 +38,15 @@ abstract class AbstractVariantProject extends AbstractAndroidProject {
}

private GradleProject build() {
def properties = projectGradleProperties
if (!DebugAware.debug) {
// There is a Gradle bug that makes tests break when the test uses CC and we're also debugging
properties += GradleProperties.enableConfigurationCache()
}

return newAndroidGradleProjectBuilder(agpVersion)
.withRootProject { root ->
root.gradleProperties = projectGradleProperties + GradleProperties.enableConfigurationCache()
root.gradleProperties = properties
}
.withAndroidSubproject('app') { a ->
a.sources = sources
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import com.autonomousapps.model.ProjectAdvice
import static com.autonomousapps.AdviceHelper.*
import static com.autonomousapps.kit.gradle.Dependency.project
import static com.autonomousapps.kit.gradle.dependencies.Dependencies.appcompat
import static com.autonomousapps.kit.gradle.dependencies.Dependencies.kotlinStdLib

final class DataBindingUsagesExclusionsProject extends AbstractAndroidProject {

Expand Down Expand Up @@ -59,7 +58,6 @@ final class DataBindingUsagesExclusionsProject extends AbstractAndroidProject {
lib.withBuildScript { bs ->
bs.plugins = [Plugins.androidLib, Plugins.kotlinAndroid, Plugins.kapt]
bs.android = defaultAndroidLibBlock(true, 'com.example.lib')
bs.dependencies = libDependencies
bs.withGroovy("android.buildFeatures.dataBinding true")
}
lib.sources = libSources
Expand Down Expand Up @@ -93,9 +91,8 @@ final class DataBindingUsagesExclusionsProject extends AbstractAndroidProject {
]

private List<Dependency> appDependencies = [
kotlinStdLib("implementation"),
appcompat("implementation"),
project("implementation", ":lib"),
appcompat('implementation'),
project('implementation', ':lib'),
]

private libSources = [
Expand All @@ -114,10 +111,6 @@ final class DataBindingUsagesExclusionsProject extends AbstractAndroidProject {
)
]

private List<Dependency> libDependencies = [
kotlinStdLib('api')
]

private final Set<ProjectAdvice> expectedBuildHealthWithExclusions = [
projectAdviceForDependencies(':app', [
Advice.ofRemove(projectCoordinates(':lib'), 'implementation')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

package com.autonomousapps.internal.analyzer

import com.android.build.gradle.api.BaseVariant
import com.autonomousapps.internal.ArtifactAttributes
import com.autonomousapps.internal.OutputPaths
import com.autonomousapps.internal.android.AndroidGradlePluginFactory
Expand All @@ -16,50 +15,44 @@ import com.autonomousapps.services.InMemoryCache
import com.autonomousapps.tasks.*
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.register
import java.io.File

/**
* Base class for analyzing an Android project (com.android.application or com.android.library only).
*/
/** Base class for analyzing an Android project (`com.android.application` or `com.android.library` only). */
internal abstract class AndroidAnalyzer(
project: Project,
protected val variant: BaseVariant,
protected val variantSourceSet: VariantSourceSet,
protected val variant: AndroidVariant,
protected val androidSources: AndroidSources,
agpVersion: String,
) : AbstractDependencyAnalyzer(project) {

protected val agp = AndroidGradlePluginFactory(project, agpVersion).newAdapter()
private val dataBindingEnabled = agp.isDataBindingEnabled()
private val viewBindingEnabled = agp.isViewBindingEnabled()

final override val flavorName: String = variant.flavorName
final override val variantName: String = variant.name
final override val buildType: String = variant.buildType.name
final override val kind: SourceSetKind = variantSourceSet.variant.kind
final override val variantName: String = variant.variantName
final override val buildType: String = variant.buildType
final override val kind: SourceSetKind = androidSources.variant.kind
final override val variantNameCapitalized: String = variantName.capitalizeSafely()
final override val taskNameSuffix: String = computeTaskNameSuffix()
final override val compileConfigurationName = variantSourceSet.compileClasspathConfigurationName
final override val runtimeConfigurationName = variantSourceSet.runtimeClasspathConfigurationName
final override val compileConfigurationName = androidSources.compileClasspathConfigurationName
final override val runtimeConfigurationName = androidSources.runtimeClasspathConfigurationName
final override val kaptConfigurationName = kaptConfName()
final override val annotationProcessorConfigurationName = "${variantName}AnnotationProcessorClasspath"
final override val testInstrumentationRunner: String? = variant.mergedFlavor.testInstrumentationRunner
final override val kotlinSourceFiles: FileCollection = getKotlinSources()
final override val javaSourceFiles: FileCollection = getJavaSources()
final override val groovySourceFiles: FileCollection = getGroovySources()
final override val scalaSourceFiles: FileCollection = getScalaSources()
final override val testInstrumentationRunner: Provider<String?> = variant.testInstrumentationRunner
final override val kotlinSourceFiles: Provider<Iterable<File>> = androidSources.getKotlinSources()
final override val javaSourceFiles: Provider<Iterable<File>> = androidSources.getJavaSources()
final override val groovySourceFiles: Provider<Iterable<File>> = project.provider { project.files() }
final override val scalaSourceFiles: Provider<Iterable<File>> = project.provider { project.files() }

// TODO(2.0): verify this is the correct attribute.
final override val attributeValueJar = ArtifactAttributes.ANDROID_CLASSES_JAR

final override val isDataBindingEnabled: Boolean = dataBindingEnabled
final override val isViewBindingEnabled: Boolean = viewBindingEnabled
final override val isDataBindingEnabled: Provider<Boolean> = agp.isDataBindingEnabled()
final override val isViewBindingEnabled: Provider<Boolean> = agp.isViewBindingEnabled()

final override val outputPaths = OutputPaths(project, "$variantName${kind.taskNameSuffix}")

Expand Down Expand Up @@ -98,17 +91,17 @@ internal abstract class AndroidAnalyzer(

final override fun registerExplodeXmlSourceTask(): TaskProvider<XmlSourceExploderTask> {
return project.tasks.register<XmlSourceExploderTask>("explodeXmlSource$taskNameSuffix") {
androidLocalRes.setFrom(getAndroidRes())
layouts(variant.sourceSets.flatMap { it.resDirectories })
manifestFiles.setFrom(variant.sourceSets.map { it.manifestFile })
androidLocalRes.setFrom(androidSources.getAndroidRes())
layoutFiles.setFrom(androidSources.getLayoutFiles())
manifestFiles.setFrom(androidSources.getManifestFiles())
namespace.set(agp.namespace())
output.set(outputPaths.androidResToResUsagePath)
}
}

final override fun registerExplodeAssetSourceTask(): TaskProvider<AssetSourceExploderTask> {
return project.tasks.register<AssetSourceExploderTask>("explodeAssetSource$taskNameSuffix") {
androidLocalAssets.setFrom(getAndroidAssets())
androidLocalAssets.setFrom(androidSources.getAndroidAssets())
output.set(outputPaths.androidAssetSourcePath)
}
}
Expand Down Expand Up @@ -146,7 +139,7 @@ internal abstract class AndroidAnalyzer(
}

private fun kaptConfName(): String {
return when (variantSourceSet.variant.kind) {
return when (androidSources.variant.kind) {
SourceSetKind.MAIN -> "kapt$variantNameCapitalized"
SourceSetKind.TEST -> "kaptTest"
SourceSetKind.ANDROID_TEST -> "kaptAndroidTest"
Expand All @@ -156,7 +149,7 @@ internal abstract class AndroidAnalyzer(

// Known to exist in Kotlin 1.3.61.
private fun kotlinCompileTask(): TaskProvider<Task>? {
return when (variantSourceSet.variant.kind) {
return when (androidSources.variant.kind) {
SourceSetKind.MAIN -> project.tasks.namedOrNull("compile${variantNameCapitalized}Kotlin")
SourceSetKind.TEST -> project.tasks.namedOrNull("compile${variantNameCapitalized}UnitTestKotlin")
SourceSetKind.ANDROID_TEST -> project.tasks.namedOrNull("compile${variantNameCapitalized}AndroidTestKotlin")
Expand All @@ -167,7 +160,7 @@ internal abstract class AndroidAnalyzer(
// Known to exist in AGP 3.5, 3.6, and 4.0, albeit with different backing classes (AndroidJavaCompile,
// JavaCompile)
private fun javaCompileTask(): TaskProvider<Task> {
return when (variantSourceSet.variant.kind) {
return when (androidSources.variant.kind) {
SourceSetKind.MAIN -> project.tasks.named("compile${variantNameCapitalized}JavaWithJavac")
SourceSetKind.TEST -> project.tasks.named("compile${variantNameCapitalized}UnitTestJavaWithJavac")
SourceSetKind.ANDROID_TEST -> project.tasks.named("compile${variantNameCapitalized}AndroidTestJavaWithJavac")
Expand All @@ -176,76 +169,37 @@ internal abstract class AndroidAnalyzer(
}

private fun computeTaskNameSuffix(): String {
return if (variantSourceSet.variant.kind == SourceSetKind.MAIN) {
return if (androidSources.variant.kind == SourceSetKind.MAIN) {
// "flavorDebug" -> "FlavorDebug"
variantName.capitalizeSafely()
} else {
// "flavorDebug" + "Test" -> "FlavorDebugTest"
variantName.capitalizeSafely() + variantSourceSet.variant.kind.taskNameSuffix
variantName.capitalizeSafely() + androidSources.variant.kind.taskNameSuffix
}
}

private fun getGroovySources(): FileCollection = getSourceDirectories().matching(Language.filterOf(Language.GROOVY))
private fun getJavaSources(): FileCollection = getSourceDirectories().matching(Language.filterOf(Language.JAVA))
private fun getKotlinSources(): FileCollection = getSourceDirectories().matching(Language.filterOf(Language.KOTLIN))
private fun getScalaSources(): FileCollection = getSourceDirectories().matching(Language.filterOf(Language.SCALA))

private fun getSourceDirectories(): FileTree {
// Java dirs regardless of whether they exist
val javaDirs = variantSourceSet.androidSourceSets.flatMap { it.javaDirectories }

// Kotlin dirs, only if they exist. If we filtered the above for existence, and there was no
// Java dir, then this would also be empty.
val kotlinDirs = javaDirs
.map { it.path }
.map { it.removeSuffix("java") + "kotlin" }
.map { File(it) }
.filter { it.exists() }

// Now finally filter Java dirs for existence
return project.files(javaDirs.filter { it.exists() } + kotlinDirs).asFileTree
}

private fun getAndroidRes(): FileTree {
val resDirs = variant.sourceSets.flatMap {
it.resDirectories
}.filter { it.exists() }

return project.files(resDirs).asFileTree.matching {
include("**/*.xml")
}
}

private fun getAndroidAssets(): FileCollection {
val assetsDirs = variant.sourceSets.flatMap {
it.assetsDirectories
}.filter { it.exists() }

return project.files(assetsDirs).asFileTree
}
}

internal class AndroidAppAnalyzer(
project: Project,
variant: BaseVariant,
variant: AndroidVariant,
agpVersion: String,
variantSourceSet: VariantSourceSet,
androidSources: AndroidSources,
) : AndroidAnalyzer(
project = project,
variant = variant,
variantSourceSet = variantSourceSet,
androidSources = androidSources,
agpVersion = agpVersion
)

internal class AndroidLibAnalyzer(
project: Project,
variant: BaseVariant,
variant: AndroidVariant,
agpVersion: String,
variantSourceSet: VariantSourceSet,
androidSources: AndroidSources,
) : AndroidAnalyzer(
project = project,
variant = variant,
variantSourceSet = variantSourceSet,
androidSources = androidSources,
agpVersion = agpVersion
) {

Expand Down
Loading

0 comments on commit 58d9581

Please sign in to comment.