| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.build.gradle.internal.ide.v2 |
| |
| import com.android.SdkConstants |
| import com.android.Version |
| import com.android.build.api.artifact.ScopedArtifact |
| import com.android.build.api.component.impl.DeviceTestImpl |
| import com.android.build.api.component.impl.ScreenshotTestImpl |
| import com.android.build.api.component.impl.UnitTestImpl |
| import com.android.build.api.dsl.AndroidResources |
| import com.android.build.api.dsl.ApplicationExtension |
| import com.android.build.api.dsl.BuildFeatures |
| import com.android.build.api.dsl.BuildType |
| import com.android.build.api.dsl.CommonExtension |
| import com.android.build.api.dsl.DefaultConfig |
| import com.android.build.api.dsl.Installation |
| import com.android.build.api.dsl.ProductFlavor |
| import com.android.build.api.dsl.TestExtension |
| import com.android.build.api.variant.ScopedArtifacts.Scope.ALL |
| import com.android.build.api.variant.ScopedArtifacts.Scope.PROJECT |
| import com.android.build.api.variant.impl.BuiltArtifactsImpl |
| import com.android.build.api.variant.impl.HasTestFixtures |
| import com.android.build.api.variant.impl.InternalHasDeviceTests |
| import com.android.build.api.variant.impl.HasHostTestsCreationConfig |
| import com.android.build.gradle.internal.component.DeviceTestCreationConfig |
| import com.android.build.gradle.internal.component.ApkCreationConfig |
| import com.android.build.gradle.internal.component.ApplicationCreationConfig |
| import com.android.build.gradle.internal.component.ComponentCreationConfig |
| import com.android.build.gradle.internal.component.ConsumableCreationConfig |
| import com.android.build.gradle.internal.component.LibraryCreationConfig |
| import com.android.build.gradle.internal.component.TestVariantCreationConfig |
| import com.android.build.gradle.internal.component.VariantCreationConfig |
| import com.android.build.gradle.internal.dsl.CommonExtensionImpl |
| import com.android.build.gradle.internal.dsl.ModulePropertyKey |
| import com.android.build.gradle.internal.errors.SyncIssueReporterImpl.GlobalSyncIssueService |
| import com.android.build.gradle.internal.ide.DependencyFailureHandler |
| import com.android.build.gradle.internal.ide.Utils.getGeneratedResourceFolders |
| import com.android.build.gradle.internal.ide.Utils.getGeneratedSourceFolders |
| import com.android.build.gradle.internal.ide.Utils.getGeneratedSourceFoldersForUnitTests |
| import com.android.build.gradle.internal.ide.dependencies.FullDependencyGraphBuilder |
| import com.android.build.gradle.internal.ide.dependencies.GraphEdgeCache |
| import com.android.build.gradle.internal.ide.dependencies.LibraryService |
| import com.android.build.gradle.internal.ide.dependencies.LibraryServiceImpl |
| import com.android.build.gradle.internal.ide.dependencies.getArtifactsForModelBuilder |
| import com.android.build.gradle.internal.ide.dependencies.getVariantName |
| import com.android.build.gradle.internal.lint.getLocalCustomLintChecksForModel |
| import com.android.build.gradle.internal.publishing.AndroidArtifacts |
| import com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.PROVIDED_CLASSPATH |
| import com.android.build.gradle.internal.scope.BuildFeatureValues |
| import com.android.build.gradle.internal.scope.InternalArtifactType |
| import com.android.build.gradle.internal.scope.InternalArtifactType.UNIT_TEST_CONFIG_DIRECTORY |
| import com.android.build.gradle.internal.scope.MutableTaskContainer |
| import com.android.build.gradle.internal.services.getBuildService |
| import com.android.build.gradle.internal.tasks.AnchorTaskNames |
| import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask |
| import com.android.build.gradle.internal.tasks.ExportConsumerProguardFilesTask.Companion.checkProguardFiles |
| import com.android.build.gradle.internal.tasks.ExtractPrivacySandboxCompatApks |
| import com.android.build.gradle.internal.tasks.GenerateAdditionalApkSplitForDeploymentViaApk |
| import com.android.build.gradle.internal.tasks.getPublishedCustomLintChecks |
| import com.android.build.gradle.internal.utils.getDesugarLibConfigFile |
| import com.android.build.gradle.internal.utils.getDesugaredMethods |
| import com.android.build.gradle.internal.utils.toImmutableSet |
| import com.android.build.gradle.internal.variant.VariantModel |
| import com.android.build.gradle.options.BooleanOption |
| import com.android.build.gradle.options.ProjectOptionService |
| import com.android.build.gradle.options.ProjectOptions |
| import com.android.build.gradle.tasks.BuildPrivacySandboxSdkApks |
| import com.android.builder.core.ComponentTypeImpl |
| import com.android.builder.errors.IssueReporter |
| import com.android.builder.model.SyncIssue |
| import com.android.builder.model.v2.ide.AndroidArtifact |
| import com.android.builder.model.v2.ide.AndroidGradlePluginProjectFlags.BooleanFlag |
| import com.android.builder.model.v2.ide.ArtifactDependencies |
| import com.android.builder.model.v2.ide.ArtifactDependenciesAdjacencyList |
| import com.android.builder.model.v2.ide.BasicArtifact |
| import com.android.builder.model.v2.ide.BundleInfo |
| import com.android.builder.model.v2.ide.BytecodeTransformation |
| import com.android.builder.model.v2.ide.CodeShrinker |
| import com.android.builder.model.v2.ide.JavaArtifact |
| import com.android.builder.model.v2.ide.PrivacySandboxSdkInfo |
| import com.android.builder.model.v2.ide.SourceProvider |
| import com.android.builder.model.v2.ide.SourceSetContainer |
| import com.android.builder.model.v2.ide.TestInfo |
| import com.android.builder.model.v2.ide.TestedTargetVariant |
| import com.android.builder.model.v2.models.AndroidDsl |
| import com.android.builder.model.v2.models.AndroidProject |
| import com.android.builder.model.v2.models.BasicAndroidProject |
| import com.android.builder.model.v2.models.ModelBuilderParameter |
| import com.android.builder.model.v2.models.ProjectSyncIssues |
| import com.android.builder.model.v2.models.VariantDependencies |
| import com.android.builder.model.v2.models.VariantDependenciesAdjacencyList |
| import com.android.builder.model.v2.models.Versions |
| import com.google.common.collect.ImmutableList |
| import com.google.common.collect.ImmutableMap |
| import com.google.common.collect.ImmutableSet |
| import org.gradle.api.Project |
| import org.gradle.tooling.provider.model.ParameterizedToolingModelBuilder |
| import java.io.File |
| import java.io.FileInputStream |
| import java.io.IOException |
| import java.io.Serializable |
| import javax.xml.namespace.QName |
| import javax.xml.stream.XMLInputFactory |
| import javax.xml.stream.XMLStreamException |
| import javax.xml.stream.events.EndElement |
| |
| class ModelBuilder< |
| BuildFeaturesT : BuildFeatures, |
| BuildTypeT : BuildType, |
| DefaultConfigT : DefaultConfig, |
| ProductFlavorT : ProductFlavor, |
| AndroidResourcesT : AndroidResources, |
| InstallationT : Installation, |
| ExtensionT : CommonExtension< |
| BuildFeaturesT, |
| BuildTypeT, |
| DefaultConfigT, |
| ProductFlavorT, |
| AndroidResourcesT, |
| InstallationT>>( |
| private val project: Project, |
| private val variantModel: VariantModel, |
| private val extension: ExtensionT, |
| ) : ParameterizedToolingModelBuilder<ModelBuilderParameter> { |
| |
| override fun getParameterType(): Class<ModelBuilderParameter> { |
| return ModelBuilderParameter::class.java |
| } |
| |
| override fun canBuild(className: String): Boolean { |
| return className == Versions::class.java.name |
| || className == BasicAndroidProject::class.java.name |
| || className == AndroidProject::class.java.name |
| || className == AndroidDsl::class.java.name |
| || className == VariantDependencies::class.java.name |
| || className == VariantDependenciesAdjacencyList::class.java.name |
| || className == ProjectSyncIssues::class.java.name |
| } |
| |
| /** |
| * Non-parameterized model query. Valid for all but the VariantDependencies model |
| */ |
| override fun buildAll(className: String, project: Project): Any = when (className) { |
| Versions::class.java.name -> buildModelVersions() |
| BasicAndroidProject::class.java.name -> buildBasicAndroidProjectModel(project) |
| AndroidProject::class.java.name -> buildAndroidProjectModel(project) |
| AndroidDsl::class.java.name -> buildAndroidDslModel(project) |
| ProjectSyncIssues::class.java.name -> buildProjectSyncIssueModel(project) |
| VariantDependencies::class.java.name, |
| VariantDependenciesAdjacencyList::class.java.name -> throw RuntimeException( |
| "Please use parameterized Tooling API to obtain ${className.split(".").last()} model." |
| ) |
| else -> throw RuntimeException("Does not support model '$className'") |
| } |
| |
| /** |
| * Parameterized model query. Valid only for the VariantDependencies model |
| */ |
| override fun buildAll( |
| className: String, |
| parameter: ModelBuilderParameter, |
| project: Project |
| ): Any? = when (className) { |
| VariantDependencies::class.java.name -> buildVariantDependenciesModel(project, parameter) |
| VariantDependenciesAdjacencyList::class.java.name -> buildVariantDependenciesModel(project, parameter, adjacencyList=true) |
| Versions::class.java.name, |
| AndroidProject::class.java.name, |
| AndroidDsl::class.java.name, |
| ProjectSyncIssues::class.java.name -> throw RuntimeException( |
| "Please use non-parameterized Tooling API to obtain $className model." |
| ) |
| else -> throw RuntimeException("Does not support model '$className'") |
| } |
| |
| private fun buildModelVersions(): Versions { |
| /** |
| * This is currently unused, but could be used in the future to allow for softer breaking |
| * changes, where some models are unavailable, but the project can still be imported. |
| */ |
| val v2Version = VersionImpl(0,1) |
| /** |
| * The version of the model-producing code (i.e. the model builders in AGP) |
| * |
| * The minor version is increased every time an addition is made to the model interfaces, |
| * or other semantic change that the code injected by studio should react to, such as |
| * instructing studio to stop calling a method that returns a collection that is no longer |
| * populated. |
| * |
| * The major version is increased, and the minor version reset to 0 every time AGP is |
| * branched to allow for changes in that branch to be modelled. |
| * |
| * Changes made to the model must always be compatible with the MINIMUM_MODEL_CONSUMER |
| * version of Android Studio. To make a breaking change, such as removing an older model |
| * method not called by current versions of Studio, the MINIMUM_MODEL_CONSUMER version must |
| * be increased to exclude all older versions of Studio that called that method. |
| */ |
| val modelProducer = VersionImpl(8, 9, humanReadable = "Android Gradle Plugin 8.4") |
| /** |
| * The minimum required model consumer version, to allow AGP to control support for older |
| * versions of Android Studio. |
| * |
| * Android Studio's model consumer version will be incremented after each release branching. |
| * |
| * The below Android Gradle plugin's minimumModelConsumerVersion will usually be increased |
| * after the next version of Studio becomes stable, dropping support for previous |
| * Android Studio versions. |
| */ |
| val minimumModelConsumerVersion = VersionImpl(major = 66, minor = 1, humanReadable = "Android Studio Iguana") |
| return VersionsImpl( |
| agp = Version.ANDROID_GRADLE_PLUGIN_VERSION, |
| versions = mutableMapOf<String, Versions.Version>( |
| Versions.BASIC_ANDROID_PROJECT to v2Version, |
| Versions.ANDROID_PROJECT to v2Version, |
| Versions.ANDROID_DSL to v2Version, |
| Versions.VARIANT_DEPENDENCIES to v2Version, |
| Versions.NATIVE_MODULE to v2Version, |
| Versions.MODEL_PRODUCER to modelProducer, |
| Versions.MINIMUM_MODEL_CONSUMER to minimumModelConsumerVersion |
| ) |
| ) |
| } |
| |
| /** |
| * Indicates the dimensions used for a variant |
| */ |
| private data class DimensionInformation( |
| val buildTypes: Set<String>, |
| val flavors: Set<Pair<String, String>> |
| ) { |
| fun isNotEmpty(): Boolean = buildTypes.isNotEmpty() || flavors.isNotEmpty() |
| |
| companion object { |
| fun createFrom(components: Collection<ComponentCreationConfig>): DimensionInformation { |
| val buildTypes = mutableSetOf<String>() |
| val flavors = mutableSetOf<Pair<String, String>>() |
| |
| for (component in components) { |
| component.buildType?.let { buildTypes.add(it) } |
| flavors.addAll(component.productFlavors) |
| } |
| |
| return DimensionInformation(buildTypes, flavors) |
| } |
| } |
| } |
| |
| private fun buildBasicAndroidProjectModel(project: Project): BasicAndroidProject { |
| // Cannot be injected, as the project might not be the same as the project used to construct |
| // the model builder e.g. when lint explicitly builds the model. |
| val projectOptions = |
| getBuildService(project.gradle.sharedServices, ProjectOptionService::class.java) |
| .get().projectOptions |
| |
| val sdkSetupCorrectly = variantModel.versionedSdkLoader.get().sdkSetupCorrectly.get() |
| |
| // Get the boot classpath. This will ensure the target is configured. |
| val bootClasspath = if (sdkSetupCorrectly) { |
| variantModel.filteredBootClasspath.get().map { it.asFile } |
| } else { |
| // SDK not set up, error will be reported as a sync issue. |
| emptyList() |
| } |
| |
| val variantInputs = variantModel.inputs |
| |
| val variants = variantModel.variants |
| |
| // compute for each main, androidTest, unitTest and testFixtures which buildType and flavors |
| // they applied to. This will allow excluding from the model sourcesets that are not |
| // used by any of them. |
| // Not doing this is confusing to users as they see folders marked as source that aren't |
| // used by anything. |
| val variantDimensionInfo = DimensionInformation.createFrom(variants) |
| val androidTests = DimensionInformation.createFrom(variantModel.testComponents.filterIsInstance<DeviceTestCreationConfig>()) |
| val unitTests = DimensionInformation.createFrom(variantModel.testComponents.filterIsInstance<UnitTestImpl>()) |
| val testFixtures = DimensionInformation.createFrom(variants.mapNotNull { (it as? HasTestFixtures)?.testFixtures }) |
| val screenshotTests = DimensionInformation.createFrom(variantModel.testComponents.filterIsInstance<ScreenshotTestImpl>()) |
| |
| // for now grab the first buildFeatureValues as they cannot be different. |
| val buildFeatures = variantModel.buildFeatures |
| |
| // gather the default config |
| val defaultConfigData = variantInputs.defaultConfigData |
| val defaultConfig = if (variantDimensionInfo.isNotEmpty()) { |
| |
| SourceSetContainerImpl( |
| sourceProvider = defaultConfigData.sourceSet.convert(buildFeatures), |
| deviceTestSourceProviders = mutableMapOf<String, SourceProvider>().apply { |
| defaultConfigData.getSourceSetForModel(ComponentTypeImpl.ANDROID_TEST) |
| ?.takeIf { androidTests.isNotEmpty() } |
| ?.convert(buildFeatures) |
| ?.let { this.put(ComponentTypeImpl.ANDROID_TEST.artifactName, it) } |
| }, |
| hostTestSourceProviders = mutableMapOf<String, SourceProvider>().apply { |
| defaultConfigData.getSourceSetForModel(ComponentTypeImpl.UNIT_TEST) |
| ?.takeIf { unitTests.isNotEmpty() } |
| ?.convert(buildFeatures) |
| ?.let { this.put(ComponentTypeImpl.UNIT_TEST.artifactName, it) } |
| defaultConfigData.getSourceSetForModel(ComponentTypeImpl.SCREENSHOT_TEST) |
| ?.takeIf { screenshotTests.isNotEmpty() } |
| ?.convert(buildFeatures) |
| ?.let { this.put(ComponentTypeImpl.SCREENSHOT_TEST.artifactName, it) } |
| }, |
| testFixturesSourceProvider = defaultConfigData.getSourceSetForModel(ComponentTypeImpl.TEST_FIXTURES) |
| ?.takeIf { testFixtures.isNotEmpty() } |
| ?.convert(buildFeatures) |
| ) |
| } else null |
| |
| // gather all the build types |
| val buildTypes = mutableListOf<SourceSetContainer>() |
| for (buildType in variantInputs.buildTypes.values) { |
| val buildTypeName = buildType.buildType.name |
| |
| if (variantDimensionInfo.buildTypes.contains(buildTypeName)) { |
| // Mixin works only when there are no flavours. |
| // When a flavour is there source provider will be initialized |
| // with variant sources. |
| val mixinVariantSources: VariantCreationConfig? = |
| if (variantInputs.productFlavors.values.isEmpty()) { |
| variants.find { it.name == buildTypeName } |
| } else null |
| buildTypes.add( |
| SourceSetContainerImpl( |
| sourceProvider = buildType.sourceSet.convert(buildFeatures, mixinVariantSources), |
| deviceTestSourceProviders = mutableMapOf<String, SourceProvider>().apply { |
| buildType.getSourceSetForModel(ComponentTypeImpl.ANDROID_TEST) |
| ?.takeIf { androidTests.buildTypes.contains(buildTypeName) } |
| ?.convert(buildFeatures) |
| ?.let { |
| this.put(ComponentTypeImpl.ANDROID_TEST.artifactName, it) |
| } |
| }, |
| hostTestSourceProviders = mutableMapOf<String, SourceProvider>().apply { |
| buildType.getSourceSetForModel(ComponentTypeImpl.UNIT_TEST) |
| ?.takeIf { unitTests.buildTypes.contains(buildTypeName) } |
| ?.convert(buildFeatures) |
| ?.let { this.put(ComponentTypeImpl.UNIT_TEST.artifactName, it) } |
| buildType.getSourceSetForModel(ComponentTypeImpl.SCREENSHOT_TEST) |
| ?.takeIf { screenshotTests.buildTypes.contains(buildTypeName) } |
| ?.convert(buildFeatures) |
| ?.let { this.put(ComponentTypeImpl.SCREENSHOT_TEST.artifactName, it) } |
| }, |
| testFixturesSourceProvider = |
| buildType.getSourceSetForModel(ComponentTypeImpl.TEST_FIXTURES) |
| ?.takeIf { testFixtures.buildTypes.contains(buildTypeName) } |
| ?.convert(buildFeatures) |
| ) |
| ) |
| } |
| } |
| |
| // gather product flavors |
| val productFlavors = mutableListOf<SourceSetContainer>() |
| for (flavor in variantInputs.productFlavors.values) { |
| val flavorDimensionName = flavor.productFlavor.dimension to flavor.productFlavor.name |
| |
| if (variantDimensionInfo.flavors.contains(flavorDimensionName)) { |
| productFlavors.add( |
| SourceSetContainerImpl( |
| sourceProvider = flavor.sourceSet.convert(buildFeatures), |
| deviceTestSourceProviders = mutableMapOf<String, SourceProvider>().apply { |
| flavor.getSourceSetForModel(ComponentTypeImpl.ANDROID_TEST) |
| ?.takeIf { androidTests.flavors.contains(flavorDimensionName)} |
| ?.convert(buildFeatures) |
| ?.let { |
| this.put(ComponentTypeImpl.ANDROID_TEST.artifactName, it) |
| } |
| }, |
| hostTestSourceProviders = mutableMapOf<String, SourceProvider>().apply { |
| flavor.getSourceSetForModel(ComponentTypeImpl.UNIT_TEST) |
| ?.takeIf { unitTests.flavors.contains(flavorDimensionName) } |
| ?.convert(buildFeatures) |
| ?.let { this.put(ComponentTypeImpl.UNIT_TEST.artifactName, it) } |
| flavor.getSourceSetForModel(ComponentTypeImpl.SCREENSHOT_TEST) |
| ?.takeIf { screenshotTests.flavors.contains(flavorDimensionName) } |
| ?.convert(buildFeatures) |
| ?.let { this.put(ComponentTypeImpl.SCREENSHOT_TEST.artifactName, it) } |
| }, |
| testFixturesSourceProvider = |
| flavor.getSourceSetForModel(ComponentTypeImpl.TEST_FIXTURES) |
| ?.takeIf { testFixtures.flavors.contains(flavorDimensionName) } |
| ?.convert(buildFeatures) |
| ) |
| ) |
| } |
| } |
| |
| // gather variants |
| val variantList = variants.map { createBasicVariant(it, buildFeatures) } |
| |
| return BasicAndroidProjectImpl( |
| path = project.path, |
| buildFolder = project.layout.buildDirectory.get().asFile, |
| |
| projectType = variantModel.projectType, |
| |
| mainSourceSet = defaultConfig, |
| buildTypeSourceSets = buildTypes, |
| productFlavorSourceSets = productFlavors, |
| |
| variants = variantList, |
| |
| bootClasspath = bootClasspath, |
| ) |
| } |
| |
| private fun buildAndroidProjectModel(project: Project): AndroidProject { |
| val variants = variantModel.variants |
| |
| // Keep track of the result of parsing each manifest for instant app value. |
| // This prevents having to reparse the |
| val instantAppResultMap = mutableMapOf<File, Boolean>() |
| |
| // gather variants |
| var namespace: String? = null |
| var androidTestNamespace: String? = null |
| var testFixturesNamespace: String? = null |
| val variantList = variants.map { |
| namespace = it.namespace.get() |
| if (androidTestNamespace == null && it is InternalHasDeviceTests) { |
| it.defaultDeviceTest?.let { androidTest -> |
| androidTestNamespace = androidTest.namespace.get() |
| } |
| } |
| if (testFixturesNamespace == null && it is HasTestFixtures) { |
| testFixturesNamespace = it.testFixtures?.namespace?.get() |
| } |
| |
| checkProguardFiles(it) |
| |
| createVariant(it, instantAppResultMap) |
| } |
| |
| val desugarLibConfig = if (extension.compileOptions.isCoreLibraryDesugaringEnabled) |
| getDesugarLibConfigFile(project) |
| else |
| listOf() |
| |
| return AndroidProjectImpl( |
| namespace = namespace ?: "", |
| androidTestNamespace = androidTestNamespace, |
| testFixturesNamespace = testFixturesNamespace, |
| variants = variantList, |
| javaCompileOptions = extension.compileOptions.convert(), |
| resourcePrefix = extension.resourcePrefix, |
| dynamicFeatures = (extension as? ApplicationExtension)?.dynamicFeatures?.toImmutableSet(), |
| viewBindingOptions = ViewBindingOptionsImpl( |
| variantModel.variants.any { it.buildFeatures.viewBinding } |
| ), |
| flags = getAgpFlags( |
| variants = variantModel.variants, |
| projectOptions = variantModel.projectOptions |
| |
| ), |
| lintChecksJars = getLocalCustomLintChecksForModel(project, variantModel.syncIssueReporter), |
| desugarLibConfig = desugarLibConfig, |
| // Using first as we are going to use the global artifacts anyway |
| lintJar = variantModel.variants.firstOrNull()?.global?.getPublishedCustomLintChecks()?.files?.singleOrNull() |
| ) |
| } |
| |
| private fun checkProguardFiles(component: VariantCreationConfig) { |
| // We check for default files unless it's a base module, which can include default files. |
| val isBaseModule = component.componentType.isBaseModule |
| val isDynamicFeature = component.componentType.isDynamicFeature |
| if (!isBaseModule) { |
| checkProguardFiles( |
| project.layout.buildDirectory, |
| isDynamicFeature, |
| component.optimizationCreationConfig.consumerProguardFilePaths |
| ) { errorMessage: String -> variantModel |
| .syncIssueReporter |
| .reportError(IssueReporter.Type.GENERIC, errorMessage) |
| } |
| } |
| } |
| |
| private fun buildAndroidDslModel(project: Project): AndroidDsl { |
| |
| val variantInputs = variantModel.inputs |
| |
| // for now grab the first buildFeatureValues as they cannot be different. |
| val buildFeatures = variantModel.buildFeatures |
| |
| // gather the default config |
| val defaultConfig = variantInputs.defaultConfigData.defaultConfig.convert(buildFeatures) |
| |
| // gather all the build types |
| val buildTypes = mutableListOf<com.android.builder.model.v2.dsl.BuildType>() |
| for (buildType in variantInputs.buildTypes.values) { |
| buildTypes.add(buildType.buildType.convert(buildFeatures)) |
| } |
| |
| // gather product flavors |
| val productFlavors = mutableListOf<com.android.builder.model.v2.dsl.ProductFlavor>() |
| for (flavor in variantInputs.productFlavors.values) { |
| productFlavors.add(flavor.productFlavor.convert(buildFeatures)) |
| } |
| |
| val dependenciesInfo = |
| if (extension is ApplicationExtension) { |
| DependenciesInfoImpl( |
| extension.dependenciesInfo.includeInApk, |
| extension.dependenciesInfo.includeInBundle |
| ) |
| } else null |
| |
| val extensionImpl = |
| extension as? CommonExtensionImpl<*, *, *, *, *, *> |
| ?: throw RuntimeException("Wrong extension provided to v2 ModelBuilder") |
| val compileSdkVersion = extensionImpl.compileSdkVersion ?: "unknown" |
| |
| return AndroidDslImpl( |
| buildToolsVersion = extension.buildToolsVersion, |
| groupId = project.group.toString(), |
| compileTarget = compileSdkVersion, |
| defaultConfig = defaultConfig, |
| buildTypes = buildTypes, |
| flavorDimensions = ImmutableList.copyOf(extension.flavorDimensions), |
| productFlavors = productFlavors, |
| signingConfigs = extension.signingConfigs.map { it.convert() }, |
| aaptOptions = extension.androidResources.convert(), |
| lintOptions = extension.lint.convert(), |
| installation = extension.installation.convert(), |
| dependenciesInfo = dependenciesInfo, |
| ) |
| } |
| |
| private fun buildProjectSyncIssueModel(project: Project): ProjectSyncIssues { |
| variantModel.syncIssueReporter.lockHandler() |
| |
| val allIssues = ImmutableSet.builder<SyncIssue>() |
| allIssues.addAll(variantModel.syncIssueReporter.syncIssues) |
| allIssues.addAll( |
| getBuildService(project.gradle.sharedServices, GlobalSyncIssueService::class.java) |
| .get() |
| .getAllIssuesAndClear() |
| ) |
| |
| // For now we have to convert from the v1 to the v2 model. |
| // FIXME: separate the internal-AGP and builder-model version of the SyncIssue classes |
| val issues = allIssues.build().map { |
| SyncIssueImpl( |
| it.severity, |
| it.type, |
| it.data, |
| it.message, |
| it.multiLineMessage |
| ) |
| } |
| |
| return ProjectSyncIssuesImpl(issues) |
| } |
| |
| private fun buildVariantDependenciesModel( |
| project: Project, |
| parameter: ModelBuilderParameter, |
| adjacencyList: Boolean = false |
| ): Serializable? { |
| // get the variant to return the dependencies for |
| val variantName = parameter.variantName |
| val variant = variantModel.variants |
| .singleOrNull { it.name == variantName } |
| ?: return null |
| |
| val globalLibraryBuildService = |
| getBuildService( |
| project.gradle.sharedServices, |
| GlobalSyncService::class.java |
| ).get() |
| |
| val graphEdgeCache = globalLibraryBuildService.graphEdgeCache |
| val libraryService = LibraryServiceImpl(globalLibraryBuildService.libraryCache) |
| |
| if (adjacencyList) { |
| val deviceTestArtifacts = mutableMapOf<String, ArtifactDependenciesAdjacencyList>() |
| (variant as? InternalHasDeviceTests)?.deviceTests?.filterIsInstance<DeviceTestImpl>()?.forEach { |
| deviceTestArtifacts[it.artifactName] = |
| createDependenciesWithAdjacencyList( |
| it, |
| libraryService, |
| graphEdgeCache, |
| parameter.dontBuildAndroidTestRuntimeClasspath |
| ) |
| } |
| val hostTestArtifacts = mutableMapOf<String, ArtifactDependenciesAdjacencyList>() |
| (variant as? HasHostTestsCreationConfig)?.hostTests?.values?.forEach { hostTest -> |
| hostTestArtifacts[hostTest.componentType.artifactName] = |
| createDependenciesWithAdjacencyList( |
| hostTest, |
| libraryService, |
| graphEdgeCache, |
| parameter.dontBuildUnitTestRuntimeClasspath |
| ) |
| } |
| |
| return VariantDependenciesAdjacencyListImpl( |
| name = variantName, |
| mainArtifact = createDependenciesWithAdjacencyList( |
| variant, |
| libraryService, |
| graphEdgeCache, |
| parameter.dontBuildRuntimeClasspath |
| ), |
| deviceTestArtifacts = deviceTestArtifacts, |
| hostTestArtifacts = hostTestArtifacts, |
| testFixturesArtifact = (variant as? HasTestFixtures)?.testFixtures?.let { |
| createDependenciesWithAdjacencyList( |
| it, |
| libraryService, |
| graphEdgeCache, |
| parameter.dontBuildTestFixtureRuntimeClasspath |
| ) |
| }, |
| libraryService.getAllLibraries() |
| ) |
| } else { |
| val deviceTestArtifacts = mutableMapOf<String, ArtifactDependencies>() |
| (variant as? InternalHasDeviceTests)?.deviceTests?.filterIsInstance<DeviceTestImpl>()?.forEach { |
| deviceTestArtifacts[it.artifactName] = |
| createDependencies( |
| it, |
| libraryService, |
| parameter.dontBuildAndroidTestRuntimeClasspath |
| ) |
| } |
| val hostTestArtifacts = mutableMapOf<String, ArtifactDependencies>() |
| (variant as? HasHostTestsCreationConfig)?.hostTests?.values?.forEach { |
| hostTestArtifacts[it.componentType.artifactName] = createDependencies( |
| it, |
| libraryService, |
| parameter.dontBuildHostTestRuntimeClasspath[it.componentType.suffix] ?: false, |
| ) |
| } |
| return VariantDependenciesImpl( |
| name = variantName, |
| mainArtifact = createDependencies( |
| variant, |
| libraryService, |
| parameter.dontBuildRuntimeClasspath |
| ), |
| deviceTestArtifacts = deviceTestArtifacts, |
| hostTestArtifacts = hostTestArtifacts, |
| testFixturesArtifact = (variant as? HasTestFixtures)?.testFixtures?.let { |
| createDependencies( |
| it, |
| libraryService, |
| parameter.dontBuildTestFixtureRuntimeClasspath |
| ) |
| }, |
| libraryService.getAllLibraries() |
| ) |
| } |
| } |
| |
| private fun createBasicVariant( |
| variant: VariantCreationConfig, |
| features: BuildFeatureValues |
| ): BasicVariantImpl { |
| val deviceTestArtifacts = mutableMapOf<String, BasicArtifact>() |
| (variant as? InternalHasDeviceTests)?.deviceTests?.filterIsInstance<DeviceTestImpl>()?.forEach { |
| deviceTestArtifacts[it.artifactName] = createBasicArtifact(it, features) |
| } |
| val hostTestArtifacts = mutableMapOf<String, BasicArtifact>() |
| (variant as? HasHostTestsCreationConfig)?.hostTests?.values?.forEach { hostTest -> |
| hostTestArtifacts[hostTest.componentType.artifactName] = |
| createBasicArtifact(hostTest, features) |
| |
| } |
| return BasicVariantImpl( |
| name = variant.name, |
| mainArtifact = createBasicArtifact(variant, features), |
| deviceTestArtifacts = deviceTestArtifacts, |
| hostTestArtifacts = hostTestArtifacts, |
| testFixturesArtifact = (variant as? HasTestFixtures)?.testFixtures?.let { |
| createBasicArtifact(it, features) |
| }, |
| buildType = variant.buildType, |
| productFlavors = variant.productFlavorList.map { it.name }, |
| ) |
| } |
| |
| private fun createBasicArtifact( |
| component: ComponentCreationConfig, |
| features: BuildFeatureValues |
| ): BasicArtifact { |
| return BasicArtifactImpl( |
| variantSourceProvider = component.sources.variantSourceProvider?.convert( |
| component.sources |
| ), |
| multiFlavorSourceProvider = component.sources.multiFlavorSourceProvider?.convert( |
| features |
| ), |
| ) |
| } |
| |
| private fun createVariant( |
| variant: VariantCreationConfig, |
| instantAppResultMap: MutableMap<File, Boolean> |
| ): VariantImpl { |
| val deviceTestArtifacts = mutableMapOf<String, AndroidArtifact>() |
| (variant as? InternalHasDeviceTests)?.deviceTests?.filterIsInstance<DeviceTestImpl>()?.forEach { |
| deviceTestArtifacts[it.artifactName] = createAndroidArtifact(it) |
| } |
| val hostTestArtifacts = mutableMapOf<String, JavaArtifact>() |
| (variant as? HasHostTestsCreationConfig)?.hostTests?.values?.forEach { hostTest -> |
| hostTestArtifacts[hostTest.componentType.artifactName] = |
| createJavaArtifact(hostTest) |
| |
| } |
| return VariantImpl( |
| name = variant.name, |
| displayName = variant.baseName, |
| mainArtifact = createAndroidArtifact(variant), |
| deviceTestArtifacts = deviceTestArtifacts, |
| hostTestArtifacts = hostTestArtifacts, |
| testFixturesArtifact = (variant as? HasTestFixtures)?.testFixtures?.let { |
| createAndroidArtifact(it) |
| }, |
| testedTargetVariant = getTestTargetVariant(variant), |
| runTestInSeparateProcess = ModulePropertyKey.BooleanWithDefault.SELF_INSTRUMENTING.getValue( |
| variant.experimentalProperties.get()), |
| isInstantAppCompatible = inspectManifestForInstantTag(variant, instantAppResultMap), |
| desugaredMethods = getDesugaredMethods( |
| variant.services, |
| variant.isCoreLibraryDesugaringEnabledLintCheck, |
| variant.minSdk, |
| variant.global |
| ).files.toList(), |
| ) |
| } |
| |
| private fun createPrivacySandboxSdkInfo(component: ComponentCreationConfig): PrivacySandboxSdkInfo? { |
| if (component.privacySandboxCreationConfig == null) { |
| return null |
| } |
| if (component !is ApplicationCreationConfig) { |
| return null |
| } |
| val extractedApksFromPrivacySandboxIdeModel = |
| component.artifacts.get(InternalArtifactType.EXTRACTED_APKS_FROM_PRIVACY_SANDBOX_SDKs_IDE_MODEL).orNull?.asFile |
| ?: return null |
| val legacyExtractedApksForPrivacySandboxIdeModel = |
| component.artifacts.get(InternalArtifactType.APK_FROM_SDKS_IDE_MODEL).orNull?.asFile |
| ?: return null |
| val additionalApkSplitFile = |
| component.artifacts.get(InternalArtifactType.USES_SDK_LIBRARY_SPLIT_FOR_LOCAL_DEPLOYMENT).orNull?.file( |
| BuiltArtifactsImpl.METADATA_FILE_NAME)?.asFile |
| ?: return null |
| |
| return PrivacySandboxSdkInfoImpl( |
| task = BuildPrivacySandboxSdkApks.CreationAction.getTaskName(component), |
| outputListingFile = extractedApksFromPrivacySandboxIdeModel, |
| additionalApkSplitTask = GenerateAdditionalApkSplitForDeploymentViaApk.CreationAction.computeTaskName(component), |
| additionalApkSplitFile = additionalApkSplitFile, |
| taskLegacy = ExtractPrivacySandboxCompatApks.CreationAction.getTaskName(component), |
| outputListingLegacyFile = legacyExtractedApksForPrivacySandboxIdeModel |
| ) |
| } |
| |
| private fun createAndroidArtifact(component: ComponentCreationConfig): AndroidArtifactImpl { |
| val taskContainer: MutableTaskContainer = component.taskContainer |
| |
| // FIXME need to find a better way for this. We should be using PROJECT_CLASSES_DIRS. |
| // The class folders. This is supposed to be the output of the compilation steps + other |
| // steps that create bytecode |
| val classesFolders = mutableSetOf<File>() |
| classesFolders.add(component.artifacts.get(InternalArtifactType.JAVAC).get().asFile) |
| component.oldVariantApiLegacySupport?.let{ |
| classesFolders.addAll(it.variantData.allPreJavacGeneratedBytecode.files) |
| classesFolders.addAll(it.variantData.allPostJavacGeneratedBytecode.files) |
| } |
| component.androidResourcesCreationConfig?.compiledRClassArtifact?.get()?.asFile?.let { |
| classesFolders.add(it) |
| } |
| |
| val generatedClassPaths = addGeneratedClassPaths(component, classesFolders) |
| |
| val testInfo: TestInfo? = when(component) { |
| is TestVariantCreationConfig, is DeviceTestCreationConfig -> { |
| val runtimeApks: Collection<File> = project |
| .configurations |
| .findByName(SdkConstants.GRADLE_ANDROID_TEST_UTIL_CONFIGURATION)?.files |
| ?: listOf() |
| |
| DeviceProviderInstrumentTestTask.checkForNonApks(runtimeApks) { message -> |
| variantModel.syncIssueReporter.reportError(IssueReporter.Type.GENERIC, message) |
| } |
| |
| val testOptionsDsl = extension.testOptions |
| |
| val testTaskName = taskContainer.connectedTestTask?.name ?: "".also { |
| variantModel.syncIssueReporter.reportError( |
| IssueReporter.Type.GENERIC, |
| "unable to find connectedCheck task name for ${component.name}" |
| ) |
| } |
| |
| TestInfoImpl( |
| animationsDisabled = testOptionsDsl.animationsDisabled, |
| execution = testOptionsDsl.execution.convertToExecution(), |
| additionalRuntimeApks = runtimeApks, |
| instrumentedTestTaskName = testTaskName |
| ) |
| } |
| else -> null |
| } |
| |
| val signingConfig = if (component is ApkCreationConfig) |
| component.signingConfig else null |
| |
| val minSdkVersion = |
| ApiVersionImpl(component.minSdk.apiLevel, component.minSdk.codename) |
| val targetSdkVersionOverride = when (component) { |
| is ApkCreationConfig -> component.targetSdkOverride |
| is LibraryCreationConfig -> component.targetSdkOverride |
| else -> null |
| }?.let { ApiVersionImpl(it.apiLevel, it.codename) } |
| val maxSdkVersion = |
| if (component is VariantCreationConfig) component.maxSdk else null |
| |
| val coreLibDesugaring = (component as? ConsumableCreationConfig)?.isCoreLibraryDesugaringEnabledLintCheck |
| ?: false |
| val outputsAreSigned = component.oldVariantApiLegacySupport?.variantData?.outputsAreSigned ?: false |
| val isSigned = (signingConfig?.hasConfig() ?: false) || outputsAreSigned |
| |
| return AndroidArtifactImpl( |
| minSdkVersion = minSdkVersion, |
| targetSdkVersionOverride = targetSdkVersionOverride, |
| maxSdkVersion = maxSdkVersion, |
| |
| signingConfigName = signingConfig?.name, |
| isSigned = isSigned, |
| |
| applicationId = getApplicationId(component), |
| |
| abiFilters = (component as? ConsumableCreationConfig)?.nativeBuildCreationConfig?.supportedAbis ?: emptySet(), |
| testInfo = testInfo, |
| bundleInfo = getBundleInfo(component), |
| codeShrinker = CodeShrinker.R8.takeIf { |
| component is ConsumableCreationConfig && |
| component.optimizationCreationConfig.minifiedEnabled |
| }, |
| |
| assembleTaskName = taskContainer.assembleTask.name, |
| compileTaskName = taskContainer.compileTask.name, |
| sourceGenTaskName = taskContainer.sourceGenTask.name, |
| resGenTaskName = if (component.buildFeatures.androidResources) taskContainer.resourceGenTask.name else null, |
| ideSetupTaskNames = setOf(taskContainer.sourceGenTask.name), |
| |
| generatedSourceFolders = getGeneratedSourceFolders(component), |
| generatedResourceFolders = getGeneratedResourceFolders(component), |
| classesFolders = classesFolders, |
| assembleTaskOutputListingFile = if (component.componentType.isApk) |
| component.artifacts.get(InternalArtifactType.APK_IDE_REDIRECT_FILE).get().asFile |
| else |
| null, |
| privacySandboxSdkInfo = createPrivacySandboxSdkInfo(component), |
| desugaredMethodsFiles = getDesugaredMethods( |
| component.services, |
| coreLibDesugaring, |
| component.minSdk, |
| component.global |
| ).files.toList(), |
| generatedClassPaths = generatedClassPaths, |
| bytecodeTransformations = getBytecodeTransformations(component), |
| ) |
| } |
| |
| private fun getApplicationId(component: ComponentCreationConfig): String? { |
| if (!component.componentType.isApk || component.componentType.isDynamicFeature) { |
| return null |
| } |
| return try { |
| component.applicationId.orNull ?: "" |
| } catch (e: Exception) { |
| variantModel.syncIssueReporter.reportWarning( |
| IssueReporter.Type.APPLICATION_ID_MUST_NOT_BE_DYNAMIC, |
| RuntimeException("Failed to read applicationId for ${component.name}.\n" + |
| "Setting the application ID to the output of a task in the variant " + |
| "api is not supported", |
| e)) |
| "" |
| } |
| } |
| |
| private fun getBytecodeTransformations(component: ComponentCreationConfig): List<BytecodeTransformation> { |
| val jacoco = (component as? ApkCreationConfig)?.useJacocoTransformInstrumentation == true |
| val classesProject = component.artifacts.forScope(PROJECT).getScopedArtifactsContainer( |
| ScopedArtifact.CLASSES |
| ).artifactsAltered.get() |
| val classesAll = component.artifacts.forScope(ALL).getScopedArtifactsContainer( |
| ScopedArtifact.CLASSES |
| ).artifactsAltered.get() |
| |
| val asmProject = |
| component.instrumentationCreationConfig?.projectClassesAreInstrumented == true |
| val asmAll = |
| component.instrumentationCreationConfig?.dependenciesClassesAreInstrumented == true |
| return listOfNotNull( |
| BytecodeTransformation.JACOCO_INSTRUMENTATION.takeIf { jacoco }, |
| BytecodeTransformation.MODIFIES_PROJECT_CLASS_FILES.takeIf { classesProject }, |
| BytecodeTransformation.MODIFIES_ALL_CLASS_FILES.takeIf { classesAll }, |
| BytecodeTransformation.ASM_API_PROJECT.takeIf { asmProject }, |
| BytecodeTransformation.ASM_API_ALL.takeIf { asmAll }, |
| ) |
| } |
| |
| private fun createJavaArtifact(component: ComponentCreationConfig): JavaArtifact { |
| val taskContainer: MutableTaskContainer = component.taskContainer |
| |
| // FIXME need to find a better way for this. We should be using PROJECT_CLASSES_DIRS. |
| // The class folders. This is supposed to be the output of the compilation steps + other |
| // steps that create bytecode |
| val classesFolders = mutableSetOf<File>() |
| classesFolders.add(component.artifacts.get(InternalArtifactType.JAVAC).get().asFile) |
| component.oldVariantApiLegacySupport?.let{ |
| classesFolders.addAll(it.variantData.allPreJavacGeneratedBytecode.files) |
| classesFolders.addAll(it.variantData.allPostJavacGeneratedBytecode.files) |
| } |
| // The separately compile R class, if applicable. |
| if (extension.testOptions.unitTests.isIncludeAndroidResources || |
| component.componentType.isForScreenshotPreview) { |
| classesFolders.add(component.artifacts.get(UNIT_TEST_CONFIG_DIRECTORY).get().asFile) |
| } |
| // TODO(b/111168382): When namespaced resources is on, then the provider returns null, so let's skip for now and revisit later |
| if (!extension.androidResources.namespaced) { |
| component.androidResourcesCreationConfig?.compiledRClassArtifact?.get()?.asFile?.let { |
| classesFolders.add(it) |
| } |
| } |
| |
| val generatedClassPaths = addGeneratedClassPaths(component, classesFolders) |
| |
| return JavaArtifactImpl( |
| assembleTaskName = taskContainer.assembleTask.name, |
| compileTaskName = taskContainer.compileTask.name, |
| ideSetupTaskNames = setOf(component.global.taskNames.createMockableJar), |
| |
| classesFolders = classesFolders, |
| generatedSourceFolders = getGeneratedSourceFoldersForUnitTests(component), |
| runtimeResourceFolder = |
| component.oldVariantApiLegacySupport!!.variantData.javaResourcesForUnitTesting, |
| |
| mockablePlatformJar = variantModel.mockableJarArtifact.files.singleOrNull(), |
| generatedClassPaths = generatedClassPaths, |
| bytecodeTransformations = getBytecodeTransformations(component), |
| ) |
| } |
| |
| private fun createDependencies( |
| component: ComponentCreationConfig, |
| libraryService: LibraryService, |
| dontBuildRuntimeClasspath: Boolean, |
| ) = getGraphBuilder(dontBuildRuntimeClasspath, component, libraryService).build() |
| |
| private fun createDependenciesWithAdjacencyList( |
| component: ComponentCreationConfig, |
| libraryService: LibraryService, |
| graphEdgeCache: GraphEdgeCache, |
| dontBuildRuntimeClasspath: Boolean |
| ): ArtifactDependenciesAdjacencyList = getGraphBuilder( |
| dontBuildRuntimeClasspath, |
| component, |
| libraryService, |
| graphEdgeCache |
| ).buildWithAdjacencyList() |
| |
| private fun getGraphBuilder( |
| dontBuildRuntimeClasspath: Boolean, |
| component: ComponentCreationConfig, |
| libraryService: LibraryService, |
| graphEdgeCache: GraphEdgeCache? = null, |
| ): FullDependencyGraphBuilder { |
| if (dontBuildRuntimeClasspath && component.variantDependencies.isLibraryConstraintsApplied) { |
| variantModel.syncIssueReporter.reportWarning( |
| IssueReporter.Type.GENERIC, """ |
| You have experimental IDE flag gradle.ide.gradle.skip.runtime.classpath.for.libraries enabled, |
| but AGP boolean option ${BooleanOption.EXCLUDE_LIBRARY_COMPONENTS_FROM_CONSTRAINTS.propertyName} is not used. |
| |
| Please set below in gradle.properties: |
| |
| ${BooleanOption.EXCLUDE_LIBRARY_COMPONENTS_FROM_CONSTRAINTS.propertyName}=true |
| |
| """.trimIndent() |
| ) |
| } |
| |
| return FullDependencyGraphBuilder( |
| { configType, root -> getArtifactsForModelBuilder(component, configType, root) }, |
| project.path, |
| component.variantDependencies, |
| libraryService, |
| graphEdgeCache, |
| component.services.projectOptions.get(BooleanOption.ADDITIONAL_ARTIFACTS_IN_MODEL), |
| dontBuildRuntimeClasspath |
| ) |
| } |
| |
| private fun getBundleInfo( |
| component: ComponentCreationConfig |
| ): BundleInfo? { |
| if (!component.componentType.isBaseModule) { |
| return null |
| } |
| |
| // TODO(b/111168382): Remove when bundle can build apps with namespaced turned on. |
| if (extension.androidResources.namespaced) { |
| return null |
| } |
| |
| // FIXME need to find a better way for this. |
| val taskContainer: MutableTaskContainer = component.taskContainer |
| val artifacts = component.artifacts |
| |
| return BundleInfoImpl( |
| bundleTaskName = taskContainer.bundleTask?.name ?: error("failed to find bundle task name for ${component.name}"), |
| bundleTaskOutputListingFile = artifacts.get(InternalArtifactType.BUNDLE_IDE_REDIRECT_FILE).get().asFile, |
| apkFromBundleTaskName = AnchorTaskNames.getExtractApksAnchorTaskName(component), |
| apkFromBundleTaskOutputListingFile = artifacts.get(InternalArtifactType.APK_FROM_BUNDLE_IDE_REDIRECT_FILE).get().asFile |
| ) |
| } |
| |
| // FIXME this is coming from the v1 Model Builder and this needs to be rethought. b/160970116 |
| private fun inspectManifestForInstantTag( |
| component: ComponentCreationConfig, |
| instantAppResultMap: MutableMap<File, Boolean> |
| ): Boolean { |
| if (!component.componentType.isBaseModule && !component.componentType.isDynamicFeature) { |
| return false |
| } |
| |
| // get the manifest in descending order of priority. First one to return |
| val manifests = mutableListOf<File>() |
| manifests.addAll(component.sources.manifestOverlayFiles.get().filter { it.isFile }) |
| val mainManifest = component.sources.manifestFile.get() |
| if (mainManifest.isFile) { |
| manifests.add(mainManifest) |
| } |
| |
| if (manifests.isEmpty()) { |
| return false |
| } |
| for (manifest in manifests) { |
| // check if the manifest was already parsed. If so, just pull the information |
| // from the map |
| val parseResult = instantAppResultMap[manifest] |
| if (parseResult != null) { |
| if (parseResult) { |
| return true |
| } |
| continue |
| } |
| |
| try { |
| FileInputStream(manifest).use { inputStream -> |
| val factory = XMLInputFactory.newInstance() |
| val eventReader = factory.createXMLEventReader(inputStream) |
| while (eventReader.hasNext() && !eventReader.peek().isEndDocument) { |
| val event = eventReader.nextTag() |
| if (event.isStartElement) { |
| val startElement = event.asStartElement() |
| if (startElement.name.namespaceURI == SdkConstants.DIST_URI |
| && startElement.name.localPart.equals("module", ignoreCase = true) |
| ) { |
| val instant = startElement.getAttributeByName( |
| QName(SdkConstants.DIST_URI, "instant") |
| ) |
| if (instant != null |
| && ( |
| instant.value == SdkConstants.VALUE_TRUE |
| || instant.value == SdkConstants.VALUE_1) |
| ) { |
| eventReader.close() |
| instantAppResultMap[manifest] = true |
| return true |
| } |
| } |
| } else if (event.isEndElement |
| && (event as EndElement).name.localPart.equals("manifest", ignoreCase = true) |
| ) { |
| break |
| } |
| } |
| eventReader.close() |
| } |
| } catch (e: XMLStreamException) { |
| variantModel.syncIssueReporter.reportError( |
| IssueReporter.Type.GENERIC, |
| """ |
| Failed to parse XML in ${manifest.path} |
| ${e.message} |
| """.trimIndent() |
| ) |
| } catch (e: IOException) { |
| variantModel.syncIssueReporter.reportError( |
| IssueReporter.Type.GENERIC, |
| """ |
| Failed to parse XML in ${manifest.path} |
| ${e.message} |
| """.trimIndent() |
| ) |
| } finally { |
| // check that we have not yet put a true in there |
| instantAppResultMap.putIfAbsent(manifest, false) |
| } |
| } |
| return false |
| } |
| |
| private fun getTestTargetVariant( |
| component: ComponentCreationConfig |
| ): TestedTargetVariant? { |
| if (extension is TestExtension) { |
| val targetPath = extension.targetProjectPath ?: return null |
| |
| // to get the target variant we need to get the result of the dependency resolution |
| val apkArtifacts = component |
| .variantDependencies |
| .getArtifactCollection( |
| PROVIDED_CLASSPATH, |
| AndroidArtifacts.ArtifactScope.ALL, |
| AndroidArtifacts.ArtifactType.APK |
| ) |
| |
| // while there should be a single result, the list may be empty if the variant |
| // matching is broken |
| if (apkArtifacts.artifacts.size == 1) { |
| val result = apkArtifacts.artifacts.single() |
| // if the name of the variant is missing, then just return null, but this |
| // should not happen |
| val variantName = result.getVariantName() ?: return null |
| return TestedTargetVariantImpl(targetPath, variantName) |
| |
| } else if (!apkArtifacts.failures.isEmpty()) { |
| // probably there was an error... |
| DependencyFailureHandler() |
| .addErrors( |
| "${project.path}@${component.name}/testTarget", |
| apkArtifacts.failures |
| ) |
| .registerIssues(variantModel.syncIssueReporter) |
| } |
| } |
| return null |
| } |
| |
| private fun addGeneratedClassPaths( |
| component: ComponentCreationConfig, |
| classesFolders: MutableSet<File> |
| ): Map<String, File> { |
| val generatedClassPaths = mutableMapOf<String, File>() |
| |
| val buildConfigJar = component.artifacts.get(InternalArtifactType.COMPILE_BUILD_CONFIG_JAR) |
| if (buildConfigJar.isPresent) { |
| classesFolders.add(buildConfigJar.get().asFile) |
| generatedClassPaths["buildConfigGeneratedClasses"] = buildConfigJar.get().asFile |
| } |
| |
| return generatedClassPaths |
| } |
| |
| companion object { |
| internal fun getAgpFlags( |
| variants: List<VariantCreationConfig>, |
| projectOptions: ProjectOptions |
| ): AndroidGradlePluginProjectFlagsImpl { |
| val flags = |
| ImmutableMap.builder<BooleanFlag, Boolean>() |
| |
| val finalResIds = !projectOptions[BooleanOption.USE_NON_FINAL_RES_IDS] |
| |
| flags.put(BooleanFlag.APPLICATION_R_CLASS_CONSTANT_IDS, finalResIds) |
| flags.put(BooleanFlag.TEST_R_CLASS_CONSTANT_IDS, finalResIds) |
| flags.put( |
| BooleanFlag.JETPACK_COMPOSE, |
| variants.any { it.buildFeatures.compose } |
| ) |
| flags.put( |
| BooleanFlag.ML_MODEL_BINDING, |
| variants.any { it.buildFeatures.mlModelBinding } |
| ) |
| flags.put( |
| BooleanFlag.TRANSITIVE_R_CLASS, |
| !projectOptions[BooleanOption.NON_TRANSITIVE_R_CLASS] |
| ) |
| flags.put( |
| BooleanFlag.UNIFIED_TEST_PLATFORM, |
| projectOptions[BooleanOption.ANDROID_TEST_USES_UNIFIED_TEST_PLATFORM] |
| ) |
| flags.put( |
| BooleanFlag.USE_ANDROID_X, |
| projectOptions[BooleanOption.USE_ANDROID_X] |
| ) |
| flags.put( |
| BooleanFlag.BUILD_FEATURE_ANDROID_RESOURCES, |
| variants.any { it.buildFeatures.androidResources } |
| ) |
| |
| return AndroidGradlePluginProjectFlagsImpl(flags.build()) |
| } |
| } |
| } |