diff --git a/agp-patch/src/main/kotlin/com/android/build/gradle/internal/DependencyConfigurator.kt b/agp-patch/src/main/kotlin/com/android/build/gradle/internal/DependencyConfigurator.kt index da2004e..e64e01d 100644 --- a/agp-patch/src/main/kotlin/com/android/build/gradle/internal/DependencyConfigurator.kt +++ b/agp-patch/src/main/kotlin/com/android/build/gradle/internal/DependencyConfigurator.kt @@ -40,6 +40,7 @@ import com.android.build.gradle.internal.dependency.AsmClassesTransform.Companio import com.android.build.gradle.internal.dependency.ClassesDirToClassesTransform import com.android.build.gradle.internal.dependency.CollectClassesTransform import com.android.build.gradle.internal.dependency.CollectResourceSymbolsTransform +import com.android.build.gradle.internal.dependency.DexingRegistration import com.android.build.gradle.internal.dependency.EnumerateClassesTransform import com.android.build.gradle.internal.dependency.ExtractAarTransform import com.android.build.gradle.internal.dependency.ExtractCompileSdkShimTransform @@ -60,7 +61,6 @@ import com.android.build.gradle.internal.dependency.PlatformAttrTransform import com.android.build.gradle.internal.dependency.RecalculateStackFramesTransform.Companion.registerGlobalRecalculateStackFramesTransform import com.android.build.gradle.internal.dependency.RecalculateStackFramesTransform.Companion.registerRecalculateStackFramesTransformForComponent import com.android.build.gradle.internal.dependency.VersionedCodeShrinker -import com.android.build.gradle.internal.dependency.getDexingArtifactConfigurations import com.android.build.gradle.internal.dependency.registerDexingOutputSplitTransform import com.android.build.gradle.internal.dsl.BaseFlavor import com.android.build.gradle.internal.dsl.BuildType @@ -69,16 +69,16 @@ import com.android.build.gradle.internal.dsl.ModulePropertyKey import com.android.build.gradle.internal.dsl.ProductFlavor import com.android.build.gradle.internal.dsl.SigningConfig import com.android.build.gradle.internal.packaging.getDefaultDebugKeystoreSigningConfig -import com.android.build.gradle.internal.publishing.AndroidArtifacts import com.android.build.gradle.internal.publishing.AarOrJarTypeToConsume +import com.android.build.gradle.internal.publishing.AndroidArtifacts import com.android.build.gradle.internal.res.namespaced.AutoNamespacePreProcessTransform import com.android.build.gradle.internal.res.namespaced.AutoNamespaceTransform import com.android.build.gradle.internal.scope.InternalArtifactType import com.android.build.gradle.internal.services.AndroidLocationsBuildService import com.android.build.gradle.internal.services.ProjectServices import com.android.build.gradle.internal.services.getBuildService +import com.android.build.gradle.internal.signing.SigningConfigData import com.android.build.gradle.internal.tasks.AsarToApksTransform -import com.android.build.gradle.internal.tasks.AsarToManifestSnippetTransform import com.android.build.gradle.internal.tasks.AsarTransform import com.android.build.gradle.internal.tasks.factory.BootClasspathConfig import com.android.build.gradle.internal.utils.ATTR_ENABLE_CORE_LIBRARY_DESUGARING @@ -101,13 +101,13 @@ import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.api.artifacts.transform.TransformAction -import org.gradle.api.artifacts.transform.TransformParameters import org.gradle.api.artifacts.transform.TransformSpec import org.gradle.api.artifacts.type.ArtifactTypeDefinition +import org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE import org.gradle.api.attributes.AttributesSchema import org.gradle.api.attributes.Category import org.gradle.api.attributes.Usage -import org.gradle.api.internal.artifacts.ArtifactAttributes +import org.gradle.api.provider.Provider import org.gradle.testing.jacoco.plugins.JacocoPluginExtension import java.lang.Boolean.FALSE import java.lang.Boolean.TRUE @@ -216,7 +216,7 @@ class DependencyConfigurator( spec.parameters.projectName.set(project.name) spec.parameters.returnDefaultValues.set(true) spec.from.attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.JAR.type ) spec.from.attribute( @@ -224,7 +224,7 @@ class DependencyConfigurator( true ) spec.to.attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.TYPE_MOCKABLE_JAR ) spec.to.attribute( @@ -239,7 +239,7 @@ class DependencyConfigurator( spec.parameters.projectName.set(project.name) spec.parameters.returnDefaultValues.set(false) spec.from.attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.JAR.type ) spec.from.attribute( @@ -247,7 +247,7 @@ class DependencyConfigurator( false ) spec.to.attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.TYPE_MOCKABLE_JAR ) spec.to.attribute( @@ -296,7 +296,7 @@ class DependencyConfigurator( AarToClassTransform::class.java ) { reg: TransformSpec -> reg.from.attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, aarOrJarTypeToConsume.aar.type ) reg.from.attribute( @@ -304,7 +304,7 @@ class DependencyConfigurator( apiUsage ) reg.to.attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type ) reg.to.attribute( @@ -323,7 +323,7 @@ class DependencyConfigurator( AarToClassTransform::class.java ) { reg: TransformSpec -> reg.from.attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, aarOrJarTypeToConsume.aar.type ) reg.from.attribute( @@ -331,7 +331,7 @@ class DependencyConfigurator( runtimeUsage ) reg.to.attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type ) reg.to.attribute( @@ -406,7 +406,7 @@ class DependencyConfigurator( configuration .attributes .attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, aarOrJarTypeToConsume.jar.type ) } @@ -466,31 +466,6 @@ class DependencyConfigurator( } fun configurePrivacySandboxSdkConsumerTransforms(): DependencyConfigurator { - if (projectServices.projectOptions.get(BooleanOption.PRIVACY_SANDBOX_SDK_SUPPORT)) { - val defaultDebugSigning = getBuildService( - projectServices.buildServiceRegistry, - AndroidLocationsBuildService::class.java - ).map { it.getDefaultDebugKeystoreSigningConfig() } - registerTransform( - AsarToApksTransform::class.java, - AndroidArtifacts.ArtifactType.ANDROID_PRIVACY_SANDBOX_SDK_ARCHIVE, - AndroidArtifacts.ArtifactType.ANDROID_PRIVACY_SANDBOX_SDK_APKS - ) { params -> - projectServices.initializeAapt2Input(params.aapt2) - - params.signingConfigData.set(defaultDebugSigning) - params.signingConfigValidationResultDir.set( - ArtifactsImpl(project, - "global").get(InternalArtifactType.VALIDATE_SIGNING_CONFIG) - ) - } - registerTransform( - AsarToManifestSnippetTransform::class.java, - AndroidArtifacts.ArtifactType.ANDROID_PRIVACY_SANDBOX_SDK_ARCHIVE, - AndroidArtifacts.ArtifactType.ANDROID_PRIVACY_SANDBOX_SDK_USES_SDK_LIBRARY_MANIFEST_SNIPPET - ) { - it.signingConfigData.set(defaultDebugSigning) - } for (from in AsarTransform.supportedAsarTransformTypes) { registerTransform( AsarTransform::class.java, @@ -500,7 +475,7 @@ class DependencyConfigurator( it.targetType.set(from) } } - } + return this } @@ -510,19 +485,12 @@ class DependencyConfigurator( buildToolsRevision: Revision, bootstrapCreationConfig: BootClasspathConfig ): DependencyConfigurator { - if (!projectServices.projectOptions.get(BooleanOption.PRIVACY_SANDBOX_SDK_SUPPORT)) { - return this - } - - fun configureExtractSdkShimTransforms(variant: VariantCreationConfig) { + fun configureExtractSdkShimTransforms(experimentalProperties: Map) { val extractSdkShimTransformParamConfig = { reg: TransformSpec -> - val experimentalProperties = variant.experimentalProperties - experimentalProperties.finalizeValue() - val experimentalPropertiesApiGenerator: Dependency? = ModulePropertyKey.Dependencies.ANDROID_PRIVACY_SANDBOX_SDK_API_GENERATOR - .getValue(experimentalProperties.get())?.single() + .getValue(experimentalProperties)?.single() val apigeneratorArtifact: Dependency = experimentalPropertiesApiGenerator ?: project.dependencies.create( @@ -531,7 +499,7 @@ class DependencyConfigurator( ) as Dependency val experimentalPropertiesRuntimeApigeneratorDependencies = - ModulePropertyKey.Dependencies.ANDROID_PRIVACY_SANDBOX_SDK_API_GENERATOR_GENERATED_RUNTIME_DEPENDENCIES.getValue(experimentalProperties.get()) + ModulePropertyKey.Dependencies.ANDROID_PRIVACY_SANDBOX_SDK_API_GENERATOR_GENERATED_RUNTIME_DEPENDENCIES.getValue(experimentalProperties) val runtimeDependenciesForShimSdk: List = experimentalPropertiesRuntimeApigeneratorDependencies ?: (projectServices.projectOptions @@ -590,7 +558,7 @@ class DependencyConfigurator( ) { reg -> val usageObj: Usage = project.objects.named(Usage::class.java, usage) reg.from.attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.ANDROID_PRIVACY_SANDBOX_SDK_INTERFACE_DESCRIPTOR.type ) reg.from.attribute( @@ -598,7 +566,7 @@ class DependencyConfigurator( usageObj ) reg.to.attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.CLASSES_JAR.type ) reg.to.attribute( @@ -612,9 +580,68 @@ class DependencyConfigurator( registerExtractSdkShimTransform(Usage.JAVA_RUNTIME) } - for (variant in variants) { - configureExtractSdkShimTransforms(variant) - } + val properties = variants.map { variant -> + variant.experimentalProperties.also { it.disallowChanges() }.get().filterKeys { + it == ModulePropertyKey.Dependencies.ANDROID_PRIVACY_SANDBOX_SDK_API_GENERATOR_GENERATED_RUNTIME_DEPENDENCIES.key || + it == ModulePropertyKey.Dependencies.ANDROID_PRIVACY_SANDBOX_SDK_API_GENERATOR.key + } + }.distinct() + + when(properties.size) { + 0 -> {} // No variants, problem will be reported elsewhere. + 1 -> configureExtractSdkShimTransforms(properties.single()) + else -> error("It is not possible to override Privacy Sandbox experimental properties per variant.\n" + + "Properties with different values defined across multiple variants: ${properties.joinToString()} ") + } + + fun registerAsarToApksTransform(variants: List) { + // For signing privacy sandbox artifacts we allow per project signing configuration + // by the use of experimental properties. To reduce the expense of registering per + // variant we set a limit of one signing config in all variants, then register the + // AsarToApksTransform once. To maintain the semantic, the build file must explicitly + // declare the same signing config for all variants. + val variantSigningConfigs = variants.map { variant -> + val experimentalProps = variant.experimentalProperties + experimentalProps.finalizeValue() + SigningConfigData.fromExperimentalPropertiesSigningConfig(variant.experimentalProperties) + }.distinct() + + val signingConfigProvider: Provider = + when (variantSigningConfigs.count()) { + 0 -> return // No variants + 1 -> if (variantSigningConfigs.singleOrNull() != null) { + // An identical signing config is set in all variants by experimental properties. + variants.first().services.provider { + variantSigningConfigs.singleOrNull() + } + } else { + // No experimental properties are set, use the default. + getBuildService( + variants.first().services.buildServiceRegistry, + AndroidLocationsBuildService::class.java + ).map(AndroidLocationsBuildService::getDefaultDebugKeystoreSigningConfig) + } + + else -> throw UnsupportedOperationException( + "It is not possible to override Privacy Sandbox experimental properties per variant.\n" + + "Set the same signing config using experimental properties in each variant explicitly.") + } + registerTransform( + AsarToApksTransform::class.java, + AndroidArtifacts.ArtifactType.ANDROID_PRIVACY_SANDBOX_SDK_ARCHIVE, + AndroidArtifacts.ArtifactType.ANDROID_PRIVACY_SANDBOX_SDK_APKS + ) { params -> + projectServices.initializeAapt2Input(params.aapt2) + + params.signingConfigData.set(signingConfigProvider) + params.signingConfigValidationResultDir.set( + ArtifactsImpl(project, + "global").get(InternalArtifactType.VALIDATE_SIGNING_CONFIG) + ) + } + } + registerAsarToApksTransform(variants) + return this } @@ -685,22 +712,23 @@ class DependencyConfigurator( project.dependencies.registerTransform( transformClass ) { spec: TransformSpec -> - spec.from.attribute(ArtifactAttributes.ARTIFACT_FORMAT, fromArtifactType) - spec.to.attribute(ArtifactAttributes.ARTIFACT_FORMAT, toArtifactType) + spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, fromArtifactType) + spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, toArtifactType) spec.parameters.projectName.setDisallowChanges(project.name) parametersSetter?.let { it(spec.parameters) } } } fun configureAttributeMatchingStrategies( - variantInputModel: VariantInputModel - ): DependencyConfigurator { + variantInputModel: VariantInputModel, + supportPrivacySandbox: Boolean + ): DependencyConfigurator { val schema = project.dependencies.attributesSchema // custom strategy for build-type and product-flavor. setBuildTypeStrategy(schema, variantInputModel) setupFlavorStrategy(schema, variantInputModel) - setupModelStrategy(schema) + setupModelStrategy(schema, supportPrivacySandbox) setUpAgpVersionStrategy(schema) return this @@ -783,8 +811,8 @@ class DependencyConfigurator( } } - private fun setupModelStrategy(attributesSchema: AttributesSchema) { - setUp(attributesSchema) + private fun setupModelStrategy(attributesSchema: AttributesSchema, supportPrivacySandbox: Boolean) { + setUp(attributesSchema, supportPrivacySandbox) } /** This is to enforce AGP version across a single or composite build. */ @@ -834,22 +862,21 @@ class DependencyConfigurator( if (allComponents.isNotEmpty()) { val bootClasspath = project.files(bootClasspathConfig.bootClasspath) val services = allComponents.first().services - if (projectOptions[BooleanOption.ENABLE_DEXING_ARTIFACT_TRANSFORM]) { - // !!! (dellisd) This reverts the change made to resolve https://issuetracker.google.com/issues/246326007 - val disableIncrementalDexing = false /* allComponents.any { it.componentType.isDynamicFeature } */ - for (artifactConfiguration in getDexingArtifactConfigurations( - allComponents - )) { - artifactConfiguration.registerTransform( - project.name, - dependencies, - bootClasspath, - getDesugarLibConfig(services), - SyncOptions.getErrorFormatMode(projectOptions), - disableIncrementalDexing = disableIncrementalDexing + DexingRegistration.registerTransforms( + allComponents, + DexingRegistration.ComponentAgnosticParameters( + projectName = project.name, + dependencyHandler = dependencies, + bootClasspath = bootClasspath, + libConfiguration = getDesugarLibConfig(services), + errorFormat = SyncOptions.getErrorFormatMode(projectOptions), + // Disable incremental dexing for main and androidTest components in dynamic + // feature module (b/246326007) + // !!! (dellisd) This reverts the change made to resolve https://issuetracker.google.com/issues/246326007 + disableIncrementalDexing = false,/* allComponents.any { it.componentType.isDynamicFeature }, */ + components = allComponents ) - } - } + ) val d8Version = Version.getVersionString() @@ -862,9 +889,9 @@ class DependencyConfigurator( parameters.coreLibDesugarConfig.set(getDesugarLibConfig(services)) parameters.bootclasspath.from(bootClasspath) } - spec.from.attribute(ArtifactAttributes.ARTIFACT_FORMAT, ArtifactTypeDefinition.JAR_TYPE) + spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE) spec.from.attribute(ATTR_ENABLE_CORE_LIBRARY_DESUGARING, TRUE.toString()) - spec.to.attribute(ArtifactAttributes.ARTIFACT_FORMAT, D8_DESUGAR_METHODS) + spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, D8_DESUGAR_METHODS) spec.to.attribute(ATTR_ENABLE_CORE_LIBRARY_DESUGARING, TRUE.toString()) } @@ -875,9 +902,9 @@ class DependencyConfigurator( spec.parameters { parameters -> parameters.d8Version.set(d8Version) } - spec.from.attribute(ArtifactAttributes.ARTIFACT_FORMAT, ArtifactTypeDefinition.JAR_TYPE) + spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE) spec.from.attribute(ATTR_ENABLE_CORE_LIBRARY_DESUGARING, FALSE.toString()) - spec.to.attribute(ArtifactAttributes.ARTIFACT_FORMAT, D8_DESUGAR_METHODS) + spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, D8_DESUGAR_METHODS) spec.to.attribute(ATTR_ENABLE_CORE_LIBRARY_DESUGARING, FALSE.toString()) } } @@ -891,12 +918,12 @@ class DependencyConfigurator( ) { reg: TransformSpec -> reg.from .attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.UNFILTERED_PROGUARD_RULES.type ) reg.to .attribute( - ArtifactAttributes.ARTIFACT_FORMAT, + ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.FILTERED_PROGUARD_RULES.type ) reg.parameters { params: FilterShrinkerRulesTransform.Parameters -> diff --git a/agp-patch/src/main/kotlin/com/android/build/gradle/internal/dependency/DexingTransform.kt b/agp-patch/src/main/kotlin/com/android/build/gradle/internal/dependency/DexingTransform.kt index 71a7162..afafde3 100644 --- a/agp-patch/src/main/kotlin/com/android/build/gradle/internal/dependency/DexingTransform.kt +++ b/agp-patch/src/main/kotlin/com/android/build/gradle/internal/dependency/DexingTransform.kt @@ -19,18 +19,16 @@ package com.android.build.gradle.internal.dependency -import com.android.build.api.variant.impl.getFeatureLevel import com.android.build.gradle.internal.LoggerWrapper import com.android.build.gradle.internal.component.ApkCreationConfig import com.android.build.gradle.internal.component.ComponentCreationConfig -import com.android.build.gradle.internal.dependency.AsmClassesTransform.Companion.ATTR_ASM_TRANSFORMED_VARIANT import com.android.build.gradle.internal.dexing.readDesugarGraph import com.android.build.gradle.internal.dexing.writeDesugarGraph import com.android.build.gradle.internal.errors.MessageReceiverImpl import com.android.build.gradle.internal.publishing.AndroidArtifacts import com.android.build.gradle.internal.scope.Java8LangSupport import com.android.build.gradle.options.BooleanOption -import com.android.build.gradle.options.SyncOptions +import com.android.build.gradle.options.SyncOptions.ErrorFormatMode import com.android.build.gradle.tasks.toSerializable import com.android.builder.dexing.ClassFileInput import com.android.builder.dexing.ClassFileInputs @@ -42,7 +40,6 @@ import com.android.builder.dexing.MutableDependencyGraph import com.android.builder.dexing.isJarFile import com.android.builder.dexing.r8.ClassFileProviderFactory import com.android.builder.files.SerializableFileChanges -import com.android.sdklib.AndroidVersion import com.android.utils.FileUtils import com.google.common.hash.Hashing import com.google.common.io.Closer @@ -84,7 +81,7 @@ abstract class BaseDexingTransform : Transfo @get:Classpath val bootClasspath: ConfigurableFileCollection @get:Internal - val errorFormat: Property + val errorFormat: Property @get:Optional @get:Input val libConfiguration: Property @@ -367,167 +364,236 @@ abstract class DexingWithClasspathTransform : BaseDexingTransform): Set { - return components - .filterIsInstance() - .map { getDexingArtifactConfiguration(it) }.toSet() +/** + * Dexing transform which uses the full classpath. This classpath consists of all external artifacts + * ([com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.EXTERNAL]) + * in addition to the input artifact's dependencies provided by Gradle through + * [org.gradle.api.artifacts.transform.InputArtifactDependencies]. + */ +@CacheableTransform +abstract class DexingWithFullClasspathTransform : + BaseDexingTransform() { + + @get:CompileClasspath + @get:InputArtifactDependencies + abstract val inputArtifactDependencies: FileCollection + + interface Parameters : BaseDexingTransform.Parameters { + + @get:CompileClasspath + val externalArtifacts: ConfigurableFileCollection + } + + override fun computeClasspathFiles() = + inputArtifactDependencies.files.toList() + parameters.externalArtifacts.files } -fun getDexingArtifactConfiguration(creationConfig: ApkCreationConfig): DexingArtifactConfiguration { - return DexingArtifactConfiguration( - minSdk = creationConfig.dexingCreationConfig.minSdkVersionForDexing.getFeatureLevel(), - isDebuggable = creationConfig.debuggable, - enableDesugaring = - creationConfig.dexingCreationConfig.java8LangSupportType == Java8LangSupport.D8, - enableCoreLibraryDesugaring = creationConfig.dexingCreationConfig.isCoreLibraryDesugaringEnabled, - asmTransformedVariant = - if (creationConfig.instrumentationCreationConfig?.dependenciesClassesAreInstrumented == true) { - creationConfig.name - } else { - null - }, - useJacocoTransformInstrumentation = creationConfig.useJacocoTransformInstrumentation, - enableGlobalSynthetics = creationConfig.enableGlobalSynthetics, - enableApiModeling = creationConfig.enableApiModeling - ) +object DexingRegistration { + + /** Parameters that are shared across all [ComponentCreationConfig]s. */ + class ComponentAgnosticParameters( + val projectName: String, + val dependencyHandler: DependencyHandler, + val bootClasspath: ConfigurableFileCollection, + val errorFormat: ErrorFormatMode, + val libConfiguration: Provider, + val disableIncrementalDexing: Boolean, + val components: List + ) + + /** + * Parameters that are specific to a given [ComponentCreationConfig]. + * + * Note: This class is a data class so that we can identify equivalent instances of this class + * (see [registerTransforms]). + * + * IMPORTANT: The properties of this class must be of primitive types (e.g., [Boolean], [Int], + * [String]) because the [getAttributes] method relies on [toString], and the implementation of + * [toString] on non-primitive types are not well-defined and subject to change (i.e., it can't + * be used to uniquely represent an object). + */ + data class ComponentSpecificParameters( + val minSdkVersion: Int, + val debuggable: Boolean, + val enableCoreLibraryDesugaring: Boolean, + val enableGlobalSynthetics: Boolean, + val enableApiModeling: Boolean, + val dependenciesClassesAreInstrumented: Boolean, + val asmTransformComponent: String?, // Not-null iff dependenciesClassesAreInstrumented == true + val useJacocoTransformInstrumentation: Boolean, + val enableDesugaring: Boolean, + val needsClasspath: Boolean, + val useFullClasspath: Boolean, + val componentIfUsingFullClasspath: String? // Not-null iff useFullClasspath == true + ) { + + constructor(creationConfig: ApkCreationConfig) : this( + minSdkVersion = creationConfig.dexing.minSdkVersionForDexing, + debuggable = creationConfig.debuggable, + enableCoreLibraryDesugaring = creationConfig.dexing.isCoreLibraryDesugaringEnabled, + enableGlobalSynthetics = creationConfig.enableGlobalSynthetics, + enableApiModeling = creationConfig.enableApiModeling, + dependenciesClassesAreInstrumented = creationConfig.instrumentationCreationConfig?.dependenciesClassesAreInstrumented == true, + asmTransformComponent = creationConfig.name.takeIf { creationConfig.instrumentationCreationConfig?.dependenciesClassesAreInstrumented == true }, + useJacocoTransformInstrumentation = creationConfig.useJacocoTransformInstrumentation, + enableDesugaring = needsDesugaring(creationConfig), + needsClasspath = needsClasspath(creationConfig), + useFullClasspath = useFullClasspath(creationConfig), + componentIfUsingFullClasspath = creationConfig.name.takeIf { useFullClasspath(creationConfig) } + ) + + companion object { + + private fun needsDesugaring(creationConfig: ApkCreationConfig): Boolean = + creationConfig.dexing.java8LangSupportType == Java8LangSupport.D8 + + private fun needsClasspath(creationConfig: ApkCreationConfig): Boolean = + needsDesugaring(creationConfig) && + creationConfig.dexing.minSdkVersionForDexing < 24 + + private fun useFullClasspath(creationConfig: ApkCreationConfig): Boolean = + needsClasspath(creationConfig) && + creationConfig.services.projectOptions.get(BooleanOption.USE_FULL_CLASSPATH_FOR_DEXING_TRANSFORM) + } -data class DexingArtifactConfiguration( - private val minSdk: Int, - private val isDebuggable: Boolean, - private val enableDesugaring: Boolean, - private val enableCoreLibraryDesugaring: Boolean, - private val asmTransformedVariant: String?, - private val useJacocoTransformInstrumentation: Boolean, - private val enableGlobalSynthetics: Boolean, - private val enableApiModeling: Boolean, -) { - - // If we want to do desugaring and our minSdk (or the API level of the device we're deploying - // to) is lower than N then we need a classpath in order to properly do the desugaring. - private val needsClasspath = enableDesugaring && minSdk < AndroidVersion.VersionCodes.N + /** + * Returns [AndroidAttributes] that uniquely represent the contents of this object. + * + * These attributes will be used when registering the transforms and when consuming the + * artifacts to ensure correct artifact production/consumption. + */ + fun getAttributes() = + AndroidAttributes( + Attribute.of("dexing-component-attributes", String::class.java) to this.toString() + ) + asmTransformComponent?.let { + // When asmTransformComponent != null, the consumed artifacts have the attribute + // below, so we need to specify it here as well to allow unambiguous artifact + // selection. + AndroidAttributes(AsmClassesTransform.ATTR_ASM_TRANSFORMED_VARIANT to asmTransformComponent) + } + } + + fun registerTransforms( + components: List, + parameters: ComponentAgnosticParameters + ) { + // To improve performance and avoid duplicate registrations, instead of setting up one + // transform per component, we will set up one transform per group of equivalent components + // (components whose registered transforms would be identical if registered separately). + components.filterIsInstance() + .mapTo(linkedSetOf()) { ComponentSpecificParameters(it) } + .forEach { + registerTransform(parameters, it) + } + } fun registerTransform( - projectName: String, - dependencyHandler: DependencyHandler, - bootClasspath: FileCollection, - libConfiguration: Provider, - errorFormat: SyncOptions.ErrorFormatMode, - disableIncrementalDexing: Boolean + allComponents: ComponentAgnosticParameters, + component: ComponentSpecificParameters ) { - dependencyHandler.registerTransform(getTransformClass()) { spec -> - spec.parameters { parameters -> - parameters.projectName.set(projectName) - parameters.minSdkVersion.set(minSdk) - parameters.debuggable.set(isDebuggable) - parameters.enableDesugaring.set(enableDesugaring) - // bootclasspath is required by d8 to do API conversion for library desugaring - if (needsClasspath || enableCoreLibraryDesugaring) { - parameters.bootClasspath.from(bootClasspath) - } - parameters.errorFormat.set(errorFormat) - if (enableCoreLibraryDesugaring) { - parameters.libConfiguration.set(libConfiguration) + @Suppress("UNCHECKED_CAST") + val transformClass = when { + !component.needsClasspath -> DexingNoClasspathTransform::class.java + !component.useFullClasspath -> DexingWithClasspathTransform::class.java + else -> DexingWithFullClasspathTransform::class.java as Class> } - parameters.enableGlobalSynthetics.set(enableGlobalSynthetics) - parameters.enableApiModeling.set(enableApiModeling) - } - // There are 2 transform flows for DEX: - // 1. (JACOCO_)CLASSES_DIR -> (JACOCO_)CLASSES -> DEX - // 2. (JACOCO_)CLASSES_JAR -> (JACOCO_)CLASSES -> DEX - // - // For incremental dexing, when requesting DEX the consumer will indicate a - // preference for CLASSES_DIR over CLASSES_JAR (see DexMergingTask), otherwise - // Gradle will select CLASSES_JAR by default. - // - // However, there could be an issue if CLASSES_DIR is selected: For Java libraries - // using Kotlin, CLASSES_DIR has two separate directories: one for compiled Java - // classes and one for compiled Kotlin classes. Classes in one directory may - // reference classes in the other directory, but each directory is transformed to - // DEX independently. Therefore, if dexing requires a classpath (desugaring is - // enabled and minSdk < 24), desugaring may not work correctly. - // - // Android libraries do not have this issue, as their CLASSES_DIR is one directory - // containing both Java and Kotlin classes. - // - // Therefore, to ensure correctness in all cases, we transform CLASSES to DEX only - // when dexing does not require a classpath, and it is not for main and androidTest - // components in dynamic feature module(b/246326007). Otherwise, we transform - // CLASSES_JAR to DEX directly so that CLASSES_DIR will not be selected. - // - // In the case that the JacocoTransform is executed, the Jacoco equivalent artifact is - // used. These artifacts are the same as CLASSES, CLASSES_JAR and ASM_INSTRUMENTED_JARS, - // but they have been offline instrumented by Jacoco and include Jacoco dependencies. - val inputArtifact: AndroidArtifacts.ArtifactType = - if (useJacocoTransformInstrumentation) { - when { - asmTransformedVariant != null -> - AndroidArtifacts.ArtifactType.JACOCO_ASM_INSTRUMENTED_JARS - !needsClasspath && !disableIncrementalDexing -> - AndroidArtifacts.ArtifactType.JACOCO_CLASSES - else -> - AndroidArtifacts.ArtifactType.JACOCO_CLASSES_JAR - } - } else { - when { - asmTransformedVariant != null -> - AndroidArtifacts.ArtifactType.ASM_INSTRUMENTED_JARS - !needsClasspath && !disableIncrementalDexing -> - AndroidArtifacts.ArtifactType.CLASSES - else -> - AndroidArtifacts.ArtifactType.CLASSES_JAR - } - } - spec.from.attribute( - ARTIFACT_TYPE_ATTRIBUTE, - inputArtifact.type - ) - if (enableGlobalSynthetics) { - spec.to.attribute( - ARTIFACT_TYPE_ATTRIBUTE, - AndroidArtifacts.ArtifactType.D8_OUTPUTS.type - ) - } else { - spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.DEX.type) - } - - getAttributes().apply { - addAttributesToContainer(spec.from) - addAttributesToContainer(spec.to) - } - } - } - - private fun getTransformClass(): Class> { - return if (needsClasspath) { - DexingWithClasspathTransform::class.java - } else { - DexingNoClasspathTransform::class.java + allComponents.dependencyHandler.registerTransform(transformClass) { spec -> + spec.parameters.apply { + projectName.set(allComponents.projectName) + minSdkVersion.set(component.minSdkVersion) + debuggable.set(component.debuggable) + enableDesugaring.set(component.enableDesugaring) + // bootclasspath is required by d8 to do API conversion for library desugaring + if (component.needsClasspath || component.enableCoreLibraryDesugaring) { + bootClasspath.from(allComponents.bootClasspath) + } + errorFormat.set(allComponents.errorFormat) + if (component.enableCoreLibraryDesugaring) { + libConfiguration.set(allComponents.libConfiguration) + } + enableGlobalSynthetics.set(component.enableGlobalSynthetics) + enableApiModeling.set(component.enableApiModeling) + } + // There are 2 transform flows for DEX: + // 1. (JACOCO_)CLASSES_DIR -> (JACOCO_)CLASSES -> DEX + // 2. (JACOCO_)CLASSES_JAR -> (JACOCO_)CLASSES -> DEX + // + // For incremental dexing, when requesting DEX the consumer will indicate a + // preference for CLASSES_DIR over CLASSES_JAR (see DexMergingTask), otherwise + // Gradle will select CLASSES_JAR by default. + // + // However, there could be an issue if CLASSES_DIR is selected: For Java libraries + // using Kotlin, CLASSES_DIR has two separate directories: one for compiled Java + // classes and one for compiled Kotlin classes. Classes in one directory may + // reference classes in the other directory, but each directory is transformed to + // DEX independently. Therefore, if dexing requires a classpath (desugaring is + // enabled and minSdk < 24), desugaring may not work correctly. + // + // Android libraries do not have this issue, as their CLASSES_DIR is one directory + // containing both Java and Kotlin classes. + // + // Therefore, to ensure correctness in all cases, we transform CLASSES to DEX only + // when dexing does not require a classpath, and it is not for main and androidTest + // components in dynamic feature module(b/246326007). Otherwise, we transform + // CLASSES_JAR to DEX directly so that CLASSES_DIR will not be selected. + // + // In the case that the JacocoTransform is executed, the Jacoco equivalent artifact is + // used. These artifacts are the same as CLASSES, CLASSES_JAR and ASM_INSTRUMENTED_JARS, + // but they have been offline instrumented by Jacoco and include Jacoco dependencies. + val inputArtifactType: AndroidArtifacts.ArtifactType = + if (component.useJacocoTransformInstrumentation) { + when { + component.dependenciesClassesAreInstrumented -> + AndroidArtifacts.ArtifactType.JACOCO_ASM_INSTRUMENTED_JARS + !component.needsClasspath && !allComponents.disableIncrementalDexing -> + AndroidArtifacts.ArtifactType.JACOCO_CLASSES + else -> + AndroidArtifacts.ArtifactType.JACOCO_CLASSES_JAR + } + } else { + when { + component.dependenciesClassesAreInstrumented -> + AndroidArtifacts.ArtifactType.ASM_INSTRUMENTED_JARS + !component.needsClasspath && !allComponents.disableIncrementalDexing -> + AndroidArtifacts.ArtifactType.CLASSES + else -> + AndroidArtifacts.ArtifactType.CLASSES_JAR + } + } + + if (component.useFullClasspath) { + val componentName = component.componentIfUsingFullClasspath!! + val creationConfig = allComponents.components.first { it.name == componentName } + (spec.parameters as DexingWithFullClasspathTransform.Parameters).externalArtifacts.from( + creationConfig.variantDependencies.getArtifactCollection( + AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, + AndroidArtifacts.ArtifactScope.EXTERNAL, + inputArtifactType + ).artifactFiles + ) + } + + spec.from.attribute(ARTIFACT_TYPE_ATTRIBUTE, inputArtifactType.type) + if (component.enableGlobalSynthetics) { + spec.to.attribute( + ARTIFACT_TYPE_ATTRIBUTE, + AndroidArtifacts.ArtifactType.D8_OUTPUTS.type + ) + } else { + spec.to.attribute(ARTIFACT_TYPE_ATTRIBUTE, AndroidArtifacts.ArtifactType.DEX.type) + } + + component.getAttributes().apply { + addAttributesToContainer(spec.from) + addAttributesToContainer(spec.to) + } + } } - } - fun getAttributes(): AndroidAttributes { - return AndroidAttributes( - mapOf( - ATTR_MIN_SDK to minSdk.toString(), - ATTR_IS_DEBUGGABLE to isDebuggable.toString(), - ATTR_ENABLE_DESUGARING to enableDesugaring.toString(), - ATTR_ENABLE_JACOCO_INSTRUMENTATION to useJacocoTransformInstrumentation.toString(), - ATTR_ASM_TRANSFORMED_VARIANT to (asmTransformedVariant ?: "NONE") - ) - ) - } } -val ATTR_MIN_SDK: Attribute = Attribute.of("dexing-min-sdk", String::class.java) -val ATTR_IS_DEBUGGABLE: Attribute = - Attribute.of("dexing-is-debuggable", String::class.java) -val ATTR_ENABLE_DESUGARING: Attribute = - Attribute.of("dexing-enable-desugaring", String::class.java) -val ATTR_ENABLE_JACOCO_INSTRUMENTATION: Attribute = - Attribute.of("dexing-enable-jacoco-instrumentation", String::class.java) - private val logger = LoggerWrapper.getLogger(BaseDexingTransform::class.java) private const val DESUGAR_GRAPH_FILE_NAME = "desugar_graph.bin" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3bccdef..7facc7b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] -agp = "8.2.2" -android-tools = "31.2.1" # agp version + 23.0.0 +agp = "8.3.0" # keep in sync with android-tools +android-tools = "31.3.0" # = 23.0.0 + agp compilerTesting = "0.2.1" compose = "1.4.7" kotlin = "1.8.21" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7c4d75a..216f4e3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Oct 20 09:50:20 EDT 2022 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists