Skip to content

Commit

Permalink
Support Gradle configuration cache #248
Browse files Browse the repository at this point in the history
  • Loading branch information
johnsonlee authored and neighbWang committed Jul 9, 2022
1 parent 1f0f65d commit 5f7de66
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 112 deletions.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.didiglobal.booster.gradle

import com.android.build.gradle.AppExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.BaseVariant
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project

Expand All @@ -13,34 +16,37 @@ import org.gradle.api.Project
class BoosterPlugin : Plugin<Project> {

override fun apply(project: Project) {
project.gradle.addListener(BoosterTransformTaskExecutionListener(project))
project.extensions.findByName("android") ?: throw GradleException("$project is not an Android project")

when {
project.plugins.hasPlugin("com.android.application") || project.plugins.hasPlugin("com.android.dynamic-feature") -> project.getAndroid<AppExtension>().let { android ->
android.registerTransform(BoosterTransform.newInstance(project))
if (!GTE_V3_6) {
project.gradle.addListener(BoosterTransformTaskExecutionListener(project))
}

val android = project.getAndroid<BaseExtension>()
when (android) {
is AppExtension -> android.applicationVariants
is LibraryExtension -> android.libraryVariants
else -> emptyList<BaseVariant>()
}.takeIf<Collection<BaseVariant>>(Collection<BaseVariant>::isNotEmpty)?.let { variants ->
android.registerTransform(BoosterTransform.newInstance(project))
if (project.state.executed) {
project.setup(variants)
} else {
project.afterEvaluate {
loadVariantProcessors(project).let { processors ->
android.applicationVariants.forEach { variant ->
processors.forEach { processor ->
processor.process(variant)
}
}
}
project.setup(variants)
}
}
project.plugins.hasPlugin("com.android.library") -> project.getAndroid<LibraryExtension>().let { android ->
android.registerTransform(BoosterTransform.newInstance(project))
project.afterEvaluate {
loadVariantProcessors(project).let { processors ->
android.libraryVariants.forEach { variant ->
processors.forEach { processor ->
processor.process(variant)
}
}
}
}
}
}

private fun Project.setup(variants: Collection<BaseVariant>) {
val processors = loadVariantProcessors(this)
variants.forEach { variant ->
processors.forEach { processor ->
processor.process(variant)
}
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,24 @@ package com.didiglobal.booster.gradle
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformInvocation
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.internal.pipeline.TransformManager
import com.didiglobal.booster.annotations.Priority
import com.didiglobal.booster.gradle.internal.BoosterTransformV34
import com.didiglobal.booster.transform.AbstractKlassPool
import org.gradle.api.Project

/**
* Represents the transform base
*
* @author johnsonlee
*/
open class BoosterTransform protected constructor(val project: Project) : Transform() {
open class BoosterTransform protected constructor(
internal val parameter: TransformParameter
) : Transform() {

/*
* Preload transformers as List to fix NoSuchElementException caused by ServiceLoader in parallel mode
*/
internal val transformers = loadTransformers(project.buildscript.classLoader).sortedBy {
it.javaClass.getAnnotation(Priority::class.java)?.value ?: 0
internal val verifyEnabled by lazy {
parameter.properties[OPT_TRANSFORM_VERIFY]?.toString()?.toBoolean() ?: false
}

internal val verifyEnabled = project.getProperty(OPT_TRANSFORM_VERIFY, false)

private val android: BaseExtension = project.getAndroid()

private lateinit var androidKlassPool: AbstractKlassPool

init {
project.afterEvaluate {
androidKlassPool = object : AbstractKlassPool(android.bootClasspath) {}
}
}

val bootKlassPool: AbstractKlassPool
get() = androidKlassPool

override fun getName() = "booster"
override fun getName() = parameter.name

override fun isIncremental() = !verifyEnabled

Expand All @@ -48,18 +29,18 @@ open class BoosterTransform protected constructor(val project: Project) : Transf
override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> = TransformManager.CONTENT_CLASS

override fun getScopes(): MutableSet<in QualifiedContent.Scope> = when {
transformers.isEmpty() -> mutableSetOf()
project.plugins.hasPlugin("com.android.library") -> SCOPE_PROJECT
project.plugins.hasPlugin("com.android.application") -> SCOPE_FULL_PROJECT
project.plugins.hasPlugin("com.android.dynamic-feature") -> SCOPE_FULL_WITH_FEATURES
parameter.transformers.isEmpty() -> mutableSetOf()
parameter.plugins.hasPlugin("com.android.library") -> SCOPE_PROJECT
parameter.plugins.hasPlugin("com.android.application") -> SCOPE_FULL_PROJECT
parameter.plugins.hasPlugin("com.android.dynamic-feature") -> SCOPE_FULL_WITH_FEATURES
else -> TODO("Not an Android project")
}

override fun getReferencedScopes(): MutableSet<in QualifiedContent.Scope> = when {
transformers.isEmpty() -> when {
project.plugins.hasPlugin("com.android.library") -> SCOPE_PROJECT
project.plugins.hasPlugin("com.android.application") -> SCOPE_FULL_PROJECT
project.plugins.hasPlugin("com.android.dynamic-feature") -> SCOPE_FULL_WITH_FEATURES
parameter.transformers.isEmpty() -> when {
parameter.plugins.hasPlugin("com.android.library") -> SCOPE_PROJECT
parameter.plugins.hasPlugin("com.android.application") -> SCOPE_FULL_PROJECT
parameter.plugins.hasPlugin("com.android.dynamic-feature") -> SCOPE_FULL_WITH_FEATURES
else -> TODO("Not an Android project")
}
else -> super.getReferencedScopes()
Expand All @@ -78,9 +59,12 @@ open class BoosterTransform protected constructor(val project: Project) : Transf

companion object {

fun newInstance(project: Project): BoosterTransform = when {
GTE_V3_4 -> BoosterTransformV34(project)
else -> BoosterTransform(project)
fun newInstance(project: Project, name: String = "booster"): BoosterTransform {
val parameter = project.newTransformParameter(name)
return when {
GTE_V3_4 -> BoosterTransformV34(parameter)
else -> BoosterTransform(parameter)
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Status.NOTCHANGED
import com.android.build.api.transform.Status.REMOVED
import com.android.build.api.transform.TransformInvocation
import com.android.build.gradle.BaseExtension
import com.android.dex.DexFormat
import com.didiglobal.booster.gradle.util.dex
import com.didiglobal.booster.kotlinx.NCPU
Expand All @@ -17,6 +18,7 @@ import com.didiglobal.booster.transform.AbstractKlassPool
import com.didiglobal.booster.transform.ArtifactManager
import com.didiglobal.booster.transform.Collector
import com.didiglobal.booster.transform.TransformContext
import com.didiglobal.booster.transform.Transformer
import com.didiglobal.booster.transform.artifacts
import com.didiglobal.booster.transform.util.CompositeCollector
import com.didiglobal.booster.transform.util.collect
Expand All @@ -38,15 +40,30 @@ import java.util.concurrent.TimeUnit
*/
internal class BoosterTransformInvocation(
private val delegate: TransformInvocation,
internal val transform: BoosterTransform
private val transform: BoosterTransform
) : TransformInvocation by delegate, TransformContext, ArtifactManager {

private val project = transform.project

private val outputs = CopyOnWriteArrayList<File>()

private val collectors = CopyOnWriteArrayList<Collector<*>>()

/*
* Preload transformers as List to fix NoSuchElementException caused by ServiceLoader in parallel mode
*/
private val transformers: List<Transformer> = transform.parameter.transformers.map {
try {
it.getConstructor(ClassLoader::class.java).newInstance(transform.parameter.buildscript.classLoader)
} catch (e1: Throwable) {
try {
it.getConstructor().newInstance()
} catch (e2: Throwable) {
throw e2.apply {
addSuppressed(e1)
}
}
}
}

override val name: String = delegate.context.variantName

override val projectDir: File = project.projectDir
Expand All @@ -71,7 +88,13 @@ internal class BoosterTransformInvocation(
}
}

override val klassPool: AbstractKlassPool = object : AbstractKlassPool(compileClasspath, transform.bootKlassPool) {}
private val bootKlassPool by lazy {
object : AbstractKlassPool(project.getAndroid<BaseExtension>().bootClasspath) {}
}

override val klassPool by lazy {
object : AbstractKlassPool(compileClasspath, bootKlassPool) {}
}

override val applicationId = delegate.applicationId

Expand Down Expand Up @@ -114,13 +137,13 @@ internal class BoosterTransformInvocation(
}

private fun onPreTransform() {
transform.transformers.forEach {
transformers.forEach {
it.onPreTransform(this)
}
}

private fun onPostTransform() {
transform.transformers.forEach {
transformers.forEach {
it.onPostTransform(this)
}
}
Expand Down Expand Up @@ -241,7 +264,7 @@ internal class BoosterTransformInvocation(
}

private fun ByteArray.transform(): ByteArray {
return transform.transformers.fold(this) { bytes, transformer ->
return transformers.fold(this) { bytes, transformer ->
transformer.transform(this@BoosterTransformInvocation, bytes)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import org.gradle.api.Task
import org.gradle.api.execution.TaskExecutionAdapter

/**
* Only for AGP version lower than 3.6.0
*
* @author neighbWang
*/
class BoosterTransformTaskExecutionListener(private val project: Project) : TaskExecutionAdapter() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,75 @@ internal interface ServiceLoader<T> {
fun load(vararg args: Any): List<T>
}

internal class ServiceLoaderFactory<T>(private val classLoader: ClassLoader, private val service: Class<T>) {
@Suppress("UNCHECKED_CAST")
private class ServiceLoaderImpl<T>(
private val classLoader: ClassLoader,
private val service: Class<T>,
private vararg val types: Class<*>
) : ServiceLoader<T> {

fun newServiceLoader(vararg types: Class<*>) = object : ServiceLoader<T> {
private val name = "META-INF/services/${service.name}"

@Suppress("UNCHECKED_CAST")
override fun load(vararg args: Any) = classLoader.getResources("META-INF/services/${service.name}")?.asSequence()?.map(::parse)?.flatten()?.toSet()?.map { provider ->
override fun load(vararg args: Any): List<T> {
return lookup<T>().map { provider ->
try {
val providerClass = Class.forName(provider, false, classLoader)
if (!service.isAssignableFrom(providerClass)) {
throw ServiceConfigurationError("Provider $provider not a subtype")
}

try {
providerClass.getConstructor(*types).newInstance(*args) as T
provider.getConstructor(*types).newInstance(*args) as T
} catch (e: NoSuchMethodException) {
providerClass.newInstance() as T
provider.newInstance() as T
}
} catch (e: ClassNotFoundException) {
throw ServiceConfigurationError("Provider $provider not found")
}
} ?: emptyList()
}
}

fun <T> lookup(): Set<Class<T>> {
return classLoader.getResources(name)?.asSequence()?.map(::parse)?.flatten()?.toSet()?.mapNotNull { provider ->
try {
val providerClass = Class.forName(provider, false, classLoader)
if (!service.isAssignableFrom(providerClass)) {
throw ServiceConfigurationError("Provider $provider not a subtype")
}
providerClass as Class<T>
} catch (e: Throwable) {
null
}
}?.toSet() ?: emptySet()
}
}

internal class ServiceLoaderFactory<T>(private val classLoader: ClassLoader, private val service: Class<T>) {
fun newServiceLoader(vararg types: Class<*>): ServiceLoader<T> = ServiceLoaderImpl(classLoader, service, *types)
}

internal inline fun <reified T> newServiceLoader(classLoader: ClassLoader, vararg types: Class<*>): ServiceLoader<T> {
return ServiceLoaderFactory(classLoader, T::class.java).newServiceLoader(*types)
}

internal inline fun <reified T> newServiceLoader(classLoader: ClassLoader, vararg types: Class<*>) = ServiceLoaderFactory(classLoader, T::class.java).newServiceLoader(*types)
/**
* Load [Transformer]s with the specified [classLoader]
*/
@Throws(ServiceConfigurationError::class)
internal fun lookupTransformers(classLoader: ClassLoader): Set<Class<Transformer>> {
return ServiceLoaderImpl(classLoader, Transformer::class.java, ClassLoader::class.java).lookup()
}

/**
* Load [Transformer]s with the specified [classLoader]
*/
@Throws(ServiceConfigurationError::class)
internal fun loadTransformers(classLoader: ClassLoader) = newServiceLoader<Transformer>(classLoader, ClassLoader::class.java).load(classLoader)
internal fun loadTransformers(classLoader: ClassLoader): List<Transformer> {
return newServiceLoader<Transformer>(classLoader, ClassLoader::class.java).load(classLoader)
}

/**
* Load [VariantProcessor]s with the specified [classLoader]
*/
@Throws(ServiceConfigurationError::class)
internal fun loadVariantProcessors(project: Project) = newServiceLoader<VariantProcessor>(project.buildscript.classLoader, Project::class.java).load(project)
internal fun loadVariantProcessors(project: Project): List<VariantProcessor> {
return newServiceLoader<VariantProcessor>(project.buildscript.classLoader, Project::class.java).load(project)
}

@Throws(ServiceConfigurationError::class)
private fun parse(u: URL) = try {
Expand Down
Loading

0 comments on commit 5f7de66

Please sign in to comment.