From c82c8866805109eb42d94faeefb8b0e53b68b74b 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 | 68 +++++++++++++++++ .../android/gradle/tasks/reaper/Register.kt | 8 +- gradle/libs.versions.toml | 2 +- 8 files changed, 253 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 6c2a84da..80ab007a 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 - 2024-12-12 Changes since 4.0.4-beta01: 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..1e8f9f4f --- /dev/null +++ b/gradle-plugin/plugin/src/main/kotlin/com/emergetools/android/gradle/tasks/distribution/Register.kt @@ -0,0 +1,68 @@ +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) + } +} 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 26c12367..90f13cad 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" +emerge-gradle-plugin = "4.1.0" emerge-performance = "2.1.2" emerge-reaper = "1.0.0" emerge-snapshots = "1.3.0-rc03"