/* * Copyright (C) 2015 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.model; import static com.android.build.gradle.model.AndroidComponentModelPlugin.COMPONENT_NAME; import static com.android.build.gradle.model.ModelConstants.ANDROID_BUILDER; import static com.android.build.gradle.model.ModelConstants.ANDROID_CONFIG_ADAPTOR; import static com.android.build.gradle.model.ModelConstants.EXTRA_MODEL_INFO; import static com.android.builder.core.BuilderConstants.DEBUG; import static com.android.builder.model.AndroidProject.FD_INTERMEDIATES; import com.android.annotations.NonNull; import com.android.build.gradle.AndroidGradleOptions; import com.android.build.gradle.internal.AndroidConfigHelper; import com.android.build.gradle.internal.ExecutionConfigurationUtil; import com.android.build.gradle.internal.ExtraModelInfo; import com.android.build.gradle.internal.LibraryCache; import com.android.build.gradle.internal.LoggerWrapper; import com.android.build.gradle.internal.NdkOptionsHelper; import com.android.build.gradle.internal.SdkHandler; import com.android.build.gradle.internal.TaskManager; import com.android.build.gradle.internal.VariantManager; import com.android.build.gradle.internal.coverage.JacocoPlugin; import com.android.build.gradle.internal.process.GradleJavaProcessExecutor; import com.android.build.gradle.internal.process.GradleProcessExecutor; import com.android.build.gradle.internal.profile.RecordingBuildListener; import com.android.build.gradle.internal.tasks.DependencyReportTask; import com.android.build.gradle.internal.tasks.SigningReportTask; import com.android.build.gradle.internal.variant.VariantFactory; import com.android.build.gradle.managed.AndroidConfig; import com.android.build.gradle.managed.BuildType; import com.android.build.gradle.managed.ClassField; import com.android.build.gradle.managed.NdkConfig; import com.android.build.gradle.managed.NdkOptions; import com.android.build.gradle.managed.ProductFlavor; import com.android.build.gradle.managed.SigningConfig; import com.android.build.gradle.managed.adaptor.AndroidConfigAdaptor; import com.android.build.gradle.managed.adaptor.BuildTypeAdaptor; import com.android.build.gradle.managed.adaptor.ProductFlavorAdaptor; import com.android.build.gradle.tasks.JillTask; import com.android.build.gradle.tasks.PreDex; import com.android.builder.Version; import com.android.builder.core.AndroidBuilder; import com.android.builder.internal.compiler.JackConversionCache; import com.android.builder.internal.compiler.PreDexCache; import com.android.builder.profile.ProcessRecorderFactory; import com.android.builder.profile.Recorder; import com.android.builder.profile.ThreadRecorder; import com.android.builder.sdk.TargetInfo; import com.android.builder.signing.DefaultSigningConfig; import com.android.ide.common.internal.ExecutorSingleton; import com.android.ide.common.process.LoggedProcessOutputHandler; import com.android.ide.common.signing.KeystoreHelper; import com.android.prefs.AndroidLocation; import com.android.utils.ILogger; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.execution.TaskExecutionGraph; import org.gradle.api.logging.LogLevel; import org.gradle.api.plugins.JavaBasePlugin; import org.gradle.internal.reflect.Instantiator; import org.gradle.internal.service.ServiceRegistry; import org.gradle.language.base.FunctionalSourceSet; import org.gradle.language.base.LanguageSourceSet; import org.gradle.model.Defaults; import org.gradle.model.Model; import org.gradle.model.ModelMap; import org.gradle.model.Mutate; import org.gradle.model.Path; import org.gradle.model.RuleSource; import org.gradle.model.internal.core.ModelCreators; import org.gradle.model.internal.core.ModelReference; import org.gradle.model.internal.registry.ModelRegistry; import org.gradle.platform.base.BinaryContainer; import org.gradle.platform.base.ComponentSpecContainer; import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry; import java.io.File; import java.io.IOException; import java.security.KeyStore; import java.util.List; import javax.inject.Inject; import groovy.lang.Closure; public class BaseComponentModelPlugin implements Plugin<Project> { private ToolingModelBuilderRegistry toolingRegistry; private ModelRegistry modelRegistry; @Inject protected BaseComponentModelPlugin(ToolingModelBuilderRegistry toolingRegistry, ModelRegistry modelRegistry) { this.toolingRegistry = toolingRegistry; this.modelRegistry = modelRegistry; } /** * Replace BasePlugin's apply method for component model. */ @Override public void apply(Project project) { ExecutionConfigurationUtil.setThreadPoolSize(project); try { List<Recorder.Property> propertyList = Lists.newArrayList( new Recorder.Property("plugin_version", Version.ANDROID_GRADLE_PLUGIN_VERSION), new Recorder.Property("next_gen_plugin", "true"), new Recorder.Property("gradle_version", project.getGradle().getGradleVersion()) ); String benchmarkName = AndroidGradleOptions.getBenchmarkName(project); if (benchmarkName != null) { propertyList.add(new Recorder.Property("benchmark_name", benchmarkName)); } String benchmarkMode = AndroidGradleOptions.getBenchmarkMode(project); if (benchmarkMode != null) { propertyList.add(new Recorder.Property("benchmark_mode", benchmarkMode)); } ProcessRecorderFactory.initialize( new LoggerWrapper(project.getLogger()), project.getRootProject() .file("profiler" + System.currentTimeMillis() + ".json"), propertyList); } catch (IOException e) { throw new RuntimeException("Unable to initialize ProcessRecorderFactory"); } project.getGradle().addListener(new RecordingBuildListener(ThreadRecorder.get())); project.getPlugins().apply(AndroidComponentModelPlugin.class); project.getPlugins().apply(JavaBasePlugin.class); project.getPlugins().apply(JacocoPlugin.class); // TODO: Create configurations for build types and flavors, or migrate to new dependency // management if it's ready. ConfigurationContainer configurations = project.getConfigurations(); createConfiguration(configurations, "compile", "Classpath for default sources."); createConfiguration(configurations, "default-metadata", "Metadata for published APKs"); createConfiguration(configurations, "default-mapping", "Metadata for published APKs"); project.getPlugins().apply(NdkComponentModelPlugin.class); // Remove this when our models no longer depends on Project. modelRegistry.create(ModelCreators .bridgedInstance(ModelReference.of("projectModel", Project.class), project) .descriptor("Model of project.").build()); toolingRegistry.register(new ComponentModelBuilder(modelRegistry)); // Inserting the ToolingModelBuilderRegistry into the model so that it can be use to create // TaskManager in child classes. modelRegistry.create(ModelCreators.bridgedInstance( ModelReference.of("toolingRegistry", ToolingModelBuilderRegistry.class), toolingRegistry).descriptor("Tooling model builder model registry.").build()); } private static void createConfiguration(@NonNull ConfigurationContainer configurations, @NonNull String configurationName, @NonNull String configurationDescription) { Configuration configuration = configurations.findByName(configurationName); if (configuration == null) { configuration = configurations.create(configurationName); } configuration.setVisible(false); configuration.setDescription(configurationDescription); } @SuppressWarnings("MethodMayBeStatic") public static class Rules extends RuleSource { @Defaults public void configureAndroidModel( AndroidConfig androidModel, ServiceRegistry serviceRegistry) { Instantiator instantiator = serviceRegistry.get(Instantiator.class); AndroidConfigHelper.configure(androidModel, instantiator); androidModel.getSigningConfigs().create(DEBUG, new Action<SigningConfig>() { @Override public void execute(SigningConfig signingConfig) { try { signingConfig.setStoreFile(KeystoreHelper.defaultDebugKeystoreLocation()); signingConfig.setStorePassword(DefaultSigningConfig.DEFAULT_PASSWORD); signingConfig.setKeyAlias(DefaultSigningConfig.DEFAULT_ALIAS); signingConfig.setKeyPassword(DefaultSigningConfig.DEFAULT_PASSWORD); signingConfig.setStoreType(KeyStore.getDefaultType()); } catch (AndroidLocation.AndroidLocationException e) { throw new RuntimeException(e); } } }); } // com.android.build.gradle.AndroidConfig do not contain an NdkConfig. Copy it to the // defaultConfig for now. @Defaults public void copyNdkConfig( @Path("android.defaultConfig.ndk") NdkOptions defaultNdkConfig, @Path("android.ndk") NdkConfig pluginNdkConfig) { NdkOptionsHelper.init(defaultNdkConfig); NdkOptionsHelper.merge(defaultNdkConfig, pluginNdkConfig); } // TODO: Remove code duplicated from BasePlugin. @Model(EXTRA_MODEL_INFO) public ExtraModelInfo createExtraModelInfo( Project project, @NonNull @Path("isApplication") Boolean isApplication) { return new ExtraModelInfo(project, isApplication); } @Model public SdkHandler createSdkHandler(final Project project) { final ILogger logger = new LoggerWrapper(project.getLogger()); final SdkHandler sdkHandler = new SdkHandler(project, logger); // call back on execution. This is called after the whole build is done (not // after the current project is done). // This is will be called for each (android) projects though, so this should support // being called 2+ times. project.getGradle().buildFinished(new Closure<Object>(this, this) { public void doCall(Object it) { ExecutorSingleton.shutdown(); sdkHandler.unload(); try { PreDexCache.getCache().clear(project.getRootProject() .file(String.valueOf(project.getRootProject().getBuildDir()) + "/" + FD_INTERMEDIATES + "/dex-cache/cache.xml"), logger); JackConversionCache.getCache().clear(project.getRootProject() .file(String.valueOf(project.getRootProject().getBuildDir()) + "/" + FD_INTERMEDIATES + "/jack-cache/cache.xml"), logger); } catch (IOException e) { throw new RuntimeException(e); } LibraryCache.getCache().unload(); } public void doCall() { doCall(null); } }); project.getGradle().getTaskGraph().whenReady(new Closure<Void>(this, this) { public void doCall(TaskExecutionGraph taskGraph) { for (Task task : taskGraph.getAllTasks()) { if (task instanceof PreDex) { PreDexCache.getCache().load(project.getRootProject() .file(String.valueOf(project.getRootProject().getBuildDir()) + "/" + FD_INTERMEDIATES + "/dex-cache/cache.xml")); break; } else if (task instanceof JillTask) { JackConversionCache.getCache().load(project.getRootProject() .file(String.valueOf(project.getRootProject().getBuildDir()) + "/" + FD_INTERMEDIATES + "/jack-cache/cache.xml")); break; } } } }); // setup SDK repositories. for (final File file : sdkHandler.getSdkLoader().getRepositories()) { project.getRepositories().maven(new Action<MavenArtifactRepository>() { @Override public void execute(MavenArtifactRepository repo) { repo.setUrl(file.toURI()); } }); } return sdkHandler; } @Model(ANDROID_BUILDER) public AndroidBuilder createAndroidBuilder(Project project, ExtraModelInfo extraModelInfo) { String creator = "Android Gradle"; ILogger logger = new LoggerWrapper(project.getLogger()); return new AndroidBuilder(project.equals(project.getRootProject()) ? project.getName() : project.getPath(), creator, new GradleProcessExecutor(project), new GradleJavaProcessExecutor(project), extraModelInfo, logger, project.getLogger().isEnabled(LogLevel.INFO)); } @Mutate public void initDebugBuildTypes( @Path("android.buildTypes") ModelMap<BuildType> buildTypes, @Path("android.signingConfigs") final ModelMap<SigningConfig> signingConfigs) { buildTypes.beforeEach(new Action<BuildType>() { @Override public void execute(BuildType buildType) { initBuildType(buildType); } }); buildTypes.named(DEBUG, new Action<BuildType>() { @Override public void execute(BuildType buildType) { buildType.setSigningConfig(signingConfigs.get(DEBUG)); } }); } private static void initBuildType(@NonNull BuildType buildType) { buildType.setDebuggable(false); buildType.setTestCoverageEnabled(false); buildType.setPseudoLocalesEnabled(false); buildType.setRenderscriptDebuggable(false); buildType.setRenderscriptOptimLevel(3); buildType.setMinifyEnabled(false); buildType.setZipAlignEnabled(true); buildType.setEmbedMicroApp(true); buildType.setUseJack(false); buildType.setShrinkResources(false); buildType.setProguardFiles(Sets.<File>newHashSet()); buildType.setConsumerProguardFiles(Sets.<File>newHashSet()); buildType.setTestProguardFiles(Sets.<File>newHashSet()); } @Mutate public void initDefaultConfig(@Path("android.defaultConfig") ProductFlavor defaultConfig) { initProductFlavor(defaultConfig); } @Mutate public void initProductFlavors( @Path("android.productFlavors") final ModelMap<ProductFlavor> productFlavors) { productFlavors.beforeEach(new Action<ProductFlavor>() { @Override public void execute(ProductFlavor productFlavor) { initProductFlavor(productFlavor); } }); } private void initProductFlavor(ProductFlavor productFlavor) { productFlavor.setProguardFiles(Sets.<File>newHashSet()); productFlavor.setConsumerProguardFiles(Sets.<File>newHashSet()); productFlavor.setTestProguardFiles(Sets.<File>newHashSet()); productFlavor.setResourceConfigurations(Sets.<String>newHashSet()); productFlavor.setJarJarRuleFiles(Lists.<File>newArrayList()); productFlavor.getBuildConfigFields().beforeEach(new Action<ClassField>() { @Override public void execute(ClassField classField) { classField.setAnnotations(Sets.<String>newHashSet()); } }); productFlavor.getResValues().beforeEach(new Action<ClassField>() { @Override public void execute(ClassField classField) { classField.setAnnotations(Sets.<String>newHashSet()); } }); } @Mutate public void addDefaultAndroidSourceSet( @Path("android.sources") AndroidComponentModelSourceSet sources) { sources.addDefaultSourceSet("resources", AndroidLanguageSourceSet.class); sources.addDefaultSourceSet("java", AndroidLanguageSourceSet.class); sources.addDefaultSourceSet("manifest", AndroidLanguageSourceSet.class); sources.addDefaultSourceSet("res", AndroidLanguageSourceSet.class); sources.addDefaultSourceSet("assets", AndroidLanguageSourceSet.class); sources.addDefaultSourceSet("aidl", AndroidLanguageSourceSet.class); sources.addDefaultSourceSet("renderscript", AndroidLanguageSourceSet.class); sources.addDefaultSourceSet("jniLibs", AndroidLanguageSourceSet.class); sources.all(new Action<FunctionalSourceSet>() { @Override public void execute(FunctionalSourceSet functionalSourceSet) { LanguageSourceSet manifest = functionalSourceSet.getByName("manifest"); manifest.getSource().setIncludes(ImmutableList.of("AndroidManifest.xml")); } }); } @Model(ANDROID_CONFIG_ADAPTOR) public com.android.build.gradle.AndroidConfig createModelAdaptor( ServiceRegistry serviceRegistry, AndroidConfig androidExtension, Project project, @Path("isApplication") Boolean isApplication) { Instantiator instantiator = serviceRegistry.get(Instantiator.class); return new AndroidConfigAdaptor(androidExtension, AndroidConfigHelper .createSourceSetsContainer(project, instantiator, !isApplication)); } @Mutate public void createAndroidComponents( ComponentSpecContainer androidSpecs, ServiceRegistry serviceRegistry, AndroidConfig androidExtension, com.android.build.gradle.AndroidConfig adaptedModel, @Path("android.buildTypes") ModelMap<BuildType> buildTypes, @Path("android.productFlavors") ModelMap<ProductFlavor> productFlavors, @Path("android.signingConfigs") ModelMap<SigningConfig> signingConfigs, VariantFactory variantFactory, TaskManager taskManager, Project project, AndroidBuilder androidBuilder, SdkHandler sdkHandler, ExtraModelInfo extraModelInfo, @Path("isApplication") Boolean isApplication) { Instantiator instantiator = serviceRegistry.get(Instantiator.class); // check if the target has been set. TargetInfo targetInfo = androidBuilder.getTargetInfo(); if (targetInfo == null) { sdkHandler.initTarget(androidExtension.getCompileSdkVersion(), androidExtension.getBuildToolsRevision(), androidExtension.getLibraryRequests(), androidBuilder); } VariantManager variantManager = new VariantManager(project, androidBuilder, adaptedModel, variantFactory, taskManager, instantiator); for (BuildType buildType : buildTypes.values()) { variantManager.addBuildType(new BuildTypeAdaptor(buildType)); } for (ProductFlavor productFlavor : productFlavors.values()) { variantManager.addProductFlavor(new ProductFlavorAdaptor(productFlavor)); } DefaultAndroidComponentSpec spec = (DefaultAndroidComponentSpec) androidSpecs.get(COMPONENT_NAME); spec.setExtension(androidExtension); spec.setVariantManager(variantManager); } @Mutate public void createVariantData( ModelMap<AndroidBinary> binaries, ModelMap<AndroidComponentSpec> specs, TaskManager taskManager) { final VariantManager variantManager = ((DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME)).getVariantManager(); binaries.afterEach(new Action<AndroidBinary>() { @Override public void execute(AndroidBinary androidBinary) { DefaultAndroidBinary binary = (DefaultAndroidBinary) androidBinary; List<ProductFlavorAdaptor> adaptedFlavors = Lists.newArrayList(); for (ProductFlavor flavor : binary.getProductFlavors()) { adaptedFlavors.add(new ProductFlavorAdaptor(flavor)); } binary.setVariantData( variantManager.createVariantData( new BuildTypeAdaptor(binary.getBuildType()), adaptedFlavors)); variantManager.getVariantDataList().add(binary.getVariantData()); } }); } @Mutate public void createLifeCycleTasks(ModelMap<Task> tasks, TaskManager taskManager) { taskManager.createTasksBeforeEvaluate(new TaskModelMapAdaptor(tasks)); } @Mutate public void createAndroidTasks( ModelMap<Task> tasks, ModelMap<AndroidComponentSpec> androidSpecs, TaskManager taskManager, SdkHandler sdkHandler, Project project, AndroidComponentModelSourceSet androidSources) { // setup SDK repositories. for (final File file : sdkHandler.getSdkLoader().getRepositories()) { project.getRepositories().maven(new Action<MavenArtifactRepository>() { @Override public void execute(MavenArtifactRepository repo) { repo.setUrl(file.toURI()); } }); } // TODO: determine how to provide functionalities of variant API objects. } // TODO: Use @BinaryTasks after figuring how to configure non-binary specific tasks. @Mutate public void createBinaryTasks( final ModelMap<Task> tasks, BinaryContainer binaries, ModelMap<AndroidComponentSpec> specs, TaskManager taskManager) { final VariantManager variantManager = ((DefaultAndroidComponentSpec) specs.get(COMPONENT_NAME)).getVariantManager(); binaries.withType(AndroidBinary.class, new Action<AndroidBinary>() { @Override public void execute(AndroidBinary androidBinary) { DefaultAndroidBinary binary = (DefaultAndroidBinary) androidBinary; variantManager.createTasksForVariantData( new TaskModelMapAdaptor(tasks), binary.getVariantData()); } }); } /** * Create tasks that must be created after other tasks for variants are created. */ @Mutate public void createRemainingTasks( ModelMap<Task> tasks, TaskManager taskManager, ModelMap<AndroidComponentSpec> spec) { VariantManager variantManager = ((DefaultAndroidComponentSpec)spec.get(COMPONENT_NAME)).getVariantManager(); // create the test tasks. taskManager.createTopLevelTestTasks(new TaskModelMapAdaptor(tasks), !variantManager.getProductFlavors().isEmpty()); } @Mutate public void createReportTasks( ModelMap<Task> tasks, ModelMap<AndroidComponentSpec> specs) { final VariantManager variantManager = ((DefaultAndroidComponentSpec)specs.get(COMPONENT_NAME)).getVariantManager(); tasks.create("androidDependencies", DependencyReportTask.class, new Action<DependencyReportTask>() { @Override public void execute(DependencyReportTask dependencyReportTask) { dependencyReportTask.setDescription( "Displays the Android dependencies of the project"); dependencyReportTask.setVariants(variantManager.getVariantDataList()); dependencyReportTask.setGroup("Android"); } }); tasks.create("signingReport", SigningReportTask.class, new Action<SigningReportTask>() { @Override public void execute(SigningReportTask signingReportTask) { signingReportTask .setDescription("Displays the signing info for each variant"); signingReportTask.setVariants(variantManager.getVariantDataList()); signingReportTask.setGroup("Android"); } }); } @Mutate public void modifyAssembleTaskDescription(@Path("tasks.assemble") Task assembleTask) { assembleTask.setDescription( "Assembles all variants of all applications and secondary packages."); } } }