| /* |
| * Copyright (C) 2014 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; |
| |
| import static com.android.build.gradle.internal.dependency.DexingOutputSplitTransformKt.registerDexingOutputSplitTransform; |
| import static com.android.build.gradle.internal.dependency.DexingTransformKt.getDexingArtifactConfigurations; |
| import static com.android.build.gradle.internal.dependency.L8DexDesugarLibTransformKt.getDesugarLibConfigurations; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.FILTERED_PROGUARD_RULES; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.UNFILTERED_PROGUARD_RULES; |
| import static com.android.build.gradle.internal.utils.DesugarLibUtils.getDesugarLibConfig; |
| import static com.android.builder.core.VariantTypeImpl.ANDROID_TEST; |
| import static com.android.builder.core.VariantTypeImpl.UNIT_TEST; |
| import static org.gradle.api.internal.artifacts.ArtifactAttributes.ARTIFACT_FORMAT; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.build.api.attributes.ProductFlavorAttr; |
| import com.android.build.api.component.ComponentIdentity; |
| import com.android.build.api.component.TestComponentProperties; |
| import com.android.build.api.component.impl.AndroidTestImpl; |
| import com.android.build.api.component.impl.AndroidTestPropertiesImpl; |
| import com.android.build.api.component.impl.ComponentImpl; |
| import com.android.build.api.component.impl.ComponentPropertiesImpl; |
| import com.android.build.api.component.impl.UnitTestImpl; |
| import com.android.build.api.component.impl.UnitTestPropertiesImpl; |
| import com.android.build.api.variant.impl.VariantImpl; |
| import com.android.build.api.variant.impl.VariantPropertiesImpl; |
| import com.android.build.gradle.BaseExtension; |
| import com.android.build.gradle.TestedAndroidConfig; |
| import com.android.build.gradle.internal.api.DefaultAndroidSourceSet; |
| import com.android.build.gradle.internal.api.ReadOnlyObjectProvider; |
| import com.android.build.gradle.internal.api.VariantFilter; |
| import com.android.build.gradle.internal.api.artifact.BuildArtifactSpec; |
| import com.android.build.gradle.internal.api.dsl.DslScope; |
| import com.android.build.gradle.internal.core.VariantBuilder; |
| import com.android.build.gradle.internal.core.VariantDslInfo; |
| import com.android.build.gradle.internal.core.VariantDslInfoImpl; |
| import com.android.build.gradle.internal.core.VariantSources; |
| import com.android.build.gradle.internal.crash.ExternalApiUsageException; |
| import com.android.build.gradle.internal.dependency.AndroidXDependencySubstitution; |
| import com.android.build.gradle.internal.dependency.DesugarLibConfiguration; |
| import com.android.build.gradle.internal.dependency.DexingArtifactConfiguration; |
| import com.android.build.gradle.internal.dependency.FilterShrinkerRulesTransform; |
| import com.android.build.gradle.internal.dependency.SourceSetManager; |
| import com.android.build.gradle.internal.dependency.VariantDependencies; |
| import com.android.build.gradle.internal.dependency.VariantDependenciesBuilder; |
| import com.android.build.gradle.internal.dependency.VersionedCodeShrinker; |
| import com.android.build.gradle.internal.dsl.ActionableVariantObjectOperationsExecutor; |
| import com.android.build.gradle.internal.dsl.BaseAppModuleExtension; |
| import com.android.build.gradle.internal.dsl.BuildType; |
| import com.android.build.gradle.internal.dsl.DefaultConfig; |
| import com.android.build.gradle.internal.dsl.ProductFlavor; |
| import com.android.build.gradle.internal.dsl.SigningConfig; |
| import com.android.build.gradle.internal.pipeline.TransformManager; |
| import com.android.build.gradle.internal.profile.AnalyticsUtil; |
| import com.android.build.gradle.internal.publishing.PublishingSpecs; |
| import com.android.build.gradle.internal.scope.BuildArtifactsHolder; |
| import com.android.build.gradle.internal.scope.GlobalScope; |
| import com.android.build.gradle.internal.scope.InternalArtifactType; |
| import com.android.build.gradle.internal.scope.MutableTaskContainer; |
| import com.android.build.gradle.internal.scope.SingleArtifactType; |
| import com.android.build.gradle.internal.scope.VariantBuildArtifactsHolder; |
| import com.android.build.gradle.internal.scope.VariantScope; |
| import com.android.build.gradle.internal.scope.VariantScopeImpl; |
| import com.android.build.gradle.internal.variant.BaseVariantData; |
| import com.android.build.gradle.internal.variant.DimensionCombination; |
| import com.android.build.gradle.internal.variant.DimensionCombinator; |
| import com.android.build.gradle.internal.variant.TestVariantData; |
| import com.android.build.gradle.internal.variant.TestedVariantData; |
| import com.android.build.gradle.internal.variant.VariantFactory; |
| import com.android.build.gradle.internal.variant.VariantInputModel; |
| import com.android.build.gradle.internal.variant.VariantPathHelper; |
| import com.android.build.gradle.options.BooleanOption; |
| import com.android.build.gradle.options.ProjectOptions; |
| import com.android.build.gradle.options.SigningOptions; |
| import com.android.build.gradle.options.SyncOptions; |
| import com.android.builder.core.DefaultManifestParser; |
| import com.android.builder.core.ManifestAttributeSupplier; |
| import com.android.builder.core.VariantType; |
| import com.android.builder.errors.IssueReporter; |
| import com.android.builder.model.CodeShrinker; |
| import com.android.builder.profile.ProcessProfileWriter; |
| import com.android.builder.profile.Recorder; |
| import com.android.utils.Pair; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.wireless.android.sdk.stats.ApiVersion; |
| import com.google.wireless.android.sdk.stats.GradleBuildVariant; |
| import java.io.File; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import org.gradle.api.Action; |
| import org.gradle.api.Project; |
| import org.gradle.api.artifacts.dsl.DependencyHandler; |
| import org.gradle.api.attributes.Attribute; |
| import org.gradle.api.file.ConfigurableFileCollection; |
| import org.gradle.api.file.FileCollection; |
| import org.gradle.api.file.FileSystemLocation; |
| import org.gradle.api.model.ObjectFactory; |
| import org.gradle.api.provider.Provider; |
| |
| /** Class to create, manage variants. */ |
| public class VariantManager { |
| |
| private static final String MULTIDEX_VERSION = "1.0.2"; |
| |
| protected static final String COM_ANDROID_SUPPORT_MULTIDEX = |
| "com.android.support:multidex:" + MULTIDEX_VERSION; |
| protected static final String COM_ANDROID_SUPPORT_MULTIDEX_INSTRUMENTATION = |
| "com.android.support:multidex-instrumentation:" + MULTIDEX_VERSION; |
| |
| protected static final String ANDROIDX_MULTIDEX_MULTIDEX = |
| AndroidXDependencySubstitution.getAndroidXMappings() |
| .get("com.android.support:multidex"); |
| protected static final String ANDROIDX_MULTIDEX_MULTIDEX_INSTRUMENTATION = |
| AndroidXDependencySubstitution.getAndroidXMappings() |
| .get("com.android.support:multidex-instrumentation"); |
| |
| @NonNull private final Project project; |
| @NonNull private final ProjectOptions projectOptions; |
| @NonNull private final BaseExtension extension; |
| @NonNull private final VariantFactory variantFactory; |
| @NonNull private final VariantInputModel variantInputModel; |
| @NonNull private final TaskManager taskManager; |
| @NonNull private final SourceSetManager sourceSetManager; |
| @NonNull private final Recorder recorder; |
| @NonNull private final VariantFilter variantFilter; |
| @NonNull private final List<ComponentPropertiesImpl> components; |
| @NonNull private final Map<File, ManifestAttributeSupplier> manifestParserMap; |
| @NonNull protected final GlobalScope globalScope; |
| @Nullable private final SigningConfig signingOverride; |
| // We cannot use gradle's state of executed as that returns true while inside afterEvalute. |
| // Wew want this to only be true after all tasks have been create. |
| private boolean hasCreatedTasks = false; |
| public static final Attribute<String> SHRINKER_ATTR = |
| Attribute.of("codeShrinker", String.class); |
| |
| public VariantManager( |
| @NonNull GlobalScope globalScope, |
| @NonNull Project project, |
| @NonNull ProjectOptions projectOptions, |
| @NonNull BaseExtension extension, |
| @NonNull VariantFactory variantFactory, |
| @NonNull VariantInputModel variantInputModel, |
| @NonNull TaskManager taskManager, |
| @NonNull SourceSetManager sourceSetManager, |
| @NonNull Recorder recorder) { |
| this.globalScope = globalScope; |
| this.extension = extension; |
| this.project = project; |
| this.projectOptions = projectOptions; |
| this.variantFactory = variantFactory; |
| this.variantInputModel = variantInputModel; |
| this.taskManager = taskManager; |
| this.sourceSetManager = sourceSetManager; |
| this.recorder = recorder; |
| this.signingOverride = createSigningOverride(); |
| this.variantFilter = new VariantFilter(new ReadOnlyObjectProvider()); |
| this.components = Lists.newArrayList(); |
| this.manifestParserMap = Maps.newHashMap(); |
| } |
| |
| /** Registers a new variant. */ |
| private void addComponent(@NonNull ComponentPropertiesImpl component) { |
| components.add(component); |
| } |
| |
| /** Returns a list of all created {@link VariantScope}s. */ |
| @NonNull |
| public List<ComponentPropertiesImpl> getComponents() { |
| return components; |
| } |
| |
| /** Creates the variants and their tasks. */ |
| public List<ComponentPropertiesImpl> createVariantsAndTasks() { |
| variantFactory.validateModel(variantInputModel); |
| variantFactory.preVariantWork(project); |
| |
| if (components.isEmpty()) { |
| computeVariants(); |
| } |
| |
| // Create top level test tasks. |
| taskManager.createTopLevelTestTasks(!variantInputModel.getProductFlavors().isEmpty()); |
| |
| // Create tasks for all variants (main and tests) |
| for (ComponentPropertiesImpl component : components) { |
| createTasksForVariant(component); |
| } |
| |
| taskManager.createReportTasks(components); |
| |
| return components; |
| } |
| |
| /** Create tasks for the specified variant. */ |
| private void createTasksForVariant(@NonNull ComponentPropertiesImpl componentProperties) { |
| final VariantType variantType = componentProperties.getVariantType(); |
| |
| taskManager.createAssembleTask(componentProperties); |
| if (variantType.isBaseModule()) { |
| taskManager.createBundleTask(componentProperties); |
| } |
| |
| if (variantType.isTestComponent()) { |
| TestComponentProperties testComponent = (TestComponentProperties) componentProperties; |
| VariantPropertiesImpl testedVariant = |
| (VariantPropertiesImpl) testComponent.getTestedVariant(); |
| |
| VariantDslInfo variantDslInfo = componentProperties.getVariantDslInfo(); |
| VariantDependencies variantDependencies = componentProperties.getVariantDependencies(); |
| |
| if (testedVariant.getVariantDslInfo().getRenderscriptSupportModeEnabled()) { |
| project.getDependencies() |
| .add( |
| variantDependencies.getCompileClasspath().getName(), |
| project.files( |
| globalScope |
| .getSdkComponents() |
| .getRenderScriptSupportJarProvider())); |
| } |
| |
| if (variantType.isApk()) { // ANDROID_TEST |
| if (variantDslInfo.isLegacyMultiDexMode()) { |
| String multiDexInstrumentationDep = |
| globalScope.getProjectOptions().get(BooleanOption.USE_ANDROID_X) |
| ? ANDROIDX_MULTIDEX_MULTIDEX_INSTRUMENTATION |
| : COM_ANDROID_SUPPORT_MULTIDEX_INSTRUMENTATION; |
| project.getDependencies() |
| .add( |
| variantDependencies.getCompileClasspath().getName(), |
| multiDexInstrumentationDep); |
| project.getDependencies() |
| .add( |
| variantDependencies.getRuntimeClasspath().getName(), |
| multiDexInstrumentationDep); |
| } |
| |
| taskManager.createAndroidTestVariantTasks( |
| (AndroidTestPropertiesImpl) componentProperties, |
| getVariantPropertiesList()); |
| } else { // UNIT_TEST |
| taskManager.createUnitTestVariantTasks( |
| (UnitTestPropertiesImpl) componentProperties); |
| } |
| |
| } else { |
| taskManager.createTasksForVariantScope( |
| (VariantPropertiesImpl) componentProperties, getVariantPropertiesList()); |
| } |
| } |
| |
| private List<VariantPropertiesImpl> getVariantPropertiesList() { |
| return components |
| .stream() |
| .filter(component -> component instanceof VariantPropertiesImpl) |
| .map(component -> (VariantPropertiesImpl) component) |
| .collect(Collectors.toList()); |
| } |
| |
| /** Publish intermediate artifacts in the BuildArtifactsHolder based on PublishingSpecs. */ |
| public void publishBuildArtifacts(@NonNull ComponentPropertiesImpl componentProperties) { |
| BuildArtifactsHolder buildArtifactsHolder = componentProperties.getArtifacts(); |
| for (PublishingSpecs.OutputSpec outputSpec : |
| componentProperties.getVariantScope().getPublishingSpec().getOutputs()) { |
| SingleArtifactType<? extends FileSystemLocation> buildArtifactType = |
| outputSpec.getOutputType(); |
| |
| // Gradle only support publishing single file. Therefore, unless Gradle starts |
| // supporting publishing multiple files, PublishingSpecs should not contain any |
| // OutputSpec with an appendable ArtifactType. |
| if (BuildArtifactSpec.Companion.has(buildArtifactType) |
| && BuildArtifactSpec.Companion.get(buildArtifactType).getAppendable()) { |
| throw new RuntimeException( |
| String.format( |
| "Appendable ArtifactType '%1s' cannot be published.", |
| buildArtifactType.name())); |
| } |
| |
| if (buildArtifactsHolder.hasFinalProduct(buildArtifactType)) { |
| Provider<? extends FileSystemLocation> artifact = |
| buildArtifactsHolder.getFinalProduct(buildArtifactType); |
| |
| componentProperties |
| .getVariantScope() |
| .publishIntermediateArtifact( |
| artifact, |
| outputSpec.getArtifactType(), |
| outputSpec.getPublishedConfigTypes()); |
| } else { |
| if (buildArtifactType == InternalArtifactType.ALL_CLASSES.INSTANCE) { |
| Provider<FileCollection> allClasses = |
| buildArtifactsHolder.getFinalProductAsFileCollection( |
| InternalArtifactType.ALL_CLASSES.INSTANCE); |
| Provider<File> file = allClasses.map(FileCollection::getSingleFile); |
| |
| componentProperties |
| .getVariantScope() |
| .publishIntermediateArtifact( |
| file, |
| outputSpec.getArtifactType(), |
| outputSpec.getPublishedConfigTypes()); |
| } |
| } |
| } |
| } |
| |
| @NonNull |
| private Map<Attribute<ProductFlavorAttr>, ProductFlavorAttr> getFlavorSelection( |
| @NonNull VariantDslInfo variantDslInfo) { |
| ObjectFactory factory = project.getObjects(); |
| |
| return variantDslInfo |
| .getMissingDimensionStrategies() |
| .entrySet() |
| .stream() |
| .collect( |
| Collectors.toMap( |
| entry -> Attribute.of(entry.getKey(), ProductFlavorAttr.class), |
| entry -> |
| factory.named( |
| ProductFlavorAttr.class, |
| entry.getValue().getRequested()))); |
| |
| } |
| |
| /** Configure artifact transforms that require variant-specific attribute information. */ |
| private void configureVariantArtifactTransforms() { |
| DependencyHandler dependencies = project.getDependencies(); |
| |
| if (globalScope.getProjectOptions().get(BooleanOption.ENABLE_DEXING_ARTIFACT_TRANSFORM)) { |
| for (DexingArtifactConfiguration artifactConfiguration : |
| getDexingArtifactConfigurations(components)) { |
| artifactConfiguration.registerTransform( |
| globalScope.getProject().getName(), |
| dependencies, |
| globalScope.getBootClasspath(), |
| getDesugarLibConfig(globalScope.getProject()), |
| SyncOptions.getErrorFormatMode(globalScope.getProjectOptions()), |
| projectOptions.get(BooleanOption.ENABLE_INCREMENTAL_DEXING_V2)); |
| } |
| } |
| |
| if (globalScope.getProjectOptions().get(BooleanOption.ENABLE_PROGUARD_RULES_EXTRACTION)) { |
| Set<CodeShrinker> shrinkers = |
| components |
| .stream() |
| .map(component -> component.getVariantScope().getCodeShrinker()) |
| .filter(Objects::nonNull) |
| .collect(Collectors.toSet()); |
| for (CodeShrinker shrinker : shrinkers) { |
| dependencies.registerTransform( |
| FilterShrinkerRulesTransform.class, |
| reg -> { |
| reg.getFrom() |
| .attribute( |
| ARTIFACT_FORMAT, UNFILTERED_PROGUARD_RULES.getType()); |
| reg.getTo() |
| .attribute(ARTIFACT_FORMAT, FILTERED_PROGUARD_RULES.getType()); |
| |
| reg.getFrom().attribute(SHRINKER_ATTR, shrinker.toString()); |
| reg.getTo().attribute(SHRINKER_ATTR, shrinker.toString()); |
| |
| reg.parameters( |
| params -> { |
| params.getShrinker() |
| .set(VersionedCodeShrinker.of(shrinker)); |
| params.getProjectName().set(project.getName()); |
| }); |
| }); |
| } |
| } |
| |
| for (DesugarLibConfiguration configuration : getDesugarLibConfigurations(components)) { |
| configuration.registerTransform(dependencies); |
| } |
| |
| registerDexingOutputSplitTransform(dependencies); |
| } |
| |
| /** |
| * Returns a modified name. |
| * |
| * <p>This name is used to request a missing dimension. It is the same name as the flavor that |
| * sets up the request, which means it's not going to be matched, and instead it'll go to a |
| * custom fallbacks provided by the flavor. |
| * |
| * <p>We are just modifying the name to avoid collision in case the same name exists in |
| * different dimensions |
| */ |
| public static String getModifiedName(@NonNull String name) { |
| return "____" + name; |
| } |
| |
| /** Create all variants. */ |
| @VisibleForTesting |
| public void computeVariants() { |
| List<String> flavorDimensionList = extension.getFlavorDimensionList(); |
| |
| DimensionCombinator computer = |
| new DimensionCombinator( |
| variantInputModel, |
| globalScope.getDslScope().getIssueReporter(), |
| variantFactory.getVariantType(), |
| flavorDimensionList); |
| |
| List<DimensionCombination> variants = computer.computeVariants(); |
| |
| // get some info related to testing |
| BuildTypeData testBuildTypeData = getTestBuildTypeData(); |
| |
| // loop on all the new variant objects to create the legacy ones. |
| for (DimensionCombination variant : variants) { |
| createVariantsFromCombination(variant, testBuildTypeData); |
| } |
| |
| configureVariantArtifactTransforms(); |
| } |
| |
| @Nullable |
| private BuildTypeData getTestBuildTypeData() { |
| BuildTypeData testBuildTypeData = null; |
| if (extension instanceof TestedAndroidConfig) { |
| TestedAndroidConfig testedExtension = (TestedAndroidConfig) extension; |
| |
| testBuildTypeData = |
| variantInputModel.getBuildTypes().get(testedExtension.getTestBuildType()); |
| if (testBuildTypeData == null) { |
| throw new RuntimeException( |
| String.format( |
| "Test Build Type '%1$s' does not" + " exist.", |
| testedExtension.getTestBuildType())); |
| } |
| } |
| return testBuildTypeData; |
| } |
| |
| @Nullable |
| private Pair<VariantImpl<VariantPropertiesImpl>, VariantPropertiesImpl> createVariant( |
| @NonNull DimensionCombination dimensionCombination, |
| @NonNull BuildTypeData buildTypeData, |
| @NonNull List<ProductFlavorData<ProductFlavor>> productFlavorDataList, |
| @NonNull VariantType variantType) { |
| // entry point for a given buildType/Flavors/VariantType combo. |
| // Need to run the new variant API to selectively ignore variants. |
| // in order to do this, we need access to the VariantDslInfo, to create a |
| DslScope dslScope = globalScope.getDslScope(); |
| |
| final ProductFlavorData<DefaultConfig> defaultConfig = variantInputModel.getDefaultConfig(); |
| DefaultAndroidSourceSet defaultConfigSourceProvider = defaultConfig.getSourceSet(); |
| |
| VariantBuilder variantBuilder = |
| VariantBuilder.getBuilder( |
| dimensionCombination, |
| variantType, |
| defaultConfig.getProductFlavor(), |
| defaultConfigSourceProvider, |
| buildTypeData.getBuildType(), |
| buildTypeData.getSourceSet(), |
| signingOverride, |
| getParser( |
| defaultConfigSourceProvider.getManifestFile(), |
| variantType.getRequiresManifest()), |
| globalScope.getProjectOptions(), |
| dslScope.getIssueReporter(), |
| this::canParseManifest); |
| |
| // We must first add the flavors to the variant config, in order to get the proper |
| // variant-specific and multi-flavor name as we add/create the variant providers later. |
| for (ProductFlavorData<ProductFlavor> productFlavorData : productFlavorDataList) { |
| variantBuilder.addProductFlavor( |
| productFlavorData.getProductFlavor(), productFlavorData.getSourceSet()); |
| } |
| |
| VariantDslInfoImpl variantDslInfo = variantBuilder.createVariantDslInfo(); |
| ComponentIdentity componentIdentity = variantDslInfo.getComponentIdentity(); |
| |
| // create the Variant object so that we can run the action which may interrupt the creation |
| // (in case of enabled = false) |
| VariantImpl<VariantPropertiesImpl> variant = |
| variantFactory.createVariantObject(componentIdentity, variantDslInfo); |
| |
| // HACK, we need access to the new type rather than the old. This will go away in the |
| // future |
| ActionableVariantObjectOperationsExecutor commonExtension = |
| (ActionableVariantObjectOperationsExecutor) extension; |
| //noinspection unchecked |
| commonExtension.executeVariantOperations(variant); |
| |
| if (!variant.getEnabled()) { |
| return null; |
| } |
| |
| // now that we have the result of the filter, we can continue configuring the variant |
| |
| createCompoundSourceSets(productFlavorDataList, variantBuilder, sourceSetManager); |
| |
| VariantSources variantSources = variantBuilder.createVariantSources(); |
| |
| // Only record release artifacts |
| if (!buildTypeData.getBuildType().isDebuggable() |
| && variantType.isApk() |
| && !variantDslInfo.getVariantType().isForTesting()) { |
| ProcessProfileWriter.get().recordApplicationId(variantDslInfo::getApplicationId); |
| } |
| |
| // Add the container of dependencies. |
| // The order of the libraries is important, in descending order: |
| // variant-specific, build type, multi-flavor, flavor1, flavor2, ..., defaultConfig. |
| // variant-specific if the full combo of flavors+build type. Does not exist if no flavors. |
| // multi-flavor is the combination of all flavor dimensions. Does not exist if <2 dimension. |
| final List<DefaultAndroidSourceSet> variantSourceSets = |
| Lists.newArrayListWithExpectedSize(productFlavorDataList.size() + 4); |
| |
| // 1. add the variant-specific if applicable. |
| if (!productFlavorDataList.isEmpty()) { |
| variantSourceSets.add( |
| (DefaultAndroidSourceSet) variantSources.getVariantSourceProvider()); |
| } |
| |
| // 2. the build type. |
| variantSourceSets.add(buildTypeData.getSourceSet()); |
| |
| // 3. the multi-flavor combination |
| if (productFlavorDataList.size() > 1) { |
| variantSourceSets.add( |
| (DefaultAndroidSourceSet) variantSources.getMultiFlavorSourceProvider()); |
| } |
| |
| // 4. the flavors. |
| for (ProductFlavorData<ProductFlavor> productFlavor : productFlavorDataList) { |
| variantSourceSets.add(productFlavor.getSourceSet()); |
| } |
| |
| // 5. The defaultConfig |
| variantSourceSets.add(variantInputModel.getDefaultConfig().getSourceSet()); |
| |
| // Create VariantDependencies |
| VariantDependenciesBuilder builder = |
| VariantDependenciesBuilder.builder( |
| project, |
| projectOptions, |
| globalScope.getDslScope().getIssueReporter(), |
| variantDslInfo) |
| .setFlavorSelection(getFlavorSelection(variantDslInfo)) |
| .addSourceSets(variantSourceSets); |
| |
| if (extension instanceof BaseAppModuleExtension) { |
| builder.setFeatureList(((BaseAppModuleExtension) extension).getDynamicFeatures()); |
| } |
| |
| final VariantDependencies variantDependencies = builder.build(globalScope); |
| |
| // Done. Create the (too) many variant objects |
| |
| VariantPathHelper pathHelper = new VariantPathHelper(project, variantDslInfo, dslScope); |
| VariantBuildArtifactsHolder artifacts = |
| new VariantBuildArtifactsHolder( |
| project, componentIdentity.getName(), globalScope.getBuildDir()); |
| MutableTaskContainer taskContainer = new MutableTaskContainer(); |
| |
| // create the obsolete VariantScope |
| VariantScopeImpl variantScope = |
| new VariantScopeImpl( |
| componentIdentity, |
| variantDslInfo, |
| variantSources, |
| variantDependencies, |
| pathHelper, |
| artifacts, |
| globalScope, |
| new TransformManager(project, dslScope.getIssueReporter(), recorder), |
| null /* testedVariantProperties*/); |
| |
| // and the obsolete variant data |
| BaseVariantData variantData = |
| variantFactory.createVariantData( |
| componentIdentity, |
| variantDslInfo, |
| variantDependencies, |
| variantSources, |
| pathHelper, |
| artifacts, |
| globalScope, |
| taskManager, |
| taskContainer); |
| |
| // then the new VariantProperties which will contain the 2 old objects. |
| VariantPropertiesImpl variantProperties = |
| variantFactory.createVariantPropertiesObject( |
| componentIdentity, |
| variantDslInfo, |
| variantDependencies, |
| variantSources, |
| pathHelper, |
| artifacts, |
| variantScope, |
| variantData); |
| |
| if (variantDslInfo.isLegacyMultiDexMode()) { |
| String multiDexDependency = |
| globalScope.getProjectOptions().get(BooleanOption.USE_ANDROID_X) |
| ? ANDROIDX_MULTIDEX_MULTIDEX |
| : COM_ANDROID_SUPPORT_MULTIDEX; |
| project.getDependencies() |
| .add(variantDependencies.getCompileClasspath().getName(), multiDexDependency); |
| project.getDependencies() |
| .add(variantDependencies.getRuntimeClasspath().getName(), multiDexDependency); |
| } |
| |
| if (variantDslInfo.getRenderscriptSupportModeEnabled()) { |
| final ConfigurableFileCollection fileCollection = |
| project.files( |
| globalScope.getSdkComponents().getRenderScriptSupportJarProvider()); |
| project.getDependencies() |
| .add(variantDependencies.getCompileClasspath().getName(), fileCollection); |
| if (variantType.isApk() && !variantType.isForTesting()) { |
| project.getDependencies() |
| .add(variantDependencies.getRuntimeClasspath().getName(), fileCollection); |
| } |
| } |
| |
| // Run the VariantProperties actions |
| //noinspection unchecked |
| commonExtension.executeVariantPropertiesOperations(variantProperties); |
| |
| // also execute the delayed actions registered on the Variant object itself |
| //noinspection unchecked |
| variant.executePropertiesActions(variantProperties); |
| |
| return Pair.of(variant, variantProperties); |
| } |
| |
| private static void createCompoundSourceSets( |
| @NonNull List<ProductFlavorData<ProductFlavor>> productFlavorList, |
| @NonNull VariantBuilder variantBuilder, |
| @NonNull SourceSetManager sourceSetManager) { |
| final VariantType variantType = variantBuilder.getVariantType(); |
| |
| if (!productFlavorList.isEmpty() /* && !variantConfig.getType().isSingleBuildType()*/) { |
| DefaultAndroidSourceSet variantSourceSet = |
| (DefaultAndroidSourceSet) |
| sourceSetManager.setUpSourceSet( |
| VariantBuilder.computeSourceSetName( |
| variantBuilder.getName(), variantType), |
| variantType.isTestComponent()); |
| variantBuilder.setVariantSourceProvider(variantSourceSet); |
| } |
| |
| if (productFlavorList.size() > 1) { |
| DefaultAndroidSourceSet multiFlavorSourceSet = |
| (DefaultAndroidSourceSet) |
| sourceSetManager.setUpSourceSet( |
| VariantBuilder.computeSourceSetName( |
| variantBuilder.getFlavorName(), variantType), |
| variantType.isTestComponent()); |
| variantBuilder.setMultiFlavorSourceProvider(multiFlavorSourceSet); |
| } |
| } |
| |
| /** Create a TestVariantData for the specified testedVariantData. */ |
| @Nullable |
| public ComponentPropertiesImpl createTestComponents( |
| @NonNull DimensionCombination dimensionCombination, |
| @NonNull BuildTypeData buildTypeData, |
| @NonNull List<ProductFlavorData<ProductFlavor>> productFlavorDataList, |
| @NonNull VariantImpl<VariantPropertiesImpl> testedVariant, |
| @NonNull VariantPropertiesImpl testedVariantProperties, |
| @NonNull VariantType variantType) { |
| |
| // handle test variant |
| // need a suppress warning because ProductFlavor.getTestSourceSet(type) is annotated |
| // to return @Nullable and the constructor is @NonNull on this parameter, |
| // but it's never the case on defaultConfigData |
| // The constructor does a runtime check on the instances so we should be safe. |
| final DefaultAndroidSourceSet testSourceSet = |
| variantInputModel.getDefaultConfig().getTestSourceSet(variantType); |
| DslScope dslScope = globalScope.getDslScope(); |
| @SuppressWarnings("ConstantConditions") |
| VariantBuilder variantBuilder = |
| VariantBuilder.getBuilder( |
| dimensionCombination, |
| variantType, |
| variantInputModel.getDefaultConfig().getProductFlavor(), |
| testSourceSet, |
| buildTypeData.getBuildType(), |
| buildTypeData.getTestSourceSet(variantType), |
| signingOverride, |
| testSourceSet != null |
| ? getParser( |
| testSourceSet.getManifestFile(), |
| variantType.getRequiresManifest()) |
| : null, |
| globalScope.getProjectOptions(), |
| dslScope.getIssueReporter(), |
| this::canParseManifest); |
| |
| variantBuilder.setTestedVariant( |
| (VariantDslInfoImpl) testedVariantProperties.getVariantDslInfo()); |
| |
| List<ProductFlavor> productFlavorList = |
| testedVariantProperties.getVariantDslInfo().getProductFlavorList(); |
| |
| // We must first add the flavors to the variant builder, in order to get the proper |
| // variant-specific and multi-flavor name as we add/create the variant providers later. |
| final Map<String, ProductFlavorData<ProductFlavor>> productFlavors = |
| variantInputModel.getProductFlavors(); |
| for (ProductFlavor productFlavor : productFlavorList) { |
| ProductFlavorData<ProductFlavor> data = productFlavors.get(productFlavor.getName()); |
| |
| //noinspection ConstantConditions |
| variantBuilder.addProductFlavor( |
| data.getProductFlavor(), data.getTestSourceSet(variantType)); |
| } |
| |
| VariantDslInfoImpl variantDslInfo = variantBuilder.createVariantDslInfo(); |
| |
| // this is ANDROID_TEST |
| ComponentImpl component; |
| if (variantType.isApk()) { |
| AndroidTestImpl androidTestVariant = |
| variantFactory.createAndroidTestObject( |
| variantDslInfo.getComponentIdentity(), variantDslInfo); |
| |
| testedVariant.executeAndroidTestActions(androidTestVariant); |
| |
| component = androidTestVariant; |
| } else { |
| // this is UNIT_TEST |
| UnitTestImpl unitTestVariant = |
| variantFactory.createUnitTestObject( |
| variantDslInfo.getComponentIdentity(), variantDslInfo); |
| |
| testedVariant.executeUnitTestActions(unitTestVariant); |
| |
| component = unitTestVariant; |
| } |
| |
| if (!component.getEnabled()) { |
| return null; |
| } |
| |
| // now that we have the result of the filter, we can continue configuring the variant |
| createCompoundSourceSets(productFlavorDataList, variantBuilder, sourceSetManager); |
| |
| VariantSources variantSources = variantBuilder.createVariantSources(); |
| |
| // Add the container of dependencies, the order of the libraries is important. |
| // In descending order: build type (only for unit test), flavors, defaultConfig. |
| |
| // Add the container of dependencies. |
| // The order of the libraries is important, in descending order: |
| // variant-specific, build type (, multi-flavor, flavor1, flavor2, ..., defaultConfig. |
| // variant-specific if the full combo of flavors+build type. Does not exist if no flavors. |
| // multi-flavor is the combination of all flavor dimensions. Does not exist if <2 dimension. |
| List<ProductFlavor> testProductFlavors = variantDslInfo.getProductFlavorList(); |
| List<DefaultAndroidSourceSet> testVariantSourceSets = |
| Lists.newArrayListWithExpectedSize(4 + testProductFlavors.size()); |
| |
| // 1. add the variant-specific if applicable. |
| if (!testProductFlavors.isEmpty()) { |
| testVariantSourceSets.add( |
| (DefaultAndroidSourceSet) variantSources.getVariantSourceProvider()); |
| } |
| |
| // 2. the build type. |
| DefaultAndroidSourceSet buildTypeConfigurationProvider = |
| buildTypeData.getTestSourceSet(variantType); |
| if (buildTypeConfigurationProvider != null) { |
| testVariantSourceSets.add(buildTypeConfigurationProvider); |
| } |
| |
| // 3. the multi-flavor combination |
| if (testProductFlavors.size() > 1) { |
| testVariantSourceSets.add( |
| (DefaultAndroidSourceSet) variantSources.getMultiFlavorSourceProvider()); |
| } |
| |
| // 4. the flavors. |
| for (ProductFlavor productFlavor : testProductFlavors) { |
| testVariantSourceSets.add( |
| variantInputModel |
| .getProductFlavors() |
| .get(productFlavor.getName()) |
| .getTestSourceSet(variantType)); |
| } |
| |
| // now add the default config |
| testVariantSourceSets.add( |
| variantInputModel.getDefaultConfig().getTestSourceSet(variantType)); |
| |
| // If the variant being tested is a library variant, VariantDependencies must be |
| // computed after the tasks for the tested variant is created. Therefore, the |
| // VariantDependencies is computed here instead of when the VariantData was created. |
| VariantDependenciesBuilder builder = |
| VariantDependenciesBuilder.builder( |
| project, |
| projectOptions, |
| globalScope.getDslScope().getIssueReporter(), |
| variantDslInfo) |
| .addSourceSets(testVariantSourceSets) |
| .setFlavorSelection(getFlavorSelection(variantDslInfo)) |
| .setTestedVariant(testedVariantProperties); |
| |
| final VariantDependencies variantDependencies = builder.build(globalScope); |
| |
| VariantPathHelper pathHelper = new VariantPathHelper(project, variantDslInfo, dslScope); |
| ComponentIdentity componentIdentity = variantDslInfo.getComponentIdentity(); |
| VariantBuildArtifactsHolder artifacts = |
| new VariantBuildArtifactsHolder( |
| project, componentIdentity.getName(), globalScope.getBuildDir()); |
| MutableTaskContainer taskContainer = new MutableTaskContainer(); |
| |
| VariantScopeImpl variantScope = |
| new VariantScopeImpl( |
| componentIdentity, |
| variantDslInfo, |
| variantSources, |
| variantDependencies, |
| pathHelper, |
| artifacts, |
| globalScope, |
| new TransformManager(project, dslScope.getIssueReporter(), recorder), |
| testedVariantProperties); |
| |
| // create the internal storage for this variant. |
| TestVariantData testVariantData = |
| new TestVariantData( |
| componentIdentity, |
| variantDslInfo, |
| variantDependencies, |
| variantSources, |
| pathHelper, |
| artifacts, |
| (TestedVariantData) testedVariantProperties.getVariantData(), |
| globalScope, |
| taskManager, |
| taskContainer); |
| |
| // this is ANDROID_TEST |
| ComponentPropertiesImpl componentProperties; |
| if (variantType.isApk()) { |
| AndroidTestPropertiesImpl androidTestProperties = |
| variantFactory.createAndroidTestProperties( |
| componentIdentity, |
| variantDslInfo, |
| variantDependencies, |
| variantSources, |
| pathHelper, |
| artifacts, |
| variantScope, |
| testVariantData, |
| testedVariantProperties); |
| |
| component.executePropertiesActions(androidTestProperties); |
| |
| componentProperties = androidTestProperties; |
| } else { |
| // this is UNIT_TEST |
| UnitTestPropertiesImpl unitTestProperties = |
| variantFactory.createUnitTestProperties( |
| componentIdentity, |
| variantDslInfo, |
| variantDependencies, |
| variantSources, |
| pathHelper, |
| artifacts, |
| variantScope, |
| testVariantData, |
| testedVariantProperties); |
| |
| // also execute the delayed actions registered on the Variant object itself |
| component.executePropertiesActions(unitTestProperties); |
| |
| componentProperties = unitTestProperties; |
| } |
| |
| // register |
| testedVariantProperties |
| .getTestComponents() |
| .put(variantDslInfo.getVariantType(), componentProperties); |
| |
| return componentProperties; |
| } |
| |
| /** |
| * Creates Variant objects for a specific {@link ComponentIdentity} |
| * |
| * <p>This will create both the prod and the androidTest/unitTest variants. |
| */ |
| private void createVariantsFromCombination( |
| @NonNull DimensionCombination dimensionCombination, |
| @Nullable BuildTypeData testBuildTypeData) { |
| VariantType variantType = variantFactory.getVariantType(); |
| |
| // first run the old variantFilter API |
| // This acts on buildtype/flavor only, and applies in one pass to prod/tests. |
| Action<com.android.build.api.variant.VariantFilter> variantFilterAction = |
| extension.getVariantFilter(); |
| |
| DefaultConfig defaultConfig = variantInputModel.getDefaultConfig().getProductFlavor(); |
| |
| BuildTypeData buildTypeData = |
| variantInputModel.getBuildTypes().get(dimensionCombination.getBuildType()); |
| BuildType buildType = buildTypeData.getBuildType(); |
| |
| // get the list of ProductFlavorData from the list of flavor name |
| List<ProductFlavorData<ProductFlavor>> productFlavorDataList = |
| dimensionCombination |
| .getProductFlavors() |
| .stream() |
| .map(it -> variantInputModel.getProductFlavors().get(it.getSecond())) |
| .collect(Collectors.toList()); |
| |
| List<ProductFlavor> productFlavorList = |
| productFlavorDataList |
| .stream() |
| .map(ProductFlavorData::getProductFlavor) |
| .collect(Collectors.toList()); |
| |
| boolean ignore = false; |
| |
| if (variantFilterAction != null) { |
| variantFilter.reset( |
| dimensionCombination, defaultConfig, buildType, variantType, productFlavorList); |
| |
| try { |
| // variantFilterAction != null always true here. |
| variantFilterAction.execute(variantFilter); |
| } catch (Throwable t) { |
| throw new ExternalApiUsageException(t); |
| } |
| ignore = variantFilter.getIgnore(); |
| } |
| |
| if (!ignore) { |
| // create the prod variant |
| Pair<VariantImpl<VariantPropertiesImpl>, VariantPropertiesImpl> variantInfo = |
| createVariant( |
| dimensionCombination, |
| buildTypeData, |
| productFlavorDataList, |
| variantType); |
| if (variantInfo != null) { |
| addComponent(variantInfo.getSecond()); |
| |
| VariantPropertiesImpl variantProperties = variantInfo.getSecond(); |
| |
| VariantDslInfo variantDslInfo = variantProperties.getVariantDslInfo(); |
| VariantScope variantScope = variantProperties.getVariantScope(); |
| |
| int minSdkVersion = variantDslInfo.getMinSdkVersion().getApiLevel(); |
| int targetSdkVersion = variantDslInfo.getTargetSdkVersion().getApiLevel(); |
| if (minSdkVersion > 0 && targetSdkVersion > 0 && minSdkVersion > targetSdkVersion) { |
| globalScope |
| .getDslScope() |
| .getIssueReporter() |
| .reportWarning( |
| IssueReporter.Type.GENERIC, |
| String.format( |
| Locale.US, |
| "minSdkVersion (%d) is greater than targetSdkVersion" |
| + " (%d) for variant \"%s\". Please change the" |
| + " values such that minSdkVersion is less than or" |
| + " equal to targetSdkVersion.", |
| minSdkVersion, |
| targetSdkVersion, |
| variantProperties.getName())); |
| } |
| |
| GradleBuildVariant.Builder profileBuilder = |
| ProcessProfileWriter.getOrCreateVariant( |
| project.getPath(), variantProperties.getName()) |
| .setIsDebug(buildType.isDebuggable()) |
| .setMinSdkVersion( |
| AnalyticsUtil.toProto(variantDslInfo.getMinSdkVersion())) |
| .setMinifyEnabled(variantScope.getCodeShrinker() != null) |
| .setUseMultidex(variantDslInfo.isMultiDexEnabled()) |
| .setUseLegacyMultidex(variantDslInfo.isLegacyMultiDexMode()) |
| .setVariantType( |
| variantProperties |
| .getVariantType() |
| .getAnalyticsVariantType()) |
| .setDexBuilder(AnalyticsUtil.toProto(variantScope.getDexer())) |
| .setDexMerger(AnalyticsUtil.toProto(variantScope.getDexMerger())) |
| .setCoreLibraryDesugaringEnabled( |
| variantScope.isCoreLibraryDesugaringEnabled()) |
| .setTestExecution( |
| AnalyticsUtil.toProto( |
| globalScope |
| .getExtension() |
| .getTestOptions() |
| .getExecutionEnum())); |
| |
| if (variantScope.getCodeShrinker() != null) { |
| profileBuilder.setCodeShrinker( |
| AnalyticsUtil.toProto(variantScope.getCodeShrinker())); |
| } |
| |
| if (variantDslInfo.getTargetSdkVersion().getApiLevel() > 0) { |
| profileBuilder.setTargetSdkVersion( |
| AnalyticsUtil.toProto(variantDslInfo.getTargetSdkVersion())); |
| } |
| if (variantDslInfo.getMaxSdkVersion() != null) { |
| profileBuilder.setMaxSdkVersion( |
| ApiVersion.newBuilder().setApiLevel(variantDslInfo.getMaxSdkVersion())); |
| } |
| |
| VariantScope.Java8LangSupport supportType = variantScope.getJava8LangSupportType(); |
| if (supportType != VariantScope.Java8LangSupport.INVALID |
| && supportType != VariantScope.Java8LangSupport.UNUSED) { |
| profileBuilder.setJava8LangSupport(AnalyticsUtil.toProto(supportType)); |
| } |
| |
| if (variantFactory.hasTestScope()) { |
| if (buildTypeData == testBuildTypeData) { |
| ComponentPropertiesImpl androidTestVariantProperties = |
| createTestComponents( |
| dimensionCombination, |
| buildTypeData, |
| productFlavorDataList, |
| variantInfo.getFirst(), |
| variantProperties, |
| ANDROID_TEST); |
| if (androidTestVariantProperties != null) { |
| addComponent(androidTestVariantProperties); |
| } |
| } |
| |
| ComponentPropertiesImpl unitTestVariantProperties = |
| createTestComponents( |
| dimensionCombination, |
| buildTypeData, |
| productFlavorDataList, |
| variantInfo.getFirst(), |
| variantProperties, |
| UNIT_TEST); |
| if (unitTestVariantProperties != null) { |
| addComponent(unitTestVariantProperties); |
| } |
| } |
| } |
| } |
| } |
| |
| private SigningConfig createSigningOverride() { |
| SigningOptions signingOptions = SigningOptions.readSigningOptions(projectOptions); |
| if (signingOptions != null) { |
| com.android.build.gradle.internal.dsl.SigningConfig signingConfigDsl = |
| new com.android.build.gradle.internal.dsl.SigningConfig("externalOverride"); |
| |
| signingConfigDsl.setStoreFile(new File(signingOptions.getStoreFile())); |
| signingConfigDsl.setStorePassword(signingOptions.getStorePassword()); |
| signingConfigDsl.setKeyAlias(signingOptions.getKeyAlias()); |
| signingConfigDsl.setKeyPassword(signingOptions.getKeyPassword()); |
| |
| if (signingOptions.getStoreType() != null) { |
| signingConfigDsl.setStoreType(signingOptions.getStoreType()); |
| } |
| |
| if (signingOptions.getV1Enabled() != null) { |
| signingConfigDsl.setV1SigningEnabled(signingOptions.getV1Enabled()); |
| } |
| |
| if (signingOptions.getV2Enabled() != null) { |
| signingConfigDsl.setV2SigningEnabled(signingOptions.getV2Enabled()); |
| } |
| |
| return signingConfigDsl; |
| } |
| return null; |
| } |
| |
| @NonNull |
| private ManifestAttributeSupplier getParser( |
| @NonNull File file, boolean isManifestFileRequired) { |
| return manifestParserMap.computeIfAbsent( |
| file, |
| f -> |
| new DefaultManifestParser( |
| f, |
| this::canParseManifest, |
| isManifestFileRequired, |
| globalScope.getDslScope().getIssueReporter())); |
| } |
| |
| private boolean canParseManifest() { |
| return hasCreatedTasks || !projectOptions.get(BooleanOption.DISABLE_EARLY_MANIFEST_PARSING); |
| } |
| |
| public void setHasCreatedTasks(boolean hasCreatedTasks) { |
| this.hasCreatedTasks = hasCreatedTasks; |
| } |
| } |