Skip to content

Commit

Permalink
Implement lazy variant API and add androidTest base variant (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
arunkumar9t2 committed Jul 19, 2023
1 parent 5197182 commit bd1f318
Show file tree
Hide file tree
Showing 23 changed files with 673 additions and 1,099 deletions.
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 0 additions & 6 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,7 @@ maven_install(
"androidx.test.ext:junit:1.1.5",
"androidx.test:monitor:1.6.1",
"junit:junit:4.13.2",
"org.jacoco:org.jacoco.ant:0.8.7",
"org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.8.10",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31",
"org.jetbrains.kotlin:kotlin-stdlib:1.7.10",
],
excluded_artifacts = ["androidx.test.espresso:espresso-contrib"],
fail_on_missing_checksum = False,
Expand Down Expand Up @@ -141,9 +138,6 @@ maven_install(
"androidx.test:monitor",
"com.android.support:cardview-v7",
"junit:junit",
"org.jacoco:org.jacoco.ant",
"org.jetbrains.kotlin:kotlin-annotation-processing-gradle",
"org.jetbrains.kotlin:kotlin-stdlib",
"org.jetbrains.kotlin:kotlin-stdlib-jdk8",
],
maven_install_json = "//:maven_install.json",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ import com.grab.grazel.gradle.dependencies.DependenciesGraphsBuilder
import com.grab.grazel.gradle.dependencies.DependenciesModule
import com.grab.grazel.gradle.dependencies.DependencyGraphs
import com.grab.grazel.gradle.dependencies.MavenInstallArtifactsCalculator
import com.grab.grazel.gradle.variant.AndroidVariantDataSource
import com.grab.grazel.gradle.variant.VariantBuilder
import com.grab.grazel.gradle.variant.VariantMatcher
import com.grab.grazel.gradle.variant.VariantModule
import com.grab.grazel.hybrid.HybridBuildExecutor
import com.grab.grazel.hybrid.HybridBuildModule
import com.grab.grazel.migrate.MigrationModule
import com.grab.grazel.migrate.android.AndroidInstrumentationBinaryDataExtractor
import com.grab.grazel.migrate.android.AndroidLibraryDataExtractor
import com.grab.grazel.migrate.android.ManifestValuesBuilder
Expand Down Expand Up @@ -76,6 +78,7 @@ internal interface GrazelComponent {
fun artifactsPinner(): Lazy<ArtifactsPinner>
fun dependenciesDataSource(): Lazy<DependenciesDataSource>
fun mavenInstallArtifactsCalculator(): Lazy<MavenInstallArtifactsCalculator>
fun androidVariantDataSource(): Lazy<AndroidVariantDataSource>
fun hybridBuildExecutor(): HybridBuildExecutor

fun androidInstrumentationBinaryDataExtractor(): Lazy<AndroidInstrumentationBinaryDataExtractor>
Expand All @@ -90,6 +93,7 @@ internal interface GrazelComponent {
@Module(
includes = [
MigrationCriteriaModule::class,
MigrationModule::class,
DependenciesModule::class,
HybridBuildModule::class,
VariantModule::class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.grab.grazel.extension

import com.android.build.gradle.api.BaseVariant
import com.android.builder.model.BuildType
import com.android.builder.model.ProductFlavor
import groovy.lang.Closure
Expand Down Expand Up @@ -81,15 +80,4 @@ interface VariantFilter {
val buildType: BuildType
val flavors: List<ProductFlavor>
val name: String
}

internal class DefaultVariantFilter(variant: BaseVariant) : VariantFilter {
var ignored: Boolean = false
override fun setIgnore(ignore: Boolean) {
ignored = ignore
}

override val buildType: BuildType = variant.buildType
override val flavors: List<ProductFlavor> = variant.productFlavors
override val name: String = variant.name
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ package com.grab.grazel.gradle

import com.android.build.gradle.api.BaseVariant
import com.grab.grazel.GrazelExtension
import com.grab.grazel.gradle.VariantInfo.AndroidFlavor
import com.grab.grazel.gradle.VariantInfo.AndroidVariant
import com.grab.grazel.gradle.dependencies.BuildGraphType
import com.grab.grazel.gradle.variant.AndroidVariantDataSource
import org.gradle.api.Project
Expand Down Expand Up @@ -70,11 +68,6 @@ internal interface ConfigurationDataSource {
vararg variants: BaseVariant?,
configuration: Configuration
): Boolean

fun configurationByVariant(
project: Project,
vararg scopes: ConfigurationScope
): Map<VariantInfo, List<Configuration>>
}

@Singleton
Expand Down Expand Up @@ -114,6 +107,8 @@ internal class DefaultConfigurationDataSource @Inject constructor(
.filter { !it.name.contains("coreLibraryDesugaring") }
.filter { !it.name.startsWith("_") }
.filter { !it.name.contains("archives") }
.filter { !it.name.contains("KaptWorker") }
.filter { !it.name.contains("androidJacocoAnt") }
.filter { !it.isDynamicConfiguration() } // Remove when Grazel support dynamic-feature plugin
.filter { configuration ->
when {
Expand Down Expand Up @@ -156,44 +151,6 @@ internal class DefaultConfigurationDataSource @Inject constructor(
*buildGraphTypes.map { it.configurationScope }.toTypedArray()
).filter { it.isCanBeResolved }
}

override fun configurationByVariant(
project: Project,
vararg scopes: ConfigurationScope
): Map<VariantInfo, List<Configuration>> {
return if (project.isAndroid) {
val availableConfigurations = configurations(project)
val availableVariants = androidVariantDataSource.getMigratableVariants(project)
return availableConfigurations
.groupBy { configuration -> calculateVariantKey(availableVariants, configuration) }
} else {
mapOf(VariantInfo.Default to configurations(project).toList())
}
}

/** Return the grouping key that will be used to split the configurations,
* first checks a matching variant, then flavor, then build type and fallback to `VariantInfo.Default`.
* We could ideally just group by variant name but that is not sufficient since
* there can be configurations like `debugImplementation` etc that might not
* have been caught by variant name check but will be caught by buildType check.
*/
private fun calculateVariantKey(
availableVariants: List<BaseVariant>,
configuration: Configuration
): VariantInfo {
val matchingVariant = availableVariants
.firstOrNull() { variant -> configuration.name.startsWith(variant.name) }
val matchingFlavor = availableVariants
.firstOrNull() { variant -> configuration.name.startsWith(variant.flavorName) }
val matchingBuildType = availableVariants
.firstOrNull() { variant -> configuration.name.startsWith(variant.buildType.name) }
return when {
matchingVariant != null -> AndroidVariant(matchingVariant)
matchingFlavor?.flavorName?.isNotEmpty() == true -> AndroidFlavor(matchingFlavor.flavorName)
matchingBuildType != null -> AndroidFlavor(matchingBuildType.name)
else -> VariantInfo.Default
}
}
}

internal fun Configuration.isUnitTest() = name.contains("UnitTest", true) || name.startsWith("test")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@

package com.grab.grazel.gradle.dependencies

import com.android.build.gradle.internal.utils.toImmutableMap
import com.grab.grazel.GrazelExtension
import com.grab.grazel.di.qualifiers.RootProject
import com.grab.grazel.gradle.ConfigurationDataSource
import com.grab.grazel.gradle.RepositoryDataSource
import com.grab.grazel.gradle.VariantInfo.Default
import com.grab.grazel.util.merge
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ExternalDependency
Expand Down Expand Up @@ -56,6 +53,8 @@ class MavenExternalArtifact(
override fun toString() = id
}


@Deprecated("Use task based dependency resolution instead")
internal class MavenInstallArtifactsCalculator
@Inject
constructor(
Expand All @@ -70,11 +69,7 @@ constructor(
}

fun calculate(): Map<String, List<MavenExternalArtifact>> {
val variantConfigs = calculateVariantConfigurations()
// Resolve the dependencies in each variant bucket from the configuration
val variantDependencies = resolveVariantDependencies(variantConfigs)
// Remove all dependencies from flavors which are already present in default.
return filterDependencies(variantDependencies)
return emptyMap()
}

/**
Expand Down Expand Up @@ -156,49 +151,6 @@ constructor(
}.filterValues { it.isNotEmpty() }
}

/**
* Calculate a `Map` of `Variant` and its `Configuration`s for the whole project.
*/
private fun calculateVariantConfigurations(): Map<String, List<Configuration>> {
return rootProject
.subprojects
.map { project ->
configurationDataSource
.configurationByVariant(project = project)
.mapKeys { it.key.toString() }
}.merge { prev, next -> (prev + next) }
.toImmutableMap()
}

/**
* `variantDependencies` should contain all dependencies per flavor/variant but they might be
* duplicated across all the buckets due to Gradle's configuration hierarchy. For example,
* `flavor1DebugImplementation` will contain all dependencies from `default`. To find the
* dependencies that only belong to `flavor1DebugImplementation` we filter all by looking against
* dependencies in `default` configuration.
*/
private fun filterDependencies(
variantDependencies: Map<String, List<MavenExternalArtifact>>
): Map<String, List<MavenExternalArtifact>> {
val defaultDependencies = variantDependencies.getOrDefault(Default.toString(), emptyList())
val defaultDependenciesMap = defaultDependencies.groupBy { it.id }

val filteredDependencies = mutableMapOf<String, List<MavenExternalArtifact>>().apply {
put(Default.toString(), defaultDependencies)
}
variantDependencies
.asSequence()
.filter { it.key != Default.toString() }
.forEach { (variantName, dependencies) ->
filteredDependencies[variantName] = dependencies
.filter { !defaultDependenciesMap.contains(it.id) }
.sortedBy { it.id }
}
return filteredDependencies
.filterValues { it.isNotEmpty() }
.toImmutableMap()
}

private fun ExternalDependency.extractExcludeRules(): Set<ExcludeRule> {
return excludeRules
.asSequence()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.android.builder.model.ProductFlavor
import com.google.common.base.MoreObjects
import com.grab.grazel.gradle.variant.Classpath.Compile
import com.grab.grazel.gradle.variant.Classpath.Runtime
import com.grab.grazel.gradle.variant.DefaultVariants.Default
import com.grab.grazel.gradle.variant.VariantType.AndroidBuild
import com.grab.grazel.gradle.variant.VariantType.AndroidTest
import com.grab.grazel.gradle.variant.VariantType.JvmBuild
Expand Down Expand Up @@ -41,11 +40,12 @@ class AndroidVariant(

override val extendsFrom: Set<String> by lazy {
buildList {
add(Default.toString())
add(DEFAULT_VARIANT)
addAll(backingVariant.productFlavors.map { it.name })
add(backingVariant.buildType.name)
if (variantType.isTest) {
add(DefaultVariants.Test.toString())
add(TEST_VARIANT)
if (variantType.isAndroidTest) add(ANDROID_TEST_VARIANT)
add(backingVariant.buildType.name + variantType.testSuffix)
}
}.filter { it != name }.toSet()
Expand Down Expand Up @@ -151,9 +151,9 @@ class AndroidBuildType(
toIgnoreKeywords = flavors
) {
override val extendsFrom: Set<String> = buildList {
add(Default.toString())
add(DEFAULT_VARIANT)
if (variantType.isTest) add(backingVariant.name)
if (variantType == Test) add(DefaultVariants.Test.toString())
if (variantType == Test) add(TEST_VARIANT)
}.toSet()
}

Expand All @@ -179,9 +179,9 @@ class AndroidFlavor(
toIgnoreKeywords = buildTypes
) {
override val extendsFrom: Set<String> = buildList {
add(Default.toString())
add(DEFAULT_VARIANT)
if (variantType.isTest) add(backingVariant.name)
if (variantType == Test) add(DefaultVariants.Test.toString())
if (variantType == Test) add(TEST_VARIANT)
}.toSet()
}

Expand All @@ -190,8 +190,9 @@ data class DefaultVariantData(
val variantType: VariantType,
val ignoreKeywords: Set<String>,
val name: String = when (variantType) {
AndroidBuild -> Default.toString()
else -> DefaultVariants.Test.toString()
AndroidBuild -> DEFAULT_VARIANT
AndroidTest -> ANDROID_TEST_VARIANT
else -> TEST_VARIANT
},
)

Expand All @@ -213,7 +214,10 @@ class AndroidDefaultVariant(
override val backingVariant: DefaultVariantData get() = defaultVariantData
override val project: Project get() = defaultVariantData.project
override val variantType: VariantType get() = defaultVariantData.variantType
override val extendsFrom: Set<String> = setOf(Default.toString())
override val extendsFrom: Set<String> = buildSet {
if (variantType.isTest) add(DEFAULT_VARIANT)
if (variantType.isAndroidTest) add(TEST_VARIANT)
}

private val ignoreKeywords get() = defaultVariantData.ignoreKeywords

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ package com.grab.grazel.gradle.variant
import com.android.build.gradle.AppExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.api.UnitTestVariant
import com.android.builder.model.BuildType
import com.android.builder.model.ProductFlavor
import com.grab.grazel.gradle.isAndroidApplication
import com.grab.grazel.gradle.isAndroidDynamicFeature
import com.grab.grazel.gradle.isAndroidLibrary
import org.gradle.api.DomainObjectSet
import org.gradle.api.Project
import org.gradle.kotlin.dsl.the
import javax.inject.Inject
import javax.inject.Singleton

internal interface AndroidVariantsExtractor {
fun allVariants(project: Project): Set<BaseVariant>
fun getUnitTestVariants(project: Project): Set<BaseVariant>
fun getTestVariants(project: Project): Set<BaseVariant>
fun getVariants(project: Project): Set<BaseVariant>
fun allVariants(project: Project): DomainObjectSet<out BaseVariant>
fun allVariants(project: Project, variantAction: (BaseVariant) -> Unit)
fun getUnitTestVariants(project: Project): DomainObjectSet<out BaseVariant>
fun getTestVariants(project: Project): DomainObjectSet<out BaseVariant>
fun getVariants(project: Project): DomainObjectSet<out BaseVariant>
fun getFlavors(project: Project): Set<ProductFlavor>
fun getBuildTypes(project: Project): Set<BuildType>
}
Expand All @@ -30,31 +31,40 @@ constructor() : AndroidVariantsExtractor {

private val Project.isAndroidAppOrDynFeature get() = project.isAndroidApplication || project.isAndroidDynamicFeature

override fun allVariants(project: Project): Set<BaseVariant> {
return getVariants(project) + getTestVariants(project) + getUnitTestVariants(project)
override fun allVariants(project: Project): DomainObjectSet<BaseVariant> {
return project.objects.domainObjectSet(BaseVariant::class.java).apply {
addAll(getVariants(project) + getTestVariants(project) + getUnitTestVariants(project))
}
}

override fun allVariants(project: Project, variantAction: (BaseVariant) -> Unit) {
getVariants(project).all(variantAction)
getTestVariants(project).all(variantAction)
getUnitTestVariants(project).all(variantAction)
}

override fun getVariants(project: Project): Set<BaseVariant> {

override fun getVariants(project: Project): DomainObjectSet<out BaseVariant> {
return when {
project.isAndroidAppOrDynFeature -> project.the<AppExtension>().applicationVariants
project.isAndroidLibrary -> project.the<LibraryExtension>().libraryVariants
else -> emptySet()
else -> project.objects.domainObjectSet(BaseVariant::class.java)
}
}

override fun getTestVariants(project: Project): Set<BaseVariant> {
override fun getTestVariants(project: Project): DomainObjectSet<out BaseVariant> {
return when {
project.isAndroidAppOrDynFeature -> project.the<AppExtension>().testVariants
project.isAndroidLibrary -> project.the<LibraryExtension>().testVariants
else -> emptySet()
else -> project.objects.domainObjectSet(BaseVariant::class.java)
}
}

override fun getUnitTestVariants(project: Project): Set<UnitTestVariant> {
override fun getUnitTestVariants(project: Project): DomainObjectSet<out BaseVariant> {
return when {
project.isAndroidAppOrDynFeature -> project.the<AppExtension>().unitTestVariants
project.isAndroidLibrary -> project.the<LibraryExtension>().unitTestVariants
else -> emptySet()
else -> project.objects.domainObjectSet(BaseVariant::class.java)
}
}

Expand Down
Loading

0 comments on commit bd1f318

Please sign in to comment.