| /* |
| * 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.SdkConstants.FD_RES; |
| import static com.android.SdkConstants.FN_RESOURCE_TEXT; |
| import static com.android.build.api.transform.QualifiedContent.DefaultContentType.RESOURCES; |
| import static com.android.build.gradle.internal.cxx.model.TryCreateCxxModuleModelKt.tryCreateCxxModuleModel; |
| import static com.android.build.gradle.internal.dependency.VariantDependencies.CONFIG_NAME_ANDROID_APIS; |
| import static com.android.build.gradle.internal.dependency.VariantDependencies.CONFIG_NAME_LINTCHECKS; |
| import static com.android.build.gradle.internal.dependency.VariantDependencies.CONFIG_NAME_LINTPUBLISH; |
| import static com.android.build.gradle.internal.pipeline.ExtendedContentType.NATIVE_LIBS; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.ALL; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.EXTERNAL; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.PROJECT; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.APKS_FROM_BUNDLE; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.CLASSES; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.CONSUMER_PROGUARD_RULES; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.JAVA_RES; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.METADATA_CLASSES; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.METADATA_VALUES; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.MODULE_PATH; |
| import static com.android.build.gradle.internal.publishing.AndroidArtifacts.PublishedConfigType.RUNTIME_ELEMENTS; |
| import static com.android.build.gradle.internal.scope.ArtifactPublishingUtil.publishArtifactToConfiguration; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.APK_MAPPING; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.COMPILE_AND_RUNTIME_NOT_NAMESPACED_R_CLASS_JAR; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.FEATURE_RESOURCE_PKG; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.GENERATED_PROGUARD_FILE; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.JAVAC; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.LINT_PUBLISH_JAR; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.MERGED_ASSETS; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.MERGED_JAVA_RES; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.MERGED_MANIFESTS; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.MERGED_NOT_COMPILED_RES; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.PROCESSED_RES; |
| import static com.android.build.gradle.internal.scope.InternalArtifactType.RUNTIME_R_CLASS_CLASSES; |
| import static com.android.builder.core.BuilderConstants.CONNECTED; |
| import static com.android.builder.core.BuilderConstants.DEVICE; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.base.Preconditions.checkState; |
| import static com.google.common.base.Strings.nullToEmpty; |
| |
| import android.databinding.tool.DataBindingBuilder; |
| import com.android.SdkConstants; |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.build.OutputFile; |
| import com.android.build.api.artifact.BuildableArtifact; |
| import com.android.build.api.transform.QualifiedContent; |
| import com.android.build.api.transform.QualifiedContent.DefaultContentType; |
| import com.android.build.api.transform.QualifiedContent.Scope; |
| import com.android.build.api.transform.QualifiedContent.ScopeType; |
| import com.android.build.api.transform.Transform; |
| import com.android.build.gradle.AndroidConfig; |
| import com.android.build.gradle.FeatureExtension; |
| import com.android.build.gradle.api.AndroidSourceSet; |
| import com.android.build.gradle.api.AnnotationProcessorOptions; |
| import com.android.build.gradle.api.JavaCompileOptions; |
| import com.android.build.gradle.internal.api.DefaultAndroidSourceSet; |
| import com.android.build.gradle.internal.core.Abi; |
| import com.android.build.gradle.internal.core.GradleVariantConfiguration; |
| import com.android.build.gradle.internal.coverage.JacocoConfigurations; |
| import com.android.build.gradle.internal.coverage.JacocoReportTask; |
| import com.android.build.gradle.internal.cxx.model.CxxModuleModel; |
| import com.android.build.gradle.internal.dsl.AbiSplitOptions; |
| import com.android.build.gradle.internal.dsl.BaseAppModuleExtension; |
| import com.android.build.gradle.internal.dsl.CoreProductFlavor; |
| import com.android.build.gradle.internal.dsl.DataBindingOptions; |
| import com.android.build.gradle.internal.dsl.PackagingOptions; |
| import com.android.build.gradle.internal.dsl.ViewBindingOptions; |
| import com.android.build.gradle.internal.packaging.GradleKeystoreHelper; |
| import com.android.build.gradle.internal.pipeline.ExtendedContentType; |
| import com.android.build.gradle.internal.pipeline.OriginalStream; |
| import com.android.build.gradle.internal.pipeline.TransformManager; |
| import com.android.build.gradle.internal.pipeline.TransformTask; |
| import com.android.build.gradle.internal.publishing.AndroidArtifacts; |
| import com.android.build.gradle.internal.publishing.PublishingSpecs; |
| import com.android.build.gradle.internal.res.GenerateLibraryRFileTask; |
| import com.android.build.gradle.internal.res.LinkAndroidResForBundleTask; |
| import com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask; |
| import com.android.build.gradle.internal.res.ParseLibraryResourcesTask; |
| import com.android.build.gradle.internal.res.namespaced.NamespacedResourcesTaskManager; |
| import com.android.build.gradle.internal.scope.AnchorOutputType; |
| import com.android.build.gradle.internal.scope.ApkData; |
| import com.android.build.gradle.internal.scope.BuildArtifactsHolder; |
| import com.android.build.gradle.internal.scope.CodeShrinker; |
| 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.VariantScope; |
| import com.android.build.gradle.internal.scope.VariantScope.Java8LangSupport; |
| import com.android.build.gradle.internal.tasks.AndroidReportTask; |
| import com.android.build.gradle.internal.tasks.CheckDuplicateClassesTask; |
| import com.android.build.gradle.internal.tasks.CheckManifest; |
| import com.android.build.gradle.internal.tasks.CheckProguardFiles; |
| import com.android.build.gradle.internal.tasks.D8MainDexListTask; |
| import com.android.build.gradle.internal.tasks.DependencyReportTask; |
| import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask; |
| import com.android.build.gradle.internal.tasks.DexFileDependenciesTask; |
| import com.android.build.gradle.internal.tasks.DexMergingAction; |
| import com.android.build.gradle.internal.tasks.DexMergingTask; |
| import com.android.build.gradle.internal.tasks.ExtractProguardFiles; |
| import com.android.build.gradle.internal.tasks.ExtractTryWithResourcesSupportJar; |
| import com.android.build.gradle.internal.tasks.GenerateApkDataTask; |
| import com.android.build.gradle.internal.tasks.GenerateLibraryProguardRulesTask; |
| import com.android.build.gradle.internal.tasks.InstallVariantTask; |
| import com.android.build.gradle.internal.tasks.JacocoTask; |
| import com.android.build.gradle.internal.tasks.LintCompile; |
| import com.android.build.gradle.internal.tasks.MergeAaptProguardFilesCreationAction; |
| import com.android.build.gradle.internal.tasks.MergeClassesTask; |
| import com.android.build.gradle.internal.tasks.MergeGeneratedProguardFilesCreationAction; |
| import com.android.build.gradle.internal.tasks.MergeJavaResourceTask; |
| import com.android.build.gradle.internal.tasks.MergeNativeLibsTask; |
| import com.android.build.gradle.internal.tasks.PackageForUnitTest; |
| import com.android.build.gradle.internal.tasks.PrepareLintJar; |
| import com.android.build.gradle.internal.tasks.PrepareLintJarForPublish; |
| import com.android.build.gradle.internal.tasks.ProcessJavaResTask; |
| import com.android.build.gradle.internal.tasks.RecalculateStackFramesTask; |
| import com.android.build.gradle.internal.tasks.SigningConfigWriterTask; |
| import com.android.build.gradle.internal.tasks.SigningReportTask; |
| import com.android.build.gradle.internal.tasks.SourceSetsTask; |
| import com.android.build.gradle.internal.tasks.TaskInputHelper; |
| import com.android.build.gradle.internal.tasks.TestServerTask; |
| import com.android.build.gradle.internal.tasks.UninstallTask; |
| import com.android.build.gradle.internal.tasks.ValidateSigningTask; |
| import com.android.build.gradle.internal.tasks.databinding.DataBindingCompilerArguments; |
| import com.android.build.gradle.internal.tasks.databinding.DataBindingExportBuildInfoTask; |
| import com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask; |
| import com.android.build.gradle.internal.tasks.databinding.DataBindingMergeBaseClassLogTask; |
| import com.android.build.gradle.internal.tasks.databinding.DataBindingMergeDependencyArtifactsTask; |
| import com.android.build.gradle.internal.tasks.factory.TaskCreationAction; |
| import com.android.build.gradle.internal.tasks.factory.TaskFactory; |
| import com.android.build.gradle.internal.tasks.factory.TaskFactoryImpl; |
| import com.android.build.gradle.internal.tasks.factory.TaskFactoryUtils; |
| import com.android.build.gradle.internal.tasks.factory.TaskProviderCallback; |
| import com.android.build.gradle.internal.tasks.factory.VariantTaskCreationAction; |
| import com.android.build.gradle.internal.tasks.featuresplit.FeatureSplitUtils; |
| import com.android.build.gradle.internal.test.AbstractTestDataImpl; |
| import com.android.build.gradle.internal.test.BundleTestDataImpl; |
| import com.android.build.gradle.internal.test.TestDataImpl; |
| import com.android.build.gradle.internal.transforms.CustomClassTransform; |
| import com.android.build.gradle.internal.transforms.DesugarTransform; |
| import com.android.build.gradle.internal.transforms.DexArchiveBuilderTransform; |
| import com.android.build.gradle.internal.transforms.DexArchiveBuilderTransformBuilder; |
| import com.android.build.gradle.internal.transforms.DexSplitterTransform; |
| import com.android.build.gradle.internal.transforms.ProGuardTransform; |
| import com.android.build.gradle.internal.transforms.ProguardConfigurable; |
| import com.android.build.gradle.internal.transforms.R8Transform; |
| import com.android.build.gradle.internal.transforms.ShrinkBundleResourcesTask; |
| import com.android.build.gradle.internal.transforms.ShrinkResourcesTransform; |
| import com.android.build.gradle.internal.variant.AndroidArtifactVariantData; |
| import com.android.build.gradle.internal.variant.ApkVariantData; |
| import com.android.build.gradle.internal.variant.BaseVariantData; |
| import com.android.build.gradle.internal.variant.MultiOutputPolicy; |
| import com.android.build.gradle.internal.variant.TestVariantData; |
| import com.android.build.gradle.internal.variant.VariantFactory; |
| import com.android.build.gradle.options.BooleanOption; |
| import com.android.build.gradle.options.IntegerOption; |
| import com.android.build.gradle.options.ProjectOptions; |
| import com.android.build.gradle.options.StringOption; |
| import com.android.build.gradle.options.SyncOptions; |
| import com.android.build.gradle.tasks.AidlCompile; |
| import com.android.build.gradle.tasks.AndroidJavaCompile; |
| import com.android.build.gradle.tasks.BuildArtifactReportTask; |
| import com.android.build.gradle.tasks.CleanBuildCache; |
| import com.android.build.gradle.tasks.CompatibleScreensManifest; |
| import com.android.build.gradle.tasks.CopyOutputs; |
| import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask; |
| import com.android.build.gradle.tasks.ExternalNativeBuildTask; |
| import com.android.build.gradle.tasks.ExternalNativeCleanTask; |
| import com.android.build.gradle.tasks.ExternalNativeJsonGenerator; |
| import com.android.build.gradle.tasks.GenerateBuildConfig; |
| import com.android.build.gradle.tasks.GenerateResValues; |
| import com.android.build.gradle.tasks.GenerateSplitAbiRes; |
| import com.android.build.gradle.tasks.GenerateTestConfig; |
| import com.android.build.gradle.tasks.JavaPreCompileTask; |
| import com.android.build.gradle.tasks.LintFixTask; |
| import com.android.build.gradle.tasks.LintGlobalTask; |
| import com.android.build.gradle.tasks.LintPerVariantTask; |
| import com.android.build.gradle.tasks.MainApkListPersistence; |
| import com.android.build.gradle.tasks.ManifestProcessorTask; |
| import com.android.build.gradle.tasks.MergeResources; |
| import com.android.build.gradle.tasks.MergeSourceSetFolders; |
| import com.android.build.gradle.tasks.PackageApplication; |
| import com.android.build.gradle.tasks.PackageSplitAbi; |
| import com.android.build.gradle.tasks.PackageSplitRes; |
| import com.android.build.gradle.tasks.ProcessAndroidResources; |
| import com.android.build.gradle.tasks.ProcessAnnotationsTask; |
| import com.android.build.gradle.tasks.ProcessApplicationManifest; |
| import com.android.build.gradle.tasks.ProcessLibraryManifest; |
| import com.android.build.gradle.tasks.ProcessTestManifest; |
| import com.android.build.gradle.tasks.RenderscriptCompile; |
| import com.android.build.gradle.tasks.ShaderCompile; |
| import com.android.build.gradle.tasks.factory.AndroidUnitTest; |
| import com.android.builder.core.DefaultDexOptions; |
| import com.android.builder.core.DesugarProcessArgs; |
| import com.android.builder.core.VariantType; |
| import com.android.builder.dexing.DexerTool; |
| import com.android.builder.dexing.DexingType; |
| import com.android.builder.errors.EvalIssueException; |
| import com.android.builder.errors.EvalIssueReporter.Type; |
| import com.android.builder.model.SyncIssue; |
| import com.android.builder.profile.Recorder; |
| import com.android.builder.testing.ConnectedDeviceProvider; |
| import com.android.builder.testing.api.DeviceProvider; |
| import com.android.builder.testing.api.TestServer; |
| import com.android.builder.utils.FileCache; |
| import com.android.ide.common.repository.GradleVersion; |
| import com.android.sdklib.AndroidVersion; |
| import com.android.utils.FileUtils; |
| import com.android.utils.StringHelper; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.MoreObjects; |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Splitter; |
| import com.google.common.collect.ArrayListMultimap; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.ListMultimap; |
| import com.google.common.collect.Lists; |
| import java.io.File; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.concurrent.Callable; |
| import java.util.function.Supplier; |
| import java.util.stream.Collectors; |
| import org.gradle.api.Action; |
| import org.gradle.api.DefaultTask; |
| import org.gradle.api.Project; |
| import org.gradle.api.Task; |
| import org.gradle.api.artifacts.Configuration; |
| import org.gradle.api.artifacts.ConfigurationVariant; |
| import org.gradle.api.artifacts.Dependency; |
| import org.gradle.api.artifacts.DependencySet; |
| import org.gradle.api.artifacts.PublishArtifact; |
| import org.gradle.api.attributes.Attribute; |
| import org.gradle.api.attributes.AttributeContainer; |
| import org.gradle.api.file.ConfigurableFileCollection; |
| import org.gradle.api.file.Directory; |
| import org.gradle.api.file.FileCollection; |
| import org.gradle.api.file.FileSystemLocation; |
| import org.gradle.api.file.RegularFile; |
| import org.gradle.api.logging.LogLevel; |
| import org.gradle.api.logging.Logger; |
| import org.gradle.api.logging.Logging; |
| import org.gradle.api.plugins.BasePlugin; |
| import org.gradle.api.plugins.JavaBasePlugin; |
| import org.gradle.api.plugins.JavaPlugin; |
| import org.gradle.api.provider.Provider; |
| import org.gradle.api.tasks.PathSensitivity; |
| import org.gradle.api.tasks.Sync; |
| import org.gradle.api.tasks.TaskAction; |
| import org.gradle.api.tasks.TaskInputs; |
| import org.gradle.api.tasks.TaskProvider; |
| import org.gradle.api.tasks.compile.JavaCompile; |
| import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry; |
| |
| /** Manages tasks creation. */ |
| public abstract class TaskManager { |
| |
| public static final String DIR_BUNDLES = "bundles"; |
| public static final String INSTALL_GROUP = "Install"; |
| public static final String BUILD_GROUP = BasePlugin.BUILD_GROUP; |
| public static final String ANDROID_GROUP = "Android"; |
| public static final String FEATURE_SUFFIX = "Feature"; |
| |
| // Task names. These cannot be AndroidTasks as in the component model world there is nothing to |
| // force generateTasksBeforeEvaluate to happen before the variant tasks are created. |
| public static final String MAIN_PREBUILD = "preBuild"; |
| public static final String UNINSTALL_ALL = "uninstallAll"; |
| public static final String DEVICE_CHECK = "deviceCheck"; |
| public static final String DEVICE_ANDROID_TEST = DEVICE + VariantType.ANDROID_TEST_SUFFIX; |
| public static final String CONNECTED_CHECK = "connectedCheck"; |
| public static final String CONNECTED_ANDROID_TEST = CONNECTED + VariantType.ANDROID_TEST_SUFFIX; |
| public static final String ASSEMBLE_ANDROID_TEST = "assembleAndroidTest"; |
| public static final String LINT = "lint"; |
| public static final String LINT_FIX = "lintFix"; |
| public static final String EXTRACT_PROGUARD_FILES = "extractProguardFiles"; |
| |
| @NonNull protected final Project project; |
| @NonNull protected final ProjectOptions projectOptions; |
| @NonNull protected final DataBindingBuilder dataBindingBuilder; |
| @NonNull protected final AndroidConfig extension; |
| @NonNull private final VariantFactory variantFactory; |
| @NonNull protected final ToolingModelBuilderRegistry toolingRegistry; |
| @NonNull protected final GlobalScope globalScope; |
| @NonNull protected final Recorder recorder; |
| @NonNull private final Logger logger; |
| @Nullable private final FileCache buildCache; |
| @NonNull protected final TaskFactory taskFactory; |
| |
| // Tasks. TODO: remove the mutable state from here. |
| public TaskProvider<Task> createMockableJar; |
| |
| public TaskManager( |
| @NonNull GlobalScope globalScope, |
| @NonNull Project project, |
| @NonNull ProjectOptions projectOptions, |
| @NonNull DataBindingBuilder dataBindingBuilder, |
| @NonNull AndroidConfig extension, |
| @NonNull VariantFactory variantFactory, |
| @NonNull ToolingModelBuilderRegistry toolingRegistry, |
| @NonNull Recorder recorder) { |
| this.globalScope = globalScope; |
| this.project = project; |
| this.projectOptions = projectOptions; |
| this.dataBindingBuilder = dataBindingBuilder; |
| this.extension = extension; |
| this.variantFactory = variantFactory; |
| this.toolingRegistry = toolingRegistry; |
| this.recorder = recorder; |
| this.logger = Logging.getLogger(this.getClass()); |
| |
| // It's too early to materialize the project-level cache, we'll need to get it from |
| // globalScope later on. |
| this.buildCache = globalScope.getBuildCache(); |
| |
| taskFactory = new TaskFactoryImpl(project.getTasks()); |
| } |
| |
| @NonNull |
| public TaskFactory getTaskFactory() { |
| return taskFactory; |
| } |
| |
| @NonNull |
| public DataBindingBuilder getDataBindingBuilder() { |
| return dataBindingBuilder; |
| } |
| |
| /** Creates the tasks for a given BaseVariantData. */ |
| public abstract void createTasksForVariantScope( |
| @NonNull VariantScope variantScope, @NonNull List<VariantScope> variantScopesForLint); |
| |
| /** |
| * Override to configure NDK data in the scope. |
| */ |
| public void configureScopeForNdk(@NonNull VariantScope scope) { |
| final BaseVariantData variantData = scope.getVariantData(); |
| File objFolder = new File(scope.getGlobalScope().getIntermediatesDir(), |
| "ndk/" + variantData.getVariantConfiguration().getDirName() + "/obj"); |
| for (Abi abi : Abi.values()) { |
| scope.addNdkDebuggableLibraryFolders(abi, new File(objFolder, "local/" + abi.getTag())); |
| } |
| |
| } |
| |
| /** |
| * Create tasks before the evaluation (on plugin apply). This is useful for tasks that could be |
| * referenced by custom build logic. |
| */ |
| public void createTasksBeforeEvaluate() { |
| taskFactory.register( |
| UNINSTALL_ALL, |
| uninstallAllTask -> { |
| uninstallAllTask.setDescription("Uninstall all applications."); |
| uninstallAllTask.setGroup(INSTALL_GROUP); |
| }); |
| |
| taskFactory.register( |
| DEVICE_CHECK, |
| deviceCheckTask -> { |
| deviceCheckTask.setDescription( |
| "Runs all device checks using Device Providers and Test Servers."); |
| deviceCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP); |
| }); |
| |
| taskFactory.register( |
| CONNECTED_CHECK, |
| connectedCheckTask -> { |
| connectedCheckTask.setDescription( |
| "Runs all device checks on currently connected devices."); |
| connectedCheckTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP); |
| }); |
| |
| // Make sure MAIN_PREBUILD runs first: |
| taskFactory.register(MAIN_PREBUILD); |
| |
| taskFactory.register( |
| EXTRACT_PROGUARD_FILES, |
| ExtractProguardFiles.class, |
| task -> task.dependsOn(MAIN_PREBUILD)); |
| |
| taskFactory.register(new SourceSetsTask.CreationAction(extension)); |
| |
| taskFactory.register( |
| ASSEMBLE_ANDROID_TEST, |
| assembleAndroidTestTask -> { |
| assembleAndroidTestTask.setGroup(BasePlugin.BUILD_GROUP); |
| assembleAndroidTestTask.setDescription("Assembles all the Test applications."); |
| }); |
| |
| taskFactory.register(new LintCompile.CreationAction(globalScope)); |
| |
| // Lint task is configured in afterEvaluate, but created upfront as it is used as an |
| // anchor task. |
| createGlobalLintTask(); |
| configureCustomLintChecksConfig(); |
| |
| globalScope.setAndroidJarConfig(createAndroidJarConfig(project)); |
| |
| if (buildCache != null) { |
| taskFactory.register(new CleanBuildCache.CreationAction(globalScope)); |
| } |
| |
| // for testing only. |
| taskFactory.register( |
| "resolveConfigAttr", ConfigAttrTask.class, task -> task.resolvable = true); |
| taskFactory.register( |
| "consumeConfigAttr", ConfigAttrTask.class, task -> task.consumable = true); |
| } |
| |
| private void configureCustomLintChecksConfig() { |
| // create a single configuration to point to a project or a local file that contains |
| // the lint.jar for this project. |
| // This is not the configuration that consumes lint.jar artifacts from normal dependencies, |
| // or publishes lint.jar to consumers. These are handled at the variant level. |
| globalScope.setLintChecks(createCustomLintChecksConfig(project)); |
| globalScope.setLintPublish(createCustomLintPublishConfig(project)); |
| } |
| |
| @NonNull |
| public static Configuration createCustomLintChecksConfig(@NonNull Project project) { |
| Configuration lintChecks = project.getConfigurations().maybeCreate(CONFIG_NAME_LINTCHECKS); |
| lintChecks.setVisible(false); |
| lintChecks.setDescription("Configuration to apply external lint check jar"); |
| lintChecks.setCanBeConsumed(false); |
| return lintChecks; |
| } |
| |
| @NonNull |
| public static Configuration createCustomLintPublishConfig(@NonNull Project project) { |
| Configuration lintChecks = project.getConfigurations().maybeCreate(CONFIG_NAME_LINTPUBLISH); |
| lintChecks.setVisible(false); |
| lintChecks.setDescription("Configuration to publish external lint check jar"); |
| lintChecks.setCanBeConsumed(false); |
| return lintChecks; |
| } |
| |
| // this is call before all the variants are created since they are all going to depend |
| // on the global LINT_JAR and LINT_PUBLISH_JAR task output |
| public void configureCustomLintChecks() { |
| // setup the task that reads the config and put the lint jar in the intermediate folder |
| // so that the bundle tasks can copy it, and the inter-project publishing can publish it |
| taskFactory.register(new PrepareLintJar.CreationAction(globalScope)); |
| taskFactory.register(new PrepareLintJarForPublish.CreationAction(globalScope)); |
| } |
| |
| public void createGlobalLintTask() { |
| taskFactory.register(LINT, LintGlobalTask.class, task -> {}); |
| taskFactory.configure(JavaBasePlugin.CHECK_TASK_NAME, it -> it.dependsOn(LINT)); |
| taskFactory.register(LINT_FIX, LintFixTask.class, task -> {}); |
| } |
| |
| // this is run after all the variants are created. |
| public void configureGlobalLintTask(@NonNull final Collection<VariantScope> variants) { |
| // we only care about non testing and non feature variants |
| List<VariantScope> filteredVariants = |
| variants.stream().filter(TaskManager::isLintVariant).collect(Collectors.toList()); |
| |
| if (filteredVariants.isEmpty()) { |
| return; |
| } |
| |
| // configure the global lint tasks. |
| taskFactory.configure( |
| LINT, |
| LintGlobalTask.class, |
| task -> |
| new LintGlobalTask.GlobalCreationAction(globalScope, filteredVariants) |
| .configure(task)); |
| taskFactory.configure( |
| LINT_FIX, |
| LintFixTask.class, |
| task -> |
| new LintFixTask.GlobalCreationAction(globalScope, filteredVariants) |
| .configure(task)); |
| |
| // publish the local lint.jar to all the variants. This is not for the task output itself |
| // but for the artifact publishing. |
| for (VariantScope scope : variants) { |
| scope.getArtifacts().copy(LINT_PUBLISH_JAR, globalScope.getArtifacts()); |
| } |
| } |
| |
| // This is for config attribute debugging |
| public static class ConfigAttrTask extends DefaultTask { |
| boolean consumable = false; |
| boolean resolvable = false; |
| @TaskAction |
| public void run() { |
| for (Configuration config : getProject().getConfigurations()) { |
| AttributeContainer attributes = config.getAttributes(); |
| if ((consumable && config.isCanBeConsumed()) |
| || (resolvable && config.isCanBeResolved())) { |
| System.out.println(config.getName()); |
| System.out.println("\tcanBeResolved: " + config.isCanBeResolved()); |
| System.out.println("\tcanBeConsumed: " + config.isCanBeConsumed()); |
| for (Attribute<?> attr : attributes.keySet()) { |
| System.out.println( |
| "\t" + attr.getName() + ": " + attributes.getAttribute(attr)); |
| } |
| if (consumable && config.isCanBeConsumed()) { |
| for (PublishArtifact artifact : config.getArtifacts()) { |
| System.out.println("\tArtifact: " + artifact.getName() + " (" + artifact.getFile().getName() + ")"); |
| } |
| for (ConfigurationVariant cv : config.getOutgoing().getVariants()) { |
| System.out.println("\tConfigurationVariant: " + cv.getName()); |
| for (PublishArtifact pa : cv.getArtifacts()) { |
| System.out.println("\t\tArtifact: " + pa.getFile()); |
| System.out.println("\t\tType:" + pa.getType()); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public void createMockableJarTask() { |
| project.getDependencies() |
| .add( |
| CONFIG_NAME_ANDROID_APIS, |
| project.files( |
| (Callable) |
| () -> |
| globalScope |
| .getSdkComponents() |
| .getAndroidJarProvider() |
| .getOrNull())); |
| |
| // Adding this task to help the IDE find the mockable JAR. |
| createMockableJar = project.getTasks().register("createMockableJar"); |
| createMockableJar.configure(task -> task.dependsOn(globalScope.getMockableJarArtifact())); |
| } |
| |
| @NonNull |
| public static Configuration createAndroidJarConfig(@NonNull Project project) { |
| Configuration androidJarConfig = |
| project.getConfigurations().maybeCreate(CONFIG_NAME_ANDROID_APIS); |
| androidJarConfig.setDescription( |
| "Configuration providing various types of Android JAR file"); |
| androidJarConfig.setCanBeConsumed(false); |
| return androidJarConfig; |
| } |
| |
| protected void createDependencyStreams(@NonNull final VariantScope variantScope) { |
| // Since it's going to chance the configurations, we need to do it before |
| // we start doing queries to fill the streams. |
| handleJacocoDependencies(variantScope); |
| |
| TransformManager transformManager = variantScope.getTransformManager(); |
| |
| // This might be consumed by RecalculateFixedStackFrames if that's created |
| transformManager.addStream( |
| OriginalStream.builder(project, "ext-libs-classes") |
| .addContentTypes(TransformManager.CONTENT_CLASS) |
| .addScope(Scope.EXTERNAL_LIBRARIES) |
| .setArtifactCollection( |
| variantScope.getArtifactCollection( |
| RUNTIME_CLASSPATH, EXTERNAL, CLASSES)) |
| .build()); |
| |
| // Add stream of external java resources if EXTERNAL_LIBRARIES isn't in the set of java res |
| // merging scopes. |
| if (!getJavaResMergingScopes(variantScope, RESOURCES).contains(Scope.EXTERNAL_LIBRARIES)) { |
| transformManager.addStream( |
| OriginalStream.builder(project, "ext-libs-java-res") |
| .addContentTypes(RESOURCES) |
| .addScope(Scope.EXTERNAL_LIBRARIES) |
| .setArtifactCollection( |
| variantScope.getArtifactCollection( |
| RUNTIME_CLASSPATH, EXTERNAL, JAVA_RES)) |
| .build()); |
| } |
| |
| // for the sub modules, new intermediary classes artifact has its own stream |
| transformManager.addStream( |
| OriginalStream.builder(project, "sub-projects-classes") |
| .addContentTypes(TransformManager.CONTENT_CLASS) |
| .addScope(Scope.SUB_PROJECTS) |
| .setArtifactCollection( |
| variantScope.getArtifactCollection( |
| RUNTIME_CLASSPATH, PROJECT, CLASSES)) |
| .build()); |
| |
| // same for the java resources, if SUB_PROJECTS isn't in the set of java res merging scopes. |
| if (!getJavaResMergingScopes(variantScope, RESOURCES).contains(Scope.SUB_PROJECTS)) { |
| transformManager.addStream( |
| OriginalStream.builder(project, "sub-projects-java-res") |
| .addContentTypes(RESOURCES) |
| .addScope(Scope.SUB_PROJECTS) |
| .setArtifactCollection( |
| variantScope.getArtifactCollection( |
| RUNTIME_CLASSPATH, PROJECT, JAVA_RES)) |
| .build()); |
| } |
| |
| // if variantScope.consumesFeatureJars(), add streams of classes from features or |
| // dynamic-features. |
| // The main dex list calculation for the bundle also needs the feature classes for reference |
| // only |
| if (variantScope.consumesFeatureJars() || variantScope.getNeedsMainDexListForBundle()) { |
| transformManager.addStream( |
| OriginalStream.builder(project, "metadata-classes") |
| .addContentTypes(TransformManager.CONTENT_CLASS) |
| .addScope(InternalScope.FEATURES) |
| .setArtifactCollection( |
| variantScope.getArtifactCollection( |
| METADATA_VALUES, PROJECT, METADATA_CLASSES)) |
| .build()); |
| } |
| |
| // provided only scopes. |
| transformManager.addStream( |
| OriginalStream.builder(project, "provided-classes") |
| .addContentTypes(TransformManager.CONTENT_CLASS) |
| .addScope(Scope.PROVIDED_ONLY) |
| .setFileCollection(variantScope.getProvidedOnlyClasspath()) |
| .build()); |
| |
| if (variantScope.getTestedVariantData() != null) { |
| final BaseVariantData testedVariantData = variantScope.getTestedVariantData(); |
| |
| VariantScope testedVariantScope = testedVariantData.getScope(); |
| |
| PublishingSpecs.VariantSpec testedSpec = |
| testedVariantScope |
| .getPublishingSpec() |
| .getTestingSpec(variantScope.getVariantConfiguration().getType()); |
| |
| // get the OutputPublishingSpec from the ArtifactType for this particular variant spec |
| PublishingSpecs.OutputSpec taskOutputSpec = |
| testedSpec.getSpec( |
| AndroidArtifacts.ArtifactType.CLASSES, |
| AndroidArtifacts.PublishedConfigType.RUNTIME_ELEMENTS); |
| // now get the output type |
| com.android.build.api.artifact.ArtifactType testedOutputType = |
| taskOutputSpec.getOutputType(); |
| |
| FileCollection testedCodeClasses; |
| if (testedVariantScope.getArtifacts().hasArtifact(testedOutputType)) { |
| testedCodeClasses = |
| testedVariantScope |
| .getArtifacts() |
| .getFinalArtifactFiles(testedOutputType) |
| .get(); |
| } else { |
| Provider<FileSystemLocation> finalProduct = |
| testedVariantScope.getArtifacts().getFinalProduct(testedOutputType); |
| testedCodeClasses = project.files(finalProduct); |
| } |
| |
| variantScope.getArtifacts().createBuildableArtifact( |
| InternalArtifactType.TESTED_CODE_CLASSES, |
| BuildArtifactsHolder.OperationType.INITIAL, |
| testedCodeClasses); |
| |
| // create two streams of different types. |
| transformManager.addStream( |
| OriginalStream.builder(project, "tested-code-classes") |
| .addContentTypes(DefaultContentType.CLASSES) |
| .addScope(Scope.TESTED_CODE) |
| .setFileCollection(testedCodeClasses) |
| .build()); |
| |
| transformManager.addStream( |
| OriginalStream.builder(project, "tested-code-deps") |
| .addContentTypes(DefaultContentType.CLASSES) |
| .addScope(Scope.TESTED_CODE) |
| .setArtifactCollection( |
| testedVariantScope.getArtifactCollection( |
| RUNTIME_CLASSPATH, ALL, CLASSES)) |
| .build()); |
| } |
| } |
| |
| public void createBuildArtifactReportTask(@NonNull VariantScope scope) { |
| taskFactory.register(new BuildArtifactReportTask.BuildArtifactReportCreationAction(scope)); |
| } |
| |
| public void createSourceSetArtifactReportTask(@NonNull GlobalScope scope) { |
| for (AndroidSourceSet sourceSet : scope.getExtension().getSourceSets()) { |
| if (sourceSet instanceof DefaultAndroidSourceSet) { |
| taskFactory.register( |
| new BuildArtifactReportTask.SourceSetReportCreationAction( |
| scope, (DefaultAndroidSourceSet) sourceSet)); |
| } |
| } |
| } |
| |
| public void createMergeApkManifestsTask(@NonNull VariantScope variantScope) { |
| AndroidArtifactVariantData androidArtifactVariantData = |
| (AndroidArtifactVariantData) variantScope.getVariantData(); |
| Set<String> screenSizes = androidArtifactVariantData.getCompatibleScreens(); |
| |
| taskFactory.register( |
| new CompatibleScreensManifest.CreationAction(variantScope, screenSizes)); |
| |
| TaskProvider<? extends ManifestProcessorTask> processManifestTask = |
| createMergeManifestTask(variantScope); |
| |
| final MutableTaskContainer taskContainer = variantScope.getTaskContainer(); |
| if (taskContainer.getMicroApkTask() != null) { |
| TaskFactoryUtils.dependsOn(processManifestTask, taskContainer.getMicroApkTask()); |
| } |
| } |
| |
| /** Returns whether or not dependencies from the {@link CustomClassTransform} are packaged */ |
| protected static boolean packagesCustomClassDependencies( |
| @NonNull VariantScope scope, @NonNull ProjectOptions options) { |
| return appliesCustomClassTransforms(scope, options) && !scope.getType().isFeatureSplit(); |
| } |
| |
| /** Returns whether or not custom class transforms are applied */ |
| protected static boolean appliesCustomClassTransforms( |
| @NonNull VariantScope scope, @NonNull ProjectOptions options) { |
| final VariantType type = scope.getType(); |
| return scope.getVariantConfiguration().getBuildType().isDebuggable() |
| && type.isApk() |
| && !type.isForTesting() |
| && !getAdvancedProfilingTransforms(options).isEmpty(); |
| } |
| |
| @NonNull |
| private static List<String> getAdvancedProfilingTransforms(@NonNull ProjectOptions options) { |
| String string = options.get(StringOption.IDE_ANDROID_CUSTOM_CLASS_TRANSFORMS); |
| if (string == null) { |
| return ImmutableList.of(); |
| } |
| return Splitter.on(',').splitToList(string); |
| } |
| |
| /** Creates the merge manifests task. */ |
| @NonNull |
| protected TaskProvider<? extends ManifestProcessorTask> createMergeManifestTask( |
| @NonNull VariantScope variantScope) { |
| return taskFactory.register( |
| new ProcessApplicationManifest.CreationAction( |
| variantScope, !getAdvancedProfilingTransforms(projectOptions).isEmpty())); |
| } |
| |
| public TaskProvider<? extends ManifestProcessorTask> createMergeLibManifestsTask( |
| @NonNull VariantScope scope) { |
| |
| return taskFactory.register(new ProcessLibraryManifest.CreationAction(scope)); |
| } |
| |
| protected void createProcessTestManifestTask( |
| @NonNull VariantScope scope, |
| @NonNull VariantScope testedScope) { |
| |
| Provider<FileSystemLocation> mergedManifest = |
| testedScope.getArtifacts().getFinalProduct(MERGED_MANIFESTS); |
| taskFactory.register( |
| new ProcessTestManifest.CreationAction(scope, project.files(mergedManifest))); |
| } |
| |
| public void createRenderscriptTask(@NonNull VariantScope scope) { |
| final MutableTaskContainer taskContainer = scope.getTaskContainer(); |
| |
| TaskProvider<RenderscriptCompile> rsTask = |
| taskFactory.register(new RenderscriptCompile.CreationAction(scope)); |
| |
| GradleVariantConfiguration config = scope.getVariantConfiguration(); |
| |
| TaskFactoryUtils.dependsOn(taskContainer.getResourceGenTask(), rsTask); |
| // only put this dependency if rs will generate Java code |
| if (!config.getRenderscriptNdkModeEnabled()) { |
| TaskFactoryUtils.dependsOn(taskContainer.getSourceGenTask(), rsTask); |
| } |
| } |
| |
| public TaskProvider<MergeResources> createMergeResourcesTask( |
| @NonNull VariantScope scope, |
| boolean processResources, |
| ImmutableSet<MergeResources.Flag> flags) { |
| |
| boolean unitTestRawResources = |
| globalScope |
| .getExtension() |
| .getTestOptions() |
| .getUnitTests() |
| .isIncludeAndroidResources() |
| && !projectOptions.get(BooleanOption.ENABLE_UNIT_TEST_BINARY_RESOURCES); |
| |
| boolean alsoOutputNotCompiledResources = |
| scope.getType().isApk() |
| && !scope.getType().isForTesting() |
| && (scope.useResourceShrinker() || unitTestRawResources); |
| |
| return basicCreateMergeResourcesTask( |
| scope, |
| MergeType.MERGE, |
| null /*outputLocation*/, |
| true /*includeDependencies*/, |
| processResources, |
| alsoOutputNotCompiledResources, |
| flags, |
| null /*configCallback*/); |
| } |
| |
| /** Defines the merge type for {@link #basicCreateMergeResourcesTask} */ |
| public enum MergeType { |
| /** |
| * Merge all resources with all the dependencies resources. |
| */ |
| MERGE { |
| @Override |
| public InternalArtifactType getOutputType() { |
| return InternalArtifactType.MERGED_RES; |
| } |
| }, |
| /** |
| * Merge all resources without the dependencies resources for an aar. |
| */ |
| PACKAGE { |
| @Override |
| public InternalArtifactType getOutputType() { |
| return InternalArtifactType.PACKAGED_RES; |
| } |
| }; |
| |
| public abstract InternalArtifactType getOutputType(); |
| } |
| |
| public TaskProvider<MergeResources> basicCreateMergeResourcesTask( |
| @NonNull VariantScope scope, |
| @NonNull MergeType mergeType, |
| @Nullable File outputLocation, |
| final boolean includeDependencies, |
| final boolean processResources, |
| boolean alsoOutputNotCompiledResources, |
| @NonNull ImmutableSet<MergeResources.Flag> flags, |
| @Nullable TaskProviderCallback<MergeResources> taskProviderCallback) { |
| |
| String taskNamePrefix = mergeType.name().toLowerCase(Locale.ENGLISH); |
| |
| File mergedNotCompiledDir = |
| alsoOutputNotCompiledResources |
| ? new File( |
| globalScope.getIntermediatesDir() |
| + "/merged-not-compiled-resources/" |
| + scope.getVariantConfiguration().getDirName()) |
| : null; |
| |
| TaskProvider<MergeResources> mergeResourcesTask = |
| taskFactory.register( |
| new MergeResources.CreationAction( |
| scope, |
| mergeType, |
| taskNamePrefix, |
| mergedNotCompiledDir, |
| includeDependencies, |
| processResources, |
| flags), |
| null, |
| null, |
| taskProviderCallback); |
| |
| scope.getArtifacts() |
| .producesDir( |
| mergeType.getOutputType(), |
| BuildArtifactsHolder.OperationType.INITIAL, |
| mergeResourcesTask, |
| MergeResources::getOutputDir, |
| project.getLayout() |
| .getBuildDirectory() |
| .dir( |
| MoreObjects.firstNonNull( |
| outputLocation, |
| scope.getDefaultMergeResourcesOutputDir()) |
| .getAbsolutePath()), |
| ""); |
| |
| if (alsoOutputNotCompiledResources) { |
| scope.getArtifacts() |
| .producesDir( |
| MERGED_NOT_COMPILED_RES, |
| BuildArtifactsHolder.OperationType.INITIAL, |
| mergeResourcesTask, |
| MergeResources::getMergedNotCompiledResourcesOutputDirectory, |
| project.getLayout() |
| .getBuildDirectory() |
| .dir(mergedNotCompiledDir.getAbsolutePath()), |
| ""); |
| } |
| |
| if (extension.getTestOptions().getUnitTests().isIncludeAndroidResources()) { |
| TaskFactoryUtils.dependsOn( |
| scope.getTaskContainer().getCompileTask(), mergeResourcesTask); |
| } |
| |
| return mergeResourcesTask; |
| } |
| |
| public void createMergeAssetsTask(@NonNull VariantScope scope) { |
| taskFactory.register(new MergeSourceSetFolders.MergeAppAssetCreationAction(scope)); |
| } |
| |
| @NonNull |
| public void createMergeJniLibFoldersTasks(@NonNull final VariantScope variantScope) { |
| // merge the source folders together using the proper priority. |
| taskFactory.register( |
| new MergeSourceSetFolders.MergeJniLibFoldersCreationAction(variantScope)); |
| |
| // Compute the scopes that need to be merged. |
| Set<ScopeType> mergeScopes = getJavaResMergingScopes(variantScope, NATIVE_LIBS); |
| |
| taskFactory.register(new MergeNativeLibsTask.CreationAction(mergeScopes, variantScope)); |
| } |
| |
| public void createBuildConfigTask(@NonNull VariantScope scope) { |
| TaskProvider<GenerateBuildConfig> generateBuildConfigTask = |
| taskFactory.register(new GenerateBuildConfig.CreationAction(scope)); |
| |
| TaskFactoryUtils.dependsOn( |
| scope.getTaskContainer().getSourceGenTask(), generateBuildConfigTask); |
| } |
| |
| public void createGenerateResValuesTask( |
| @NonNull VariantScope scope) { |
| TaskProvider<GenerateResValues> generateResValuesTask = |
| taskFactory.register(new GenerateResValues.CreationAction(scope)); |
| TaskFactoryUtils.dependsOn( |
| scope.getTaskContainer().getResourceGenTask(), generateResValuesTask); |
| } |
| |
| public void createApkProcessResTask( |
| @NonNull VariantScope scope) { |
| VariantType variantType = scope.getVariantData().getVariantConfiguration().getType(); |
| InternalArtifactType packageOutputType = |
| (variantType.isApk() && !variantType.isForTesting()) ? FEATURE_RESOURCE_PKG : null; |
| |
| createApkProcessResTask(scope, packageOutputType); |
| |
| if (scope.consumesFeatureJars()) { |
| taskFactory.register(new MergeAaptProguardFilesCreationAction(scope)); |
| } |
| } |
| |
| private void createApkProcessResTask(@NonNull VariantScope scope, |
| InternalArtifactType packageOutputType) { |
| createProcessResTask( |
| scope, |
| scope.getSymbolTableFile(), |
| packageOutputType, |
| MergeType.MERGE, |
| scope.getGlobalScope().getProjectBaseName()); |
| } |
| |
| protected boolean isLibrary() { |
| return false; |
| } |
| |
| public void createProcessResTask( |
| @NonNull VariantScope scope, |
| @NonNull File symbolLocation, |
| @Nullable InternalArtifactType packageOutputType, |
| @NonNull MergeType mergeType, |
| @NonNull String baseName) { |
| BaseVariantData variantData = scope.getVariantData(); |
| |
| variantData.calculateFilters(scope.getGlobalScope().getExtension().getSplits()); |
| |
| // The manifest main dex list proguard rules are always needed for the bundle, |
| // even if legacy multidex is not explicitly enabled. |
| boolean useAaptToGenerateLegacyMultidexMainDexProguardRules = scope.getNeedsMainDexList(); |
| |
| if (scope.getGlobalScope().getExtension().getAaptOptions().getNamespaced()) { |
| new NamespacedResourcesTaskManager(globalScope, taskFactory, scope) |
| .createNamespacedResourceTasks( |
| packageOutputType, |
| baseName, |
| useAaptToGenerateLegacyMultidexMainDexProguardRules); |
| |
| FileCollection rFiles = |
| project.files(scope.getArtifacts().getFinalProduct(RUNTIME_R_CLASS_CLASSES)); |
| |
| scope.getTransformManager() |
| .addStream( |
| OriginalStream.builder(project, "final-r-classes") |
| .addContentTypes( |
| scope.getNeedsJavaResStreams() |
| ? TransformManager.CONTENT_JARS |
| : ImmutableSet.of(DefaultContentType.CLASSES)) |
| .addScope(Scope.PROJECT) |
| .setFileCollection(rFiles) |
| .build()); |
| |
| scope.getArtifacts().appendArtifact(AnchorOutputType.ALL_CLASSES, rFiles); |
| return; |
| } |
| createNonNamespacedResourceTasks( |
| scope, |
| symbolLocation, |
| packageOutputType, |
| mergeType, |
| baseName, |
| useAaptToGenerateLegacyMultidexMainDexProguardRules); |
| } |
| |
| private void createNonNamespacedResourceTasks( |
| @NonNull VariantScope scope, |
| @NonNull File symbolDirectory, |
| InternalArtifactType packageOutputType, |
| @NonNull MergeType mergeType, |
| @NonNull String baseName, |
| boolean useAaptToGenerateLegacyMultidexMainDexProguardRules) { |
| File symbolTableWithPackageName = |
| FileUtils.join( |
| globalScope.getIntermediatesDir(), |
| FD_RES, |
| "symbol-table-with-package", |
| scope.getVariantConfiguration().getDirName(), |
| "package-aware-r.txt"); |
| final TaskProvider<? extends ProcessAndroidResources> task; |
| |
| File symbolFile = new File(symbolDirectory, FN_RESOURCE_TEXT); |
| BuildArtifactsHolder artifacts = scope.getArtifacts(); |
| if (mergeType == MergeType.PACKAGE) { |
| // MergeType.PACKAGE means we will only merged the resources from our current module |
| // (little merge). This is used for finding what goes into the AAR (packaging), and also |
| // for parsing the local resources and merging them with the R.txt files from its |
| // dependencies to write the R.txt for this module and R.jar for this module and its |
| // dependencies. |
| |
| // First collect symbols from this module. |
| taskFactory.register(new ParseLibraryResourcesTask.CreateAction(scope)); |
| |
| // Only generate the keep rules when we need them. |
| if (generatesProguardOutputFile(scope)) { |
| taskFactory.register(new GenerateLibraryProguardRulesTask.CreationAction(scope)); |
| } |
| |
| // Generate the R class for a library using both local symbols and symbols |
| // from dependencies. |
| task = |
| taskFactory.register( |
| new GenerateLibraryRFileTask.CreationAction( |
| scope, symbolFile, symbolTableWithPackageName)); |
| } else { |
| // MergeType.MERGE means we merged the whole universe. |
| task = |
| taskFactory.register( |
| createProcessAndroidResourcesConfigAction( |
| scope, |
| () -> symbolDirectory, |
| symbolTableWithPackageName, |
| useAaptToGenerateLegacyMultidexMainDexProguardRules, |
| mergeType, |
| baseName)); |
| |
| if (packageOutputType != null) { |
| artifacts.republish(PROCESSED_RES, packageOutputType); |
| } |
| |
| // create the task that creates the aapt output for the bundle. |
| taskFactory.register(new LinkAndroidResForBundleTask.CreationAction(scope)); |
| |
| scope.getArtifacts() |
| .appendArtifact( |
| AnchorOutputType.ALL_CLASSES, |
| project.files( |
| artifacts.getFinalProduct( |
| COMPILE_AND_RUNTIME_NOT_NAMESPACED_R_CLASS_JAR))); |
| } |
| artifacts.appendArtifact( |
| InternalArtifactType.SYMBOL_LIST, ImmutableList.of(symbolFile), task.getName()); |
| |
| // Synthetic output for AARs (see SymbolTableWithPackageNameTransform), and created in |
| // process resources for local subprojects. |
| artifacts.appendArtifact( |
| InternalArtifactType.SYMBOL_LIST_WITH_PACKAGE_NAME, |
| ImmutableList.of(symbolTableWithPackageName), |
| task.getName()); |
| } |
| |
| private static boolean generatesProguardOutputFile(VariantScope variantScope) { |
| return variantScope.getCodeShrinker() != null || variantScope.getType().isFeatureSplit(); |
| } |
| |
| protected VariantTaskCreationAction<LinkApplicationAndroidResourcesTask> |
| createProcessAndroidResourcesConfigAction( |
| @NonNull VariantScope scope, |
| @NonNull Supplier<File> symbolLocation, |
| @NonNull File symbolWithPackageName, |
| boolean useAaptToGenerateLegacyMultidexMainDexProguardRules, |
| @NonNull MergeType sourceArtifactType, |
| @NonNull String baseName) { |
| |
| return new LinkApplicationAndroidResourcesTask.CreationAction( |
| scope, |
| symbolLocation, |
| symbolWithPackageName, |
| useAaptToGenerateLegacyMultidexMainDexProguardRules, |
| sourceArtifactType, |
| baseName, |
| isLibrary()); |
| } |
| |
| /** |
| * Creates the split resources packages task if necessary. AAPT will produce split packages for |
| * all --split provided parameters. These split packages should be signed and moved unchanged to |
| * the FULL_APK build output directory. |
| */ |
| public void createSplitResourcesTasks(@NonNull VariantScope scope) { |
| BaseVariantData variantData = scope.getVariantData(); |
| |
| checkState( |
| variantData.getMultiOutputPolicy().equals(MultiOutputPolicy.SPLITS), |
| "Can only create split resources tasks for pure splits."); |
| |
| TaskProvider<PackageSplitRes> task = |
| taskFactory.register(new PackageSplitRes.CreationAction(scope)); |
| } |
| |
| public void createSplitAbiTasks(@NonNull VariantScope scope) { |
| BaseVariantData variantData = scope.getVariantData(); |
| |
| checkState( |
| variantData.getMultiOutputPolicy().equals(MultiOutputPolicy.SPLITS), |
| "split ABI tasks are only compatible with pure splits."); |
| |
| Set<String> filters = AbiSplitOptions.getAbiFilters(extension.getSplits().getAbiFilters()); |
| if (filters.isEmpty()) { |
| return; |
| } |
| |
| List<ApkData> fullApkDatas = |
| variantData.getOutputScope().getSplitsByType(OutputFile.OutputType.FULL_SPLIT); |
| if (!fullApkDatas.isEmpty()) { |
| throw new RuntimeException( |
| "In release 21 and later, there cannot be full splits and pure splits, " |
| + "found " |
| + Joiner.on(",").join(fullApkDatas) |
| + " and abi filters " |
| + Joiner.on(",").join(filters)); |
| } |
| |
| // first create the ABI specific split FULL_APK resources. |
| taskFactory.register(new GenerateSplitAbiRes.CreationAction(scope)); |
| |
| // then package those resources with the appropriate JNI libraries. |
| taskFactory.register( |
| new PackageSplitAbi.CreationAction( |
| scope, packagesCustomClassDependencies(scope, projectOptions))); |
| } |
| |
| public void createSplitTasks(@NonNull VariantScope variantScope) { |
| createSplitResourcesTasks(variantScope); |
| createSplitAbiTasks(variantScope); |
| } |
| |
| /** |
| * Returns the scopes for which the java resources should be merged. |
| * |
| * @param variantScope the scope of the variant being processed. |
| * @param contentType the contentType of java resources, must be RESOURCES or NATIVE_LIBS |
| * @return the list of scopes for which to merge the java resources. |
| */ |
| @NonNull |
| protected abstract Set<ScopeType> getJavaResMergingScopes( |
| @NonNull VariantScope variantScope, @NonNull QualifiedContent.ContentType contentType); |
| |
| /** |
| * Creates the java resources processing tasks. |
| * |
| * <p>The java processing will happen in two steps: |
| * |
| * <ul> |
| * <li>{@link Sync} task configured with {@link ProcessJavaResTask.CreationAction} will sync |
| * all source folders into a single folder identified by {@link |
| * VariantScope#getSourceFoldersJavaResDestinationDir()} |
| * <li>{@link MergeJavaResourceTask} will take the output of this merge plus the |
| * dependencies and will create a single merge with the {@link PackagingOptions} settings |
| * applied. |
| * </ul> |
| * |
| * This sets up only the Sync part. The java res merging is setup via {@link |
| * #createMergeJavaResTask(VariantScope)} |
| * |
| * @param variantScope the variant scope we are operating under. |
| */ |
| public void createProcessJavaResTask(@NonNull VariantScope variantScope) { |
| // Copy the source folders java resources into the temporary location, mainly to |
| // maintain the PluginDsl COPY semantics. |
| |
| // TODO: move this file computation completely out of VariantScope. |
| File destinationDir = variantScope.getSourceFoldersJavaResDestinationDir(); |
| |
| TaskProvider<ProcessJavaResTask> processJavaResourcesTask = |
| taskFactory.register( |
| new ProcessJavaResTask.CreationAction(variantScope, destinationDir)); |
| |
| // create the task outputs for others to consume |
| variantScope |
| .getArtifacts() |
| .appendArtifact( |
| InternalArtifactType.JAVA_RES, |
| ImmutableList.of(destinationDir), |
| processJavaResourcesTask.getName()); |
| |
| // create the stream generated from this task, but only if a library with custom transforms, |
| // in which case the custom transforms must be applied before java res merging. |
| if (variantScope.getNeedsJavaResStreams()) { |
| variantScope |
| .getTransformManager() |
| .addStream( |
| OriginalStream.builder(project, "processed-java-res") |
| .addContentType(RESOURCES) |
| .addScope(Scope.PROJECT) |
| .setFileCollection( |
| variantScope |
| .getArtifacts() |
| .getFinalArtifactFiles( |
| InternalArtifactType.JAVA_RES) |
| .get()) |
| .build()); |
| } |
| } |
| |
| /** |
| * Sets up the Merge Java Res task. |
| * |
| * @param variantScope the variant scope we are operating under. |
| * @see #createProcessJavaResTask(VariantScope) |
| */ |
| public void createMergeJavaResTask(@NonNull VariantScope variantScope) { |
| TransformManager transformManager = variantScope.getTransformManager(); |
| |
| // Compute the scopes that need to be merged. |
| Set<ScopeType> mergeScopes = getJavaResMergingScopes(variantScope, RESOURCES); |
| |
| taskFactory.register(new MergeJavaResourceTask.CreationAction(mergeScopes, variantScope)); |
| |
| // also add a new merged java res stream if needed. |
| if (variantScope.getNeedsMergedJavaResStream()) { |
| Provider<RegularFile> mergedJavaResProvider = |
| variantScope.getArtifacts().getFinalProduct(MERGED_JAVA_RES); |
| transformManager.addStream( |
| OriginalStream.builder(project, "merged-java-res") |
| .addContentTypes(TransformManager.CONTENT_RESOURCES) |
| .addScopes(mergeScopes) |
| .setFileCollection(project.getLayout().files(mergedJavaResProvider)) |
| .build()); |
| } |
| |
| |
| } |
| |
| public TaskProvider<AidlCompile> createAidlTask(@NonNull VariantScope scope) { |
| MutableTaskContainer taskContainer = scope.getTaskContainer(); |
| |
| TaskProvider<AidlCompile> aidlCompileTask = |
| taskFactory.register(new AidlCompile.CreationAction(scope)); |
| |
| TaskFactoryUtils.dependsOn(taskContainer.getSourceGenTask(), aidlCompileTask); |
| |
| return aidlCompileTask; |
| } |
| |
| public void createShaderTask(@NonNull VariantScope scope) { |
| // merge the shader folders together using the proper priority. |
| TaskProvider<MergeSourceSetFolders> mergeShadersTask = |
| taskFactory.register( |
| new MergeSourceSetFolders.MergeShaderSourceFoldersCreationAction(scope)); |
| |
| // compile the shaders |
| TaskProvider<ShaderCompile> shaderCompileTask = |
| taskFactory.register(new ShaderCompile.CreationAction(scope)); |
| |
| TaskFactoryUtils.dependsOn(scope.getTaskContainer().getAssetGenTask(), shaderCompileTask); |
| } |
| |
| protected abstract void postJavacCreation(@NonNull final VariantScope scope); |
| |
| /** |
| * Creates the task for creating *.class files using javac. These tasks are created regardless |
| * of whether Jack is used or not, but assemble will not depend on them if it is. They are |
| * always used when running unit tests. |
| */ |
| public TaskProvider<? extends JavaCompile> createJavacTask(@NonNull final VariantScope scope) { |
| taskFactory.register(new JavaPreCompileTask.CreationAction(scope)); |
| |
| boolean processAnnotationsTaskCreated = ProcessAnnotationsTask.taskShouldBeCreated(scope); |
| if (processAnnotationsTaskCreated) { |
| taskFactory.register(new ProcessAnnotationsTask.CreationAction(scope)); |
| } |
| |
| final TaskProvider<? extends JavaCompile> javacTask = |
| taskFactory.register( |
| new AndroidJavaCompile.CreationAction( |
| scope, processAnnotationsTaskCreated)); |
| |
| postJavacCreation(scope); |
| |
| return javacTask; |
| } |
| |
| /** |
| * Add stream of classes compiled by javac to transform manager. |
| * |
| * <p>This should not be called for classes that will also be compiled from source by jack. |
| */ |
| protected void addJavacClassesStream(VariantScope scope) { |
| BuildArtifactsHolder artifacts = scope.getArtifacts(); |
| Provider<Directory> javaOutputs = artifacts.getFinalProduct(JAVAC); |
| Preconditions.checkNotNull(javaOutputs); |
| // create separate streams for the output of JAVAC and for the pre/post javac |
| // bytecode hooks |
| scope.getTransformManager() |
| .addStream( |
| OriginalStream.builder(project, "javac-output") |
| // Need both classes and resources because some annotation |
| // processors generate resources |
| .addContentTypes( |
| scope.getNeedsJavaResStreams() |
| ? TransformManager.CONTENT_JARS |
| : ImmutableSet.of(DefaultContentType.CLASSES)) |
| .addScope(Scope.PROJECT) |
| .setFileCollection(project.getLayout().files(javaOutputs)) |
| .build()); |
| |
| scope.getTransformManager() |
| .addStream( |
| OriginalStream.builder(project, "pre-javac-generated-bytecode") |
| .addContentTypes( |
| scope.getNeedsJavaResStreams() |
| ? TransformManager.CONTENT_JARS |
| : ImmutableSet.of(DefaultContentType.CLASSES)) |
| .addScope(Scope.PROJECT) |
| .setFileCollection( |
| scope.getVariantData().getAllPreJavacGeneratedBytecode()) |
| .build()); |
| |
| scope.getTransformManager() |
| .addStream( |
| OriginalStream.builder(project, "post-javac-generated-bytecode") |
| .addContentTypes( |
| scope.getNeedsJavaResStreams() |
| ? TransformManager.CONTENT_JARS |
| : ImmutableSet.of(DefaultContentType.CLASSES)) |
| .addScope(Scope.PROJECT) |
| .setFileCollection( |
| scope.getVariantData().getAllPostJavacGeneratedBytecode()) |
| .build()); |
| |
| if (scope.getGlobalScope().getExtension().getAaptOptions().getNamespaced() |
| && projectOptions.get(BooleanOption.CONVERT_NON_NAMESPACED_DEPENDENCIES)) { |
| // This might be consumed by RecalculateFixedStackFrames if that's created |
| scope.getTransformManager() |
| .addStream( |
| OriginalStream.builder(project, "auto-namespaced-dependencies-classes") |
| .addContentTypes(DefaultContentType.CLASSES) |
| .addScope(Scope.EXTERNAL_LIBRARIES) |
| .setFileCollection( |
| artifacts |
| .getFinalArtifactFiles( |
| InternalArtifactType |
| .NAMESPACED_CLASSES_JAR) |
| .get()) |
| .build()); |
| } |
| } |
| |
| protected void createCompileTask(@NonNull VariantScope variantScope) { |
| TaskProvider<? extends JavaCompile> javacTask = createJavacTask(variantScope); |
| addJavacClassesStream(variantScope); |
| setJavaCompilerTask(javacTask, variantScope); |
| createPostCompilationTasks(variantScope); |
| } |
| |
| |
| /** Makes the given task the one used by top-level "compile" task. */ |
| public static void setJavaCompilerTask( |
| @NonNull TaskProvider<? extends JavaCompile> javaCompilerTask, |
| @NonNull VariantScope scope) { |
| TaskFactoryUtils.dependsOn(scope.getTaskContainer().getCompileTask(), javaCompilerTask); |
| } |
| |
| /** |
| * Creates the task that will handle micro apk. |
| * |
| * New in 2.2, it now supports the unbundled mode, in which the apk is not bundled |
| * anymore, but we still have an XML resource packaged, and a custom entry in the manifest. |
| * This is triggered by passing a null {@link Configuration} object. |
| * |
| * @param scope the variant scope |
| * @param config an optional Configuration object. if non null, this will embed the micro apk, |
| * if null this will trigger the unbundled mode. |
| */ |
| public void createGenerateMicroApkDataTask( |
| @NonNull VariantScope scope, |
| @Nullable FileCollection config) { |
| TaskProvider<GenerateApkDataTask> generateMicroApkTask = |
| taskFactory.register(new GenerateApkDataTask.CreationAction(scope, config)); |
| |
| // the merge res task will need to run after this one. |
| TaskFactoryUtils.dependsOn( |
| scope.getTaskContainer().getResourceGenTask(), generateMicroApkTask); |
| } |
| |
| public void createExternalNativeBuildJsonGenerators(@NonNull VariantScope scope) { |
| CxxModuleModel module = tryCreateCxxModuleModel(scope.getGlobalScope()); |
| |
| if (module == null) { |
| return; |
| } |
| |
| scope.getTaskContainer() |
| .setExternalNativeJsonGenerator( |
| TaskInputHelper.memoizeToProvider( |
| project, () -> ExternalNativeJsonGenerator.create(module, scope))); |
| } |
| |
| public void createExternalNativeBuildTasks(@NonNull VariantScope scope) { |
| final MutableTaskContainer taskContainer = scope.getTaskContainer(); |
| Provider<ExternalNativeJsonGenerator> generator = |
| taskContainer.getExternalNativeJsonGenerator(); |
| if (generator == null) { |
| return; |
| } |
| |
| // Set up JSON generation tasks |
| TaskProvider<? extends Task> generateTask = |
| taskFactory.register( |
| ExternalNativeBuildJsonTask.createTaskConfigAction(generator, scope)); |
| |
| ProjectOptions projectOptions = globalScope.getProjectOptions(); |
| |
| // Set up build tasks |
| TaskProvider<ExternalNativeBuildTask> buildTask = |
| taskFactory.register( |
| new ExternalNativeBuildTask.CreationAction(generator, generateTask, scope)); |
| |
| TaskFactoryUtils.dependsOn(taskContainer.getCompileTask(), buildTask); |
| |
| // Set up clean tasks |
| TaskProvider<Task> cleanTask = taskFactory.named("clean"); |
| TaskFactoryUtils.dependsOn( |
| cleanTask, |
| taskFactory.register(new ExternalNativeCleanTask.CreationAction(generator, scope))); |
| } |
| |
| /** Creates the tasks to build unit tests. */ |
| public void createUnitTestVariantTasks(@NonNull TestVariantData variantData) { |
| VariantScope variantScope = variantData.getScope(); |
| BuildArtifactsHolder artifacts = variantScope.getArtifacts(); |
| BaseVariantData testedVariantData = |
| checkNotNull(variantScope.getTestedVariantData(), "Not a unit test variant"); |
| VariantScope testedVariantScope = testedVariantData.getScope(); |
| |
| boolean includeAndroidResources = extension.getTestOptions().getUnitTests() |
| .isIncludeAndroidResources(); |
| boolean enableBinaryResources = includeAndroidResources |
| && globalScope.getProjectOptions().get( |
| BooleanOption.ENABLE_UNIT_TEST_BINARY_RESOURCES); |
| |
| createAnchorTasks(variantScope); |
| |
| // Create all current streams (dependencies mostly at this point) |
| createDependencyStreams(variantScope); |
| |
| // process java resources |
| createProcessJavaResTask(variantScope); |
| |
| if (includeAndroidResources) { |
| if (testedVariantScope.getType().isAar()) { |
| // Add a task to process the manifest |
| createProcessTestManifestTask(variantScope, testedVariantData.getScope()); |
| |
| // Add a task to create the res values |
| createGenerateResValuesTask(variantScope); |
| |
| // Add a task to merge the assets folders |
| createMergeAssetsTask(variantScope); |
| |
| if (enableBinaryResources) { |
| createMergeResourcesTask(variantScope, true, ImmutableSet.of()); |
| // Add a task to process the Android Resources and generate source files |
| createApkProcessResTask(variantScope, FEATURE_RESOURCE_PKG); |
| taskFactory.register(new PackageForUnitTest.CreationAction(variantScope)); |
| } else { |
| createMergeResourcesTask(variantScope, false, ImmutableSet.of()); |
| } |
| } else if (testedVariantScope.getType().isApk()) { |
| if (enableBinaryResources) { |
| // The IDs will have been inlined for an non-namespaced application |
| // so just re-export the artifacts here. |
| artifacts.copy(PROCESSED_RES, testedVariantScope.getArtifacts()); |
| artifacts.copy(MERGED_ASSETS, testedVariantScope.getArtifacts()); |
| |
| taskFactory.register(new PackageForUnitTest.CreationAction(variantScope)); |
| } else { |
| // TODO: don't implicitly subtract tested component in APKs, as that only |
| // makes sense for instrumentation tests. For now, rely on the production |
| // merged resources. |
| artifacts.copy( |
| InternalArtifactType.MERGED_RES, |
| testedVariantScope.getArtifacts(), |
| MERGED_NOT_COMPILED_RES); |
| } |
| } else { |
| throw new IllegalStateException( |
| "Tested variant " |
| + testedVariantScope.getFullVariantName() |
| + " in " |
| + globalScope.getProject().getPath() |
| + " must be a library or an application to have unit tests."); |
| } |
| |
| TaskProvider<GenerateTestConfig> generateTestConfig = |
| taskFactory.register(new GenerateTestConfig.CreationAction(variantScope)); |
| TaskProvider<? extends Task> compileTask = |
| variantScope.getTaskContainer().getCompileTask(); |
| TaskFactoryUtils.dependsOn(compileTask, generateTestConfig); |
| // The GenerateTestConfig task has 2 types of inputs: direct inputs and indirect inputs. |
| // Only the direct inputs are registered with Gradle, whereas the indirect inputs are |
| // not (see that class for details). |
| // Since the compile task also depends on the indirect inputs to the GenerateTestConfig |
| // task, making the compile task depend on the GenerateTestConfig task is not enough, we |
| // also need to register those inputs with Gradle explicitly here. (We can't register |
| // @Nested objects programmatically, so it's important to keep these inputs consistent |
| // with those defined in TestConfigInputs.) |
| compileTask.configure( |
| task -> { |
| GenerateTestConfig.TestConfigInputs testConfigInputs = |
| new GenerateTestConfig.TestConfigInputs(variantScope); |
| TaskInputs taskInputs = task.getInputs(); |
| taskInputs.property( |
| "isUseRelativePathEnabled", |
| testConfigInputs.isUseRelativePathEnabled()); |
| taskInputs |
| .files(testConfigInputs.getResourceApk()) |
| .withPropertyName("resourceApk") |
| .optional() |
| .withPathSensitivity(PathSensitivity.RELATIVE); |
| taskInputs |
| .files(testConfigInputs.getMergedResources()) |
| .withPropertyName("mergedResources") |
| .optional() |
| .withPathSensitivity(PathSensitivity.RELATIVE); |
| taskInputs |
| .files(testConfigInputs.getMergedAssets()) |
| .withPropertyName("mergedAssets") |
| .withPathSensitivity(PathSensitivity.RELATIVE); |
| taskInputs |
| .files(testConfigInputs.getMergedManifest()) |
| .withPropertyName("mergedManifest") |
| .withPathSensitivity(PathSensitivity.RELATIVE); |
| taskInputs.property("mainApkInfo", testConfigInputs.getMainApkInfo()); |
| taskInputs.property( |
| "packageNameOfFinalRClassProvider", |
| (Supplier<String>) testConfigInputs::getPackageNameOfFinalRClass); |
| }); |
| } |
| |
| // :app:compileDebugUnitTestSources should be enough for running tests from AS, so add |
| // dependencies on tasks that prepare necessary data files. |
| TaskProvider<? extends Task> compileTask = variantScope.getTaskContainer().getCompileTask(); |
| TaskFactoryUtils.dependsOn( |
| compileTask, |
| variantScope.getTaskContainer().getProcessJavaResourcesTask(), |
| testedVariantScope.getTaskContainer().getProcessJavaResourcesTask()); |
| |
| // Empty R class jar. TODO: Resources support for unit tests? |
| artifacts.emptyFile(InternalArtifactType.COMPILE_ONLY_NAMESPACED_R_CLASS_JAR); |
| |
| TaskProvider<? extends JavaCompile> javacTask = createJavacTask(variantScope); |
| addJavacClassesStream(variantScope); |
| setJavaCompilerTask(javacTask, variantScope); |
| // This should be done automatically by the classpath |
| // TaskFactoryUtils.dependsOn(javacTask, testedVariantScope.getTaskContainer().getJavacTask()); |
| |
| // TODO: use merged java res for unit tests (bug 118690729) |
| |
| createRunUnitTestTask(variantScope); |
| |
| // This hides the assemble unit test task from the task list. |
| |
| variantScope.getTaskContainer().getAssembleTask().configure(task -> task.setGroup(null)); |
| } |
| |
| protected void registerRClassTransformStream(@NonNull VariantScope variantScope) { |
| if (globalScope.getExtension().getAaptOptions().getNamespaced()) { |
| return; |
| } |
| |
| FileCollection rClassJar = |
| project.files( |
| variantScope |
| .getArtifacts() |
| .getFinalProduct( |
| InternalArtifactType |
| .COMPILE_AND_RUNTIME_NOT_NAMESPACED_R_CLASS_JAR)); |
| |
| variantScope |
| .getTransformManager() |
| .addStream( |
| OriginalStream.builder(project, "compile-and-runtime-light-r-classes") |
| .addContentTypes(TransformManager.CONTENT_CLASS) |
| .addScope(QualifiedContent.Scope.PROJECT) |
| .setFileCollection(rClassJar) |
| .build()); |
| } |
| |
| /** Creates the tasks to build android tests. */ |
| public void createAndroidTestVariantTasks( |
| @NonNull TestVariantData variantData, |
| @NonNull List<VariantScope> variantScopesForLint) { |
| VariantScope variantScope = variantData.getScope(); |
| |
| createAnchorTasks(variantScope); |
| |
| // Create all current streams (dependencies mostly at this point) |
| createDependencyStreams(variantScope); |
| |
| // persist variant's output |
| taskFactory.register(new MainApkListPersistence.CreationAction(variantScope)); |
| |
| // Add a task to process the manifest |
| createProcessTestManifestTask( |
| variantScope, checkNotNull(variantScope.getTestedVariantData()).getScope()); |
| |
| // Add a task to create the res values |
| createGenerateResValuesTask(variantScope); |
| |
| // Add a task to compile renderscript files. |
| createRenderscriptTask(variantScope); |
| |
| // Add a task to merge the resource folders |
| createMergeResourcesTask(variantScope, true, ImmutableSet.of()); |
| |
| // Add tasks to compile shader |
| createShaderTask(variantScope); |
| |
| // Add a task to merge the assets folders |
| createMergeAssetsTask(variantScope); |
| |
| // Add a task to create the BuildConfig class |
| createBuildConfigTask(variantScope); |
| |
| // Add a task to generate resource source files |
| createApkProcessResTask(variantScope); |
| |
| registerRClassTransformStream(variantScope); |
| |
| // process java resources |
| createProcessJavaResTask(variantScope); |
| |
| createAidlTask(variantScope); |
| |
| // add tasks to merge jni libs. |
| createMergeJniLibFoldersTasks(variantScope); |
| |
| // Add data binding tasks if enabled |
| createDataBindingTasksIfNecessary(variantScope, MergeType.MERGE); |
| |
| // Add a task to compile the test application |
| TaskProvider<? extends JavaCompile> javacTask = createJavacTask(variantScope); |
| addJavacClassesStream(variantScope); |
| setJavaCompilerTask(javacTask, variantScope); |
| createPostCompilationTasks(variantScope); |
| |
| // Add a task to produce the signing config file |
| createValidateSigningTask(variantScope); |
| taskFactory.register(new SigningConfigWriterTask.CreationAction(variantScope)); |
| |
| createPackagingTask(variantScope); |
| |
| maybeCreateLintVitalTask( |
| (ApkVariantData) variantScope.getVariantData(), variantScopesForLint); |
| |
| taskFactory.configure( |
| ASSEMBLE_ANDROID_TEST, |
| assembleTest -> |
| assembleTest.dependsOn( |
| variantData.getTaskContainer().getAssembleTask().getName())); |
| |
| createConnectedTestForVariant(variantScope); |
| } |
| |
| /** Is the given variant relevant for lint? */ |
| static boolean isLintVariant(@NonNull VariantScope variantScope) { |
| // Only create lint targets for variants like debug and release, not debugTest |
| final VariantType variantType = variantScope.getVariantConfiguration().getType(); |
| return !variantType.isForTesting() && !variantType.isHybrid(); |
| } |
| |
| /** |
| * Add tasks for running lint on individual variants. We've already added a lint task earlier |
| * which runs on all variants. |
| */ |
| public void createLintTasks( |
| final VariantScope scope, @NonNull List<VariantScope> variantScopes) { |
| if (!isLintVariant(scope)) { |
| return; |
| } |
| taskFactory.register(new LintPerVariantTask.CreationAction(scope, variantScopes)); |
| } |
| |
| /** Returns the full path of a task given its name. */ |
| private String getTaskPath(String taskName) { |
| return project.getRootProject() == project |
| ? ':' + taskName |
| : project.getPath() + ':' + taskName; |
| } |
| |
| public void maybeCreateLintVitalTask( |
| @NonNull ApkVariantData variantData, @NonNull List<VariantScope> variantScopes) { |
| VariantScope variantScope = variantData.getScope(); |
| GradleVariantConfiguration variantConfig = variantData.getVariantConfiguration(); |
| |
| if (!isLintVariant(variantScope) |
| || variantConfig.getBuildType().isDebuggable() |
| || !extension.getLintOptions().isCheckReleaseBuilds()) { |
| return; |
| } |
| |
| TaskProvider<LintPerVariantTask> lintReleaseCheck = |
| taskFactory.register( |
| new LintPerVariantTask.VitalCreationAction(variantScope, variantScopes), |
| null, |
| task -> task.dependsOn(variantScope.getTaskContainer().getJavacTask()), |
| null); |
| |
| TaskFactoryUtils.dependsOn( |
| variantScope.getTaskContainer().getAssembleTask(), lintReleaseCheck); |
| |
| // If lint is being run, we do not need to run lint vital. |
| project.getGradle() |
| .getTaskGraph() |
| .whenReady( |
| taskGraph -> { |
| if (taskGraph.hasTask(getTaskPath(LINT))) { |
| project.getTasks() |
| .getByName(lintReleaseCheck.getName()) |
| .setEnabled(false); |
| } |
| }); |
| } |
| |
| private void createRunUnitTestTask( |
| @NonNull final VariantScope variantScope) { |
| TaskProvider<AndroidUnitTest> runTestsTask = |
| taskFactory.register(new AndroidUnitTest.CreationAction(variantScope)); |
| |
| taskFactory.configure(JavaPlugin.TEST_TASK_NAME, test -> test.dependsOn(runTestsTask)); |
| } |
| |
| public void createTopLevelTestTasks(boolean hasFlavors) { |
| createMockableJarTask(); |
| |
| final List<String> reportTasks = Lists.newArrayListWithExpectedSize(2); |
| |
| List<DeviceProvider> providers = extension.getDeviceProviders(); |
| |
| // If more than one flavor, create a report aggregator task and make this the parent |
| // task for all new connected tasks. Otherwise, create a top level connectedAndroidTest |
| // Task. |
| |
| TaskProvider<? extends Task> connectedAndroidTestTask; |
| if (hasFlavors) { |
| connectedAndroidTestTask = |
| taskFactory.register( |
| new AndroidReportTask.CreationAction( |
| globalScope, |
| AndroidReportTask.CreationAction.TaskKind.CONNECTED)); |
| reportTasks.add(connectedAndroidTestTask.getName()); |
| } else { |
| connectedAndroidTestTask = |
| taskFactory.register( |
| CONNECTED_ANDROID_TEST, |
| connectedTask -> { |
| connectedTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP); |
| connectedTask.setDescription( |
| "Installs and runs instrumentation tests " |
| + "for all flavors on connected devices."); |
| }); |
| } |
| |
| taskFactory.configure( |
| CONNECTED_CHECK, check -> check.dependsOn(connectedAndroidTestTask.getName())); |
| |
| TaskProvider<? extends Task> deviceAndroidTestTask; |
| // if more than one provider tasks, either because of several flavors, or because of |
| // more than one providers, then create an aggregate report tasks for all of them. |
| if (providers.size() > 1 || hasFlavors) { |
| deviceAndroidTestTask = |
| taskFactory.register( |
| new AndroidReportTask.CreationAction( |
| globalScope, |
| AndroidReportTask.CreationAction.TaskKind.DEVICE_PROVIDER)); |
| reportTasks.add(deviceAndroidTestTask.getName()); |
| } else { |
| deviceAndroidTestTask = |
| taskFactory.register( |
| DEVICE_ANDROID_TEST, |
| providerTask -> { |
| providerTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP); |
| providerTask.setDescription( |
| "Installs and runs instrumentation tests " |
| + "using all Device Providers."); |
| }); |
| } |
| |
| taskFactory.configure( |
| DEVICE_CHECK, check -> check.dependsOn(deviceAndroidTestTask.getName())); |
| |
| // Create top level unit test tasks. |
| |
| taskFactory.register( |
| JavaPlugin.TEST_TASK_NAME, |
| unitTestTask -> { |
| unitTestTask.setGroup(JavaBasePlugin.VERIFICATION_GROUP); |
| unitTestTask.setDescription("Run unit tests for all variants."); |
| }); |
| taskFactory.configure( |
| JavaBasePlugin.CHECK_TASK_NAME, |
| check -> check.dependsOn(JavaPlugin.TEST_TASK_NAME)); |
| |
| // If gradle is launched with --continue, we want to run all tests and generate an |
| // aggregate report (to help with the fact that we may have several build variants, or |
| // or several device providers). |
| // To do that, the report tasks must run even if one of their dependent tasks (flavor |
| // or specific provider tasks) fails, when --continue is used, and the report task is |
| // meant to run (== is in the task graph). |
| // To do this, we make the children tasks ignore their errors (ie they won't fail and |
| // stop the build). |
| //TODO: move to mustRunAfter once is stable. |
| if (!reportTasks.isEmpty() && project.getGradle().getStartParameter() |
| .isContinueOnFailure()) { |
| project.getGradle() |
| .getTaskGraph() |
| .whenReady( |
| taskGraph -> { |
| for (String reportTask : reportTasks) { |
| if (taskGraph.hasTask(getTaskPath(reportTask))) { |
| taskFactory.configure( |
| reportTask, |
| task -> ((AndroidReportTask) task).setWillRun()); |
| } |
| } |
| }); |
| } |
| } |
| |
| protected void createConnectedTestForVariant(@NonNull final VariantScope testVariantScope) { |
| final BaseVariantData baseVariantData = |
| checkNotNull(testVariantScope.getTestedVariantData()); |
| final TestVariantData testVariantData = (TestVariantData) testVariantScope.getVariantData(); |
| |
| boolean isLibrary = baseVariantData.getVariantConfiguration().getType().isAar(); |
| |
| AbstractTestDataImpl testData; |
| if (baseVariantData.getVariantConfiguration().getType().isDynamicFeature()) { |
| testData = |
| new BundleTestDataImpl( |
| testVariantData, |
| testVariantScope |
| .getArtifacts() |
| .getFinalProduct(InternalArtifactType.APK), |
| FeatureSplitUtils.getFeatureName(globalScope.getProject().getPath()), |
| baseVariantData |
| .getScope() |
| .getArtifactFileCollection( |
| RUNTIME_CLASSPATH, PROJECT, APKS_FROM_BUNDLE)); |
| } else { |
| ConfigurableFileCollection testedApkFileCollection = |
| project.files( |
| testVariantData |
| .getTestedVariantData() |
| .getScope() |
| .getArtifacts() |
| .getFinalProduct(InternalArtifactType.APK)); |
| |
| testData = |
| new TestDataImpl( |
| testVariantData, |
| testVariantScope |
| .getArtifacts() |
| .getFinalProduct(InternalArtifactType.APK), |
| isLibrary ? null : testedApkFileCollection); |
| } |
| |
| configureTestData(testData); |
| |
| TaskProvider<DeviceProviderInstrumentTestTask> connectedTask = |
| taskFactory.register( |
| new DeviceProviderInstrumentTestTask.CreationAction( |
| testVariantScope, |
| new ConnectedDeviceProvider( |
| () -> |
| globalScope |
| .getSdkComponents() |
| .getAdbExecutableProvider() |
| .get(), |
| extension.getAdbOptions().getTimeOutInMs(), |
| new LoggerWrapper(logger)), |
| DeviceProviderInstrumentTestTask.CreationAction.Type |
| .INTERNAL_CONNECTED_DEVICE_PROVIDER, |
| testData, |
| project.files() /* testTargetMetadata */)); |
| |
| taskFactory.configure( |
| CONNECTED_ANDROID_TEST, |
| connectedAndroidTest -> connectedAndroidTest.dependsOn(connectedTask)); |
| |
| if (baseVariantData.getVariantConfiguration().getBuildType().isTestCoverageEnabled()) { |
| |
| Configuration jacocoAntConfiguration = |
| JacocoConfigurations.getJacocoAntTaskConfiguration( |
| project, JacocoTask.getJacocoVersion(testVariantScope)); |
| TaskProvider<JacocoReportTask> reportTask = |
| taskFactory.register( |
| new JacocoReportTask.CreationAction( |
| testVariantScope, jacocoAntConfiguration)); |
| |
| TaskFactoryUtils.dependsOn( |
| baseVariantData.getScope().getTaskContainer().getCoverageReportTask(), |
| reportTask); |
| |
| taskFactory.configure( |
| CONNECTED_ANDROID_TEST, |
| connectedAndroidTest -> connectedAndroidTest.dependsOn(reportTask)); |
| } |
| |
| List<DeviceProvider> providers = extension.getDeviceProviders(); |
| |
| // now the providers. |
| for (DeviceProvider deviceProvider : providers) { |
| |
| final TaskProvider<DeviceProviderInstrumentTestTask> providerTask = |
| taskFactory.register( |
| new DeviceProviderInstrumentTestTask.CreationAction( |
| testVariantData.getScope(), |
| deviceProvider, |
| DeviceProviderInstrumentTestTask.CreationAction.Type |
| .CUSTOM_DEVICE_PROVIDER, |
| testData, |
| project.files() /* testTargetMetadata */)); |
| |
| taskFactory.configure( |
| DEVICE_ANDROID_TEST, |
| deviceAndroidTest -> deviceAndroidTest.dependsOn(providerTask)); |
| } |
| |
| // now the test servers |
| List<TestServer> servers = extension.getTestServers(); |
| for (final TestServer testServer : servers) { |
| final TaskProvider<TestServerTask> serverTask = |
| taskFactory.register( |
| new TestServerTask.TestServerTaskCreationAction( |
| testVariantScope, testServer)); |
| TaskFactoryUtils.dependsOn( |
| serverTask, testVariantScope.getTaskContainer().getAssembleTask()); |
| |
| taskFactory.configure( |
| DEVICE_CHECK, deviceAndroidTest -> deviceAndroidTest.dependsOn(serverTask)); |
| } |
| } |
| |
| /** |
| * Creates the post-compilation tasks for the given Variant. |
| * |
| * These tasks create the dex file from the .class files, plus optional intermediary steps like |
| * proguard and jacoco |
| */ |
| public void createPostCompilationTasks( |
| @NonNull final VariantScope variantScope) { |
| |
| checkNotNull(variantScope.getTaskContainer().getJavacTask()); |
| |
| final BaseVariantData variantData = variantScope.getVariantData(); |
| final GradleVariantConfiguration config = variantData.getVariantConfiguration(); |
| |
| TransformManager transformManager = variantScope.getTransformManager(); |
| |
| taskFactory.register(new MergeGeneratedProguardFilesCreationAction(variantScope)); |
| |
| // ---- Code Coverage first ----- |
| boolean isTestCoverageEnabled = |
| config.getBuildType().isTestCoverageEnabled() && !config.getType().isForTesting(); |
| if (isTestCoverageEnabled) { |
| createJacocoTask(variantScope); |
| } |
| |
| maybeCreateDesugarTask( |
| variantScope, config.getMinSdkVersion(), transformManager, isTestCoverageEnabled); |
| |
| AndroidConfig extension = variantScope.getGlobalScope().getExtension(); |
| |
| // Merge Java Resources. |
| createMergeJavaResTask(variantScope); |
| |
| // ----- External Transforms ----- |
| // apply all the external transforms. |
| List<Transform> customTransforms = extension.getTransforms(); |
| List<List<Object>> customTransformsDependencies = extension.getTransformsDependencies(); |
| |
| for (int i = 0, count = customTransforms.size(); i < count; i++) { |
| Transform transform = customTransforms.get(i); |
| |
| List<Object> deps = customTransformsDependencies.get(i); |
| transformManager.addTransform( |
| taskFactory, |
| variantScope, |
| transform, |
| null, |
| task -> { |
| if (!deps.isEmpty()) { |
| task.dependsOn(deps); |
| } |
| }, |
| taskProvider -> { |
| // if the task is a no-op then we make assemble task depend on it. |
| if (transform.getScopes().isEmpty()) { |
| TaskFactoryUtils.dependsOn( |
| variantScope.getTaskContainer().getAssembleTask(), |
| taskProvider); |
| } |
| }); |
| } |
| |
| // Add a task to create merged runtime classes if this is a feature, a dynamic-feature, |
| // or a base module consuming feature jars. Merged runtime classes are needed if code |
| // minification is enabled in a project with features or dynamic-features. |
| if (variantData.getType().isFeatureSplit() || variantScope.consumesFeatureJars()) { |
| taskFactory.register(new MergeClassesTask.CreationAction(variantScope)); |
| } |
| |
| // ----- Android studio profiling transforms |
| if (appliesCustomClassTransforms(variantScope, projectOptions)) { |
| for (String jar : getAdvancedProfilingTransforms(projectOptions)) { |
| if (jar != null) { |
| transformManager.addTransform( |
| taskFactory, |
| variantScope, |
| new CustomClassTransform( |
| jar, |
| packagesCustomClassDependencies(variantScope, projectOptions))); |
| } |
| } |
| } |
| |
| // ----- Minify next ----- |
| CodeShrinker shrinker = maybeCreateJavaCodeShrinkerTransform(variantScope); |
| if (shrinker == CodeShrinker.R8) { |
| maybeCreateResourcesShrinkerTransform(variantScope); |
| maybeCreateDexSplitterTransform(variantScope); |
| // TODO: create JavaResSplitterTransform and call it here (http://b/77546738) |
| return; |
| } |
| |
| // ----- Multi-Dex support |
| DexingType dexingType = variantScope.getDexingType(); |
| |
| // Upgrade from legacy multi-dex to native multi-dex if possible when using with a device |
| if (dexingType == DexingType.LEGACY_MULTIDEX) { |
| if (variantScope.getVariantConfiguration().isMultiDexEnabled() |
| && variantScope |
| .getVariantConfiguration() |
| .getMinSdkVersionWithTargetDeviceApi() |
| .getFeatureLevel() |
| >= 21) { |
| dexingType = DexingType.NATIVE_MULTIDEX; |
| } |
| } |
| |
| if (variantScope.getNeedsMainDexList()) { |
| taskFactory.register(new D8MainDexListTask.CreationAction(variantScope, false)); |
| } |
| |
| if (variantScope.getNeedsMainDexListForBundle()) { |
| taskFactory.register(new D8MainDexListTask.CreationAction(variantScope, true)); |
| } |
| |
| createDexTasks(variantScope, dexingType); |
| |
| maybeCreateResourcesShrinkerTransform(variantScope); |
| |
| // TODO: support DexSplitterTransform when IR enabled (http://b/77585545) |
| maybeCreateDexSplitterTransform(variantScope); |
| // TODO: create JavaResSplitterTransform and call it here (http://b/77546738) |
| } |
| |
| private void maybeCreateDesugarTask( |
| @NonNull VariantScope variantScope, |
| @NonNull AndroidVersion minSdk, |
| @NonNull TransformManager transformManager, |
| boolean isTestCoverageEnabled) { |
| if (variantScope.getJava8LangSupportType() == Java8LangSupport.DESUGAR) { |
| FileCache userCache = getUserIntermediatesCache(); |
| |
| variantScope |
| .getTransformManager() |
| .consumeStreams( |
| ImmutableSet.of(Scope.EXTERNAL_LIBRARIES), |
| TransformManager.CONTENT_CLASS); |
| |
| taskFactory.register( |
| new RecalculateStackFramesTask.CreationAction( |
| variantScope, userCache, isTestCoverageEnabled)); |
| |
| variantScope |
| .getTransformManager() |
| .addStream( |
| OriginalStream.builder(project, "fixed-stack-frames-classes") |
| .addContentTypes(TransformManager.CONTENT_CLASS) |
| .addScope(Scope.EXTERNAL_LIBRARIES) |
| .setFileCollection( |
| project.files( |
| variantScope |
| .getArtifacts() |
| .getFinalProduct( |
| InternalArtifactType |
| .FIXED_STACK_FRAMES)) |
| .getAsFileTree()) |
| .build()); |
| |
| DesugarTransform desugarTransform = |
| new DesugarTransform( |
| variantScope.getBootClasspath(), |
| userCache, |
| minSdk.getFeatureLevel(), |
| globalScope.getJavaProcessExecutor(), |
| project.getLogger().isEnabled(LogLevel.INFO), |
| projectOptions.get(BooleanOption.ENABLE_GRADLE_WORKERS), |
| variantScope.getGlobalScope().getTmpFolder().toPath(), |
| getProjectVariantId(variantScope), |
| enableDesugarBugFixForJacoco(variantScope)); |
| transformManager.addTransform(taskFactory, variantScope, desugarTransform); |
| |
| if (minSdk.getFeatureLevel() |
| >= DesugarProcessArgs.MIN_SUPPORTED_API_TRY_WITH_RESOURCES) { |
| return; |
| } |
| |
| QualifiedContent.ScopeType scopeType = Scope.EXTERNAL_LIBRARIES; |
| if (variantScope.getVariantConfiguration().getType().isTestComponent()) { |
| BaseVariantData testedVariant = |
| Objects.requireNonNull(variantScope.getTestedVariantData()); |
| if (!testedVariant.getType().isAar()) { |
| // test variants, except for library, should not package try-with-resources jar |
| // as the tested variant already contains it. |
| scopeType = Scope.PROVIDED_ONLY; |
| } |
| } |
| |
| // add runtime classes for try-with-resources support |
| String taskName = variantScope.getTaskName(ExtractTryWithResourcesSupportJar.TASK_NAME); |
| TaskProvider<ExtractTryWithResourcesSupportJar> extractTryWithResources = |
| taskFactory.register( |
| new ExtractTryWithResourcesSupportJar.CreationAction( |
| variantScope.getTryWithResourceRuntimeSupportJar(), |
| taskName, |
| variantScope.getFullVariantName())); |
| variantScope.getTryWithResourceRuntimeSupportJar().builtBy(extractTryWithResources); |
| transformManager.addStream( |
| OriginalStream.builder(project, "runtime-deps-try-with-resources") |
| .addContentTypes(TransformManager.CONTENT_CLASS) |
| .addScope(scopeType) |
| .setFileCollection(variantScope.getTryWithResourceRuntimeSupportJar()) |
| .build()); |
| } |
| } |
| |
| /** |
| * Creates tasks used for DEX generation. This will use an incremental pipeline that uses dex |
| * archives in order to enable incremental dexing support. |
| */ |
| private void createDexTasks( |
| @NonNull VariantScope variantScope, @NonNull DexingType dexingType) { |
| TransformManager transformManager = variantScope.getTransformManager(); |
| |
| DefaultDexOptions dexOptions; |
| if (variantScope.getVariantData().getType().isTestComponent()) { |
| // Don't use custom dx flags when compiling the test FULL_APK. They can break the test FULL_APK, |
| // like --minimal-main-dex. |
| dexOptions = DefaultDexOptions.copyOf(extension.getDexOptions()); |
| dexOptions.setAdditionalParameters(ImmutableList.of()); |
| } else { |
| dexOptions = extension.getDexOptions(); |
| } |
| |
| Java8LangSupport java8SLangSupport = variantScope.getJava8LangSupportType(); |
| boolean minified = variantScope.getCodeShrinker() != null; |
| boolean supportsDesugaring = |
| java8SLangSupport == Java8LangSupport.UNUSED |
| || (java8SLangSupport == Java8LangSupport.D8 |
| && projectOptions.get( |
| BooleanOption.ENABLE_DEXING_DESUGARING_ARTIFACT_TRANSFORM)); |
| boolean enableDexingArtifactTransform = |
| globalScope.getProjectOptions().get(BooleanOption.ENABLE_DEXING_ARTIFACT_TRANSFORM) |
| && extension.getTransforms().isEmpty() |
| && !minified |
| && supportsDesugaring |
| && !appliesCustomClassTransforms(variantScope, projectOptions); |
| FileCache userLevelCache = getUserDexCache(minified, dexOptions.getPreDexLibraries()); |
| DexArchiveBuilderTransform preDexTransform = |
| new DexArchiveBuilderTransformBuilder() |
| .setAndroidJarClasspath(globalScope.getFilteredBootClasspath()) |
| .setDexOptions(dexOptions) |
| .setMessageReceiver(variantScope.getGlobalScope().getMessageReceiver()) |
| .setErrorFormatMode( |
| SyncOptions.getErrorFormatMode( |
| variantScope.getGlobalScope().getProjectOptions())) |
| .setUserLevelCache(userLevelCache) |
| .setMinSdkVersion( |
| variantScope |
| .getVariantConfiguration() |
| .getMinSdkVersionWithTargetDeviceApi() |
| .getFeatureLevel()) |
| .setDexer(variantScope.getDexer()) |
| .setUseGradleWorkers( |
| projectOptions.get(BooleanOption.ENABLE_GRADLE_WORKERS)) |
| .setInBufferSize(projectOptions.get(IntegerOption.DEXING_READ_BUFFER_SIZE)) |
| .setOutBufferSize( |
| projectOptions.get(IntegerOption.DEXING_WRITE_BUFFER_SIZE)) |
| .setIsDebuggable( |
| variantScope |
| .getVariantConfiguration() |
| .getBuildType() |
| .isDebuggable()) |
| .setJava8LangSupportType(java8SLangSupport) |
| .setProjectVariant(getProjectVariantId(variantScope)) |
| .setNumberOfBuckets( |
| projectOptions.get(IntegerOption.DEXING_NUMBER_OF_BUCKETS)) |
| .setIncludeFeaturesInScope(variantScope.consumesFeatureJars()) |
| .setEnableDexingArtifactTransform(enableDexingArtifactTransform) |
| .createDexArchiveBuilderTransform(); |
| transformManager.addTransform(taskFactory, variantScope, preDexTransform); |
| |
| if (projectOptions.get(BooleanOption.ENABLE_DUPLICATE_CLASSES_CHECK)) { |
| taskFactory.register(new CheckDuplicateClassesTask.CreationAction(variantScope)); |
| } |
| |
| createDexMergingTasks(variantScope, dexingType, enableDexingArtifactTransform); |
| } |
| |
| /** |
| * Set up dex merging tasks when artifact transforms are used. |
| * |
| * <p>External libraries are merged in mono-dex and native multidex modes. In case of a native |
| * multidex debuggable variant these dex files get packaged. In mono-dex case, we will re-merge |
| * these files. Because this task will be almost always up-to-date, having a second merger run |
| * over the external libraries will not cause a performance regression. In addition to that, |
| * second dex merger will perform less I/O compared to reading all external library dex files |
| * individually. For legacy multidex, we must merge all dex files in a single invocation in |
| * order to generate correct primary dex file in presence of desugaring. See b/120039166. |
| * |
| * <p>When merging native multidex, debuggable variant, project's dex files are merged |
| * independently. Also, the library projects' dex files are merged independently. |
| * |
| * <p>For all other variants (release, mono-dex, legacy-multidex), we merge all dex files in a |
| * single invocation. This means that external libraries, library projects and project dex files |
| * will be merged in a single task. |
| */ |
| private void createDexMergingTasks( |
| @NonNull VariantScope variantScope, |
| @NonNull DexingType dexingType, |
| boolean dexingUsingArtifactTransforms) { |
| |
| // When desugaring, The file dependencies are dexed in a task with the whole |
| // remote classpath present, as they lack dependency information to desugar |
| // them correctly in an artifact transform. |
| boolean separateFileDependenciesDexingTask = |
| variantScope.getJava8LangSupportType() == Java8LangSupport.D8 |
| && dexingUsingArtifactTransforms; |
| if (separateFileDependenciesDexingTask) { |
| DexFileDependenciesTask.CreationAction desugarFileDeps = |
| new DexFileDependenciesTask.CreationAction(variantScope); |
| taskFactory.register(desugarFileDeps); |
| } |
| |
| if (dexingType == DexingType.LEGACY_MULTIDEX) { |
| DexMergingTask.CreationAction configAction = |
| new DexMergingTask.CreationAction( |
| variantScope, |
| DexMergingAction.MERGE_ALL, |
| dexingType, |
| dexingUsingArtifactTransforms, |
| separateFileDependenciesDexingTask); |
| taskFactory.register(configAction); |
| } else if (variantScope.getCodeShrinker() != null) { |
| DexMergingTask.CreationAction configAction = |
| new DexMergingTask.CreationAction( |
| variantScope, |
| DexMergingAction.MERGE_ALL, |
| dexingType, |
| dexingUsingArtifactTransforms); |
| taskFactory.register(configAction); |
| } else { |
| boolean produceSeparateOutputs = |
| dexingType == DexingType.NATIVE_MULTIDEX |
| && variantScope.getVariantConfiguration().getBuildType().isDebuggable(); |
| |
| taskFactory.register( |
| new DexMergingTask.CreationAction( |
| variantScope, |
| DexMergingAction.MERGE_EXTERNAL_LIBS, |
| DexingType.NATIVE_MULTIDEX, |
| dexingUsingArtifactTransforms, |
| separateFileDependenciesDexingTask, |
| produceSeparateOutputs |
| ? InternalArtifactType.DEX |
| : InternalArtifactType.EXTERNAL_LIBS_DEX)); |
| |
| if (produceSeparateOutputs) { |
| DexMergingTask.CreationAction mergeProject = |
| new DexMergingTask.CreationAction( |
| variantScope, |
| DexMergingAction.MERGE_PROJECT, |
| dexingType, |
| dexingUsingArtifactTransforms); |
| taskFactory.register(mergeProject); |
| |
| DexMergingTask.CreationAction mergeLibraries = |
| new DexMergingTask.CreationAction( |
| variantScope, |
| DexMergingAction.MERGE_LIBRARY_PROJECTS, |
| dexingType, |
| dexingUsingArtifactTransforms); |
| taskFactory.register(mergeLibraries); |
| } else { |
| DexMergingTask.CreationAction configAction = |
| new DexMergingTask.CreationAction( |
| variantScope, |
| DexMergingAction.MERGE_ALL, |
| dexingType, |
| dexingUsingArtifactTransforms); |
| taskFactory.register(configAction); |
| } |
| } |
| |
| variantScope |
| .getTransformManager() |
| .addStream( |
| OriginalStream.builder(project, "final-dex") |
| .addContentTypes(ExtendedContentType.DEX) |
| .addScope(Scope.PROJECT) |
| .setFileCollection( |
| variantScope.getGlobalScope().getProject().files( |
| variantScope |
| .getArtifacts() |
| .getFinalProducts(InternalArtifactType.DEX) |
| )) |
| .build()); |
| } |
| |
| @NonNull |
| private static String getProjectVariantId(@NonNull VariantScope variantScope) { |
| return variantScope.getGlobalScope().getProject().getName() |
| + ":" |
| + variantScope.getFullVariantName(); |
| } |
| |
| @Nullable |
| private FileCache getUserDexCache(boolean isMinifiedEnabled, boolean preDexLibraries) { |
| if (!preDexLibraries || isMinifiedEnabled) { |
| return null; |
| } |
| |
| return getUserIntermediatesCache(); |
| } |
| |
| @Nullable |
| private FileCache getUserIntermediatesCache() { |
| if (globalScope |
| .getProjectOptions() |
| .get(BooleanOption.ENABLE_INTERMEDIATE_ARTIFACTS_CACHE)) { |
| return globalScope.getBuildCache(); |
| } else { |
| return null; |
| } |
| } |
| |
| protected void handleJacocoDependencies(@NonNull VariantScope variantScope) { |
| GradleVariantConfiguration config = variantScope.getVariantConfiguration(); |
| // we add the jacoco jar if coverage is enabled, but we don't add it |
| // for test apps as it's already part of the tested app. |
| // For library project, since we cannot use the local jars of the library, |
| // we add it as well. |
| boolean isTestCoverageEnabled = |
| config.getBuildType().isTestCoverageEnabled() |
| && (!config.getType().isTestComponent() |
| || (config.getTestedConfig() != null |
| && config.getTestedConfig().getType().isAar())); |
| if (isTestCoverageEnabled) { |
| if (variantScope.getDexer() == DexerTool.DX) { |
| globalScope |
| .getErrorHandler() |
| .reportWarning( |
| Type.GENERIC, |
| String.format( |
| "Jacoco version is downgraded to %s because dx is used. " |
| + "This is due to -P%s=false flag. See " |
| + "https://issuetracker.google.com/37116789 for " |
| + "more details.", |
| JacocoConfigurations.VERSION_FOR_DX, |
| BooleanOption.ENABLE_D8.getPropertyName())); |
| } |
| |
| String jacocoAgentRuntimeDependency = |
| JacocoConfigurations.getAgentRuntimeDependency( |
| JacocoTask.getJacocoVersion(variantScope)); |
| project.getDependencies() |
| .add( |
| variantScope.getVariantDependencies().getRuntimeClasspath().getName(), |
| jacocoAgentRuntimeDependency); |
| |
| // we need to force the same version of Jacoco we use for instrumentation |
| variantScope |
| .getVariantDependencies() |
| .getRuntimeClasspath() |
| .resolutionStrategy(r -> r.force(jacocoAgentRuntimeDependency)); |
| } |
| } |
| |
| /** |
| * If a fix in Desugar should be enabled to handle broken bytecode produced by older Jacoco, see |
| * http://b/62623509. |
| */ |
| private boolean enableDesugarBugFixForJacoco(@NonNull VariantScope scope) { |
| try { |
| GradleVersion current = GradleVersion.parse(JacocoTask.getJacocoVersion(scope)); |
| return JacocoConfigurations.MIN_WITHOUT_BROKEN_BYTECODE.compareTo(current) > 0; |
| } catch (Throwable ignored) { |
| // Cannot determine using version comparison, avoid passing the flag. |
| return true; |
| } |
| } |
| |
| public void createJacocoTask(@NonNull final VariantScope variantScope) { |
| variantScope |
| .getTransformManager() |
| .consumeStreams( |
| ImmutableSet.of(Scope.PROJECT), |
| ImmutableSet.of(DefaultContentType.CLASSES)); |
| taskFactory.register(new JacocoTask.CreationAction(variantScope)); |
| |
| FileCollection instumentedClasses = |
| project.files( |
| variantScope |
| .getArtifacts() |
| .getFinalProduct(InternalArtifactType.JACOCO_INSTRUMENTED_CLASSES), |
| project.files( |
| variantScope |
| .getArtifacts() |
| .getFinalProduct( |
| InternalArtifactType |
| .JACOCO_INSTRUMENTED_JARS)) |
| .getAsFileTree()); |
| variantScope |
| .getTransformManager() |
| .addStream( |
| OriginalStream.builder(project, "jacoco-instrumented-classes") |
| .addContentTypes(DefaultContentType.CLASSES) |
| .addScope(Scope.PROJECT) |
| .setFileCollection(instumentedClasses) |
| .build()); |
| } |
| |
| private void createDataBindingMergeArtifactsTask(@NonNull VariantScope variantScope) { |
| boolean dataBindingEnabled = extension.getDataBinding().isEnabled(); |
| boolean viewBindingEnabled = extension.getViewBinding().isEnabled(); |
| if (!dataBindingEnabled && !viewBindingEnabled) { |
| return; |
| } |
| final BaseVariantData variantData = variantScope.getVariantData(); |
| VariantType type = variantData.getType(); |
| if (type.isForTesting() && !extension.getDataBinding().isEnabledForTests()) { |
| BaseVariantData testedVariantData = checkNotNull(variantScope.getTestedVariantData()); |
| if (!testedVariantData.getType().isAar()) { |
| return; |
| } |
| } |
| taskFactory.register( |
| new DataBindingMergeDependencyArtifactsTask.CreationAction(variantScope)); |
| } |
| |
| private void createDataBindingMergeBaseClassesTask(@NonNull VariantScope variantScope) { |
| final BaseVariantData variantData = variantScope.getVariantData(); |
| VariantType type = variantData.getType(); |
| if (type.isForTesting() && !extension.getDataBinding().isEnabledForTests()) { |
| BaseVariantData testedVariantData = checkNotNull(variantScope.getTestedVariantData()); |
| if (!testedVariantData.getType().isAar()) { |
| return; |
| } |
| } |
| |
| taskFactory.register(new DataBindingMergeBaseClassLogTask.CreationAction(variantScope)); |
| } |
| |
| protected void createDataBindingTasksIfNecessary( |
| @NonNull VariantScope scope, @NonNull MergeType mergeType) { |
| boolean dataBindingEnabled = extension.getDataBinding().isEnabled(); |
| boolean viewBindingEnabled = extension.getViewBinding().isEnabled(); |
| if (!dataBindingEnabled && !viewBindingEnabled) { |
| return; |
| } |
| createDataBindingMergeBaseClassesTask(scope); |
| createDataBindingMergeArtifactsTask(scope); |
| |
| |
| VariantType type = scope.getType(); |
| if (type.isForTesting() && !extension.getDataBinding().isEnabledForTests()) { |
| BaseVariantData testedVariantData = checkNotNull(scope.getTestedVariantData()); |
| if (!testedVariantData.getType().isAar()) { |
| return; |
| } |
| } |
| |
| dataBindingBuilder.setDebugLogEnabled(getLogger().isDebugEnabled()); |
| |
| taskFactory.register(new DataBindingGenBaseClassesTask.CreationAction(scope)); |
| |
| if (dataBindingEnabled) { |
| taskFactory.register(new DataBindingExportBuildInfoTask.CreationAction(scope)); |
| setDataBindingAnnotationProcessorParams(scope, mergeType); |
| } |
| } |
| |
| private void setDataBindingAnnotationProcessorParams( |
| @NonNull VariantScope scope, @NonNull MergeType mergeType) { |
| BaseVariantData variantData = scope.getVariantData(); |
| GradleVariantConfiguration variantConfiguration = variantData.getVariantConfiguration(); |
| JavaCompileOptions javaCompileOptions = variantConfiguration.getJavaCompileOptions(); |
| AnnotationProcessorOptions processorOptions = |
| javaCompileOptions.getAnnotationProcessorOptions(); |
| if (processorOptions |
| instanceof com.android.build.gradle.internal.dsl.AnnotationProcessorOptions) { |
| com.android.build.gradle.internal.dsl.AnnotationProcessorOptions options = |
| (com.android.build.gradle.internal.dsl.AnnotationProcessorOptions) |
| processorOptions; |
| // We want to pass data binding processor's class name to the Java compiler. However, if |
| // the class names of other annotation processors were not added previously, adding the |
| // class name of data binding alone would disable Java compiler's automatic discovery of |
| // annotation processors and the other annotation processors would not be invoked. |
| // Therefore, we add data binding only if another class name was specified before. |
| if (!options.getClassNames().isEmpty() |
| && !options.getClassNames().contains(DataBindingBuilder.PROCESSOR_NAME)) { |
| options.className(DataBindingBuilder.PROCESSOR_NAME); |
| } |
| |
| DataBindingCompilerArguments dataBindingArgs = |
| DataBindingCompilerArguments.createArguments( |
| scope, |
| getLogger().isDebugEnabled(), |
| dataBindingBuilder.getPrintMachineReadableOutput()); |
| options.compilerArgumentProvider(dataBindingArgs); |
| } else { |
| getLogger().error("Cannot setup data binding for %s because java compiler options" |
| + " is not an instance of AnnotationProcessorOptions", processorOptions); |
| } |
| } |
| |
| /** |
| * Creates the final packaging task, and optionally the zipalign task (if the variant is signed) |
| * |
| * @param variantScope VariantScope object. |
| */ |
| public void createPackagingTask(@NonNull VariantScope variantScope) { |
| ApkVariantData variantData = (ApkVariantData) variantScope.getVariantData(); |
| final MutableTaskContainer taskContainer = variantData.getScope().getTaskContainer(); |
| |
| boolean signedApk = variantData.isSigned(); |
| |
| /* |
| * PrePackaging step class that will look if the packaging of the main FULL_APK split is |
| * necessary when running in InstantRun mode. In InstantRun mode targeting an api 23 or |
| * above device, resources are packaged in the main split FULL_APK. However when a warm swap |
| * is possible, it is not necessary to produce immediately the new main SPLIT since the |
| * runtime use the resources.ap_ file directly. However, as soon as an incompatible change |
| * forcing a cold swap is triggered, the main FULL_APK must be rebuilt (even if the |
| * resources were changed in a previous build). |
| */ |
| InternalArtifactType manifestType = variantScope.getManifestArtifactType(); |
| |
| final boolean splitsArePossible = |
| variantScope.getVariantData().getMultiOutputPolicy() == MultiOutputPolicy.SPLITS; |
| |
| Provider<Directory> manifests = variantScope.getArtifacts().getFinalProduct(manifestType); |
| |
| // this is where the final APKs will be located. |
| File finalApkLocation = variantScope.getApkLocation(); |
| // if we are not dealing with possible splits, we can generate in the final folder |
| // directly. |
| File outputDirectory = |
| splitsArePossible |
| ? variantScope.getFullApkPackagesOutputDirectory() |
| : finalApkLocation; |
| |
| InternalArtifactType taskOutputType = |
| splitsArePossible ? InternalArtifactType.FULL_APK : InternalArtifactType.APK; |
| |
| InternalArtifactType resourceFilesInputType = |
| variantScope.useResourceShrinker() |
| ? InternalArtifactType.SHRUNK_PROCESSED_RES |
| : InternalArtifactType.PROCESSED_RES; |
| |
| // Common code for both packaging tasks. |
| Action<Task> configureResourcesAndAssetsDependencies = |
| task -> { |
| task.dependsOn(taskContainer.getMergeAssetsTask()); |
| if (taskContainer.getProcessAndroidResTask() != null) { |
| task.dependsOn(taskContainer.getProcessAndroidResTask()); |
| } |
| }; |
| |
| TaskProvider<PackageApplication> packageApp = |
| taskFactory.register( |
| new PackageApplication.CreationAction( |
| variantScope, |
| outputDirectory, |
| resourceFilesInputType, |
| manifests, |
| manifestType, |
| variantScope.getOutputScope(), |
| globalScope.getBuildCache(), |
| taskOutputType, |
| packagesCustomClassDependencies(variantScope, projectOptions)), |
| null, |
| task -> { |
| //noinspection VariableNotUsedInsideIf - we use the whole packaging scope below. |
| |
| task.dependsOn(taskContainer.getJavacTask()); |
| |
| if (taskContainer.getPackageSplitResourcesTask() != null) { |
| task.dependsOn(taskContainer.getPackageSplitResourcesTask()); |
| } |
| if (taskContainer.getPackageSplitAbiTask() != null) { |
| task.dependsOn(taskContainer.getPackageSplitAbiTask()); |
| } |
| |
| // FIX ME : Reinstate once ShrinkResourcesTransform is converted. |
| //if ( variantOutputScope.getShrinkResourcesTask() != null) { |
| // packageApp.dependsOn( variantOutputScope.getShrinkResourcesTask()); |
| //} |
| |
| configureResourcesAndAssetsDependencies.execute(task); |
| }, |
| null); |
| |
| TaskFactoryUtils.dependsOn(taskContainer.getAssembleTask(), packageApp.getName()); |
| |
| if (splitsArePossible) { |
| |
| TaskProvider<CopyOutputs> copyOutputsTask = |
| taskFactory.register( |
| new CopyOutputs.CreationAction(variantScope, finalApkLocation)); |
| TaskFactoryUtils.dependsOn(taskContainer.getAssembleTask(), copyOutputsTask); |
| } |
| |
| // create install task for the variant Data. This will deal with finding the |
| // right output if there are more than one. |
| // Add a task to install the application package |
| if (signedApk) { |
| createInstallTask(variantScope); |
| } |
| |
| // add an uninstall task |
| final TaskProvider<UninstallTask> uninstallTask = |
| taskFactory.register(new UninstallTask.CreationAction(variantScope)); |
| |
| taskFactory.configure(UNINSTALL_ALL, uninstallAll -> uninstallAll.dependsOn(uninstallTask)); |
| } |
| |
| protected void createInstallTask(VariantScope variantScope) { |
| taskFactory.register(new InstallVariantTask.CreationAction(variantScope)); |
| } |
| |
| @Nullable |
| protected void createValidateSigningTask(@NonNull VariantScope variantScope) { |
| if (variantScope.getVariantConfiguration().getSigningConfig() == null) { |
| return; |
| } |
| |
| // FIXME create one per signing config instead of one per variant. |
| taskFactory.register( |
| new ValidateSigningTask.CreationAction( |
| variantScope, GradleKeystoreHelper.getDefaultDebugKeystoreLocation())); |
| } |
| |
| /** |
| * Create assemble* and bundle* anchor tasks. |
| * |
| * <p>This does not create the variant specific version of these tasks only the ones that are |
| * per build-type, per-flavor, per-flavor-combo and the main 'assemble' and 'bundle' ones. |
| * |
| * @param variantScopes the list of variant scopes. |
| * @param flavorCount the number of flavors |
| * @param flavorDimensionCount whether there are flavor dimensions at all. |
| * @param variantTypeCount the number of variant types generated. |
| */ |
| public void createAnchorAssembleTasks( |
| @NonNull List<VariantScope> variantScopes, |
| int flavorCount, |
| int flavorDimensionCount, |
| int variantTypeCount) { |
| |
| // sub anchor tasks that the main 'assemble' and 'bundle task will depend. |
| List<TaskProvider<? extends Task>> subAssembleTasks = Lists.newArrayList(); |
| List<TaskProvider<? extends Task>> subBundleTasks = Lists.newArrayList(); |
| |
| // There are 3 different scenarios: |
| // 1. There are 1+ flavors. In this case the variant-specific assemble task is |
| // different from all the assemble<BuildType> or assemble<Flavor> |
| // 2. There is no flavor but this is a feature plugin that has 2 different variants for |
| // the same build type (aar + feature), so we still create a specific assemble<buildType> |
| // which depends on the variant specific assemble task. |
| // 3. Else, the assemble<BuildType> is the same as the variant specific assemble task. |
| |
| // Case #1 |
| if (flavorCount > 0) { |
| // loop on the variants and record their build type/flavor usage. |
| // map from build type/flavor names to the variant-specific assemble/bundle tasks |
| ListMultimap<String, TaskProvider<? extends Task>> assembleMap = |
| ArrayListMultimap.create(); |
| ListMultimap<String, TaskProvider<? extends Task>> bundleMap = |
| ArrayListMultimap.create(); |
| |
| for (VariantScope variantScope : variantScopes) { |
| final VariantType variantType = variantScope.getType(); |
| if (!variantType.isTestComponent()) { |
| final MutableTaskContainer taskContainer = variantScope.getTaskContainer(); |
| final GradleVariantConfiguration variantConfig = |
| variantScope.getVariantConfiguration(); |
| |
| final TaskProvider<? extends Task> assembleTask = |
| taskContainer.getAssembleTask(); |
| assembleMap.put(variantConfig.getBuildType().getName(), assembleTask); |
| |
| for (CoreProductFlavor flavor : variantConfig.getProductFlavors()) { |
| assembleMap.put(flavor.getName(), assembleTask); |
| } |
| |
| // if 2+ flavor dimensions, then make an assemble for the flavor combo |
| if (flavorDimensionCount > 1) { |
| assembleMap.put(variantConfig.getFlavorName(), assembleTask); |
| } |
| |
| // fill the bundle map only if the variant supports bundles. |
| if (variantType.isBaseModule()) { |
| TaskProvider<? extends Task> bundleTask = taskContainer.getBundleTask(); |
| |
| bundleMap.put(variantConfig.getBuildType().getName(), bundleTask); |
| |
| for (CoreProductFlavor flavor : variantConfig.getProductFlavors()) { |
| bundleMap.put(flavor.getName(), bundleTask); |
| } |
| |
| // if 2+ flavor dimensions, then make an assemble for the flavor combo |
| if (flavorDimensionCount > 1) { |
| bundleMap.put(variantConfig.getFlavorName(), bundleTask); |
| } |
| } |
| } |
| } |
| |
| // loop over the map of build-type/flavor to create tasks for each, setting a dependency |
| // on the variant-specific task. |
| // these keys should be the same for bundle and assemble |
| Set<String> dimensionKeys = assembleMap.keySet(); |
| |
| for (String dimensionKey : dimensionKeys) { |
| final String dimensionName = StringHelper.capitalize(dimensionKey); |
| |
| // create the task and add it to the list |
| subAssembleTasks.add( |
| taskFactory.register( |
| "assemble" + dimensionName, |
| task -> { |
| task.setDescription( |
| "Assembles main outputs for all " |
| + dimensionName |
| + " variants."); |
| task.setGroup(BasePlugin.BUILD_GROUP); |
| task.dependsOn(assembleMap.get(dimensionKey)); |
| })); |
| |
| List<TaskProvider<? extends Task>> subBundleMap = bundleMap.get(dimensionKey); |
| if (!subBundleMap.isEmpty()) { |
| |
| // create the task and add it to the list |
| subBundleTasks.add( |
| taskFactory.register( |
| "bundle" + dimensionName, |
| task -> { |
| task.setDescription( |
| "Assembles bundles for all " |
| + dimensionName |
| + " variants."); |
| task.setGroup(BasePlugin.BUILD_GROUP); |
| task.dependsOn(subBundleMap); |
| })); |
| } |
| } |
| |
| } else if (variantTypeCount > 1) { |
| // Case #2 |
| |
| // loop on the variants and record their build type usage. |
| // map from build type to the variant-specific assemble/bundle tasks |
| ListMultimap<String, TaskProvider<? extends Task>> assembleMap = |
| ArrayListMultimap.create(); |
| ListMultimap<String, TaskProvider<? extends Task>> bundleMap = |
| ArrayListMultimap.create(); |
| |
| for (VariantScope variantScope : variantScopes) { |
| final VariantType variantType = variantScope.getType(); |
| if (!variantType.isTestComponent()) { |
| final MutableTaskContainer taskContainer = variantScope.getTaskContainer(); |
| final GradleVariantConfiguration variantConfig = |
| variantScope.getVariantConfiguration(); |
| |
| assembleMap.put( |
| variantConfig.getBuildType().getName(), |
| taskContainer.getAssembleTask()); |
| |
| // fill the bundle map only if the variant supports bundles. |
| if (variantType.isBaseModule()) { |
| TaskProvider<? extends Task> bundleTask = taskContainer.getBundleTask(); |
| |
| bundleMap.put(variantConfig.getBuildType().getName(), bundleTask); |
| } |
| } |
| } |
| |
| // loop over the map of build-type to create tasks for each, setting a dependency |
| // on the variant-specific task. |
| // these keys should be the same for bundle and assemble |
| Set<String> dimensionKeys = assembleMap.keySet(); |
| |
| for (String dimensionKey : dimensionKeys) { |
| final String dimensionName = StringHelper.capitalize(dimensionKey); |
| |
| // create the task and add it to the list |
| subAssembleTasks.add( |
| taskFactory.register( |
| "assemble" + dimensionName, |
| task -> { |
| task.setDescription( |
| "Assembles main outputs for all " |
| + dimensionName |
| + " variants."); |
| task.setGroup(BasePlugin.BUILD_GROUP); |
| task.dependsOn(assembleMap.get(dimensionKey)); |
| })); |
| |
| List<TaskProvider<? extends Task>> subBundleMap = bundleMap.get(dimensionKey); |
| if (!subBundleMap.isEmpty()) { |
| |
| // create the task and add it to the list |
| subBundleTasks.add( |
| taskFactory.register( |
| "bundle" + dimensionName, |
| task -> { |
| task.setDescription( |
| "Assembles bundles for all " |
| + dimensionName |
| + " variants."); |
| task.setGroup(BasePlugin.BUILD_GROUP); |
| task.dependsOn(subBundleMap); |
| })); |
| } |
| } |
| } else { |
| // Case #3 |
| |
| for (VariantScope variantScope : variantScopes) { |
| final VariantType variantType = variantScope.getType(); |
| if (!variantType.isTestComponent()) { |
| final MutableTaskContainer taskContainer = variantScope.getTaskContainer(); |
| |
| subAssembleTasks.add(taskContainer.getAssembleTask()); |
| |
| if (variantType.isBaseModule()) { |
| subBundleTasks.add(taskContainer.getBundleTask()); |
| } |
| } |
| } |
| } |
| |
| // --- |
| // ok now we can create the main 'assemble' and 'bundle' tasks and make them depend on the |
| // sub-tasks. |
| |
| if (!subAssembleTasks.isEmpty()) { |
| // "assemble" task is already created by the java base plugin. |
| taskFactory.configure( |
| "assemble", |
| task -> { |
| task.setDescription("Assemble main outputs for all the variants."); |
| task.setGroup(BasePlugin.BUILD_GROUP); |
| task.dependsOn(subAssembleTasks); |
| }); |
| } |
| |
| if (!subBundleTasks.isEmpty()) { |
| // root bundle task |
| taskFactory.register( |
| "bundle", |
| task -> { |
| task.setDescription("Assemble bundles for all the variants."); |
| task.setGroup(BasePlugin.BUILD_GROUP); |
| task.dependsOn(subBundleTasks); |
| }); |
| } |
| } |
| |
| public void createAssembleTask(@NonNull final BaseVariantData variantData) { |
| final VariantScope scope = variantData.getScope(); |
| taskFactory.register( |
| getAssembleTaskName(scope, "assemble"), |
| null /*preConfigAction*/, |
| task -> { |
| task.setDescription( |
| "Assembles main output for variant " |
| + scope.getVariantConfiguration().getFullName()); |
| }, |
| taskProvider -> scope.getTaskContainer().setAssembleTask(taskProvider)); |
| } |
| |
| @NonNull |
| private String getAssembleTaskName(VariantScope scope, @NonNull String prefix) { |
| String taskName; |
| if (variantFactory.getVariantConfigurationTypes().size() == 1) { |
| taskName = scope.getTaskName(prefix); |
| } else { |
| taskName = |
| StringHelper.appendCapitalized( |
| prefix, scope.getVariantConfiguration().computeHybridVariantName()); |
| } |
| return taskName; |
| } |
| |
| public void createBundleTask(@NonNull final BaseVariantData variantData) { |
| final VariantScope scope = variantData.getScope(); |
| taskFactory.register( |
| getAssembleTaskName(scope, "bundle"), |
| null, |
| task -> { |
| task.setDescription( |
| "Assembles bundle for variant " |
| + scope.getVariantConfiguration().getFullName()); |
| task.dependsOn( |
| scope.getArtifacts().getFinalProduct(InternalArtifactType.BUNDLE)); |
| }, |
| taskProvider -> scope.getTaskContainer().setBundleTask(taskProvider)); |
| } |
| |
| /** Returns created shrinker type, or null if none was created. */ |
| @Nullable |
| protected CodeShrinker maybeCreateJavaCodeShrinkerTransform( |
| @NonNull final VariantScope variantScope) { |
| CodeShrinker codeShrinker = variantScope.getCodeShrinker(); |
| |
| if (codeShrinker != null) { |
| return doCreateJavaCodeShrinkerTransform( |
| variantScope, |
| // No mapping in non-test modules. |
| codeShrinker, |
| null); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Actually creates the minify transform, using the given mapping configuration. The mapping is |
| * only used by test-only modules. Returns a type of the {@link CodeShrinker} shrinker that was |
| * created, or {@code null} if none was created. |
| */ |
| @Nullable |
| protected final CodeShrinker doCreateJavaCodeShrinkerTransform( |
| @NonNull final VariantScope variantScope, |
| @NonNull CodeShrinker codeShrinker, |
| @Nullable FileCollection mappingFileCollection) { |
| Optional<TaskProvider<TransformTask>> transformTask; |
| CodeShrinker createdShrinker = codeShrinker; |
| switch (codeShrinker) { |
| case PROGUARD: |
| transformTask = createProguardTransform(variantScope, mappingFileCollection); |
| break; |
| case R8: |
| if (variantScope.getVariantConfiguration().getType().isAar() |
| && !projectOptions.get(BooleanOption.ENABLE_R8_LIBRARIES)) { |
| transformTask = createProguardTransform(variantScope, mappingFileCollection); |
| createdShrinker = CodeShrinker.PROGUARD; |
| } else { |
| transformTask = |
| createR8Transform( |
| variantScope, |
| mappingFileCollection, |
| (transform, taskName) -> { |
| if (variantScope.getNeedsMainDexListForBundle()) { |
| Provider<RegularFile> mainDexListFile = |
| variantScope |
| .getArtifacts() |
| .getFinalProduct( |
| InternalArtifactType |
| .MAIN_DEX_LIST_FOR_BUNDLE); |
| ((R8Transform) transform) |
| .setMainDexListOutput(mainDexListFile); |
| } |
| }); |
| } |
| break; |
| default: |
| throw new AssertionError("Unknown value " + codeShrinker); |
| } |
| if (variantScope.getPostprocessingFeatures() != null && transformTask.isPresent()) { |
| TaskProvider<CheckProguardFiles> checkFilesTask = |
| taskFactory.register(new CheckProguardFiles.CreationAction(variantScope)); |
| |
| TaskFactoryUtils.dependsOn(transformTask.get(), checkFilesTask); |
| } |
| |
| return createdShrinker; |
| } |
| |
| @NonNull |
| private Optional<TaskProvider<TransformTask>> createProguardTransform( |
| @NonNull VariantScope variantScope, @Nullable FileCollection mappingFileCollection) { |
| final BaseVariantData testedVariantData = variantScope.getTestedVariantData(); |
| |
| ProGuardTransform transform = new ProGuardTransform(variantScope); |
| |
| FileCollection inputProguardMapping; |
| if (testedVariantData != null |
| && testedVariantData.getScope().getArtifacts().hasArtifact(APK_MAPPING)) { |
| inputProguardMapping = |
| testedVariantData |
| .getScope() |
| .getArtifacts() |
| .getFinalArtifactFiles(APK_MAPPING) |
| .get(); |
| } else { |
| inputProguardMapping = mappingFileCollection; |
| } |
| transform.applyTestedMapping(inputProguardMapping); |
| |
| return applyProguardRules( |
| variantScope, |
| inputProguardMapping, |
| transform.getMappingFile(), |
| testedVariantData, |
| transform, |
| null); |
| } |
| |
| private interface ProGuardTransformCallback { |
| void execute(@NonNull ProguardConfigurable transform, @NonNull String taskName); |
| } |
| |
| @NonNull |
| private Optional<TaskProvider<TransformTask>> applyProguardRules( |
| @NonNull VariantScope variantScope, |
| @Nullable FileCollection inputProguardMapping, |
| @Nullable File outputProguardMapping, |
| BaseVariantData testedVariantData, |
| @NonNull ProguardConfigurable transform, |
| @Nullable ProGuardTransformCallback callback) { |
| |
| if (testedVariantData != null) { |
| final VariantScope testedScope = testedVariantData.getScope(); |
| // This is an androidTest variant inside an app/library. |
| applyProguardDefaultsForTest(transform); |
| |
| // All -dontwarn rules for test dependencies should go in here: |
| final ConfigurableFileCollection configurationFiles = |
| project.files( |
| (Callable<Collection<File>>) testedScope::getTestProguardFiles, |
| variantScope.getArtifactFileCollection( |
| RUNTIME_CLASSPATH, ALL, CONSUMER_PROGUARD_RULES)); |
| maybeAddFeatureProguardRules(variantScope, configurationFiles); |
| transform.setConfigurationFiles(configurationFiles); |
| } else if (variantScope.getType().isForTesting() |
| && !variantScope.getType().isTestComponent()) { |
| // This is a test-only module and the app being tested was obfuscated with ProGuard. |
| applyProguardDefaultsForTest(transform); |
| |
| // All -dontwarn rules for test dependencies should go in here: |
| final ConfigurableFileCollection configurationFiles = |
| project.files( |
| (Callable<Collection<File>>) variantScope::getTestProguardFiles, |
| variantScope.getArtifactFileCollection( |
| RUNTIME_CLASSPATH, ALL, CONSUMER_PROGUARD_RULES)); |
| maybeAddFeatureProguardRules(variantScope, configurationFiles); |
| transform.setConfigurationFiles(configurationFiles); |
| } else { |
| // This is a "normal" variant in an app/library. |
| applyProguardConfigForNonTest(transform, variantScope); |
| } |
| |
| return variantScope |
| .getTransformManager() |
| .addTransform( |
| taskFactory, |
| variantScope, |
| transform, |
| taskName -> { |
| variantScope |
| .getArtifacts() |
| .appendArtifact( |
| InternalArtifactType.APK_MAPPING, |
| ImmutableList.of(checkNotNull(outputProguardMapping)), |
| taskName); |
| |
| if (callback != null) { |
| callback.execute(transform, taskName); |
| } |
| }, |
| t -> { |
| if (inputProguardMapping != null) { |
| t.dependsOn(inputProguardMapping); |
| } |
| |
| if (testedVariantData != null) { |
| // We need the mapping file for the app code to exist by the time we run. |
| // FIXME consume the BA! |
| t.dependsOn(testedVariantData.getTaskContainer().getAssembleTask()); |
| } |
| }, |
| null); |
| } |
| |
| private static void applyProguardDefaultsForTest(ProguardConfigurable transform) { |
| // Don't remove any code in tested app. |
| // We can't call dontobfuscate for Proguard, since that makes it ignore the mapping file. |
| // R8 does not have that issue, so we disable obfuscation when running R8. |
| boolean obfuscate = transform instanceof ProGuardTransform; |
| transform.setActions(new PostprocessingFeatures(false, obfuscate, false)); |
| |
| transform.keep("class * {*;}"); |
| transform.keep("interface * {*;}"); |
| transform.keep("enum * {*;}"); |
| transform.keepattributes(); |
| } |
| |
| private void applyProguardConfigForNonTest(ProguardConfigurable transform, VariantScope scope) { |
| GradleVariantConfiguration variantConfig = scope.getVariantConfiguration(); |
| |
| PostprocessingFeatures postprocessingFeatures = scope.getPostprocessingFeatures(); |
| if (postprocessingFeatures != null) { |
| transform.setActions(postprocessingFeatures); |
| } |
| |
| Callable<Collection<File>> proguardConfigFiles = scope::getProguardFiles; |
| |
| final InternalArtifactType aaptProguardFileType = |
| scope.consumesFeatureJars() |
| ? InternalArtifactType.MERGED_AAPT_PROGUARD_FILE |
| : InternalArtifactType.AAPT_PROGUARD_FILE; |
| |
| final ConfigurableFileCollection configurationFiles = |
| project.files( |
| proguardConfigFiles, |
| scope.getArtifacts().getFinalProduct(aaptProguardFileType), |
| scope.getArtifacts().getFinalProduct(GENERATED_PROGUARD_FILE), |
| scope.getArtifactFileCollection( |
| RUNTIME_CLASSPATH, ALL, CONSUMER_PROGUARD_RULES)); |
| |
| if (scope.getType().isHybrid() && scope.getType().isBaseModule()) { |
| Callable<Collection<File>> consumerProguardFiles = scope::getConsumerProguardFiles; |
| configurationFiles.from(consumerProguardFiles); |
| } |
| |
| maybeAddFeatureProguardRules(scope, configurationFiles); |
| transform.setConfigurationFiles(configurationFiles); |
| |
| if (scope.getVariantData().getType().isAar()) { |
| transform.keep("class **.R"); |
| transform.keep("class **.R$*"); |
| } |
| |
| if (variantConfig.isTestCoverageEnabled()) { |
| // when collecting coverage, don't remove the JaCoCo runtime |
| transform.keep("class com.vladium.** {*;}"); |
| transform.keep("class org.jacoco.** {*;}"); |
| transform.keep("interface org.jacoco.** {*;}"); |
| transform.dontwarn("org.jacoco.**"); |
| } |
| } |
| |
| private void maybeAddFeatureProguardRules( |
| @NonNull VariantScope variantScope, |
| @NonNull ConfigurableFileCollection configurationFiles) { |
| if (variantScope.consumesFeatureJars()) { |
| configurationFiles.from( |
| variantScope.getArtifactFileCollection( |
| METADATA_VALUES, PROJECT, CONSUMER_PROGUARD_RULES)); |
| } |
| } |
| |
| @NonNull |
| private Optional<TaskProvider<TransformTask>> createR8Transform( |
| @NonNull VariantScope variantScope, |
| @Nullable FileCollection mappingFileCollection, |
| @Nullable ProGuardTransformCallback callback) { |
| final BaseVariantData testedVariantData = variantScope.getTestedVariantData(); |
| |
| File multiDexKeepProguard = |
| variantScope.getVariantConfiguration().getMultiDexKeepProguard(); |
| FileCollection userMainDexListProguardRules; |
| if (multiDexKeepProguard != null) { |
| userMainDexListProguardRules = project.files(multiDexKeepProguard); |
| } else { |
| userMainDexListProguardRules = project.files(); |
| } |
| |
| File multiDexKeepFile = variantScope.getVariantConfiguration().getMultiDexKeepFile(); |
| FileCollection userMainDexListFiles; |
| if (multiDexKeepFile != null) { |
| userMainDexListFiles = project.files(multiDexKeepFile); |
| } else { |
| userMainDexListFiles = project.files(); |
| } |
| |
| FileCollection inputProguardMapping; |
| if (testedVariantData != null |
| && testedVariantData.getScope().getArtifacts().hasArtifact(APK_MAPPING)) { |
| inputProguardMapping = |
| testedVariantData |
| .getScope() |
| .getArtifacts() |
| .getFinalArtifactFiles(APK_MAPPING) |
| .get(); |
| } else { |
| inputProguardMapping = MoreObjects.firstNonNull(mappingFileCollection, project.files()); |
| } |
| |
| R8Transform transform = |
| new R8Transform( |
| variantScope, |
| userMainDexListFiles, |
| userMainDexListProguardRules, |
| inputProguardMapping, |
| variantScope.getOutputProguardMappingFile()); |
| |
| return applyProguardRules( |
| variantScope, |
| inputProguardMapping, |
| variantScope.getOutputProguardMappingFile(), |
| testedVariantData, |
| transform, |
| callback); |
| } |
| |
| private void maybeCreateDexSplitterTransform(@NonNull VariantScope variantScope) { |
| if (!variantScope.consumesFeatureJars()) { |
| return; |
| } |
| |
| File dexSplitterOutput = |
| FileUtils.join( |
| globalScope.getIntermediatesDir(), |
| "dex-splitter", |
| variantScope.getVariantConfiguration().getDirName()); |
| FileCollection featureJars = |
| variantScope.getArtifactFileCollection(METADATA_VALUES, PROJECT, METADATA_CLASSES); |
| BuildableArtifact baseJars = |
| variantScope |
| .getArtifacts() |
| .getFinalArtifactFiles( |
| InternalArtifactType.MODULE_AND_RUNTIME_DEPS_CLASSES); |
| BuildableArtifact mappingFileSrc = |
| variantScope.getArtifacts().hasArtifact(InternalArtifactType.APK_MAPPING) |
| ? variantScope |
| .getArtifacts() |
| .getFinalArtifactFiles(InternalArtifactType.APK_MAPPING) |
| : null; |
| Provider<RegularFile> mainDexList = |
| variantScope |
| .getArtifacts() |
| .hasFinalProduct(InternalArtifactType.MAIN_DEX_LIST_FOR_BUNDLE) |
| ? variantScope |
| .getArtifacts() |
| .getFinalProduct(InternalArtifactType.MAIN_DEX_LIST_FOR_BUNDLE) |
| : null; |
| |
| DexSplitterTransform transform = |
| new DexSplitterTransform( |
| dexSplitterOutput, featureJars, baseJars, mappingFileSrc, mainDexList); |
| |
| Optional<TaskProvider<TransformTask>> transformTask = |
| variantScope |
| .getTransformManager() |
| .addTransform( |
| taskFactory, |
| variantScope, |
| transform, |
| taskName -> |
| variantScope |
| .getArtifacts() |
| .appendArtifact( |
| InternalArtifactType.FEATURE_DEX, |
| ImmutableList.of(dexSplitterOutput), |
| taskName), |
| null, |
| null); |
| |
| if (transformTask.isPresent()) { |
| publishFeatureDex(variantScope); |
| if (mainDexList != null) { |
| transformTask.get().configure(it -> it.dependsOn(mainDexList)); |
| } |
| } else { |
| globalScope |
| .getErrorHandler() |
| .reportError( |
| Type.GENERIC, |
| new EvalIssueException( |
| "Internal error, could not add the DexSplitterTransform")); |
| } |
| } |
| |
| /** |
| * We have a separate method for publishing the classes.dex files back to the features (instead |
| * of using the typical PublishingSpecs pipeline) because multiple artifacts are published per |
| * BuildableArtifact in this case. |
| * |
| * <p>This method is similar to VariantScopeImpl.publishIntermediateArtifact, and some of the |
| * code was pulled from there. Once there's support for publishing multiple artifacts per |
| * BuildableArtifact in the PublishingSpecs pipeline, we can get rid of this method. |
| */ |
| private void publishFeatureDex(@NonNull VariantScope variantScope) { |
| // first calculate the list of module paths |
| final Collection<String> modulePaths; |
| final AndroidConfig extension = globalScope.getExtension(); |
| if (extension instanceof BaseAppModuleExtension) { |
| modulePaths = ((BaseAppModuleExtension) extension).getDynamicFeatures(); |
| } else if (extension instanceof FeatureExtension) { |
| modulePaths = FeatureModelBuilder.getDynamicFeatures(globalScope); |
| } else { |
| return; |
| } |
| |
| Configuration configuration = |
| variantScope.getVariantData().getVariantDependency().getElements(RUNTIME_ELEMENTS); |
| Preconditions.checkNotNull( |
| configuration, |
| "Publishing to Runtime Element with no Runtime Elements configuration object. " |
| + "VariantType: " |
| + variantScope.getType()); |
| BuildableArtifact artifact = |
| variantScope.getArtifacts().getFinalArtifactFiles(InternalArtifactType.FEATURE_DEX); |
| for (String modulePath : modulePaths) { |
| Provider<File> file = |
| project.provider( |
| () -> |
| new File( |
| Iterables.getOnlyElement(artifact.getFiles()), |
| getFeatureFileName(modulePath, null))); |
| Map<Attribute<String>, String> attributeMap = |
| ImmutableMap.of(MODULE_PATH, project.absoluteProjectPath(modulePath)); |
| publishArtifactToConfiguration( |
| configuration, |
| file, |
| artifact, |
| AndroidArtifacts.ArtifactType.FEATURE_DEX, |
| attributeMap); |
| } |
| } |
| |
| /** |
| * Method to reliably generate matching feature file names when dex splitter is used. |
| * |
| * @param modulePath the gradle module path for the feature |
| * @param fileExtension the desired file extension (e.g., ".jar"), or null if no file extension |
| * (e.g., for a folder) |
| * @return name of file |
| */ |
| public static String getFeatureFileName( |
| @NonNull String modulePath, @Nullable String fileExtension) { |
| final String featureName = FeatureSplitUtils.getFeatureName(modulePath); |
| final String sanitizedFeatureName = ":".equals(featureName) ? "" : featureName; |
| // Prepend "feature-" to fileName in case a non-base module has module path ":base". |
| return "feature-" + sanitizedFeatureName + nullToEmpty(fileExtension); |
| } |
| |
| /** |
| * Checks if {@link ShrinkResourcesTransform} should be added to the build pipeline and either |
| * adds it or registers a {@link SyncIssue} with the reason why it was skipped. |
| */ |
| protected void maybeCreateResourcesShrinkerTransform(@NonNull VariantScope scope) { |
| if (!scope.useResourceShrinker()) { |
| return; |
| } |
| |
| // if resources are shrink, insert a no-op transform per variant output |
| // to transform the res package into a stripped res package |
| ShrinkResourcesTransform shrinkResTransform = |
| new ShrinkResourcesTransform( |
| scope.getVariantData(), |
| scope.getArtifacts().getFinalProduct(InternalArtifactType.PROCESSED_RES), |
| logger); |
| |
| Optional<TaskProvider<TransformTask>> shrinkTask = |
| scope.getTransformManager() |
| .addTransform(taskFactory, scope, shrinkResTransform, null, null, null); |
| |
| if (!shrinkTask.isPresent()) { |
| globalScope |
| .getErrorHandler() |
| .reportError( |
| Type.GENERIC, |
| new EvalIssueException( |
| "Internal error, could not add the ShrinkResourcesTransform")); |
| } else { |
| scope.getArtifacts() |
| .producesDir( |
| InternalArtifactType.SHRUNK_PROCESSED_RES, |
| BuildArtifactsHolder.OperationType.INITIAL, |
| shrinkTask.get(), |
| TransformTask::getOutputDirectory, |
| "out"); |
| } |
| |
| // And for the bundle |
| taskFactory.register(new ShrinkBundleResourcesTask.CreationAction(scope)); |
| } |
| |
| public void createReportTasks(final List<VariantScope> variantScopes) { |
| taskFactory.register( |
| "androidDependencies", |
| DependencyReportTask.class, |
| task -> { |
| task.setDescription("Displays the Android dependencies of the project."); |
| task.setVariants(variantScopes); |
| task.setGroup(ANDROID_GROUP); |
| }); |
| |
| |
| List<VariantScope> signingReportScopes = |
| variantScopes |
| .stream() |
| .filter( |
| variantScope -> |
| variantScope.getType().isForTesting() |
| || variantScope.getType().isBaseModule()) |
| .collect(Collectors.toList()); |
| if (!signingReportScopes.isEmpty()) { |
| taskFactory.register( |
| "signingReport", |
| SigningReportTask.class, |
| task -> { |
| task.setDescription( |
| "Displays the signing info for the base and test modules"); |
| task.setVariants(signingReportScopes); |
| task.setGroup(ANDROID_GROUP); |
| }); |
| } |
| } |
| |
| public void createAnchorTasks(@NonNull VariantScope scope) { |
| createVariantPreBuildTask(scope); |
| |
| // also create sourceGenTask |
| final BaseVariantData variantData = scope.getVariantData(); |
| scope.getTaskContainer() |
| .setSourceGenTask( |
| taskFactory.register( |
| scope.getTaskName("generate", "Sources"), |
| task -> { |
| task.dependsOn(PrepareLintJar.NAME); |
| task.dependsOn(PrepareLintJarForPublish.NAME); |
| task.dependsOn(variantData.getExtraGeneratedResFolders()); |
| })); |
| // and resGenTask |
| scope.getTaskContainer() |
| .setResourceGenTask( |
| taskFactory.register(scope.getTaskName("generate", "Resources"))); |
| |
| scope.getTaskContainer() |
| .setAssetGenTask(taskFactory.register(scope.getTaskName("generate", "Assets"))); |
| |
| if (!variantData.getType().isForTesting() |
| && variantData.getVariantConfiguration().getBuildType().isTestCoverageEnabled()) { |
| scope.getTaskContainer() |
| .setCoverageReportTask( |
| taskFactory.register( |
| scope.getTaskName("create", "CoverageReport"), |
| task -> { |
| task.setGroup(JavaBasePlugin.VERIFICATION_GROUP); |
| task.setDescription( |
| String.format( |
| "Creates test coverage reports for the %s variant.", |
| variantData.getName())); |
| })); |
| } |
| |
| // and compile task |
| createCompileAnchorTask(scope); |
| } |
| |
| protected void createVariantPreBuildTask(@NonNull VariantScope scope) { |
| // default pre-built task. |
| createDefaultPreBuildTask(scope); |
| } |
| |
| protected void createDefaultPreBuildTask(@NonNull VariantScope scope) { |
| taskFactory.register(new PreBuildCreationAction(scope)); |
| } |
| |
| public abstract static class AbstractPreBuildCreationAction<T extends Task> |
| extends TaskCreationAction<T> { |
| |
| protected final VariantScope variantScope; |
| |
| @NonNull |
| @Override |
| public String getName() { |
| return variantScope.getTaskName("pre", "Build"); |
| } |
| |
| public AbstractPreBuildCreationAction(VariantScope variantScope) { |
| this.variantScope = variantScope; |
| } |
| |
| @Override |
| public void handleProvider(@NonNull TaskProvider<? extends T> taskProvider) { |
| super.handleProvider(taskProvider); |
| variantScope.getTaskContainer().setPreBuildTask(taskProvider); |
| } |
| |
| @Override |
| public void configure(@NonNull T task) { |
| task.dependsOn(MAIN_PREBUILD); |
| |
| if (variantScope.getCodeShrinker() != null) { |
| task.dependsOn(EXTRACT_PROGUARD_FILES); |
| } |
| } |
| } |
| |
| private static class PreBuildCreationAction extends AbstractPreBuildCreationAction<Task> { |
| public PreBuildCreationAction(VariantScope variantScope) { |
| super(variantScope); |
| } |
| |
| @NonNull |
| @Override |
| public Class<Task> getType() { |
| return Task.class; |
| } |
| } |
| |
| private void createCompileAnchorTask(@NonNull final VariantScope scope) { |
| scope.getTaskContainer() |
| .setCompileTask( |
| taskFactory.register( |
| scope.getTaskName("compile", "Sources"), |
| task -> task.setGroup(BUILD_GROUP))); |
| |
| // FIXME is that really needed? |
| TaskFactoryUtils.dependsOn( |
| scope.getTaskContainer().getAssembleTask(), |
| scope.getTaskContainer().getCompileTask()); |
| } |
| |
| public void createCheckManifestTask(@NonNull VariantScope scope) { |
| taskFactory.register(getCheckManifestConfig(scope)); |
| } |
| |
| protected CheckManifest.CreationAction getCheckManifestConfig(@NonNull VariantScope scope) { |
| return new CheckManifest.CreationAction(scope, false); |
| } |
| |
| @NonNull |
| protected Logger getLogger() { |
| return logger; |
| } |
| |
| public void addBindingDependenciesIfNecessary( |
| ViewBindingOptions viewBindingOptions, |
| DataBindingOptions dataBindingOptions, |
| List<VariantScope> variantScopes) { |
| ProjectOptions projectOptions = globalScope.getProjectOptions(); |
| boolean useAndroidX = projectOptions.get(BooleanOption.USE_ANDROID_X); |
| boolean viewBindingEnabled = viewBindingOptions.isEnabled(); |
| boolean dataBindingEnabled = dataBindingOptions.isEnabled(); |
| |
| if (viewBindingEnabled) { |
| String version = |
| dataBindingBuilder.getLibraryVersion(dataBindingBuilder.getCompilerVersion()); |
| String groupAndArtifact = |
| useAndroidX |
| ? SdkConstants.ANDROIDX_VIEW_BINDING_ARTIFACT |
| : SdkConstants.VIEW_BINDING_ARTIFACT; |
| project.getDependencies().add("api", groupAndArtifact + ":" + version); |
| } |
| |
| if (dataBindingEnabled) { |
| String version = |
| MoreObjects.firstNonNull( |
| dataBindingOptions.getVersion(), |
| dataBindingBuilder.getCompilerVersion()); |
| String baseLibArtifact = |
| useAndroidX |
| ? SdkConstants.ANDROIDX_DATA_BINDING_BASELIB_ARTIFACT |
| : SdkConstants.DATA_BINDING_BASELIB_ARTIFACT; |
| project.getDependencies() |
| .add( |
| "api", |
| baseLibArtifact |
| + ":" |
| + dataBindingBuilder.getBaseLibraryVersion(version)); |
| project.getDependencies() |
| .add( |
| "annotationProcessor", |
| SdkConstants.DATA_BINDING_ANNOTATION_PROCESSOR_ARTIFACT |
| + ":" |
| + version); |
| // TODO load config name from source sets |
| if (dataBindingOptions.isEnabledForTests() |
| || this instanceof LibraryTaskManager |
| || this instanceof MultiTypeTaskManager) { |
| project.getDependencies() |
| .add( |
| "androidTestAnnotationProcessor", |
| SdkConstants.DATA_BINDING_ANNOTATION_PROCESSOR_ARTIFACT |
| + ":" |
| + version); |
| } |
| if (dataBindingOptions.getAddDefaultAdapters()) { |
| String libArtifact = |
| useAndroidX |
| ? SdkConstants.ANDROIDX_DATA_BINDING_LIB_ARTIFACT |
| : SdkConstants.DATA_BINDING_LIB_ARTIFACT; |
| String adaptersArtifact = |
| useAndroidX |
| ? SdkConstants.ANDROIDX_DATA_BINDING_ADAPTER_LIB_ARTIFACT |
| : SdkConstants.DATA_BINDING_ADAPTER_LIB_ARTIFACT; |
| project.getDependencies() |
| .add( |
| "api", |
| libArtifact + ":" + dataBindingBuilder.getLibraryVersion(version)); |
| project.getDependencies() |
| .add( |
| "api", |
| adaptersArtifact |
| + ":" |
| + dataBindingBuilder.getBaseAdaptersVersion(version)); |
| } |
| project.getPluginManager() |
| .withPlugin( |
| "org.jetbrains.kotlin.kapt", |
| appliedPlugin -> { |
| configureKotlinKaptTasksForDataBinding( |
| project, variantScopes, version); |
| }); |
| } |
| } |
| |
| private void configureKotlinKaptTasksForDataBinding( |
| Project project, List<VariantScope> variantScopes, String version) { |
| DependencySet kaptDeps = project.getConfigurations().getByName("kapt").getAllDependencies(); |
| kaptDeps.forEach( |
| (Dependency dependency) -> { |
| // if it is a data binding compiler dependency w/ a different version, report error |
| if (Objects.equals( |
| dependency.getGroup() + ":" + dependency.getName(), |
| SdkConstants.DATA_BINDING_ANNOTATION_PROCESSOR_ARTIFACT) |
| && !Objects.equals(dependency.getVersion(), version)) { |
| String depString = |
| dependency.getGroup() |
| + ":" |
| + dependency.getName() |
| + ":" |
| + dependency.getVersion(); |
| globalScope |
| .getErrorHandler() |
| .reportError( |
| Type.GENERIC, |
| new EvalIssueException( |
| "Data Binding annotation processor version needs to match the" |
| + " Android Gradle Plugin version. You can remove the kapt" |
| + " dependency " |
| + depString |
| + " and Android Gradle Plugin will inject" |
| + " the right version.")); |
| } |
| }); |
| project.getDependencies() |
| .add( |
| "kapt", |
| SdkConstants.DATA_BINDING_ANNOTATION_PROCESSOR_ARTIFACT + ":" + version); |
| Class<? extends Task> kaptTaskClass = null; |
| try { |
| //noinspection unchecked |
| kaptTaskClass = |
| (Class<? extends Task>) |
| Class.forName("org.jetbrains.kotlin.gradle.internal.KaptTask"); |
| } catch (ClassNotFoundException e) { |
| logger.error( |
| "Kotlin plugin is applied to the project " |
| + project.getPath() |
| + " but we cannot find the KaptTask. Make sure you apply the" |
| + " kotlin-kapt plugin because it is necessary to use kotlin" |
| + " with data binding."); |
| } |
| if (kaptTaskClass == null) { |
| return; |
| } |
| // create a map from kapt task name to variant scope |
| Map<String, VariantScope> kaptTaskLookup = |
| variantScopes |
| .stream() |
| .collect( |
| Collectors.toMap( |
| variantScope -> |
| variantScope |
| .getVariantData() |
| .getTaskName("kapt", "Kotlin"), |
| variantScope -> variantScope)); |
| project.getTasks() |
| .withType( |
| kaptTaskClass, |
| (Action<Task>) |
| kaptTask -> { |
| // find matching scope. |
| VariantScope matchingScope = |
| kaptTaskLookup.get(kaptTask.getName()); |
| if (matchingScope != null) { |
| configureKaptTaskInScope(matchingScope, kaptTask); |
| } |
| }); |
| } |
| |
| private static void configureKaptTaskInScope( |
| @NonNull VariantScope scope, @NonNull Task kaptTask) { |
| // The data binding artifact is created through annotation processing, which is invoked |
| // by the Kapt task (when the Kapt plugin is used). Therefore, we register Kapt as the |
| // generating task. (This will overwrite the registration of JavaCompile as the generating |
| // task that took place earlier before this method is called). |
| scope.getArtifacts() |
| .createBuildableArtifact( |
| InternalArtifactType.DATA_BINDING_ARTIFACT, |
| BuildArtifactsHolder.OperationType.APPEND, |
| ImmutableList.of(scope.getBundleArtifactFolderForDataBinding()), |
| kaptTask.getName()); |
| } |
| |
| protected void configureTestData(AbstractTestDataImpl testData) { |
| testData.setAnimationsDisabled(extension.getTestOptions().getAnimationsDisabled()); |
| testData.setExtraInstrumentationTestRunnerArgs( |
| projectOptions.getExtraInstrumentationTestRunnerArgs()); |
| } |
| } |