From 5faa01b9632171878fbaf291359d3ec2b3c8a45f Mon Sep 17 00:00:00 2001 From: Hector Date: Tue, 22 Oct 2024 17:07:51 +0100 Subject: [PATCH] [distribution] Add Gradle plugin for distribution --- gradle-plugin/CHANGELOG.md | 4 + gradle-plugin/README.md | 73 +++++++++++++++++-- .../android/gradle/EmergePlugin.kt | 16 +++- .../android/gradle/EmergePluginExtension.kt | 23 ++++++ .../distribution/DistributionPreflight.kt | 72 ++++++++++++++++++ .../gradle/tasks/distribution/Register.kt | 1 + .../android/gradle/tasks/reaper/Register.kt | 8 +- gradle/libs.versions.toml | 2 +- 8 files changed, 186 insertions(+), 13 deletions(-) create mode 100644 gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/distribution/DistributionPreflight.kt create mode 100644 gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/distribution/Register.kt diff --git a/gradle-plugin/CHANGELOG.md b/gradle-plugin/CHANGELOG.md index 45d0c294..569c6223 100644 --- a/gradle-plugin/CHANGELOG.md +++ b/gradle-plugin/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- Add build distribution support to Gradle plugin. [#297](https://github.com/EmergeTools/emerge-android/pull/297) + ## 4.0.4-beta01 - 2024-12-10 - Update property access to support Gradle Plugin isolation (thanks diff --git a/gradle-plugin/README.md b/gradle-plugin/README.md index 671629e3..92efc4e6 100644 --- a/gradle-plugin/README.md +++ b/gradle-plugin/README.md @@ -257,9 +257,10 @@ emerge { // The build variants Reaper is enabled for. // When Reaper is enabled the application bytecode will be instrumented to support Reaper. enabledVariants.set(listOf("release", "releaseVariant2")) - // The key used to identify Reaper reports for your organization. Emerge recommends setting this as an environment variable - // Note: This key is not the same as the API key used for uploading to Emerge - you can find this - publishableApiKey.set(System.getenv("REAPER_API_TOKEN")) + // The key used to identify Reaper reports for your organization. Emerge recommends setting this as an environment variable. + // This key can be found at https://www.emergetools.com/settings?tab=feature-configuration&cards=reaper_enabled + // Note: This key is not the same as the API token used for uploading to Emerge. + publishableApiKey.set(System.getenv("REAPER_API_KEY")) // Optional, defaults to 'release' tag.set("release") @@ -277,6 +278,51 @@ emerge { | `enabledVariants` | `List` | `emptyList()` | The build variants Reaper is enabled for. | | `tag` | `String` | `release` | The build tag to use for grouping builds in the Emerge dashboard. | +### Distribution + +The `distribution` extension allows you to configure build distribution specific fields. + +See the [build distribution](https://docs.emergetools.com/docs/distribution-setup-android) docs for more information. + +#### Tasks + +| Task | Description | +|----------------------------------------|------------------------------------------------------------------------------------------------| +| `emergeDistributionPreflight{Variant}` | Run a preflight check to validate if distribution is properly set up for the specific variant. | + +#### Configuration + +The `distribution` extension allows you to configure Distribution-specific fields. + +```kotlin +emerge { + // .. + + distribution { + // The build variants Distribution is enabled for. + enabledVariants.set(listOf("release", "staging")) + // The key used to authenticate downloads for future updates. + // Emerge recommends setting this as an environment variable. + // This key can be found at https://www.emergetools.com/settings?tab=feature-configuration&cards=distribution_enabled + // Note: This key is not the same as the API token used for uploading to Emerge. + apiKey.set(System.getenv("DISTRIBUTION_API_KEY")) + + // Optional, defaults to 'release' + tag.set("release") + // Alternatively, use `setFromVariant()` to set the tag from the Android build variant name + tag.setFromVariant() + } +} +``` + +##### Fields + +| Field | Type | Default | Description | +|---------------------|----------------|---------------|-----------------------------------------------------------------------------| +| `apiKey` | `String` | | This key is used to authenticate update downloads. | +| `enabledVariants` | `List` | `emptyList()` | The build variants Distribution is enabled for. | +| `tag` | `String` | `release` | The build tag to use for determining which builds are possible updates. | + ### Performance #### Tasks @@ -396,15 +442,30 @@ emerge { // The build variants Reaper is enabled for. // When Reaper is enabled the application bytecode will be instrumented to support Reaper. enabledVariants.set(listOf("release", "releaseVariant2")) - // The key used to identify Reaper reports for your organization. Emerge recommends setting this as an environment variable - // Note: This key is not the same as the API key used for uploading to Emerge - you can find this - publishableApiKey.set(System.getenv("REAPER_API_TOKEN")) + // The key used to identify Reaper reports for your organization. Emerge recommends setting this as an environment variable. + // This key can be found at https://www.emergetools.com/settings?tab=feature-configuration&cards=reaper_enabled + // Note: This key is not the same as the API token used for uploading to Emerge. + publishableApiKey.set(System.getenv("REAPER_API_KEY")) // Optional, defaults to 'release' tag.set("release") // Alternatively, use `setFromVariant()` to set the tag from the Android build variant name tag.setFromVariant() + } + distribution { + // The build variants Distribution is enabled for. + enabledVariants.set(listOf("release", "staging")) + // The key used to authenticate downloads for future updates. + // Emerge recommends setting this as an environment variable. + // This key can be found at https://www.emergetools.com/settings?tab=feature-configuration&cards=distribution_enabled + // Note: This key is not the same as the API token used for uploading to Emerge. + apiKey.set(System.getenv("DISTRIBUTION_API_KEY")) + + // Optional, defaults to 'release' + tag.set("release") + // Alternatively, use `setFromVariant()` to set the tag from the Android build variant name + tag.setFromVariant() } performance { diff --git a/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/EmergePlugin.kt b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/EmergePlugin.kt index a630909f..d4a100e5 100644 --- a/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/EmergePlugin.kt +++ b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/EmergePlugin.kt @@ -9,6 +9,7 @@ import com.android.build.api.variant.ApplicationVariant import com.android.build.api.variant.TestAndroidComponentsExtension import com.android.build.gradle.internal.dsl.BaseAppModuleExtension import com.emergetools.android.gradle.instrumentation.reaper.ReaperClassLoadClassVisitorFactory +import com.emergetools.android.gradle.tasks.distribution.registerDistributionTasks import com.emergetools.android.gradle.tasks.internal.SaveExtensionConfigTask import com.emergetools.android.gradle.tasks.perf.registerGeneratePerfProjectTask import com.emergetools.android.gradle.tasks.perf.registerPerformanceTasks @@ -96,9 +97,9 @@ class EmergePlugin : Plugin { registerSizeTasks(appProject, emergeExtension, variant) } - // Always register the Reaper initialization task even if Reaper is disabled since users use - // it to help get Reaper setup for the first time. + // Reaper and distribution handle the enabled checks themselves: registerReaperTasks(appProject, emergeExtension, variant) + registerDistributionTasks(appProject, emergeExtension, variant) registerReaperTransform( project = appProject, @@ -294,6 +295,17 @@ class EmergePlugin : Plugin { ) addItem("tag (optional): ${extension.reaperOptions.tag.orEmpty()}", reaperHeading) + val distributionHeading = addHeading("distribution") + addItem( + "enabledVariants: ${extension.distributionOptions.enabledVariants.getOrElse(emptyList())}", + distributionHeading + ) + addItem( + "apiKey: ${if (extension.distributionOptions.apiKey.isPresent) "*****" else "MISSING"}", + distributionHeading + ) + addItem("tag (optional): ${extension.distributionOptions.tag.orEmpty()}", distributionHeading) + val performanceHeading = addHeading("performance") addItem("projectPath: ${extension.perfOptions.projectPath.orEmpty()}", performanceHeading) addItem("tag (optional): ${extension.perfOptions.tag.orEmpty()}", performanceHeading) diff --git a/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/EmergePluginExtension.kt b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/EmergePluginExtension.kt index 2079b702..1249fd39 100644 --- a/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/EmergePluginExtension.kt +++ b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/EmergePluginExtension.kt @@ -58,6 +58,13 @@ abstract class EmergePluginExtension @Inject constructor(objects: ObjectFactory) action.execute(reaperOptions) } + @get:Nested + abstract val distributionOptions: DistributionOptions + + fun distribution(action: Action) { + action.execute(distributionOptions) + } + @get:Nested abstract val vcsOptions: VCSOptions @@ -223,6 +230,22 @@ abstract class ReaperOptions : ProductOptions() { abstract val publishableApiKey: Property } +abstract class DistributionOptions : ProductOptions() { + /** + * The list of build variants Distribution is enabled for. + */ + abstract val enabledVariants: ListProperty + + /** + * The key used to authenticate downloads for future updates. + * Emerge recommends setting this as an environment variable. + * This key can be found at https://www.emergetools.com/settings?tab=feature-configuration&cards=distribution_enabled + * Note: This key is not the same as the API token used for uploading to Emerge. + */ + abstract val publishableApiKey: Property + +} + abstract class DebugOptions : ProductOptions() { /** diff --git a/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/distribution/DistributionPreflight.kt b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/distribution/DistributionPreflight.kt new file mode 100644 index 00000000..d52c3169 --- /dev/null +++ b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/distribution/DistributionPreflight.kt @@ -0,0 +1,72 @@ +package com.emergetools.android.gradle.tasks.distribution + +import com.emergetools.android.gradle.tasks.base.BasePreflightTask +import com.emergetools.android.gradle.util.preflight.Preflight +import com.emergetools.android.gradle.util.preflight.PreflightFailure +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction + +abstract class DistributionPreflight : BasePreflightTask() { + + @get:Input + @get:Optional + abstract val hasEmergeApiToken: Property + + @get:Input + @get:Optional + abstract val distributionApiKey: Property + + @get:Input + @get:Optional + abstract val distributionTag: Property + + @get:Input + @get:Optional + abstract val distributionEnabled: Property + + @get:Input + abstract val variantName: Property + + @get:Input + abstract val hasDistributionImplementationDependency: Property + + @TaskAction + fun execute() { + val preflight = Preflight("Reaper preflight check") + + val hasEmergeApiToken = hasEmergeApiToken.getOrElse(false) + preflight.add("Emerge API token set") { + if (!hasEmergeApiToken) { + throw PreflightFailure("Emerge API token not set. See https://docs.emergetools.com/docs/uploading-basics#obtain-an-api-key") + } + } + + val variantName = variantName.get() + preflight.add("enabled for variant: $variantName") { + if (!distributionEnabled.getOrElse(false)) { + throw PreflightFailure("Distribution not enabled for variant $variantName. Make sure \"${variantName}\" is included in `distribution.enabledVariants`") + } + } + + preflight.add("apiKey set") { + val key = distributionApiKey.orNull + if (key == null) { + throw PreflightFailure("apiKey not set. See https://docs.emergetools.com/docs/distribution-setup-android#configure-the-sdk") + } + if (key == "") { + throw PreflightFailure("apiKey must not be empty. See https://docs.emergetools.com/docs/distribution-setup-android#configure-the-sdk") + } + } + + preflight.add("Runtime SDK added") { + if (!hasDistributionImplementationDependency.getOrElse(false)) { + throw PreflightFailure("Distribution runtime SDK missing as an implementation dependency. See https://docs.emergetools.com/docs/distribution-setup-android#install-the-sdk") + } + } + + preflight.addSubPreflight(buildVcsPreflight()) + preflight.logOutput(logger) + } +} diff --git a/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/distribution/Register.kt b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/distribution/Register.kt new file mode 100644 index 00000000..3ab72242 --- /dev/null +++ b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/distribution/Register.kt @@ -0,0 +1 @@ +package com.emergetools.android.gradle.tasks.distribution import com.android.build.api.variant.Variant import com.emergetools.android.gradle.EmergePlugin.Companion.EMERGE_TASK_PREFIX import com.emergetools.android.gradle.EmergePluginExtension import com.emergetools.android.gradle.tasks.base.BasePreflightTask.Companion.setPreflightTaskInputs import com.emergetools.android.gradle.util.capitalize import com.emergetools.android.gradle.util.hasDependency import org.gradle.api.Project private const val EMERGE_DISTRIBUTION_TASK_GROUP = "Emerge Distribution" private const val DISTRIBUTION_DEP_GROUP = "com.emergetools.distribution" private const val DISTRIBUTION_DEP_NAME = "distribution" private const val PLACEHOLDER_API_KEY = "emerge.distribution.apiKey" private const val PLACEHOLDER_TAG = "emerge.distribution.tag" fun registerDistributionTasks( appProject: Project, extension: EmergePluginExtension, variant: Variant, ) { appProject.logger.debug( "Registering Distribution tasks for variant ${variant.name} in project ${appProject.path}" ) registerDistributionPreflightTask(appProject, extension, variant) // Set the build tag: variant.manifestPlaceholders.put( PLACEHOLDER_TAG, extension.distributionOptions.tag.getOrElse("release") ) // API key is special in that we only want to put it in the manifest if distribution is actually // enabled: val enabledVariants = extension.distributionOptions.enabledVariants.getOrElse(emptyList()) if (enabledVariants.contains(variant.name)) { appProject.logger.debug("Distribution enabled for variant ${variant.name}") val apiKey = extension.distributionOptions.apiKey.getOrElse("") variant.manifestPlaceholders.put(PLACEHOLDER_API_KEY, apiKey) } else { variant.manifestPlaceholders.put(PLACEHOLDER_API_KEY, "") } } private fun registerDistributionPreflightTask( appProject: Project, extension: EmergePluginExtension, variant: Variant, ) { val preflightTaskName = "${EMERGE_TASK_PREFIX}DistributionPreflight${variant.name.capitalize()}" appProject.tasks.register(preflightTaskName, DistributionPreflight::class.java) { it.group = EMERGE_DISTRIBUTION_TASK_GROUP it.description = "Validate Distribution is properly set up for variant ${variant.name}" it.variantName.set(variant.name) it.hasEmergeApiToken.set(!extension.apiToken.orNull.isNullOrBlank()) it.distributionEnabled.set( extension.distributionOptions.enabledVariants.getOrElse(emptyList()).contains(variant.name) ) it.distributionApiKey.set(extension.distributionOptions.apiKey) it.distributionApiKey.set(extension.distributionOptions.tag) it.hasDistributionImplementationDependency.set( hasDependency(appProject, variant, DISTRIBUTION_DEP_GROUP, DISTRIBUTION_DEP_NAME) ) it.setPreflightTaskInputs(extension) } } \ No newline at end of file diff --git a/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/reaper/Register.kt b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/reaper/Register.kt index 0b415f21..3ed253df 100644 --- a/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/reaper/Register.kt +++ b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/reaper/Register.kt @@ -10,9 +10,9 @@ import com.emergetools.android.gradle.util.capitalize import com.emergetools.android.gradle.util.hasDependency import org.gradle.api.Project -const val EMERGE_REAPER_TASK_GROUP = "Emerge reaper" -const val REAPER_DEP_GROUP = "com.emergetools.reaper" -const val REAPER_DEP_NAME = "reaper" +private const val EMERGE_REAPER_TASK_GROUP = "Emerge reaper" +private const val REAPER_DEP_GROUP = "com.emergetools.reaper" +private const val REAPER_DEP_NAME = "reaper" fun registerReaperTasks( appProject: Project, @@ -20,7 +20,7 @@ fun registerReaperTasks( variant: Variant, ) { appProject.logger.debug( - "Registering reaper tasks for variant ${variant.name} in project ${appProject.path}" + "Registering Reaper tasks for variant ${variant.name} in project ${appProject.path}" ) registerReaperPreflightTask(appProject, extension, variant) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index da6b2410..f6c104a8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -29,7 +29,7 @@ runtime-android = "1.7.3" foundation-layout-android = "1.7.3" # internal -emerge-gradle-plugin = "4.0.4-beta01" +emerge-gradle-plugin = "4.0.4-beta02" emerge-performance = "2.1.2" emerge-reaper = "1.0.0" emerge-snapshots = "1.3.0-rc03"