| /* |
| * Copyright (C) 2012 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; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| import static java.io.File.separator; |
| |
| import android.databinding.tool.DataBindingBuilder; |
| import com.android.SdkConstants; |
| import com.android.Version; |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.build.gradle.api.AndroidBasePlugin; |
| import com.android.build.gradle.api.BaseVariantOutput; |
| import com.android.build.gradle.internal.ApiObjectFactory; |
| import com.android.build.gradle.internal.BadPluginException; |
| import com.android.build.gradle.internal.BuildCacheUtils; |
| import com.android.build.gradle.internal.ClasspathVerifier; |
| import com.android.build.gradle.internal.DependencyResolutionChecks; |
| import com.android.build.gradle.internal.ExtraModelInfo; |
| import com.android.build.gradle.internal.LoggerWrapper; |
| import com.android.build.gradle.internal.NonFinalPluginExpiry; |
| import com.android.build.gradle.internal.PluginInitializer; |
| import com.android.build.gradle.internal.SdkComponents; |
| import com.android.build.gradle.internal.SdkLocator; |
| import com.android.build.gradle.internal.TaskManager; |
| import com.android.build.gradle.internal.VariantManager; |
| import com.android.build.gradle.internal.crash.CrashReporting; |
| import com.android.build.gradle.internal.dependency.SourceSetManager; |
| import com.android.build.gradle.internal.dsl.BuildType; |
| import com.android.build.gradle.internal.dsl.BuildTypeFactory; |
| import com.android.build.gradle.internal.dsl.ProductFlavor; |
| import com.android.build.gradle.internal.dsl.ProductFlavorFactory; |
| import com.android.build.gradle.internal.dsl.SigningConfig; |
| import com.android.build.gradle.internal.dsl.SigningConfigFactory; |
| import com.android.build.gradle.internal.dsl.Splits; |
| import com.android.build.gradle.internal.errors.DeprecationReporterImpl; |
| import com.android.build.gradle.internal.errors.SyncIssueHandler; |
| import com.android.build.gradle.internal.ide.ModelBuilder; |
| import com.android.build.gradle.internal.ide.NativeModelBuilder; |
| import com.android.build.gradle.internal.packaging.GradleKeystoreHelper; |
| import com.android.build.gradle.internal.profile.AnalyticsUtil; |
| import com.android.build.gradle.internal.profile.ProfileAgent; |
| import com.android.build.gradle.internal.profile.ProfilerInitializer; |
| import com.android.build.gradle.internal.profile.RecordingBuildListener; |
| import com.android.build.gradle.internal.scope.DelayedActionsExecutor; |
| import com.android.build.gradle.internal.scope.GlobalScope; |
| import com.android.build.gradle.internal.scope.VariantScope; |
| import com.android.build.gradle.internal.tasks.Workers; |
| import com.android.build.gradle.internal.utils.GradlePluginUtils; |
| import com.android.build.gradle.internal.variant.BaseVariantData; |
| import com.android.build.gradle.internal.variant.VariantFactory; |
| import com.android.build.gradle.internal.variant2.DslScopeImpl; |
| import com.android.build.gradle.internal.workeractions.WorkerActionServiceRegistry; |
| import com.android.build.gradle.options.BooleanOption; |
| 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.options.SyncOptions.ErrorFormatMode; |
| import com.android.build.gradle.tasks.LintBaseTask; |
| import com.android.build.gradle.tasks.factory.AbstractCompilesUtil; |
| import com.android.builder.core.BuilderConstants; |
| import com.android.builder.errors.EvalIssueReporter; |
| import com.android.builder.errors.EvalIssueReporter.Type; |
| import com.android.builder.profile.ProcessProfileWriter; |
| import com.android.builder.profile.Recorder; |
| import com.android.builder.profile.ThreadRecorder; |
| import com.android.builder.utils.FileCache; |
| import com.android.dx.command.dexer.Main; |
| import com.android.ide.common.repository.GradleVersion; |
| import com.android.sdklib.AndroidTargetHash; |
| import com.android.sdklib.SdkVersionInfo; |
| import com.android.tools.lint.gradle.api.ToolingRegistryProvider; |
| import com.android.utils.ILogger; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.CharMatcher; |
| import com.google.wireless.android.sdk.stats.GradleBuildProfileSpan.ExecutionType; |
| import com.google.wireless.android.sdk.stats.GradleBuildProject; |
| import java.io.File; |
| import java.lang.reflect.Method; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.concurrent.ForkJoinPool; |
| import org.gradle.BuildListener; |
| import org.gradle.BuildResult; |
| import org.gradle.api.Action; |
| import org.gradle.api.NamedDomainObjectContainer; |
| import org.gradle.api.Plugin; |
| import org.gradle.api.Project; |
| import org.gradle.api.artifacts.Configuration; |
| import org.gradle.api.component.SoftwareComponentFactory; |
| import org.gradle.api.initialization.Settings; |
| import org.gradle.api.invocation.Gradle; |
| import org.gradle.api.model.ObjectFactory; |
| import org.gradle.api.plugins.JavaBasePlugin; |
| import org.gradle.api.plugins.JavaPlugin; |
| import org.gradle.api.tasks.StopExecutionException; |
| import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry; |
| |
| /** Base class for all Android plugins */ |
| public abstract class BasePlugin implements Plugin<Project>, ToolingRegistryProvider { |
| |
| @VisibleForTesting |
| public static final GradleVersion GRADLE_MIN_VERSION = |
| GradleVersion.parse(SdkConstants.GRADLE_MINIMUM_VERSION); |
| |
| private BaseExtension extension; |
| |
| private VariantManager variantManager; |
| |
| protected TaskManager taskManager; |
| |
| protected Project project; |
| |
| protected ProjectOptions projectOptions; |
| |
| private GlobalScope globalScope; |
| |
| private DataBindingBuilder dataBindingBuilder; |
| |
| private VariantFactory variantFactory; |
| |
| private SourceSetManager sourceSetManager; |
| |
| @NonNull private final ToolingModelBuilderRegistry registry; |
| @NonNull private final SoftwareComponentFactory componentFactory; |
| |
| private LoggerWrapper loggerWrapper; |
| |
| protected ExtraModelInfo extraModelInfo; |
| |
| private String creator; |
| |
| private Recorder threadRecorder; |
| |
| private boolean hasCreatedTasks = false; |
| |
| BasePlugin( |
| @NonNull ToolingModelBuilderRegistry registry, |
| @NonNull SoftwareComponentFactory componentFactory) { |
| ClasspathVerifier.checkClasspathSanity(); |
| this.registry = registry; |
| this.componentFactory = componentFactory; |
| creator = "Android Gradle " + Version.ANDROID_GRADLE_PLUGIN_VERSION; |
| NonFinalPluginExpiry.verifyRetirementAge(); |
| } |
| |
| @NonNull |
| protected abstract BaseExtension createExtension( |
| @NonNull Project project, |
| @NonNull ProjectOptions projectOptions, |
| @NonNull GlobalScope globalScope, |
| @NonNull NamedDomainObjectContainer<BuildType> buildTypeContainer, |
| @NonNull NamedDomainObjectContainer<ProductFlavor> productFlavorContainer, |
| @NonNull NamedDomainObjectContainer<SigningConfig> signingConfigContainer, |
| @NonNull NamedDomainObjectContainer<BaseVariantOutput> buildOutputs, |
| @NonNull SourceSetManager sourceSetManager, |
| @NonNull ExtraModelInfo extraModelInfo); |
| |
| @NonNull |
| protected abstract GradleBuildProject.PluginType getAnalyticsPluginType(); |
| |
| @NonNull |
| protected abstract VariantFactory createVariantFactory(@NonNull GlobalScope globalScope); |
| |
| @NonNull |
| protected abstract TaskManager createTaskManager( |
| @NonNull GlobalScope globalScope, |
| @NonNull Project project, |
| @NonNull ProjectOptions projectOptions, |
| @NonNull DataBindingBuilder dataBindingBuilder, |
| @NonNull BaseExtension extension, |
| @NonNull VariantFactory variantFactory, |
| @NonNull ToolingModelBuilderRegistry toolingRegistry, |
| @NonNull Recorder threadRecorder); |
| |
| protected abstract int getProjectType(); |
| |
| @VisibleForTesting |
| public VariantManager getVariantManager() { |
| return variantManager; |
| } |
| |
| public BaseExtension getExtension() { |
| return extension; |
| } |
| |
| private ILogger getLogger() { |
| if (loggerWrapper == null) { |
| loggerWrapper = new LoggerWrapper(project.getLogger()); |
| } |
| |
| return loggerWrapper; |
| } |
| |
| @Override |
| public final void apply(@NonNull Project project) { |
| CrashReporting.runAction( |
| () -> { |
| basePluginApply(project); |
| pluginSpecificApply(project); |
| }); |
| } |
| |
| private void basePluginApply(@NonNull Project project) { |
| // We run by default in headless mode, so the JVM doesn't steal focus. |
| System.setProperty("java.awt.headless", "true"); |
| |
| this.project = project; |
| this.projectOptions = new ProjectOptions(project); |
| checkGradleVersion(project, getLogger(), projectOptions); |
| DependencyResolutionChecks.registerDependencyCheck(project, projectOptions); |
| |
| project.getPluginManager().apply(AndroidBasePlugin.class); |
| |
| checkPathForErrors(); |
| checkModulesForErrors(); |
| |
| PluginInitializer.initialize(project); |
| RecordingBuildListener buildListener = ProfilerInitializer.init(project, projectOptions); |
| ProfileAgent.INSTANCE.register(project.getName(), buildListener); |
| threadRecorder = ThreadRecorder.get(); |
| |
| Workers.INSTANCE.initFromProject( |
| projectOptions, |
| // possibly, in the future, consider using a pool with a dedicated size |
| // using the gradle parallelism settings. |
| ForkJoinPool.commonPool()); |
| |
| ProcessProfileWriter.getProject(project.getPath()) |
| .setAndroidPluginVersion(Version.ANDROID_GRADLE_PLUGIN_VERSION) |
| .setAndroidPlugin(getAnalyticsPluginType()) |
| .setPluginGeneration(GradleBuildProject.PluginGeneration.FIRST) |
| .setOptions(AnalyticsUtil.toProto(projectOptions)); |
| |
| threadRecorder.record( |
| ExecutionType.BASE_PLUGIN_PROJECT_CONFIGURE, |
| project.getPath(), |
| null, |
| this::configureProject); |
| |
| threadRecorder.record( |
| ExecutionType.BASE_PLUGIN_PROJECT_BASE_EXTENSION_CREATION, |
| project.getPath(), |
| null, |
| this::configureExtension); |
| |
| threadRecorder.record( |
| ExecutionType.BASE_PLUGIN_PROJECT_TASKS_CREATION, |
| project.getPath(), |
| null, |
| this::createTasks); |
| } |
| |
| protected abstract void pluginSpecificApply(@NonNull Project project); |
| |
| private void configureProject() { |
| final Gradle gradle = project.getGradle(); |
| ObjectFactory objectFactory = project.getObjects(); |
| |
| extraModelInfo = new ExtraModelInfo(project.getPath(), projectOptions, project.getLogger()); |
| |
| final SyncIssueHandler syncIssueHandler = extraModelInfo.getSyncIssueHandler(); |
| |
| SdkComponents sdkComponents = |
| SdkComponents.Companion.createSdkComponents( |
| project, |
| projectOptions, |
| // We pass a supplier here because extension will only be set later. |
| this::getExtension, |
| getLogger(), |
| syncIssueHandler); |
| |
| dataBindingBuilder = new DataBindingBuilder(); |
| dataBindingBuilder.setPrintMachineReadableOutput( |
| SyncOptions.getErrorFormatMode(projectOptions) == ErrorFormatMode.MACHINE_PARSABLE); |
| |
| if (projectOptions.hasRemovedOptions()) { |
| syncIssueHandler.reportWarning( |
| Type.GENERIC, projectOptions.getRemovedOptionsErrorMessage()); |
| } |
| |
| if (projectOptions.hasDeprecatedOptions()) { |
| extraModelInfo |
| .getDeprecationReporter() |
| .reportDeprecatedOptions(projectOptions.getDeprecatedOptions()); |
| } |
| |
| if (!projectOptions.getExperimentalOptions().isEmpty()) { |
| projectOptions |
| .getExperimentalOptions() |
| .forEach(extraModelInfo.getDeprecationReporter()::reportExperimentalOption); |
| } |
| |
| // Enforce minimum versions of certain plugins |
| GradlePluginUtils.enforceMinimumVersionsOfPlugins(project, syncIssueHandler); |
| |
| // Apply the Java plugin |
| project.getPlugins().apply(JavaBasePlugin.class); |
| |
| DslScopeImpl dslScope = |
| new DslScopeImpl( |
| syncIssueHandler, extraModelInfo.getDeprecationReporter(), objectFactory); |
| |
| @Nullable |
| FileCache buildCache = BuildCacheUtils.createBuildCacheIfEnabled(project, projectOptions); |
| |
| globalScope = |
| new GlobalScope( |
| project, |
| creator, |
| projectOptions, |
| dslScope, |
| sdkComponents, |
| registry, |
| buildCache, |
| extraModelInfo.getMessageReceiver(), |
| componentFactory); |
| |
| project.getTasks() |
| .named("assemble") |
| .configure( |
| task -> |
| task.setDescription( |
| "Assembles all variants of all applications and secondary packages.")); |
| |
| // 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. |
| gradle.addBuildListener( |
| new BuildListener() { |
| @Override |
| public void buildStarted(@NonNull Gradle gradle) {} |
| |
| @Override |
| public void settingsEvaluated(@NonNull Settings settings) {} |
| |
| @Override |
| public void projectsLoaded(@NonNull Gradle gradle) {} |
| |
| @Override |
| public void projectsEvaluated(@NonNull Gradle gradle) {} |
| |
| @Override |
| public void buildFinished(@NonNull BuildResult buildResult) { |
| // Do not run buildFinished for included project in composite build. |
| if (buildResult.getGradle().getParent() != null) { |
| return; |
| } |
| ModelBuilder.clearCaches(); |
| Workers.INSTANCE.shutdown(); |
| sdkComponents.unload(); |
| SdkLocator.resetCache(); |
| threadRecorder.record( |
| ExecutionType.BASE_PLUGIN_BUILD_FINISHED, |
| project.getPath(), |
| null, |
| () -> { |
| if (!projectOptions.get( |
| BooleanOption.KEEP_SERVICES_BETWEEN_BUILDS)) { |
| WorkerActionServiceRegistry.INSTANCE |
| .shutdownAllRegisteredServices( |
| ForkJoinPool.commonPool()); |
| } |
| Main.clearInternTables(); |
| }); |
| DeprecationReporterImpl.Companion.clean(); |
| } |
| }); |
| |
| createLintClasspathConfiguration(project); |
| } |
| |
| /** Creates a lint class path Configuration for the given project */ |
| public static void createLintClasspathConfiguration(@NonNull Project project) { |
| Configuration config = project.getConfigurations().create(LintBaseTask.LINT_CLASS_PATH); |
| config.setVisible(false); |
| config.setTransitive(true); |
| config.setCanBeConsumed(false); |
| config.setDescription("The lint embedded classpath"); |
| |
| project.getDependencies().add(config.getName(), "com.android.tools.lint:lint-gradle:" + |
| Version.ANDROID_TOOLS_BASE_VERSION); |
| } |
| |
| private void configureExtension() { |
| ObjectFactory objectFactory = project.getObjects(); |
| final NamedDomainObjectContainer<BuildType> buildTypeContainer = |
| project.container( |
| BuildType.class, |
| new BuildTypeFactory( |
| objectFactory, |
| project, |
| extraModelInfo.getSyncIssueHandler(), |
| extraModelInfo.getDeprecationReporter())); |
| final NamedDomainObjectContainer<ProductFlavor> productFlavorContainer = |
| project.container( |
| ProductFlavor.class, |
| new ProductFlavorFactory( |
| objectFactory, |
| project, |
| extraModelInfo.getDeprecationReporter(), |
| project.getLogger())); |
| final NamedDomainObjectContainer<SigningConfig> signingConfigContainer = |
| project.container( |
| SigningConfig.class, |
| new SigningConfigFactory( |
| objectFactory, |
| GradleKeystoreHelper.getDefaultDebugKeystoreLocation())); |
| |
| final NamedDomainObjectContainer<BaseVariantOutput> buildOutputs = |
| project.container(BaseVariantOutput.class); |
| |
| project.getExtensions().add("buildOutputs", buildOutputs); |
| |
| sourceSetManager = |
| new SourceSetManager( |
| project, |
| isPackagePublished(), |
| globalScope.getDslScope(), |
| new DelayedActionsExecutor()); |
| |
| extension = |
| createExtension( |
| project, |
| projectOptions, |
| globalScope, |
| buildTypeContainer, |
| productFlavorContainer, |
| signingConfigContainer, |
| buildOutputs, |
| sourceSetManager, |
| extraModelInfo); |
| |
| globalScope.setExtension(extension); |
| |
| variantFactory = createVariantFactory(globalScope); |
| |
| taskManager = |
| createTaskManager( |
| globalScope, |
| project, |
| projectOptions, |
| dataBindingBuilder, |
| extension, |
| variantFactory, |
| registry, |
| threadRecorder); |
| |
| variantManager = |
| new VariantManager( |
| globalScope, |
| project, |
| projectOptions, |
| extension, |
| variantFactory, |
| taskManager, |
| sourceSetManager, |
| threadRecorder); |
| |
| registerModels(registry, globalScope, variantManager, extension, extraModelInfo); |
| |
| // map the whenObjectAdded callbacks on the containers. |
| signingConfigContainer.whenObjectAdded(variantManager::addSigningConfig); |
| |
| buildTypeContainer.whenObjectAdded( |
| buildType -> { |
| if (!this.getClass().isAssignableFrom(DynamicFeaturePlugin.class)) { |
| SigningConfig signingConfig = |
| signingConfigContainer.findByName(BuilderConstants.DEBUG); |
| buildType.init(signingConfig); |
| } else { |
| // initialize it without the signingConfig for dynamic-features. |
| buildType.init(); |
| } |
| variantManager.addBuildType(buildType); |
| }); |
| |
| productFlavorContainer.whenObjectAdded(variantManager::addProductFlavor); |
| |
| // map whenObjectRemoved on the containers to throw an exception. |
| signingConfigContainer.whenObjectRemoved( |
| new UnsupportedAction("Removing signingConfigs is not supported.")); |
| buildTypeContainer.whenObjectRemoved( |
| new UnsupportedAction("Removing build types is not supported.")); |
| productFlavorContainer.whenObjectRemoved( |
| new UnsupportedAction("Removing product flavors is not supported.")); |
| |
| // create default Objects, signingConfig first as its used by the BuildTypes. |
| variantFactory.createDefaultComponents( |
| buildTypeContainer, productFlavorContainer, signingConfigContainer); |
| } |
| |
| protected void registerModels( |
| @NonNull ToolingModelBuilderRegistry registry, |
| @NonNull GlobalScope globalScope, |
| @NonNull VariantManager variantManager, |
| @NonNull BaseExtension extension, |
| @NonNull ExtraModelInfo extraModelInfo) { |
| // Register a builder for the custom tooling model |
| registerModelBuilder(registry, globalScope, variantManager, extension, extraModelInfo); |
| |
| // Register a builder for the native tooling model |
| NativeModelBuilder nativeModelBuilder = new NativeModelBuilder(globalScope, variantManager); |
| registry.register(nativeModelBuilder); |
| } |
| |
| /** Registers a builder for the custom tooling model. */ |
| protected void registerModelBuilder( |
| @NonNull ToolingModelBuilderRegistry registry, |
| @NonNull GlobalScope globalScope, |
| @NonNull VariantManager variantManager, |
| @NonNull BaseExtension extension, |
| @NonNull ExtraModelInfo extraModelInfo) { |
| registry.register( |
| new ModelBuilder<>( |
| globalScope, |
| variantManager, |
| taskManager, |
| extension, |
| extraModelInfo, |
| getProjectType())); |
| } |
| |
| private static class UnsupportedAction implements Action<Object> { |
| |
| private final String message; |
| |
| UnsupportedAction(String message) { |
| this.message = message; |
| } |
| |
| @Override |
| public void execute(@NonNull Object o) { |
| throw new UnsupportedOperationException(message); |
| } |
| } |
| |
| private void createTasks() { |
| threadRecorder.record( |
| ExecutionType.TASK_MANAGER_CREATE_TASKS, |
| project.getPath(), |
| null, |
| () -> taskManager.createTasksBeforeEvaluate()); |
| |
| project.afterEvaluate( |
| CrashReporting.afterEvaluate( |
| p -> { |
| sourceSetManager.runBuildableArtifactsActions(); |
| |
| threadRecorder.record( |
| ExecutionType.BASE_PLUGIN_CREATE_ANDROID_TASKS, |
| project.getPath(), |
| null, |
| this::createAndroidTasks); |
| })); |
| } |
| |
| static void checkGradleVersion( |
| @NonNull Project project, |
| @NonNull ILogger logger, |
| @NonNull ProjectOptions projectOptions) { |
| String currentVersion = project.getGradle().getGradleVersion(); |
| if (GRADLE_MIN_VERSION.compareTo(currentVersion) > 0) { |
| File file = new File("gradle" + separator + "wrapper" + separator + |
| "gradle-wrapper.properties"); |
| String errorMessage = |
| String.format( |
| "Minimum supported Gradle version is %s. Current version is %s. " |
| + "If using the gradle wrapper, try editing the distributionUrl in %s " |
| + "to gradle-%s-all.zip", |
| GRADLE_MIN_VERSION, |
| currentVersion, |
| file.getAbsolutePath(), |
| GRADLE_MIN_VERSION); |
| if (projectOptions.get(BooleanOption.VERSION_CHECK_OVERRIDE_PROPERTY)) { |
| logger.warning(errorMessage); |
| logger.warning( |
| "As %s is set, continuing anyway.", |
| BooleanOption.VERSION_CHECK_OVERRIDE_PROPERTY.getPropertyName()); |
| } else { |
| throw new RuntimeException(errorMessage); |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| final void createAndroidTasks() { |
| |
| if (extension.getCompileSdkVersion() == null) { |
| if (SyncOptions.getModelQueryMode(projectOptions) |
| .equals(SyncOptions.EvaluationMode.IDE)) { |
| String newCompileSdkVersion = findHighestSdkInstalled(); |
| if (newCompileSdkVersion == null) { |
| newCompileSdkVersion = "android-" + SdkVersionInfo.HIGHEST_KNOWN_STABLE_API; |
| } |
| extension.setCompileSdkVersion(newCompileSdkVersion); |
| } |
| |
| extraModelInfo |
| .getSyncIssueHandler() |
| .reportError( |
| Type.COMPILE_SDK_VERSION_NOT_SET, |
| "compileSdkVersion is not specified. Please add it to build.gradle"); |
| } |
| |
| // Make sure unit tests set the required fields. |
| checkState(extension.getCompileSdkVersion() != null, "compileSdkVersion is not specified."); |
| extension |
| .getCompileOptions() |
| .setDefaultJavaVersion( |
| AbstractCompilesUtil.getDefaultJavaVersion( |
| extension.getCompileSdkVersion())); |
| |
| // get current plugins and look for the default Java plugin. |
| if (project.getPlugins().hasPlugin(JavaPlugin.class)) { |
| throw new BadPluginException( |
| "The 'java' plugin has been applied, but it is not compatible with the Android plugins."); |
| } |
| |
| if (project.getPlugins().hasPlugin("me.tatarka.retrolambda")) { |
| String warningMsg = |
| "One of the plugins you are using supports Java 8 " |
| + "language features. To try the support built into" |
| + " the Android plugin, remove the following from " |
| + "your build.gradle:\n" |
| + " apply plugin: 'me.tatarka.retrolambda'\n" |
| + "To learn more, go to https://d.android.com/r/" |
| + "tools/java-8-support-message.html\n"; |
| extraModelInfo |
| .getSyncIssueHandler() |
| .reportWarning(EvalIssueReporter.Type.GENERIC, warningMsg); |
| } |
| |
| // don't do anything if the project was not initialized. |
| // Unless TEST_SDK_DIR is set in which case this is unit tests and we don't return. |
| // This is because project don't get evaluated in the unit test setup. |
| // See AppPluginDslTest |
| if ((!project.getState().getExecuted() || project.getState().getFailure() != null) |
| && SdkLocator.getSdkTestDirectory() == null) { |
| return; |
| } |
| |
| if (hasCreatedTasks) { |
| return; |
| } |
| hasCreatedTasks = true; |
| |
| extension.disableWrite(); |
| |
| taskManager.configureCustomLintChecks(); |
| |
| ProcessProfileWriter.getProject(project.getPath()) |
| .setCompileSdk(extension.getCompileSdkVersion()) |
| .setBuildToolsVersion(extension.getBuildToolsRevision().toString()) |
| .setSplits(AnalyticsUtil.toProto(extension.getSplits())); |
| |
| String kotlinPluginVersion = getKotlinPluginVersion(); |
| if (kotlinPluginVersion != null) { |
| ProcessProfileWriter.getProject(project.getPath()) |
| .setKotlinPluginVersion(kotlinPluginVersion); |
| } |
| AnalyticsUtil.recordFirebasePerformancePluginVersion(project); |
| |
| List<VariantScope> variantScopes = variantManager.createAndroidTasks(); |
| |
| ApiObjectFactory apiObjectFactory = |
| new ApiObjectFactory( |
| extension, |
| variantFactory, |
| project.getObjects()); |
| for (VariantScope variantScope : variantScopes) { |
| BaseVariantData variantData = variantScope.getVariantData(); |
| apiObjectFactory.create(variantData); |
| } |
| |
| // Make sure no SourceSets were added through the DSL without being properly configured |
| // Only do it if we are not restricting to a single variant (with Instant |
| // Run or we can find extra source set |
| if (projectOptions.get(StringOption.IDE_RESTRICT_VARIANT_NAME) == null) { |
| sourceSetManager.checkForUnconfiguredSourceSets(); |
| } |
| |
| // must run this after scopes are created so that we can configure kotlin |
| // kapt tasks |
| taskManager.addBindingDependenciesIfNecessary( |
| extension.getViewBinding(), |
| extension.getDataBinding(), |
| variantManager.getVariantScopes()); |
| |
| |
| // create the global lint task that depends on all the variants |
| taskManager.configureGlobalLintTask(variantManager.getVariantScopes()); |
| |
| int flavorDimensionCount = 0; |
| if (extension.getFlavorDimensionList() != null) { |
| flavorDimensionCount = extension.getFlavorDimensionList().size(); |
| } |
| |
| taskManager.createAnchorAssembleTasks( |
| variantScopes, |
| extension.getProductFlavors().size(), |
| flavorDimensionCount, |
| variantFactory.getVariantConfigurationTypes().size()); |
| |
| // now publish all variant artifacts. |
| for (VariantScope variantScope : variantManager.getVariantScopes()) { |
| variantManager.publishBuildArtifacts(variantScope); |
| } |
| |
| checkSplitConfiguration(); |
| variantManager.setHasCreatedTasks(true); |
| } |
| |
| private String findHighestSdkInstalled() { |
| String highestSdk = null; |
| File folder = new File(globalScope.getSdkComponents().getSdkFolder(), "platforms"); |
| File[] listOfFiles = folder.listFiles(); |
| |
| if (listOfFiles != null) { |
| Arrays.sort(listOfFiles, Comparator.comparing(File::getName).reversed()); |
| for (File file : listOfFiles) { |
| if (AndroidTargetHash.getPlatformVersion(file.getName()) != null) { |
| highestSdk = file.getName(); |
| break; |
| } |
| } |
| } |
| |
| return highestSdk; |
| } |
| |
| private void checkSplitConfiguration() { |
| String configApkUrl = "https://d.android.com/topic/instant-apps/guides/config-splits.html"; |
| |
| boolean isFeatureModule = project.getPlugins().hasPlugin(FeaturePlugin.class); |
| boolean generatePureSplits = extension.getGeneratePureSplits(); |
| Splits splits = extension.getSplits(); |
| boolean splitsEnabled = |
| splits.getDensity().isEnable() |
| || splits.getAbi().isEnable() |
| || splits.getLanguage().isEnable(); |
| |
| // The Play Store doesn't allow Pure splits |
| if (!isFeatureModule && generatePureSplits) { |
| extraModelInfo |
| .getSyncIssueHandler() |
| .reportWarning( |
| Type.GENERIC, |
| "Configuration APKs are supported by the Google Play Store only when publishing Android Instant Apps. To instead generate stand-alone APKs for different device configurations, set generatePureSplits=false. For more information, go to " |
| + configApkUrl); |
| } |
| |
| if (!isFeatureModule && !generatePureSplits && splits.getLanguage().isEnable()) { |
| extraModelInfo |
| .getSyncIssueHandler() |
| .reportWarning( |
| Type.GENERIC, |
| "Per-language APKs are supported only when building Android Instant Apps. For more information, go to " |
| + configApkUrl); |
| } |
| |
| if (isFeatureModule && !generatePureSplits && splitsEnabled) { |
| extraModelInfo |
| .getSyncIssueHandler() |
| .reportWarning( |
| Type.GENERIC, |
| "Configuration APKs targeting different device configurations are " |
| + "automatically built when splits are enabled for a feature module.\n" |
| + "To suppress this warning, remove \"generatePureSplits false\" " |
| + "from your build.gradle file.\n" |
| + "To learn more, see " |
| + configApkUrl); |
| } |
| } |
| |
| /** |
| * Check the sub-projects structure : |
| * So far, checks that 2 modules do not have the same identification (group+name). |
| */ |
| private void checkModulesForErrors() { |
| Project rootProject = project.getRootProject(); |
| Map<String, Project> subProjectsById = new HashMap<>(); |
| for (Project subProject : rootProject.getAllprojects()) { |
| String id = subProject.getGroup().toString() + ":" + subProject.getName(); |
| if (subProjectsById.containsKey(id)) { |
| String message = String.format( |
| "Your project contains 2 or more modules with the same " + |
| "identification %1$s\n" + |
| "at \"%2$s\" and \"%3$s\".\n" + |
| "You must use different identification (either name or group) for " + |
| "each modules.", |
| id, |
| subProjectsById.get(id).getPath(), |
| subProject.getPath() ); |
| throw new StopExecutionException(message); |
| } else { |
| subProjectsById.put(id, subProject); |
| } |
| } |
| } |
| |
| private void checkPathForErrors() { |
| // See if we're on Windows: |
| if (!System.getProperty("os.name").toLowerCase(Locale.US).contains("windows")) { |
| return; |
| } |
| |
| // See if the user disabled the check: |
| if (projectOptions.get(BooleanOption.OVERRIDE_PATH_CHECK_PROPERTY)) { |
| return; |
| } |
| |
| // See if the path contains non-ASCII characters. |
| if (CharMatcher.ascii().matchesAllOf(project.getRootDir().getAbsolutePath())) { |
| return; |
| } |
| |
| String message = |
| "Your project path contains non-ASCII characters. This will most likely " |
| + "cause the build to fail on Windows. Please move your project to a different " |
| + "directory. See http://b.android.com/95744 for details. " |
| + "This warning can be disabled by adding the line '" |
| + BooleanOption.OVERRIDE_PATH_CHECK_PROPERTY.getPropertyName() |
| + "=true' to gradle.properties file in the project directory."; |
| |
| throw new StopExecutionException(message); |
| } |
| |
| @NonNull |
| @Override |
| public ToolingModelBuilderRegistry getModelBuilderRegistry() { |
| return registry; |
| } |
| |
| /** |
| * returns the kotlin plugin version, or null if plugin is not applied to this project, or |
| * "unknown" if plugin is applied but version can't be determined. |
| */ |
| @Nullable |
| private String getKotlinPluginVersion() { |
| Plugin plugin = project.getPlugins().findPlugin("kotlin-android"); |
| if (plugin == null) { |
| return null; |
| } |
| try { |
| // No null checks below because we're catching all exceptions. |
| @SuppressWarnings("JavaReflectionMemberAccess") |
| Method method = plugin.getClass().getMethod("getKotlinPluginVersion"); |
| method.setAccessible(true); |
| return method.invoke(plugin).toString(); |
| } catch (Throwable e) { |
| // Defensively catch all exceptions because we don't want it to crash |
| // if kotlin plugin code changes unexpectedly. |
| return "unknown"; |
| } |
| } |
| |
| /** |
| * If overridden in a subclass to return "true," the package Configuration will be named |
| * "publish" instead of "apk" |
| */ |
| protected boolean isPackagePublished() { |
| return false; |
| } |
| } |