From 5562dc2069353354641766ea83e2ef1667221295 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Thu, 5 Oct 2023 19:50:44 +0530 Subject: [PATCH] feat(tooling-api): fetch project sync issues from the Android Gradle Plugin --- .../editor/ProjectHandlerActivity.kt | 12 +- .../com/itsaky/androidide/lsp/api/LSPTest.kt | 3 +- .../build/gradle/options/BooleanOption.kt | 792 ++++++++++++++++++ .../build/gradle/options/DeprecationTarget.kt | 100 +++ .../build/gradle/options/IntegerOption.kt | 147 ++++ .../android/build/gradle/options/Option.kt | 93 ++ .../build/gradle/options/OptionParsers.kt | 53 ++ .../gradle/options/OptionalBooleanOption.kt | 86 ++ .../build/gradle/options/ReplacedOption.kt | 171 ++++ .../com/android/build/gradle/options/Stage.kt | 167 ++++ .../build/gradle/options/StringOption.kt | 181 ++++ .../gradle/options/TestRunnerArguments.kt | 62 ++ .../android/build/gradle/options/Version.kt | 66 ++ .../builder/model/DefaultProjectSyncIssues.kt | 2 +- .../builder/model/DefaultSyncIssue.kt | 6 +- .../androidide/builder/model/IDESyncIssue.kt | 57 ++ .../androidide/projects/CachingProject.kt | 20 +- .../androidide/projects/IProjectManager.kt | 8 +- .../androidide/projects/ProjectManagerImpl.kt | 16 +- .../itsaky/androidide/projects/api/Project.kt | 11 +- .../projects/util/ProjectTransformer.kt | 7 +- .../projects/api/ModuleProjectTest.kt | 3 +- .../tooling/impl/ToolingApiServerImpl.kt | 3 +- .../tooling/impl/internal/ProjectImpl.kt | 17 +- .../tooling/impl/sync/AbstractModelBuilder.kt | 25 +- .../impl/sync/AndroidProjectModelBuilder.kt | 10 +- .../tooling/impl/sync/ISyncIssueReporter.kt | 48 ++ .../impl/sync/ModuleProjectModelBuilder.kt | 9 +- .../tooling/impl/sync/RootModelBuilder.kt | 40 +- .../androidide/tooling/impl/sync/types.kt | 24 +- .../itsaky/androidide/tooling/api/IProject.kt | 2 +- .../api/util/AndroidModulePropertyCopier.kt | 2 +- .../androidide/inflater/XmlInflaterTest.kt | 3 +- 33 files changed, 2188 insertions(+), 58 deletions(-) create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/BooleanOption.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/DeprecationTarget.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/IntegerOption.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Option.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/OptionParsers.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/OptionalBooleanOption.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/ReplacedOption.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Stage.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/StringOption.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/TestRunnerArguments.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Version.kt create mode 100644 subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/IDESyncIssue.kt create mode 100644 subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ISyncIssueReporter.kt diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt index 0868fff90..cd3361e4c 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt @@ -61,6 +61,8 @@ import com.itsaky.androidide.utils.resolveAttr import com.itsaky.androidide.utils.showOnUiThread import com.itsaky.androidide.utils.withIcon import com.itsaky.androidide.viewmodel.BuildVariantsViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import java.io.File import java.util.concurrent.CompletableFuture import java.util.regex.Pattern @@ -457,11 +459,13 @@ abstract class ProjectHandlerActivity : BaseEditorActivity() { } manager.cachedInitResult = result - manager.setupProject() - manager.notifyProjectUpdate() - updateBuildVariants(manager.androidBuildVariants) + activityBackroundScope.launch(Dispatchers.IO) { + manager.setupProject() + manager.notifyProjectUpdate() + updateBuildVariants(manager.androidBuildVariants) - ThreadUtils.runOnUiThread { postProjectInit(true) } + com.itsaky.androidide.tasks.runOnUiThread { postProjectInit(true) } + } } protected open fun preProjectInit() { diff --git a/lsp/testing/src/main/java/com/itsaky/androidide/lsp/api/LSPTest.kt b/lsp/testing/src/main/java/com/itsaky/androidide/lsp/api/LSPTest.kt index 6c65613e8..c0eb8370e 100644 --- a/lsp/testing/src/main/java/com/itsaky/androidide/lsp/api/LSPTest.kt +++ b/lsp/testing/src/main/java/com/itsaky/androidide/lsp/api/LSPTest.kt @@ -43,6 +43,7 @@ import com.itsaky.androidide.utils.ILogger import io.github.rosemoe.sora.text.Content import io.mockk.every import io.mockk.mockkStatic +import kotlinx.coroutines.runBlocking import org.greenrobot.eventbus.EventBus import org.junit.Before import org.junit.runner.RunWith @@ -102,7 +103,7 @@ abstract class LSPTest { val projectManager = ProjectManagerImpl.getInstance() projectManager.register() - projectManager.setupProject(project) + runBlocking { projectManager.setupProject(project) } // We need to manually setup the language server with the project here // ProjectManager.notifyProjectUpdate() diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/BooleanOption.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/BooleanOption.kt new file mode 100644 index 000000000..dfb4c8861 --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/BooleanOption.kt @@ -0,0 +1,792 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (C) 2017 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.options + +import com.android.build.gradle.options.DeprecationTarget.BUILD_CONFIG_GLOBAL_PROPERTY +import com.android.build.gradle.options.DeprecationTarget.VERSION_9_0 +import com.android.build.gradle.options.Version.VERSION_3_5 +import com.android.build.gradle.options.Version.VERSION_3_6 +import com.android.build.gradle.options.Version.VERSION_4_0 +import com.android.build.gradle.options.Version.VERSION_4_1 +import com.android.build.gradle.options.Version.VERSION_4_2 +import com.android.build.gradle.options.Version.VERSION_7_0 +import com.android.build.gradle.options.Version.VERSION_7_2 +import com.android.build.gradle.options.Version.VERSION_7_3 +import com.android.build.gradle.options.Version.VERSION_BEFORE_4_0 +import com.android.builder.model.AndroidProject +import com.android.builder.model.AndroidProject.PROPERTY_BUILD_MODEL_ONLY +import com.android.builder.model.PROPERTY_BUILD_MODEL_V2_ONLY +import com.android.builder.model.PROPERTY_BUILD_WITH_STABLE_IDS +import com.android.builder.model.PROPERTY_DEPLOY_AS_INSTANT_APP +import com.android.builder.model.PROPERTY_EXTRACT_INSTANT_APK +import com.android.builder.model.PROPERTY_INVOKED_FROM_IDE +import com.android.builder.model.PROPERTY_REFRESH_EXTERNAL_NATIVE_MODEL + +enum class BooleanOption( + override val propertyName: String, + override val defaultValue: Boolean, + val stage: Stage +) : Option { + + /* ---------- + * STABLE API + */ + + // IDE properties + IDE_INVOKED_FROM_IDE(PROPERTY_INVOKED_FROM_IDE, false, ApiStage.Stable), + IDE_BUILD_MODEL_ONLY_V2(PROPERTY_BUILD_MODEL_V2_ONLY, false, ApiStage.Stable), + @Deprecated("This is for model v1 only. Please also use IDE_BUILD_MODEL_ONLY_V2") + IDE_BUILD_MODEL_ONLY(PROPERTY_BUILD_MODEL_ONLY, false, ApiStage.Stable), + @Deprecated("Use IDE_BUILD_MODEL_ONLY_V2") + IDE_BUILD_MODEL_ONLY_ADVANCED(AndroidProject.PROPERTY_BUILD_MODEL_ONLY_ADVANCED, false, ApiStage.Stable), + @Deprecated("Use IDE_BUILD_MODEL_ONLY_V2") + IDE_BUILD_MODEL_FEATURE_FULL_DEPENDENCIES(AndroidProject.PROPERTY_BUILD_MODEL_FEATURE_FULL_DEPENDENCIES, false, ApiStage.Stable), + IDE_REFRESH_EXTERNAL_NATIVE_MODEL(PROPERTY_REFRESH_EXTERNAL_NATIVE_MODEL, false, ApiStage.Stable), + //IDE_GENERATE_SOURCES_ONLY(AndroidProject.PROPERTY_GENERATE_SOURCES_ONLY, false, ApiStage.Stable), + + // tell bundletool to only extract instant APKs. + IDE_EXTRACT_INSTANT(PROPERTY_EXTRACT_INSTANT_APK, false, ApiStage.Stable), + + // Flag used to indicate a "deploy as instant" run configuration. + IDE_DEPLOY_AS_INSTANT_APP(PROPERTY_DEPLOY_AS_INSTANT_APP, false, ApiStage.Stable), + + ENABLE_STUDIO_VERSION_CHECK("android.injected.studio.version.check", true, ApiStage.Stable), + ENABLE_STABLE_IDS(PROPERTY_BUILD_WITH_STABLE_IDS, false, ApiStage.Stable), + + // Features' default values + BUILD_FEATURE_DATABINDING("android.defaults.buildfeatures.databinding", false, ApiStage.Stable), + BUILD_FEATURE_RESVALUES("android.defaults.buildfeatures.resvalues", true, ApiStage.Stable), + BUILD_FEATURE_SHADERS("android.defaults.buildfeatures.shaders", true, ApiStage.Stable), + BUILD_FEATURE_VIEWBINDING("android.defaults.buildfeatures.viewbinding", false, ApiStage.Stable), + BUILD_FEATURE_ANDROID_RESOURCES("android.library.defaults.buildfeatures.androidresources", true, ApiStage.Stable), + + // DSLs default values + ENABLE_DATABINDING_KTX("android.defaults.databinding.addKtx", true, ApiStage.Stable), + + // AndroidX & Jetifier + USE_ANDROID_X("android.useAndroidX", false, ApiStage.Stable), + ENABLE_JETIFIER("android.enableJetifier", false, ApiStage.Stable), + + DEBUG_OBSOLETE_API("android.debug.obsoleteApi", false, ApiStage.Stable), + + // Disabled by default due to low usage. + GENERATE_MANIFEST_CLASS("android.generateManifestClass", false, ApiStage.Stable), + + USE_NON_FINAL_RES_IDS("android.nonFinalResIds", true, ApiStage.Stable), + NON_TRANSITIVE_R_CLASS("android.nonTransitiveRClass", true, ApiStage.Stable), + + /** + * Setting this field to false indicates that in the current + * project, all the APKs installed during test will be uninstalled + * after test finishes. Setting it to true means that the AGP + * will leave the test APKs untouched after test. + * + * Default is false + */ + ANDROID_TEST_LEAVE_APKS_INSTALLED_AFTER_RUN("android.injected.androidTest.leaveApksInstalledAfterRun", false, ApiStage.Stable), + + /** + * When this option is enabled, dexing transforms will use the full classpath (if desugaring + * requires a classpath). This classpath consists of all external artifacts + * ([com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.EXTERNAL]) + * in addition to the input artifact's dependencies provided by Gradle through + * [org.gradle.api.artifacts.transform.InputArtifactDependencies]. + * + * This option is useful when some dependencies are missing from the input artifact's + * dependencies (see bug 230454566), so the full classpath is needed. + * + * If dexing transforms do not require a classpath, this option is not used. + */ + USE_FULL_CLASSPATH_FOR_DEXING_TRANSFORM( + "android.useFullClasspathForDexingTransform", + false, + ApiStage.Stable + ), + + /* ------------------ + * SUPPORTED FEATURES + */ + + // Used by Studio as workaround for b/71054106, b/75955471 + ENABLE_SDK_DOWNLOAD("android.builder.sdkDownload", true, FeatureStage.Supported), + + ENFORCE_UNIQUE_PACKAGE_NAMES("android.uniquePackageNames", false, FeatureStage.Supported), + + // Flag added to work around b/130596259. + FORCE_JACOCO_OUT_OF_PROCESS("android.forceJacocoOutOfProcess", false, FeatureStage.Supported), + + PRECOMPILE_DEPENDENCIES_RESOURCES("android.precompileDependenciesResources", true, FeatureStage.Supported), + + INCLUDE_DEPENDENCY_INFO_IN_APKS("android.includeDependencyInfoInApks", true, FeatureStage.Supported), + + // FIXME switch to false once we know we don't use these getters internally. + ENABLE_LEGACY_API("android.compatibility.enableLegacyApi", true, FeatureStage.Supported), + FULL_R8("android.enableR8.fullMode", true, FeatureStage.Supported), + + /* --------------------- + * EXPERIMENTAL FEATURES + */ + + BUILD_FEATURE_MLMODELBINDING("android.defaults.buildfeatures.mlmodelbinding", false, ApiStage.Experimental), + ENABLE_DEFAULT_DEBUG_SIGNING_CONFIG("android.experimental.useDefaultDebugSigningConfigForProfileableBuildtypes", false, ApiStage.Experimental), + ENABLE_PROFILE_JSON("android.enableProfileJson", false, FeatureStage.Experimental), + DISALLOW_DEPENDENCY_RESOLUTION_AT_CONFIGURATION("android.dependencyResolutionAtConfigurationTime.disallow", false, FeatureStage.Experimental), + VERSION_CHECK_OVERRIDE_PROPERTY("android.overrideVersionCheck", false, FeatureStage.Experimental), + OVERRIDE_PATH_CHECK_PROPERTY("android.overridePathCheck", false, FeatureStage.Experimental), + DISABLE_RESOURCE_VALIDATION("android.disableResourceValidation", false, FeatureStage.Experimental), + CONSUME_DEPENDENCIES_AS_SHARED_LIBRARIES("android.consumeDependenciesAsSharedLibraries", false, FeatureStage.Experimental), + DISABLE_EARLY_MANIFEST_PARSING("android.disableEarlyManifestParsing", false, FeatureStage.Experimental), + ENABLE_RESOURCE_NAMESPACING_DEFAULT("android.enableResourceNamespacingDefault", false, FeatureStage.Experimental), + CONDITIONAL_KEEP_RULES("android.useConditionalKeepRules", false, FeatureStage.Experimental), + KEEP_SERVICES_BETWEEN_BUILDS("android.keepWorkerActionServicesBetweenBuilds", false, FeatureStage.Experimental), + ENABLE_PARTIAL_R_INCREMENTAL_BUILDS("android.enablePartialRIncrementalBuilds", false, FeatureStage.Experimental), + ENABLE_NEW_RESOURCE_SHRINKER_PRECISE("android.experimental.enableNewResourceShrinker.preciseShrinking", false, FeatureStage.Experimental), + ENABLE_LOCAL_TESTING("android.bundletool.enableLocalTesting", false, FeatureStage.Experimental), + DISABLE_MINSDKLIBRARY_CHECK("android.unsafe.disable.minSdkLibraryCheck", false, FeatureStage.Experimental), + ENABLE_INSTRUMENTATION_TEST_DESUGARING("android.experimental.library.desugarAndroidTest", false, FeatureStage.Experimental), + ENABLE_EMULATOR_CONTROL("android.experimental.androidTest.enableEmulatorControl", false, FeatureStage.Experimental), + ENABLE_SCREENSHOT_TEST("android.experimental.enableScreenshotTest", false, FeatureStage.Experimental), + /** + * When enabled, incompatible APKs installed on a testing device will be uninstalled automatically + * during an instrumentation test run (e.g. When INSTALL_FAILED_UPDATE_INCOMPATIBLE error happens + * after attempting to install APKs for testing). + */ + UNINSTALL_INCOMPATIBLE_APKS("android.experimental.testOptions.uninstallIncompatibleApks", false, FeatureStage.Experimental), + + /** + * When enabled, "-show-kernel" and "-verbose" flags are used when running an Android emulator + * for Gradle Managed devices. + */ + GRADLE_MANAGED_DEVICE_EMULATOR_SHOW_KERNEL_LOGGING("android.experimental.testOptions.managedDevices.emulator.showKernelLogging", false, FeatureStage.Experimental), + + /** + * Gradle Managed devices officially supports Android devices on API level 27 and higher because + * using the API level 26 and lower devices increase instability. When a user tries to use those + * old API devices, GMD task throws an exception and task fails by default. + * + * When this flag is enabled, it allows a user to use any old API level devices regardless of + * its instability. + */ + GRADLE_MANAGED_DEVICE_ALLOW_OLD_API_LEVEL_DEVICES("android.experimental.testOptions.managedDevices.allowOldApiLevelDevices", false, FeatureStage.Experimental), + + /** + * When enabled, Gradle Managed Device allows a custom managed device type that can be provided + * by a plugin by implementing ManagedDeviceTestRunner APIs. + */ + GRADLE_MANAGED_DEVICE_CUSTOM_DEVICE("android.experimental.testOptions.managedDevices.customDevice", false, FeatureStage.Experimental), + + /** When set R classes are treated as compilation classpath in libraries, rather than runtime classpath, with values set to 0. */ + ENABLE_ADDITIONAL_ANDROID_TEST_OUTPUT("android.enableAdditionalTestOutput", true, FeatureStage.Experimental), + + ENABLE_APP_COMPILE_TIME_R_CLASS("android.enableAppCompileTimeRClass", false, FeatureStage.Experimental), + ENABLE_EXTRACT_ANNOTATIONS("android.enableExtractAnnotations", true, FeatureStage.Experimental), + + // Marked as stable to avoid reporting deprecation twice. + CONVERT_NON_NAMESPACED_DEPENDENCIES("android.convertNonNamespacedDependencies", true, FeatureStage.Experimental), + + /** Set to true to build native .so libraries only for the device it will be run on. */ + BUILD_ONLY_TARGET_ABI("android.buildOnlyTargetAbi", true, FeatureStage.Experimental), + + ENABLE_PARALLEL_NATIVE_JSON_GEN("android.enableParallelJsonGen", false, FeatureStage.Experimental), + ENABLE_SIDE_BY_SIDE_CMAKE("android.enableSideBySideCmake", true, FeatureStage.Experimental), + ENABLE_NATIVE_COMPILER_SETTINGS_CACHE("android.enableNativeCompilerSettingsCache", false, FeatureStage.Experimental), + ENABLE_CMAKE_BUILD_COHABITATION("android.enableCmakeBuildCohabitation", false, FeatureStage.Experimental), + ENABLE_PROGUARD_RULES_EXTRACTION("android.proguard.enableRulesExtraction", true, FeatureStage.Experimental), + USE_DEPENDENCY_CONSTRAINTS("android.dependency.useConstraints", true, FeatureStage.Experimental), + EXCLUDE_LIBRARY_COMPONENTS_FROM_CONSTRAINTS("android.experimental.dependency.excludeLibraryComponentsFromConstraints", false, FeatureStage.Experimental), + ENABLE_DUPLICATE_CLASSES_CHECK("android.enableDuplicateClassesCheck", true, FeatureStage.Experimental), + MINIMAL_KEEP_RULES("android.useMinimalKeepRules", true, FeatureStage.Experimental), + EXCLUDE_RES_SOURCES_FOR_RELEASE_BUNDLES("android.bundle.excludeResSourcesForRelease", true, FeatureStage.Experimental), + ENABLE_BUILD_CONFIG_AS_BYTECODE("android.enableBuildConfigAsBytecode", false, FeatureStage.Experimental), + /** Whether lint should be run in process; the default is true. */ + RUN_LINT_IN_PROCESS("android.experimental.runLintInProcess", true, FeatureStage.Experimental), + + ENABLE_TEST_FIXTURES("android.experimental.enableTestFixtures", false, FeatureStage.Experimental), + + USE_NEW_DSL_INTERFACES("android.experimental.newDslInterfaces", false, FeatureStage.Experimental), + + /** Whether to force the APK to be deterministic. */ + FORCE_DETERMINISTIC_APK("android.experimental.forceDeterministicApk", false, FeatureStage.Experimental), + + MISSING_LINT_BASELINE_IS_EMPTY_BASELINE( + "android.experimental.lint.missingBaselineIsEmptyBaseline", + false, + FeatureStage.Experimental, + ), + + LEGACY_TRANSFORM_TASK_FORCE_NON_INCREMENTAL( + "android.experimental.legacyTransform.forceNonIncremental", + false, + FeatureStage.Experimental + ), + + PRIVACY_SANDBOX_SDK_SUPPORT("android.experimental.privacysandboxsdk.enable", false, FeatureStage.Experimental), + PRIVACY_SANDBOX_SDK_REQUIRE_SERVICES( + "android.experimental.privacysandboxsdk.requireServices", true, FeatureStage.Experimental), + + PRINT_LINT_STACK_TRACE("android.lint.printStackTrace", false, FeatureStage.Experimental), + + VERIFY_AAR_CLASSES("android.experimental.verifyLibraryClasses", false, FeatureStage.Experimental), + DISABLE_COMPILE_SDK_CHECKS("android.experimental.disableCompileSdkChecks", false, FeatureStage.Experimental), + ADDITIONAL_ARTIFACTS_IN_MODEL("android.experimental.additionalArtifactsInModel", false, FeatureStage.Experimental), + + // Whether to suppress warnings about android:extractNativeLibs set to true in dependencies + SUPPRESS_EXTRACT_NATIVE_LIBS_WARNINGS( + "android.experimental.suppressExtractNativeLibsWarnings", + false, + FeatureStage.Experimental + ), + + FUSED_LIBRARY_SUPPORT("android.experimental.fusedLibrarySupport", false, FeatureStage.Experimental), + SUPPORT_PAST_STUDIO_VERSIONS("android.experimental.support.past.studio.versions", false, FeatureStage.Experimental), + + /** + * Enables task to add version control info to APKs/Bundle + */ + ENABLE_VCS_INFO("android.enableVcsInfo", false, FeatureStage.Experimental), + + /** + * Whether to omit line numbers when writing lint baselines + */ + LINT_BASELINE_OMIT_LINE_NUMBERS( + "android.lint.baselineOmitLineNumbers", + false, + FeatureStage.Experimental + ), + + /** + * Whether to use K2 UAST when running lint + */ + LINT_USE_K2_UAST( + "android.lint.useK2Uast", + false, + FeatureStage.Experimental + ), + + /* ------------------------ + * SOFTLY-ENFORCED FEATURES + */ + ENABLE_RESOURCE_OPTIMIZATIONS( + "android.enableResourceOptimizations", + true, + FeatureStage.SoftlyEnforced(VERSION_9_0) + ), + + ANDROID_TEST_USES_UNIFIED_TEST_PLATFORM( + "android.experimental.androidTest.useUnifiedTestPlatform", + true, + FeatureStage.SoftlyEnforced(VERSION_9_0) + ), + + /** + * Whether to do lint analysis per component (instead of analysing the main variant and the test + * components in the same lint invocation). + */ + LINT_ANALYSIS_PER_COMPONENT( + "android.experimental.lint.analysisPerComponent", + true, + FeatureStage.SoftlyEnforced(VERSION_9_0) + ), + + /* ------------------- + * DEPRECATED FEATURES + */ + + BUILD_FEATURE_AIDL( + "android.defaults.buildfeatures.aidl", + false, + ApiStage.Deprecated(VERSION_9_0) + ), + + BUILD_FEATURE_RENDERSCRIPT( + "android.defaults.buildfeatures.renderscript", + false, + ApiStage.Deprecated(VERSION_9_0) + ), + + BUILD_FEATURE_BUILDCONFIG( + "android.defaults.buildfeatures.buildconfig", + false, + ApiStage.Deprecated(BUILD_CONFIG_GLOBAL_PROPERTY)) + , + + /* ----------------- + * ENFORCED FEATURES + */ + @Suppress("unused") + PREFER_CMAKE_FILE_API( + "android.preferCmakeFileApi", + true, FeatureStage.Enforced(VERSION_7_0)), + + @Suppress("unused") + ENABLE_NATIVE_CONFIGURATION_FOLDING( + "android.enableNativeConfigurationFolding", + true, + FeatureStage.Enforced(VERSION_7_0)), + + @Suppress("unused") + ENABLE_SIDE_BY_SIDE_NDK( + "android.enableSideBySideNdk", + true, + FeatureStage.Enforced( + VERSION_4_1, + "The android.enableSideBySideNdk property does not have any effect. " + + "Side-by-side NDK is always enabled." + ) + ), + + @Suppress("unused") + ENABLE_IMPROVED_DEPENDENCY_RESOLUTION( + "android.enableImprovedDependenciesResolution", + true, + FeatureStage.Enforced( + VERSION_BEFORE_4_0, + "The android.enableImprovedDependenciesResolution property does not have any effect. " + + "Dependency resolution is only performed during task execution phase." + ) + ), + + @Suppress("unused") + ENABLE_NEW_RESOURCE_PROCESSING( + "android.enableNewResourceProcessing", + true, + FeatureStage.Enforced( + VERSION_BEFORE_4_0, + "New resource processing is now always enabled." + ) + ), + + @Suppress("unused") + DISABLE_RES_MERGE_IN_LIBRARY( + "android.disable.res.merge", + true, + FeatureStage.Enforced( + VERSION_BEFORE_4_0, + "Resources from dependencies are never merged in libraries." + ) + ), + + @Suppress("unused") + ENABLE_DAEMON_MODE_AAPT2( + "android.enableAapt2DaemonMode", + true, + FeatureStage.Enforced(VERSION_BEFORE_4_0, "AAPT2 daemon mode is now always enabled.") + ), + + @Suppress("unused") + ENABLE_INCREMENTAL_DESUGARING( + "android.enableIncrementalDesugaring", + true, + FeatureStage.Enforced( + VERSION_BEFORE_4_0, + "This property has no effect, incremental desugaring is always enabled." + ) + ), + + @Suppress("unused") + ENABLE_CORE_LAMBDA_STUBS( + "android.enableCoreLambdaStubs", + true, + FeatureStage.Enforced( + VERSION_BEFORE_4_0, + "This property has no effect, core-lambda-stubs.jar is always in the bootclasspath." + ) + ), + + @Suppress("unused") + ENABLE_DEX_ARCHIVE( + "android.useDexArchive", + true, + FeatureStage.Enforced( + VERSION_BEFORE_4_0, + "This property has no effect, incremental dexing is always used." + ) + ), + + @Suppress("unused") + ENABLE_AAPT2( + "android.enableAapt2", + true, + FeatureStage.Enforced( + VERSION_BEFORE_4_0, + "This property has no effect, AAPT2 is now always used." + ) + ), + + @Suppress("unused") + USE_AAPT2_FROM_MAVEN( + "android.useAapt2FromMaven", + true, + FeatureStage.Enforced( + VERSION_BEFORE_4_0, + "This property has no effect and AAPT2 from maven.google.com is now always used. " + + "If you wish to use a local executable of AAPT2 please use the " + + "'android.aapt2FromMavenOverride' option." + ) + ), + + @Suppress("unused") + ENABLE_D8_MAIN_DEX_LIST( + "android.enableD8MainDexList", + true, + FeatureStage.Enforced( + VERSION_BEFORE_4_0, + "This property has no effect, D8 is always used to compute the main dex list." + ) + ), + + @Suppress("unused") + ENABLE_DATA_BINDING_V2( + "android.databinding.enableV2", + true, + FeatureStage.Enforced(VERSION_BEFORE_4_0, "Databinding v1 is removed.") + ), + + ENABLE_SEPARATE_R_CLASS_COMPILATION( + "android.enableSeparateRClassCompilation", + true, + FeatureStage.Enforced( + VERSION_BEFORE_4_0, + "Separate R class compilation has been enabled and can no longer be disabled." + ) + ), + + @Suppress("unused") + ENABLE_GRADLE_WORKERS( + "android.enableGradleWorkers", + true, + FeatureStage.Enforced( + VERSION_4_2, + "Gradle workers are always used." + ) + ), + + @Suppress("unused") + ENABLE_D8( + "android.enableD8", + true, + FeatureStage.Enforced( + VERSION_7_0, + "For more details, see https://d.android.com/r/studio-ui/d8-overview.html." + ) + ), + + @Suppress("unused") + ENABLE_D8_DESUGARING( + "android.enableD8.desugaring", + true, + FeatureStage.Enforced( + VERSION_7_0, + "D8 desugaring is used by default, when applicable." + ) + ), + + @Suppress("unused") + ENABLE_R8_DESUGARING( + "android.enableR8.desugaring", + true, + FeatureStage.Enforced( + VERSION_7_0, + "R8 desugaring is used by default, when applicable." + ) + ), + + USE_NEW_LINT_MODEL("android.experimental.useNewLintModel", true, FeatureStage.Enforced(VERSION_7_0)), + + /** Whether Jetifier will skip libraries that already support AndroidX. */ + JETIFIER_SKIP_IF_POSSIBLE("android.jetifier.skipIfPossible", true, FeatureStage.Enforced(VERSION_7_0)), + + @Suppress("unused") + NON_TRANSITIVE_APP_R_CLASS( + "android.experimental.nonTransitiveAppRClass", + true, + FeatureStage.Enforced( + VERSION_7_0, + "Non-transitive R classes are now enabled in app modules with the " + + "${NON_TRANSITIVE_R_CLASS.propertyName} option.")), + + /** Incremental dexing task using D8's new API for desugaring graph computation. */ + ENABLE_INCREMENTAL_DEXING_TASK_V2("android.enableIncrementalDexingTaskV2", true, FeatureStage.Enforced(VERSION_7_0)), + + /** Incremental dexing transform. */ + ENABLE_INCREMENTAL_DEXING_TRANSFORM("android.enableIncrementalDexingTransform", true, FeatureStage.Enforced(VERSION_7_0)), + + ENABLE_R8_LIBRARIES("android.enableR8.libraries", true, FeatureStage.Enforced(VERSION_7_0)), + + ENABLE_SYMBOL_TABLE_CACHING("android.enableSymbolTableCaching", true, FeatureStage.Enforced(VERSION_7_0)), + + ENABLE_JVM_RESOURCE_COMPILER("android.enableJvmResourceCompiler", true, FeatureStage.Enforced(VERSION_7_0)), + + /** Whether to use lint's partial analysis functionality. */ + USE_LINT_PARTIAL_ANALYSIS("android.enableParallelLint", true, FeatureStage.Enforced(VERSION_7_2)), + + ENABLE_AAPT2_WORKER_ACTIONS( + "android.enableAapt2WorkerActions", + true, + FeatureStage.Enforced( + VERSION_7_3, + "AAPT2 worker actions have been used unconditionally since Android Gradle Plugin 3.3" + ) + ), + ENABLE_JACOCO_TRANSFORM_INSTRUMENTATION( + "android.enableJacocoTransformInstrumentation", + true, + FeatureStage.Enforced(VERSION_7_3) + ), + + @Suppress("unused") + ENABLE_SOURCE_SET_PATHS_MAP( + "android.enableSourceSetPathsMap", + true, + FeatureStage.Enforced(Version.VERSION_8_0) + ), + + @Suppress("unused") + RELATIVE_COMPILE_LIB_RESOURCES( + "android.cacheCompileLibResources", + true, + FeatureStage.Enforced(Version.VERSION_8_0) + ), + + @Suppress("unused") + R8_FAIL_ON_MISSING_CLASSES( + "android.r8.failOnMissingClasses", + true, + FeatureStage.Enforced(Version.VERSION_8_0) + ), + + USE_NEW_JAR_CREATOR( + "android.useNewJarCreator", + true, + FeatureStage.Enforced(Version.VERSION_8_0) + ), + + USE_NEW_APK_CREATOR( + "android.useNewApkCreator", + true, + FeatureStage.Enforced(Version.VERSION_8_0) + ), + + WARN_ABOUT_DEPENDENCY_RESOLUTION_AT_CONFIGURATION( + "android.dependencyResolutionAtConfigurationTime.warn", + true, + FeatureStage.Enforced(Version.VERSION_8_0) + ), + + ENABLE_ART_PROFILES( + "android.enableArtProfiles", + true, + FeatureStage.Enforced(Version.VERSION_8_0) + ), + + USE_RELATIVE_PATH_IN_TEST_CONFIG( + "android.testConfig.useRelativePath", + true, + FeatureStage.Enforced(Version.VERSION_8_0) + ), + + INCLUDE_REPOSITORIES_IN_DEPENDENCY_REPORT( + "android.bundletool.includeRepositoriesInDependencyReport", + true, + FeatureStage.Enforced(Version.VERSION_8_0) + ), + + ENABLE_INCREMENTAL_DATA_BINDING( + "android.databinding.incremental", + true, + FeatureStage.Enforced(Version.VERSION_8_1) + ), + + ENABLE_NEW_RESOURCE_SHRINKER("android.enableNewResourceShrinker", + true, + FeatureStage.Enforced(Version.VERSION_8_0)), + + @Suppress("unused") + ENABLE_R_TXT_RESOURCE_SHRINKING( + "android.enableRTxtResourceShrinking", + true, + FeatureStage.Enforced(Version.VERSION_8_1) + ), + + @Suppress("unused") + COMPILE_CLASSPATH_LIBRARY_R_CLASSES( + "android.useCompileClasspathLibraryRClasses", + true, + FeatureStage.Enforced(Version.VERSION_8_1) + ), + + ENABLE_UNCOMPRESSED_NATIVE_LIBS_IN_BUNDLE( + "android.bundle.enableUncompressedNativeLibs", + true, + FeatureStage.Enforced(Version.VERSION_8_1) + ), + + ENABLE_GLOBAL_SYNTHETICS( + "android.enableGlobalSyntheticsGeneration", + true, + FeatureStage.Enforced(Version.VERSION_8_1) + ), + + ENABLE_DEXING_DESUGARING_ARTIFACT_TRANSFORM( + "android.enableDexingArtifactTransform.desugaring", + true, + FeatureStage.Enforced( + Version.VERSION_8_2, + "If you run into issues with dexing transforms, try setting `${USE_FULL_CLASSPATH_FOR_DEXING_TRANSFORM.propertyName} = true` instead." + ) + ), + + ENABLE_DEXING_ARTIFACT_TRANSFORM_FOR_EXTERNAL_LIBS( + "android.enableDexingArtifactTransformForExternalLibs", + true, + FeatureStage.Enforced(Version.VERSION_8_2) + ), + + ENABLE_DEXING_ARTIFACT_TRANSFORM( + "android.enableDexingArtifactTransform", + true, + FeatureStage.Enforced( + Version.VERSION_8_3, + "If you run into issues with dexing transforms, try setting `${USE_FULL_CLASSPATH_FOR_DEXING_TRANSFORM.propertyName} = true` instead." + ) + ), + + /* ---------------- + * REMOVED FEATURES + */ + + @Suppress("unused") + ENABLE_IN_PROCESS_AAPT2( + "android.enableAapt2jni", + false, + FeatureStage.Removed(VERSION_BEFORE_4_0, "AAPT2 JNI has been removed.") + ), + + @Suppress("unused") + ENABLE_DEPRECATED_NDK( + "android.useDeprecatedNdk", + false, + FeatureStage.Removed(VERSION_BEFORE_4_0, "NdkCompile is no longer supported") + ), + + @Suppress("unused") + INJECT_SDK_MAVEN_REPOS( + "android.injectSdkMavenRepos", + false, + FeatureStage.Removed( + VERSION_3_5, + "The ability to inject the Android SDK maven repos is removed in AGP 3.5" + ) + ), + + @Suppress("unused") + ENABLE_UNIT_TEST_BINARY_RESOURCES( + "android.enableUnitTestBinaryResources", + false, + FeatureStage.Removed( + VERSION_BEFORE_4_0, + "The raw resource for unit test functionality is removed." + ) + ), + + @Suppress("unused") + ENABLE_EXPERIMENTAL_FEATURE_DATABINDING( + "android.enableExperimentalFeatureDatabinding", + false, + FeatureStage.Removed( + VERSION_4_0, + "This property has no effect. The features plugin was removed in AGP 4.0.") + ), + + @Suppress("unused") + ENABLE_SEPARATE_APK_RESOURCES( + "android.enableSeparateApkRes", + false, + FeatureStage.Removed(VERSION_BEFORE_4_0, "Instant run is replaced by apply changes.") + ), + + @Suppress("unused") + KEEP_TIMESTAMPS_IN_APK( + "android.keepTimestampsInApk", + false, + FeatureStage.Removed( + VERSION_3_6, + "The ability to keep timestamps in the APK is removed in AGP 3.6" + ) + ), + + @Suppress("unused") + ENABLE_SEPARATE_ANNOTATION_PROCESSING( + "android.enableSeparateAnnotationProcessing", + false, + FeatureStage.Removed(VERSION_4_0, "This feature was removed in AGP 4.0") + ), + + @Suppress("unused") + GENERATE_R_JAVA( + "android.generateRJava", + false, + FeatureStage.Removed(VERSION_4_1, "This feature was removed in AGP 4.1")), + + @Suppress("unused") + ENABLE_BUILD_CACHE( + "android.enableBuildCache", + false, + FeatureStage.Removed(VERSION_7_0, "The Android-specific build caches were superseded by the Gradle build cache (https://docs.gradle.org/current/userguide/build_cache.html).") + ), + + @Suppress("unused") + ENABLE_INTERMEDIATE_ARTIFACTS_CACHE( + "android.enableIntermediateArtifactsCache", + false, + FeatureStage.Removed(VERSION_7_0, "The Android-specific build caches were superseded by the Gradle build cache (https://docs.gradle.org/current/userguide/build_cache.html).") + ), + + @Suppress("unused") + ENABLE_DESUGAR( + "android.enableDesugar", + false, + FeatureStage.Removed(VERSION_7_0, "Desugar tool has been removed from AGP.") + ), + + @Suppress("unused") + ENABLE_TEST_SHARDING("android.androidTest.shardBetweenDevices", false, FeatureStage.Removed(Version.VERSION_8_2, "Cross device sharding is no longer supported.")), + ; // end of enums + + override val status = stage.status + + override fun parse(value: Any): Boolean { + return parseBoolean(propertyName, value) + } +} diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/DeprecationTarget.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/DeprecationTarget.kt new file mode 100644 index 000000000..761e2a01c --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/DeprecationTarget.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 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.options +/** Enum for deprecated element removal target. */ +enum class DeprecationTarget constructor( + + /** + * The target version when a deprecated element will be removed. + * + * Usage note: Do not use this field to construct a deprecation message, use + * getDeprecationTargetMessage() instead to ensure consistent message format. + */ + val removalTarget: Version, + + /** + * Additional message to be shown below the pre-formatted error/warning message. + * + * Note that this additional message should be constructed such that it fits well in the + * overall message: + * + * "This feature will be removed in version X.Y of the Android Gradle plugin.\n + * $additionalMessage" + * + * For example, avoid writing additional messages that say "This feature is planned for + * removal", as it will be duplicated. + */ + private val additionalMessage: String? = null +) { + VERSION_9_0(Version.VERSION_9_0), + + VERSION_8_3(Version.VERSION_8_3), + + VERSION_8_0(Version.VERSION_8_0), + + // Obsolete dx Dex Options + DEX_OPTIONS(Version.VERSION_8_0, "Using it has no effect, and the Android" + + "Gradle plugin optimizes dexing automatically."), + + // Deprecation of Task Access in the variant API + TASK_ACCESS_VIA_VARIANT(Version.VERSION_9_0), + + USE_PROPERTIES( + Version.VERSION_8_0, + "Gradle Properties must be used to change Variant information." + ), + + LINT_CHECK_ONLY( + Version.VERSION_8_0, + "`check` has been renamed to `checkOnly` to make it clear that it " + + "will turn off all other checks than those explicitly listed. If that is " + + "what you actually intended, use `checkOnly`; otherwise switch to `enable`." + ), + + DEFAULT_PUBLISH_CONFIG( + Version.VERSION_8_0, + "The support for publishing artifacts with Maven Plugin is removed, " + + "please migrate Maven Publish Plugin. See " + + "https://developer.android.com/studio/build/maven-publish-plugin for more information." + ), + + TRANSFORM_API( + Version.VERSION_8_0, + """ + The Transform API support has been removed in AGP 8.0. + """.trimIndent() + ), + + DENSITY_SPLIT_API( + Version.VERSION_9_0, + "Density-based apk split feature is deprecated and will be removed in AGP 9.0. " + + "Use Android App Bundle (https://developer.android.com/guide/app-bundle) " + + "to generate optimized APKs." + ), + + BUILD_CONFIG_GLOBAL_PROPERTY( + Version.VERSION_9_0, + "You can resolve this warning in Android Studio via `Refactor` > `Migrate " + + "BuildConfig to Gradle Build Files`" + ) + ; + + fun getDeprecationTargetMessage(): String { + return removalTarget.getDeprecationTargetMessage() + + (additionalMessage?.let { "\n$it" } ?: "") + } +} \ No newline at end of file diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/IntegerOption.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/IntegerOption.kt new file mode 100644 index 000000000..ab3ddb9d4 --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/IntegerOption.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017 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.options + +import com.android.build.gradle.options.Version.VERSION_8_2 +import com.android.builder.model.AndroidProject +import com.android.build.gradle.options.Version.VERSION_BEFORE_4_0 +import com.android.builder.model.PROPERTY_BUILD_API + +enum class IntegerOption( + override val propertyName: String, + stage: ApiStage +) : Option { + ANDROID_TEST_SHARD_COUNT("android.androidTest.numShards", ApiStage.Removed(VERSION_8_2, "Cross device sharding is no longer supported.")), + ANDROID_SDK_CHANNEL("android.sdk.channel", ApiStage.Experimental), + + /** + * Returns the level of model-only mode. + * + * The model-only mode is triggered when the IDE does a sync, and therefore we do things a + * bit differently (don't throw exceptions for instance). Things evolved a bit over versions and + * the behavior changes. This reflects the mode to use. + * + * @see AndroidProject#MODEL_LEVEL_0_ORIGINAL + * @see AndroidProject#MODEL_LEVEL_1_SYNC_ISSUE + * @see AndroidProject#MODEL_LEVEL_3_VARIANT_OUTPUT_POST_BUILD + * @see AndroidProject#MODEL_LEVEL_4_NEW_DEP_MODEL + */ + IDE_BUILD_MODEL_ONLY_VERSION(AndroidProject.PROPERTY_BUILD_MODEL_ONLY_VERSIONED, ApiStage.Stable), + + /** + * The api level for the target device. + * + *

For preview versions that is the last stable version, and the {@link + * StringOption#IDE_TARGET_DEVICE_CODENAME} will also be set. + */ + IDE_TARGET_DEVICE_API(PROPERTY_BUILD_API, ApiStage.Stable), + + /** + * Size of the buffers in kilobytes used to read .class files and storage for writing .dex files + * translations into. + */ + DEXING_READ_BUFFER_SIZE("android.dexingReadBuffer.size", ApiStage.Experimental), + DEXING_WRITE_BUFFER_SIZE("android.dexingWriteBuffer.size", ApiStage.Experimental), + + /** Number of buckets used by `DexArchiveBuilderTask` and `DexMergingTask`. */ + DEXING_NUMBER_OF_BUCKETS("android.dexingNumberOfBuckets", ApiStage.Experimental), + + /** + * Maximum number of dynamic features that can be allocated before Oreo platforms. + */ + PRE_O_MAX_NUMBER_OF_FEATURES("android.maxNumberOfFeaturesBeforeOreo", ApiStage.Experimental), + + /** + * Override the thread pool size dedicated to AAPT2 work units when not running with WorkerAPI. + */ + AAPT2_THREAD_POOL_SIZE("android.aapt2ThreadPoolSize", ApiStage.Experimental), + + /** + * Max number of R8 workers to run at once + */ + R8_MAX_WORKERS("android.r8.maxWorkers", ApiStage.Experimental), + + /** + * Flags for Android Test Retention + * >=1: enable Android Test Retention and set it as maximum number of snapshots. + * 0: disable Android Test Retention. + * <0: enable Android Test Retention with unlimited number of snapshots. + */ + TEST_FAILURE_RETENTION("android.experimental.testOptions.emulatorSnapshots.maxSnapshotsForTestFailures", ApiStage.Experimental), + + /** + * The number of shards ran on Managed Devices during testing. + */ + MANAGED_DEVICE_SHARD_POOL_SIZE("android.experimental.androidTest.numManagedDeviceShards", ApiStage.Experimental), + + /** + * The timeout duration in minute for Gradle Managed Device setup steps (booting AVD and creating snapshot image) + * If the value is 0 or negative value, it will never time out. + */ + GRADLE_MANAGED_DEVICE_SETUP_TIMEOUT_MINUTES("android.experimental.testOptions.managedDevices.setupTimeoutMinutes", ApiStage.Experimental), + + /** + * Maximum number of concurrent Gradle Managed Devices (AVDs) to be active at any one point in time. + * + * If the value is 0 or negative value, there is no maximum devices. + */ + GRADLE_MANAGED_DEVICE_MAX_CONCURRENT_DEVICES("android.experimental.testOptions.managedDevices.maxConcurrentDevices", ApiStage.Experimental), + + /** + * The timeout duration in seconds for install apk + * If the value is 0 or negative value, it will be set to a default value by UTP. + */ + INSTALL_APK_TIMEOUT("android.experimental.testOptions.installApkTimeout", ApiStage.Experimental), + /* ------------ + * REMOVED APIS + */ + + THREAD_POOL_SIZE( + "android.threadPoolSize", + ApiStage.Removed( + VERSION_BEFORE_4_0, + "The android.threadPoolSize property has no effect" + ) + ), + + ; + + override val status = stage.status + + override fun parse(value: Any): Int { + if (value is CharSequence) { + try { + return Integer.parseInt(value.toString()) + } catch (ignored: NumberFormatException) { + // Throws below. + } + + } + if (value is Number) { + return value.toInt() + } + throw IllegalArgumentException( + "Cannot parse project property " + + this.propertyName + + "='" + + value + + "' of type '" + + value.javaClass + + "' as integer." + ) + } +} diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Option.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Option.kt new file mode 100644 index 000000000..104f79388 --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Option.kt @@ -0,0 +1,93 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (C) 2017 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.options + +interface Option { + + sealed class Status { + + data object EXPERIMENTAL : Status() + + data object STABLE : Status() + + class Deprecated(val deprecationTarget: DeprecationTarget) : + Status() { + + fun getDeprecationTargetMessage(): String { + return deprecationTarget.getDeprecationTargetMessage() + } + } + + class Removed( + + /** + * The version when an element was removed. + * + * Usage note: Do not use this field to construct a removal message, use + * getRemovedVersionMessage() instead to ensure consistent message format. + */ + val removedVersion: Version, + + /** + * Additional message to be shown below the pre-formatted error/warning message. + * + * Note that this additional message should be constructed such that it fits well in the + * overall message: + * + * "This feature was removed in version X.Y of the Android Gradle plugin.\n + * $additionalMessage" + * + * For example, avoid writing additional messages that say "This feature has been + * removed", as it will be duplicated. + */ + private val additionalMessage: String? = null + + ) : Status() { + + fun getRemovedVersionMessage(): String { + return removedVersion.getRemovedVersionMessage() + + (additionalMessage?.let { "\n$it" } ?: "") + } + } + } + + val propertyName: String + + val defaultValue: T? + get() = null + + val status: Status + + fun parse(value: Any): T +} diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/OptionParsers.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/OptionParsers.kt new file mode 100644 index 000000000..0deedeb1f --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/OptionParsers.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 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. + */ +@file:JvmName("OptionParsers") + +package com.android.build.gradle.options + +import java.util.Locale + +fun parseBoolean(propertyName: String, value: Any, propertyKind: String = "project"): Boolean { + return when (value) { + is Boolean -> value + is CharSequence -> + when (value.toString().lowercase(Locale.US)) { + "true" -> true + "false" -> false + else -> parseBooleanFailure(propertyName, value, propertyKind) + } + is Number -> + when (value.toInt()) { + 0 -> false + 1 -> true + else -> parseBooleanFailure(propertyName, value, propertyKind) + } + else -> parseBooleanFailure(propertyName, value, propertyKind) + } +} + +private fun parseBooleanFailure(propertyName: String, value: Any, propertyKind: String): Nothing { + throw IllegalArgumentException( + "Cannot parse " + + propertyKind + + " property " + + propertyName + + "='" + + value + + "' of type '" + + value.javaClass + + "' as boolean. Expected 'true' or 'false'." + ) +} diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/OptionalBooleanOption.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/OptionalBooleanOption.kt new file mode 100644 index 000000000..b3afd6394 --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/OptionalBooleanOption.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 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.options + +import com.android.build.gradle.options.Version.VERSION_7_0 +import com.android.build.gradle.options.Version.VERSION_8_0 +import com.android.build.gradle.options.Version.VERSION_BEFORE_4_0 +import com.android.builder.model.PROPERTY_SIGNING_V1_ENABLED +import com.android.builder.model.PROPERTY_SIGNING_V2_ENABLED +import com.android.builder.model.PROPERTY_TEST_ONLY + +enum class OptionalBooleanOption( + override val propertyName: String, + val stage: Stage, + val recommendedValue: Boolean? = null +) : Option { + SIGNING_V1_ENABLED(PROPERTY_SIGNING_V1_ENABLED, ApiStage.Stable), + SIGNING_V2_ENABLED(PROPERTY_SIGNING_V2_ENABLED, ApiStage.Stable), + IDE_TEST_ONLY(PROPERTY_TEST_ONLY, ApiStage.Stable), + + /** + * This project property is read by the firebase plugin, and has no direct impact on AGP behavior. + * + * It is included as an OptionalBooleanOption in order that its value, if set, is recorded in the AGP analytics. + */ + FIREBASE_PERF_PLUGIN_ENABLE_FLAG("firebasePerformanceInstrumentationEnabled", ApiStage.Stable), + + // Flags for Android Test Retention + ENABLE_TEST_FAILURE_RETENTION_COMPRESS_SNAPSHOT("android.experimental.testOptions.emulatorSnapshots.compressSnapshots", ApiStage.Experimental), + + ENABLE_API_MODELING_AND_GLOBAL_SYNTHETICS("android.enableApiModelingAndGlobalSynthetics", ApiStage.Experimental), + + /* ---------------- + * SOFTLY ENFORCED FEATURES + */ + + /* ---------------- + * ENFORCED FEATURES + */ + @Suppress("unused") + ENABLE_R8( + "android.enableR8", + FeatureStage.Enforced(VERSION_7_0, "Please remove it from `gradle.properties`.") + ), + + @Suppress("unused") + DISABLE_AUTOMATIC_COMPONENT_CREATION( + "android.disableAutomaticComponentCreation", + FeatureStage.Enforced(VERSION_8_0, "Please remove it from `gradle.properties`.") + ), + + /* ---------------- + * REMOVED FEATURES + */ + + @Suppress("unused") + SERIAL_AAPT2( + "android.injected.aapt2.serial", + FeatureStage.Removed( + VERSION_BEFORE_4_0, + "Invoking AAPT2 serially is no longer supported." + ) + ), + + ; + + override val status = stage.status + + override fun parse(value: Any): Boolean { + return parseBoolean(propertyName, value) + } +} diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/ReplacedOption.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/ReplacedOption.kt new file mode 100644 index 000000000..4ad52440c --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/ReplacedOption.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2017 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.options + +import com.android.build.gradle.options.Version.VERSION_4_0 +import com.android.build.gradle.options.Version.VERSION_4_1 +import com.android.build.gradle.options.Version.VERSION_7_0 +import com.android.build.gradle.options.Version.VERSION_BEFORE_4_0 + +/** + * [Option] that has been replaced by something else (e.g., another option or the DSL). + * + * Note that replaced options are different from removed options. Removed options belong to the + * [Option] subclass where they were first introduced, and should not be put in this class. + */ +enum class ReplacedOption( + override val propertyName: String, + private val replacedVersion: Version, + private val additionalMessage: String +) : Option { + + @Suppress("unused") + INCREMENTAL_JAVA_COMPILE( + "android.incrementalJavaCompile", + VERSION_BEFORE_4_0, + "The android.incrementalJavaCompile property has been replaced by a DSL property. " + + "Please add the following to your build.gradle instead:\n" + + "android {\n" + + " compileOptions.incremental = false\n" + + "}" + ), + + @Suppress("unused") + THREAD_POOL_SIZE_OLD( + "com.android.build.threadPoolSize", + VERSION_BEFORE_4_0, + "This property has been replaced by ${IntegerOption.THREAD_POOL_SIZE.propertyName}" + ), + + @Suppress("unused") + VERSION_CHECK_OVERRIDE_PROPERTY_OLD( + "com.android.build.gradle.overrideVersionCheck", + VERSION_BEFORE_4_0, + "This property has been replaced by ${BooleanOption.VERSION_CHECK_OVERRIDE_PROPERTY.propertyName}" + ), + + @Suppress("unused") + OVERRIDE_PATH_CHECK_PROPERTY_OLD( + "com.android.build.gradle.overridePathCheck", + VERSION_BEFORE_4_0, + "This property has been replaced by ${BooleanOption.OVERRIDE_PATH_CHECK_PROPERTY.propertyName}" + ), + + @Suppress("unused") + AAPT_NAMESPACING( + "android.aaptNamespacing", + VERSION_BEFORE_4_0, + "This property has been replaced by android.aaptOptions.namespaced (DSL)" + ), + + @Suppress("unused") + EXCLUDE_R_AND_MANIFEST_DOT_JAVA_FROM_GENERATED_SOURCES( + "android.excludeRAndManifestDotJavaFromGeneratedSources", + VERSION_BEFORE_4_0, + "Subsumed by ${BooleanOption.ENABLE_SEPARATE_R_CLASS_COMPILATION.propertyName}" + ), + + // This option should belong to the LongOption class, but that class has been removed so we put + // it here. + @Suppress("unused") + DEPRECATED_NDK_COMPILE_LEASE( + "android.deprecatedNdkCompileLease", + VERSION_BEFORE_4_0, + "NdkCompile is no longer supported" + ), + + @Suppress("unused") + USE_NON_FINAL_RES_IDS_IN_TESTS( + "android.androidTest.nonFinalResIds", + VERSION_BEFORE_4_0, + "This property has been replaced by ${BooleanOption.USE_NON_FINAL_RES_IDS.propertyName}" + ), + + @Suppress("unused") + ENABLE_INCREMENTAL_DESUGARING_V2( + "android.enableIncrementalDesugaringV2", + VERSION_4_0, + "This property has been replaced by ${BooleanOption.ENABLE_INCREMENTAL_DEXING_TASK_V2.propertyName}" + ), + + @Suppress("unused") + ENABLE_INCREMENTAL_DEXING_V2( + "android.enableIncrementalDexingV2", + VERSION_4_1, + "This property has been replaced by ${BooleanOption.ENABLE_INCREMENTAL_DEXING_TASK_V2.propertyName}" + ), + + @Suppress("unused") + NAMESPACED_R_CLASS( + "android.namespacedRClass", + VERSION_4_1, + "This property has been replaced by ${BooleanOption.NON_TRANSITIVE_R_CLASS.propertyName}" + ), + + @Suppress("unused") + ENABLE_PREFAB( + "android.enablePrefab", + VERSION_4_1, + "This property has been replaced by android.buildFeatures.prefab (DSL)" + ), + + // The flag was renamed. + @Suppress("unused") + ENABLE_STABLE_IDS( + "android.experimental.enableStableIds", + VERSION_4_1, + "This property has been replaced by ${BooleanOption.ENABLE_STABLE_IDS.propertyName}" + ), + + @Suppress("unused") + TEST_FAILURE_RETENTION_0( + "android.experimental.testOptions.emulatorSnapshots", + VERSION_7_0, + "This property has been replaced by ${IntegerOption.TEST_FAILURE_RETENTION.propertyName}" + ), + + @Suppress("unused") + TEST_FAILURE_RETENTION_1( + "android.testOptions.failureRetention", + VERSION_7_0, + "This property has been replaced by ${IntegerOption.TEST_FAILURE_RETENTION.propertyName}" + ), + + @Suppress("unused") + ENABLE_TEST_FAILURE_RETENTION_COMPRESS_SNAPSHOT( + "android.testOptions.failureRetention.compressSnapshots", + VERSION_7_0, + "This property has been replaced by ${OptionalBooleanOption.ENABLE_TEST_FAILURE_RETENTION_COMPRESS_SNAPSHOT.propertyName}" + ), + + // Jetifier: List of regular expressions for libraries that should not be jetified + @Suppress("unused", "WrongTerminology") + JETIFIER_BLACKLIST( + "android.jetifier.blacklist", + VERSION_7_0, + "This property has been replaced by ${StringOption.JETIFIER_IGNORE_LIST.propertyName}" + ) + + ; + + override val status: Option.Status + get() = Option.Status.Removed(replacedVersion, additionalMessage) + + override fun parse(value: Any): String { + return value.toString() + } +} diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Stage.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Stage.kt new file mode 100644 index 000000000..6c5b26bdf --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Stage.kt @@ -0,0 +1,167 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +/* + * Copyright (C) 2019 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.options + +/** + * The stage of an API or feature in its life cycle. + * + * An API or feature has an associated [Option] to allow the users to change the behavior of the + * Android Gradle plugin. The difference between them is that: + * - The [Option] associated with an API is intended to be used in the long term. For example, if + * it is a [BooleanOption] (or [OptionalBooleanOption]), one value of the option may be + * experimental at first, but eventually both values of the option will be supported. + * - The [Option] associated with a feature is intended to be used only in the short term. It can + * only be a [BooleanOption] (or [OptionalBooleanOption]). One value of the [BooleanOption] may + * be experimental at first to enable the new behavior, but eventually the option will be + * removed, either because the feature is fully supported and now enforced, or because the + * feature was not / is no longer useful and now removed. + */ +open class Stage( + + /** + * Status of the [Option] which represents an API or feature. It is related but not the same as + * the [Stage] of the API of feature. + */ + val status: Option.Status +) + +/** + * The stage of an API in its life cycle. + * + * See [Stage] for the difference between an API and a feature. + */ +sealed class ApiStage(status: Option.Status) : Stage(status) { + + /** + * Indicates that the API is experimental. + * + * It may become stable or may be removed in a future release (see stage [Stable] and + * [Removed]). + */ + object Experimental : ApiStage(Option.Status.EXPERIMENTAL) + + /** + * Indicates that the API is stable. + */ + object Stable : ApiStage(Option.Status.STABLE) + + /** + * Indicates that the API will be removed soon because it was not / is no longer useful (see + * stage [Removed]). + * + * @param removalTarget a target when the API and the corresponding [Option] will be removed + */ + class Deprecated(removalTarget: DeprecationTarget) : + ApiStage(Option.Status.Deprecated(removalTarget)) + + /** + * Indicates that the API and the corresponding [Option] have been removed. + * + * @param removedVersion the version when the API and the corresponding [Option] were removed + * @param additionalMessage the additional message to be shown if the [Option] is used + */ + class Removed(removedVersion: Version, additionalMessage: String? = null) : + ApiStage(Option.Status.Removed(removedVersion, additionalMessage)) +} + +/** + * The stage of a feature in its life cycle. + * + * See [Stage] for the difference between an API and a feature. + */ +sealed class FeatureStage(status: Option.Status) : Stage(status) { + + /** + * Indicates that the feature is experimental. + * + * It may be enforced or removed in a future release (see stage [Enforced] and [Removed]). + */ + object Experimental : FeatureStage(Option.Status.EXPERIMENTAL) + + /** + * Indicates that the feature is fully supported. + * + * It may or may not be enabled by default. If it is not yet enabled by default, it will likely + * be enabled by default in a future release. + * + * Eventually, the feature will likely be enforced (see stage [SoftlyEnforced] and [Enforced]). + * In some cases, it may be removed (see stage [Deprecated] and [Removed]). + * + * DISCOURAGED USAGE: The use of this stage is actually discouraged as it doesn't specify a + * clear timeline and features may stay in this stage for too long, thus increasing maintenance + * cost to AGP and users. Consider using [SoftlyEnforced] or [Deprecated] instead. + */ + object Supported : FeatureStage(Option.Status.STABLE) + + /** + * Indicates that the feature will be enforced soon (see stage [Enforced]). + * + * @param enforcementTarget a target when the feature will be enforced, at which point the + * corresponding [Option] will be removed (hence this parameter has type + * `DeprecationTarget`) + */ + class SoftlyEnforced(enforcementTarget: DeprecationTarget) : + FeatureStage(Option.Status.Deprecated(enforcementTarget)) + + /** + * Indicates that the feature is enforced (always enabled), and the corresponding [Option] has + * been removed. + * + * @param enforcedVersion the version when the feature is enforced and the corresponding + * [Option] was removed + * @param additionalMessage the additional message to be shown if the [Option] is used + */ + class Enforced(enforcedVersion: Version, additionalMessage: String? = null) : + FeatureStage(Option.Status.Removed(enforcedVersion, additionalMessage)) + + /** + * Indicates that the feature will be removed soon because it was not / is no longer useful (see + * stage [Removed]). + * + * @param removalTarget a target when the feature and the corresponding [Option] will be removed + */ + class Deprecated(removalTarget: DeprecationTarget) : + FeatureStage(Option.Status.Deprecated(removalTarget)) + + /** + * Indicates that the feature has been removed (always disabled), and the corresponding [Option] + * has been removed. + * + * @param removedVersion the version when the feature and the corresponding [Option] were + * removed + * @param additionalMessage the additional message to be shown if the [Option] is used + */ + class Removed(removedVersion: Version, additionalMessage: String? = null) : + FeatureStage(Option.Status.Removed(removedVersion, additionalMessage)) +} diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/StringOption.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/StringOption.kt new file mode 100644 index 000000000..ea1def3df --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/StringOption.kt @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2017 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.options + +import com.android.builder.model.AndroidProject +import com.android.builder.model.PROPERTY_APK_LOCATION +import com.android.builder.model.PROPERTY_APK_SELECT_CONFIG +import com.android.builder.model.PROPERTY_ATTRIBUTION_FILE_LOCATION +import com.android.builder.model.PROPERTY_BUILD_ABI +import com.android.builder.model.PROPERTY_BUILD_API_CODENAME +import com.android.builder.model.PROPERTY_BUILD_DENSITY +import com.android.builder.model.PROPERTY_CHECK_JETIFIER_RESULT_FILE +import com.android.builder.model.PROPERTY_INJECTED_DYNAMIC_MODULES_LIST +import com.android.builder.model.PROPERTY_NATIVE_BUILD_OUTPUT_LEVEL +import com.android.builder.model.PROPERTY_SIGNING_KEY_ALIAS +import com.android.builder.model.PROPERTY_SIGNING_KEY_PASSWORD +import com.android.builder.model.PROPERTY_SIGNING_STORE_FILE +import com.android.builder.model.PROPERTY_SIGNING_STORE_PASSWORD +import com.android.builder.model.PROPERTY_SIGNING_STORE_TYPE + +enum class StringOption( + override val propertyName: String, + stage: ApiStage +) : Option { + + /* ----------- + * STABLE APIs + */ + + IDE_BUILD_TARGET_ABI(PROPERTY_BUILD_ABI, ApiStage.Stable), + + IDE_ATTRIBUTION_FILE_LOCATION(PROPERTY_ATTRIBUTION_FILE_LOCATION, ApiStage.Stable), + + /** Absolute path to a file containing the result of the `CheckJetifier` task. */ + IDE_CHECK_JETIFIER_RESULT_FILE(PROPERTY_CHECK_JETIFIER_RESULT_FILE, ApiStage.Stable), + + // Signing options + IDE_SIGNING_STORE_TYPE(PROPERTY_SIGNING_STORE_TYPE, ApiStage.Stable), + IDE_SIGNING_STORE_FILE(PROPERTY_SIGNING_STORE_FILE, ApiStage.Stable), + IDE_SIGNING_STORE_PASSWORD(PROPERTY_SIGNING_STORE_PASSWORD, ApiStage.Stable), + IDE_SIGNING_KEY_ALIAS(PROPERTY_SIGNING_KEY_ALIAS, ApiStage.Stable), + IDE_SIGNING_KEY_PASSWORD(PROPERTY_SIGNING_KEY_PASSWORD, ApiStage.Stable), + + // device config for ApkSelect + IDE_APK_SELECT_CONFIG(PROPERTY_APK_SELECT_CONFIG, ApiStage.Stable), + + // location where to write the APK/BUNDLE + IDE_APK_LOCATION(PROPERTY_APK_LOCATION, ApiStage.Stable), + + IDE_TARGET_DEVICE_CODENAME(PROPERTY_BUILD_API_CODENAME, ApiStage.Stable), + + // Profiler plugin + IDE_ANDROID_CUSTOM_CLASS_TRANSFORMS("android.advanced.profiling.transforms", ApiStage.Stable), + + // The exact version of Android Support plugin used, e.g. 2.4.0.6 + IDE_ANDROID_STUDIO_VERSION(AndroidProject.PROPERTY_ANDROID_SUPPORT_VERSION, ApiStage.Stable), + + // The version of Android Game Development Extension used to orchestrate the build + IDE_AGDE_VERSION("agde.version", ApiStage.Stable), + + // Native + NATIVE_BUILD_OUTPUT_LEVEL(PROPERTY_NATIVE_BUILD_OUTPUT_LEVEL, ApiStage.Stable), + + // AGP suggests it should be upgraded if the compile sdk is newer than the version it was tested + // with. This option allows developers to suppress that warning. + // e.g. android.suppressUnsupportedCompileSdk=S,31,32 + SUPPRESS_UNSUPPORTED_COMPILE_SDK("android.suppressUnsupportedCompileSdk", ApiStage.Stable), + + // User-specified flag for using a profileable or debuggable build, + // if the flag is not set, the debuggable value will fallback to using the DSL 'debuggable'. + PROFILING_MODE("android.profilingMode", ApiStage.Stable), + + // Override for the default execution profile in the settings plugin. + EXECUTION_PROFILE_SELECTION("android.settings.executionProfile", ApiStage.Stable), + + /* ----------------- + * EXPERIMENTAL APIs + */ + + // Installation related options + IDE_INSTALL_DYNAMIC_MODULES_LIST(PROPERTY_INJECTED_DYNAMIC_MODULES_LIST, ApiStage.Experimental), + + // Testing + DEVICE_POOL_SERIAL("com.android.test.devicepool.serial", ApiStage.Experimental), + PROFILE_OUTPUT_DIR("android.advanced.profileOutputDir", ApiStage.Experimental), + + BUILD_ARTIFACT_REPORT_FILE("android.buildartifact.reportfile", ApiStage.Experimental), + + AAPT2_FROM_MAVEN_OVERRIDE("android.aapt2FromMavenOverride", ApiStage.Experimental), + + AAPT2_FROM_MAVEN_VERSION_OVERRIDE("android.aapt2Version", ApiStage.Experimental), + + AAPT2_FROM_MAVEN_PLATFORM_OVERRIDE("android.aapt2Platform", ApiStage.Experimental), + + SUPPRESS_UNSUPPORTED_OPTION_WARNINGS("android.suppressUnsupportedOptionWarnings", ApiStage.Experimental), + + // User-specified path to Prefab jar to return from getPrefabFromMaven. + PREFAB_CLASSPATH("android.prefabClassPath", ApiStage.Experimental), + + // User-specified Prefab version to pull from Maven in getPrefabFromMaven. + PREFAB_VERSION("android.prefabVersion", ApiStage.Experimental), + + // Jetifier: List of regular expressions for libraries that should not be jetified + JETIFIER_IGNORE_LIST("android.jetifier.ignorelist", ApiStage.Experimental), + + // Lint: Allow customization of the heap size of the process started to run lint + LINT_HEAP_SIZE("android.experimental.lint.heapSize", ApiStage.Experimental), + + // Lint: Allow customization of the amount of memory earmarked for each lint task (when lint + // runs in process) + LINT_RESERVED_MEMORY_PER_TASK("android.experimental.lint.reservedMemoryPerTask", ApiStage.Experimental), + + // Lint: Allow override of the version. Note that lint versions are generally 23 higher than + // the version of Android Gradle Plugin. So AGP 7.0.0-beta02 defaults to using lint + // 30.0.0-beta02 + LINT_VERSION_OVERRIDE("android.experimental.lint.version", ApiStage.Experimental), + + // User-specified flag to override the emulator gpu mode for Gradle Managed Devices, + // If the flag is not set, the emulator gpu mode will default to auto-no-window. + // Supported values are "auto", "auto-no-window", "host", "swiftshader_indirect", + // "angle_indirect" + GRADLE_MANAGED_DEVICE_EMULATOR_GPU_MODE("android.testoptions.manageddevices.emulator.gpu", ApiStage.Experimental), + + ANDROID_PRIVACY_SANDBOX_SDK_API_GENERATOR("android.privacySandboxSdk.apiGenerator", ApiStage.Experimental), + + ANDROID_PRIVACY_SANDBOX_SDK_API_GENERATOR_GENERATED_RUNTIME_DEPENDENCIES("android.privacySandboxSdk.apiGenerator.generatedRuntimeDependencies", ApiStage.Experimental), + + ANDROID_PRIVACY_SANDBOX_SDK_API_PACKAGER("android.privacySandboxSdk.apiPackager", ApiStage.Experimental), + /* --------------- + * DEPRECATED APIs + */ + + /* ------------ + * REMOVED APIs + */ + + @Suppress("unused") + BUILD_CACHE_DIR( + "android.buildCacheDir", + ApiStage.Removed(Version.VERSION_7_0, "The Android-specific build caches were superseded by the Gradle build cache (https://docs.gradle.org/current/userguide/build_cache.html).") + ), + + @Suppress("unused") + IDE_BUILD_TARGET_DENSITY( + PROPERTY_BUILD_DENSITY, + ApiStage.Removed(Version.VERSION_8_0, "Density property injection from Android Studio has been removed.") + ), + + ; + + override val status = stage.status + + override fun parse(value: Any): String { + if (value is CharSequence || value is Number) { + return value.toString() + } + throw IllegalArgumentException( + "Cannot parse project property " + + this.propertyName + + "='" + + value + + "' of type '" + + value.javaClass + + "' as string." + ) + } +} diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/TestRunnerArguments.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/TestRunnerArguments.kt new file mode 100644 index 000000000..10f7a78ac --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/TestRunnerArguments.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.build.gradle.options + +/** + * A list of standard instrumentation test runner keys. + * + * Details: https://developer.android.com/studio/test/command-line#AMOptionsSyntax + */ +enum class TestRunnerArguments( + private val key: String +) { + @Suppress("unused") + PACKAGE("package"), + + @Suppress("unused") + CLASS("class"), + + @Suppress("unused") + FUNC("func"), + + @Suppress("unused") + SIZE("size"), + + @Suppress("unused") + PERF("perf"), + + @Suppress("unused") + DEBUG("debug"), + + @Suppress("unused") + LOG("log"), + + @Suppress("unused") + EMMA("emma"), + + @Suppress("unused") + COVERAGE_FILE("coverageFile"), + ; + + fun getFullKey() : String = "$TEST_RUNNER_ARGS_PREFIX$key" + + fun getShortKey() : String = key + + companion object { + const val TEST_RUNNER_ARGS_PREFIX = "android.testInstrumentationRunnerArguments." + } +} \ No newline at end of file diff --git a/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Version.kt b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Version.kt new file mode 100644 index 000000000..8952c17a8 --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/android/build/gradle/options/Version.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.build.gradle.options + +/** An Android Gradle plugin version. */ +enum class Version( + + /** + * String value of the version, or `null` if it is not known (e.g., [VERSION_BEFORE_4_0]). + * + * Usage note: Do not use this field to construct a deprecation/removal message, use + * getDeprecationTargetMessage()/getRemovedVersionMessage() instead to ensure consistent message + * format. + */ + val versionString: String? +) { + + /** + * A version before version 4.0, used when the exact version is not known, except that it's + * guaranteed to be before 4.0. + */ + VERSION_BEFORE_4_0(null), + + VERSION_3_5("3.5"), + VERSION_3_6("3.6"), + VERSION_4_0("4.0"), + VERSION_4_1("4.1"), + VERSION_4_2("4.2"), + VERSION_7_0("7.0"), + VERSION_7_2("7.2"), + VERSION_7_3("7.3"), + VERSION_8_0("8.0"), + VERSION_8_1("8.1"), + VERSION_8_2("8.2"), + VERSION_8_3("8.3"), + VERSION_9_0("9.0"), + + ; // end of enums + + fun getDeprecationTargetMessage(): String { + check(this != VERSION_BEFORE_4_0) + return "It will be removed in version $versionString of the Android Gradle plugin." + } + + fun getRemovedVersionMessage(): String { + return if (this == VERSION_BEFORE_4_0) { + "It has been removed from the current version of the Android Gradle plugin." + } else { + "It was removed in version $versionString of the Android Gradle plugin." + } + } +} diff --git a/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultProjectSyncIssues.kt b/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultProjectSyncIssues.kt index 1a0f982fd..8ba431ac7 100644 --- a/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultProjectSyncIssues.kt +++ b/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultProjectSyncIssues.kt @@ -20,7 +20,7 @@ import com.android.builder.model.v2.models.ProjectSyncIssues import java.io.Serializable /** @author Akash Yadav */ -class DefaultProjectSyncIssues(override val syncIssues: Collection) : +data class DefaultProjectSyncIssues(override val syncIssues: Collection) : ProjectSyncIssues, Serializable { private val serialVersionUID = 1L } diff --git a/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSyncIssue.kt b/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSyncIssue.kt index bc47e88e4..5e9f314f6 100644 --- a/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSyncIssue.kt +++ b/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/DefaultSyncIssue.kt @@ -17,16 +17,16 @@ package com.itsaky.androidide.builder.model -import com.android.builder.model.v2.ide.SyncIssue import java.io.Serializable /** @author Akash Yadav */ -class DefaultSyncIssue( +data class DefaultSyncIssue( override val data: String?, override val message: String, override val multiLineMessage: List?, override val severity: Int, override val type: Int -) : SyncIssue, Serializable { +) : IDESyncIssue, Serializable { + private val serialVersionUID = 1L } diff --git a/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/IDESyncIssue.kt b/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/IDESyncIssue.kt new file mode 100644 index 000000000..c7b0e2136 --- /dev/null +++ b/subprojects/builder-model-impl/src/main/java/com/itsaky/androidide/builder/model/IDESyncIssue.kt @@ -0,0 +1,57 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.builder.model + +import com.android.build.gradle.options.StringOption +import com.android.build.gradle.options.StringOption.AAPT2_FROM_MAVEN_OVERRIDE +import com.android.builder.model.v2.ide.SyncIssue + +/** + * Sync issue model for AndroidIDE. + * + * @author Akash Yadav + */ +interface IDESyncIssue : SyncIssue { + companion object { + + /** + * Indicates that the Android Gradle Plugin that is being used by the project + * is too new for AndroidIDE. Data is `projectAgpVersion:maxAgpVersion`. + */ + const val TYPE_AGP_VERSION_TOO_NEW = -1 + + // Note: When adding new types, decrement the version by 1 + // The types that are defined in SyncIssue class have their values starting at 0 and incremented + // by 1 when new types are added. So, we could never know what is the latest type's value + } +} + +/** + * Checks whether this [SyncIssue] should be ignored and not reported to the user. + * + * @return Whether the issue can be ignored. + */ +fun SyncIssue.shouldBeIgnored() : Boolean { + if (this.type != SyncIssue.TYPE_UNSUPPORTED_PROJECT_OPTION_USE) { + return false + } + + // AndroidIDE sets android.aapt2FromMavenOverride in order to use a custom AAPT2 that is + // compatible with Android + return AAPT2_FROM_MAVEN_OVERRIDE.propertyName == this.data +} \ No newline at end of file diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/CachingProject.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/CachingProject.kt index 8ef05fffb..e87080907 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/CachingProject.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/CachingProject.kt @@ -42,7 +42,7 @@ open class CachingProject(val project: IProject) : IProject { private val log = ILogger.newInstance(javaClass.simpleName) private val projects = mutableListOf() - private val syncIssues = mutableMapOf() + private var syncIssues: DefaultProjectSyncIssues? = null override fun getProjects(): CompletableFuture> { return if (this.projects.isNotEmpty()) { @@ -64,23 +64,23 @@ open class CachingProject(val project: IProject) : IProject { } } - override fun getProjectSyncIssues(): CompletableFuture> { - return if (this.syncIssues.isNotEmpty()) { - log.info("Using cached sync issues...") - CompletableFuture.completedFuture(this.syncIssues) - } else this.project.getProjectSyncIssues().whenComplete { syncIssues, err -> - if (err != null || syncIssues == null) { + override fun getProjectSyncIssues(): CompletableFuture { + this.syncIssues?.also { + return CompletableFuture.completedFuture(it) + } + + return this.project.getProjectSyncIssues().whenComplete { projectSyncIssues, err -> + if (err != null || projectSyncIssues == null) { log.debug("Unable to fetch project sync issues from tooling server", err) return@whenComplete } - if (syncIssues.isEmpty()) { + if (projectSyncIssues.syncIssues.isEmpty()) { log.debug("No sync issues.") return@whenComplete } - this.syncIssues.clear() - this.syncIssues.putAll(syncIssues) + this.syncIssues = projectSyncIssues } } diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/IProjectManager.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/IProjectManager.kt index ee07e45d3..2a10ffb8f 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/IProjectManager.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/IProjectManager.kt @@ -18,6 +18,7 @@ package com.itsaky.androidide.projects import androidx.annotation.RestrictTo +import com.android.builder.model.v2.models.ProjectSyncIssues import com.itsaky.androidide.lookup.Lookup import com.itsaky.androidide.projects.api.AndroidModule import com.itsaky.androidide.projects.api.ModuleProject @@ -68,6 +69,11 @@ interface IProjectManager { val projectDir: File get() = File(projectDirPath) + /** + * Issues that were encountered during project synchronization. + */ + val projectSyncIssues: ProjectSyncIssues? + /** * Build variant information about Android modules in the project. * @@ -81,7 +87,7 @@ interface IProjectManager { * @param project The project proxy. */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - fun setupProject( + suspend fun setupProject( project: IProject = Lookup.getDefault().lookup(BuildService.KEY_PROJECT_PROXY)!! ) diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt index a833d21e9..36f310835 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt @@ -18,6 +18,7 @@ package com.itsaky.androidide.projects import androidx.annotation.RestrictTo +import com.android.builder.model.v2.models.ProjectSyncIssues import com.google.auto.service.AutoService import com.google.common.collect.ImmutableList import com.itsaky.androidide.eventbus.events.EventReceiver @@ -51,6 +52,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -84,6 +86,9 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { override val projectDirPath: String get() = projectPath + override val projectSyncIssues: ProjectSyncIssues? + get() = rootProject?.projectSyncIssues + companion object { private val log = ILogger.newInstance("ProjectManagerImpl") @@ -95,8 +100,13 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { } } - override fun setupProject(project: IProject) { - this.rootProject = ProjectTransformer().transform(CachingProject(project)) + override suspend fun setupProject(project: IProject) { + this.rootProject = withStopWatch("Transform project proxy") { + withContext(Dispatchers.IO) { + ProjectTransformer().transform(CachingProject(project)) + } + } + val rootProject = this.rootProject ?: return // build variants must be updated before the sources and classpaths are indexed @@ -104,6 +114,8 @@ class ProjectManagerImpl : IProjectManager, EventReceiver { androidBuildVariants = buildVariants } + log.info("Found ${rootProject.projectSyncIssues.syncIssues.size} project sync issues: ${rootProject.projectSyncIssues.syncIssues}") + withStopWatch("Setup project") { val indexerScope = CoroutineScope(Dispatchers.Default) val modulesFlow = flow { diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/Project.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/Project.kt index 3c52eedce..a70a1abfe 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/Project.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/api/Project.kt @@ -18,7 +18,7 @@ package com.itsaky.androidide.projects.api import com.android.builder.model.v2.ide.ProjectType -import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues +import com.android.builder.model.v2.models.ProjectSyncIssues import java.io.File import java.nio.file.Path @@ -27,11 +27,14 @@ import java.nio.file.Path * * @property rootProject The root Gradle project. * @property subProjects List of all project that are included the project. - * @property syncIssues The issues that occurred while syncing the project. + * @property projectSyncIssues The issues that occurred while syncing the project. * @author Akash Yadav */ -data class Project(val rootProject: GradleProject, val subProjects: List, - val syncIssues: Map) { +data class Project( + val rootProject: GradleProject, + val subProjects: List, + val projectSyncIssues: ProjectSyncIssues +) { /** * Finds the project by the given path. diff --git a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/util/ProjectTransformer.kt b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/util/ProjectTransformer.kt index 7f2301e68..ddb6ead70 100644 --- a/subprojects/projects/src/main/java/com/itsaky/androidide/projects/util/ProjectTransformer.kt +++ b/subprojects/projects/src/main/java/com/itsaky/androidide/projects/util/ProjectTransformer.kt @@ -57,8 +57,11 @@ class ProjectTransformer { "Root project must be either an Android project or a Gradle project") } - return Project(rootProject, CopyOnWriteArrayList(transform(allProjects, project)), - project.getProjectSyncIssues().get()) + return Project( + rootProject, + CopyOnWriteArrayList(transform(allProjects, project)), + project.getProjectSyncIssues().get() + ) } catch (error: Throwable) { log.error("Unable to transform project", error) return null diff --git a/subprojects/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt b/subprojects/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt index 126d36de2..0d5b5fde3 100644 --- a/subprojects/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt +++ b/subprojects/projects/src/test/java/com/itsaky/androidide/projects/api/ModuleProjectTest.kt @@ -30,6 +30,7 @@ import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher import com.itsaky.androidide.utils.FileProvider import com.itsaky.androidide.utils.SourceClassTrie.SourceNode +import kotlinx.coroutines.runBlocking import java.io.File import java.nio.file.Files import kotlin.io.path.pathString @@ -205,7 +206,7 @@ class ModuleProjectTest { } val projectManager = ProjectManagerImpl.getInstance() - projectManager.setupProject() + runBlocking { projectManager.setupProject() } val rootProject = IProjectManager.getInstance().rootProject assertThat(rootProject).isNotNull() diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt index c211b602b..fe8a77373 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/ToolingApiServerImpl.kt @@ -159,8 +159,7 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) : stopWatch.lapFromLast("Project read successful") stopWatch.log() - this.project.rootProject = project.rootProject - this.project.projects = project.projects + this.project.setFrom(project) this.isInitialized = true notifyBuildSuccess(emptyList()) diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/ProjectImpl.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/ProjectImpl.kt index dab75f531..28984bd0c 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/ProjectImpl.kt +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/internal/ProjectImpl.kt @@ -33,11 +33,11 @@ import java.util.concurrent.CompletableFuture /** * @author Akash Yadav */ -@Suppress("JoinDeclarationAndAssignment") internal class ProjectImpl( var rootProject: IGradleProject? = null, var rootProjectPath: String? = null, - var projects: List = emptyList() + var projects: List = emptyList(), + var projectSyncIssues: DefaultProjectSyncIssues = DefaultProjectSyncIssues(emptyList()) ) : IProject, Serializable { private val serialVersionUID = 1L @@ -57,6 +57,13 @@ internal class ProjectImpl( this.selectedProject = ForwardingProject() } + fun setFrom(other: ProjectImpl) { + this.rootProject = other.rootProject + this.rootProjectPath = other.rootProjectPath + this.projects = other.projects + this.projectSyncIssues = other.projectSyncIssues + } + private fun getProject(path: String): IGradleProject? { return if (path.isBlank()) rootProject else projects.find { it.getMetadata().get().projectPath == path @@ -69,8 +76,10 @@ internal class ProjectImpl( } } - override fun getProjectSyncIssues(): CompletableFuture> { - return CompletableFuture.completedFuture(emptyMap()) + override fun getProjectSyncIssues(): CompletableFuture { + return CompletableFuture.completedFuture( + this.projectSyncIssues ?: DefaultProjectSyncIssues(emptyList()) + ) } override fun selectProject(param: StringParameter): CompletableFuture { diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AbstractModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AbstractModelBuilder.kt index 041cb204f..db6ab14d4 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AbstractModelBuilder.kt +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AbstractModelBuilder.kt @@ -16,7 +16,10 @@ */ package com.itsaky.androidide.tooling.impl.sync +import com.android.builder.model.v2.ide.SyncIssue import com.android.builder.model.v2.models.Versions +import com.itsaky.androidide.builder.model.DefaultSyncIssue +import com.itsaky.androidide.builder.model.IDESyncIssue import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams import com.itsaky.androidide.tooling.impl.util.StopWatch import com.itsaky.androidide.utils.AndroidPluginVersion @@ -54,9 +57,13 @@ abstract class AbstractModelBuilder( * [AndroidPluginVersion.LATEST_TESTED], warns the user. * * @param versions The [Versions] model. + * @param syncIssueReporter [ISyncIssueReporter] for reporting issues with the Android Gradle Plugin version. */ @JvmStatic - protected fun checkAgpVersion(versions: Versions) { + protected fun checkAgpVersion( + versions: Versions, + syncIssueReporter: ISyncIssueReporter + ) { val agpVersion = AndroidPluginVersion.parse(versions.agp) @@ -72,14 +79,14 @@ abstract class AbstractModelBuilder( // Warn the user if the project is using a newer AGP version if (!newerAgpWarned.get() && agpVersion > LATEST_TESTED) { - log() - log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - log("You are using Android Gradle Plugin version that has not been tested with AndroidIDE.") - log("Newer versions may not work properly!") - log("Current version : ${agpVersion.toStringSimple()}") - log("Latest tested : ${LATEST_TESTED.toStringSimple()}") - log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - log() + val syncIssue = DefaultSyncIssue( + data = "${agpVersion.toStringSimple()}:${LATEST_TESTED.toStringSimple()}", + message = "You are using Android Gradle Plugin version that has not been tested with AndroidIDE.", + multiLineMessage = null, + severity = SyncIssue.SEVERITY_WARNING, + type = IDESyncIssue.TYPE_AGP_VERSION_TOO_NEW + ) + syncIssueReporter.report(syncIssue) newerAgpWarned.set(true) } } diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AndroidProjectModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AndroidProjectModelBuilder.kt index 844ae31d2..c033da698 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AndroidProjectModelBuilder.kt +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/AndroidProjectModelBuilder.kt @@ -19,6 +19,7 @@ package com.itsaky.androidide.tooling.impl.sync import com.android.builder.model.v2.models.AndroidProject import com.android.builder.model.v2.models.BasicAndroidProject import com.android.builder.model.v2.models.ModelBuilderParameter +import com.android.builder.model.v2.models.ProjectSyncIssues import com.android.builder.model.v2.models.VariantDependencies import com.itsaky.androidide.tooling.api.IAndroidProject import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams @@ -33,7 +34,7 @@ class AndroidProjectModelBuilder(initializationParams: InitializeProjectParams) AbstractModelBuilder(initializationParams) { override fun build(param: AndroidProjectModelBuilderParams): IAndroidProject { - val (controller, module, versions) = param + val (controller, module, versions, syncIssueReporter) = param val androidParams = initializationParams.androidParams val projectPath = module.gradleProject.path @@ -47,7 +48,8 @@ class AndroidProjectModelBuilder(initializationParams: InitializeProjectParams) var androidVariant = androidParams.variantSelections[projectPath] if (androidVariant != null && !variantNames.contains(androidVariant)) { - log("Configured variant '$androidVariant' not found for project '$projectPath'. Falling back to default variant.") + log( + "Configured variant '$androidVariant' not found for project '$projectPath'. Falling back to default variant.") androidVariant = null } @@ -65,6 +67,10 @@ class AndroidProjectModelBuilder(initializationParams: InitializeProjectParams) it.dontBuildRuntimeClasspath = false } + controller.findModel(module, ProjectSyncIssues::class.java)?.also { syncIssues -> + syncIssueReporter.reportAll(syncIssues) + } + return AndroidProjectImpl( module.gradleProject, configurationVariant, diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ISyncIssueReporter.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ISyncIssueReporter.kt new file mode 100644 index 000000000..a35565e34 --- /dev/null +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ISyncIssueReporter.kt @@ -0,0 +1,48 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.tooling.impl.sync + +import com.android.builder.model.v2.ide.SyncIssue +import com.android.builder.model.v2.models.ProjectSyncIssues + +/** + * A sync issue reporter reports project synchronization issues. + * + * @author Akash Yadav + */ +fun interface ISyncIssueReporter { + + /** + * Report all sync issues from the given [ProjectSyncIssues]. + */ + fun reportAll(issues: ProjectSyncIssues) { + reportAll(issues.syncIssues) + } + + /** + * Report all the given [sync issues][issues]. + */ + fun reportAll(issues: Collection) { + issues.forEach(this::report) + } + + /** + * Report the give [sync issue][issue]. + */ + fun report(issue: SyncIssue) +} \ No newline at end of file diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModuleProjectModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModuleProjectModelBuilder.kt index 545956a13..52e67e78d 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModuleProjectModelBuilder.kt +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/ModuleProjectModelBuilder.kt @@ -31,9 +31,14 @@ class ModuleProjectModelBuilder(initializationParams: InitializeProjectParams) : override fun build(param: ModuleProjectModelBuilderParams): IModuleProject { val versions = getAndroidVersions(param.module, param.controller) return if (versions != null) { - checkAgpVersion(versions) + checkAgpVersion(versions, param.syncIssueReporter) AndroidProjectModelBuilder(initializationParams) - .build(Triple(param.controller, param.module, versions)) + .build(AndroidProjectModelBuilderParams( + param.controller, + param.module, + versions, + param.syncIssueReporter + )) } else { JavaProjectModelBuilder(initializationParams).build( JavaProjectModelBuilderParams(param)) diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/RootModelBuilder.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/RootModelBuilder.kt index 4519bd5a6..db3652884 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/RootModelBuilder.kt +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/RootModelBuilder.kt @@ -17,9 +17,13 @@ package com.itsaky.androidide.tooling.impl.sync +import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues +import com.itsaky.androidide.builder.model.DefaultSyncIssue +import com.itsaky.androidide.builder.model.shouldBeIgnored import com.itsaky.androidide.tooling.api.IAndroidProject import com.itsaky.androidide.tooling.api.IProject import com.itsaky.androidide.tooling.api.messages.InitializeProjectParams +import com.itsaky.androidide.tooling.api.util.AndroidModulePropertyCopier import com.itsaky.androidide.tooling.impl.Main import com.itsaky.androidide.tooling.impl.Main.finalizeLauncher import com.itsaky.androidide.tooling.impl.internal.ProjectImpl @@ -55,20 +59,48 @@ class RootModelBuilder(initializationParams: InitializeProjectParams) : val rootProjectVersions = getAndroidVersions(rootModule, controller) + val syncIssues = hashSetOf() + val syncIssueReporter = ISyncIssueReporter { + if (it.shouldBeIgnored()) { + // this SyncIssue should not be shown to the user + return@ISyncIssueReporter + } + + val issue = it as? DefaultSyncIssue ?: AndroidModulePropertyCopier.copy(it) + syncIssues.add(issue) + } + val rootProject = if (rootProjectVersions != null) { // Root project is an Android project - checkAgpVersion(rootProjectVersions) - AndroidProjectModelBuilder(initializationParams).build(Triple(controller, rootModule, rootProjectVersions)) + checkAgpVersion(rootProjectVersions, syncIssueReporter) + AndroidProjectModelBuilder(initializationParams) + .build(AndroidProjectModelBuilderParams( + controller, + rootModule, + rootProjectVersions, + syncIssueReporter + )) } else { GradleProjectModelBuilder(initializationParams).build(rootModule.gradleProject) } val projects = ideaModules.map { ideaModule -> ModuleProjectModelBuilder(initializationParams).build( - ModuleProjectModelBuilderParams(controller, ideaProject, ideaModule, modulePaths)) + ModuleProjectModelBuilderParams( + controller, + ideaProject, + ideaModule, + modulePaths, + syncIssueReporter + )) } - return@action ProjectImpl(rootProject, rootModule.gradleProject.path, projects) + return@action ProjectImpl( + rootProject, + rootModule.gradleProject.path, + projects, + DefaultProjectSyncIssues(syncIssues) + ) } finalizeLauncher(executor) diff --git a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/types.kt b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/types.kt index 1d384c264..fc29177a5 100644 --- a/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/types.kt +++ b/subprojects/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/sync/types.kt @@ -22,10 +22,28 @@ import org.gradle.tooling.BuildController import org.gradle.tooling.model.idea.IdeaModule import org.gradle.tooling.model.idea.IdeaProject -typealias AndroidProjectModelBuilderParams = Triple +/** + * Parameters for building model for an Android project. + * + * @property controller The build controller that will be used to fetch project models. + * @property module The [IdeaModule] to fetch the models from. + * @property versions The Android Gradle Plugin version information. + * @property syncIssueReporter [ISyncIssueReporter] to report project synchronization issues. + */ +data class AndroidProjectModelBuilderParams( + val controller: BuildController, + val module: IdeaModule, + val versions: Versions, + val syncIssueReporter: ISyncIssueReporter +) -class ModuleProjectModelBuilderParams(val controller: BuildController, project: IdeaProject, - module: IdeaModule, modulePaths: Map) : JavaProjectModelBuilderParams( +class ModuleProjectModelBuilderParams( + val controller: BuildController, + project: IdeaProject, + module: IdeaModule, + modulePaths: Map, + val syncIssueReporter: ISyncIssueReporter +) : JavaProjectModelBuilderParams( project, module, modulePaths) open class JavaProjectModelBuilderParams(val project: IdeaProject, val module: IdeaModule, diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.kt index 343f4b464..3fdc4e3cc 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/IProject.kt @@ -43,7 +43,7 @@ interface IProject : IProjectQueries { * Get the project sync issues. */ @JsonRequest - fun getProjectSyncIssues(): CompletableFuture> + fun getProjectSyncIssues(): CompletableFuture companion object { diff --git a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/util/AndroidModulePropertyCopier.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/util/AndroidModulePropertyCopier.kt index 41d250ccf..8b49c60ac 100644 --- a/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/util/AndroidModulePropertyCopier.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/util/AndroidModulePropertyCopier.kt @@ -306,5 +306,5 @@ object AndroidModulePropertyCopier { } fun copy(issue: SyncIssue) = - DefaultSyncIssue(issue.data, issue.message, issue.multiLineMessage, issue.severity, issue.type) + DefaultSyncIssue(issue.data, issue.message, issue.multiLineMessage?.toTypedArray()?.let { listOf(*it) }, issue.severity, issue.type) } \ No newline at end of file diff --git a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt index ff2afc0f1..2133c1507 100644 --- a/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt +++ b/xml-inflater/src/test/java/com/itsaky/androidide/inflater/XmlInflaterTest.kt @@ -27,6 +27,7 @@ import com.itsaky.androidide.projects.api.AndroidModule import com.itsaky.androidide.projects.builder.BuildService import com.itsaky.androidide.projects.util.findAppModule import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher +import kotlinx.coroutines.runBlocking import org.junit.Ignore import org.robolectric.Robolectric import java.util.concurrent.atomic.AtomicBoolean @@ -47,7 +48,7 @@ object XmlInflaterTest { assertThat(result?.isSuccessful).isTrue() Lookup.getDefault().register(BuildService.KEY_PROJECT_PROXY, project) - IProjectManager.getInstance().setupProject(project) + runBlocking { IProjectManager.getInstance().setupProject(project) } init.set(true) } }