Skip to content

Commit

Permalink
[distribution] Add Gradle plugin for distribution
Browse files Browse the repository at this point in the history
  • Loading branch information
chromy committed Dec 13, 2024
1 parent 5ba51dd commit 566216e
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 13 deletions.
4 changes: 4 additions & 0 deletions gradle-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
73 changes: 67 additions & 6 deletions gradle-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -277,6 +278,51 @@ emerge {
| `enabledVariants` | `List<String>` | `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<String>` | `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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -96,9 +97,9 @@ class EmergePlugin : Plugin<Project> {
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,
Expand Down Expand Up @@ -294,6 +295,17 @@ class EmergePlugin : Plugin<Project> {
)
addItem("tag (optional): ${extension.reaperOptions.tag.orEmpty()}", reaperHeading)

val distributionHeading = addHeading("distribution")
addItem(
"enabledVariants: ${extension.distributionOptions.enabledVariants.getOrElse(emptyList())}",
distributionHeading
)
addItem(
"publishableApiKey: ${if (extension.distributionOptions.publishableApiKey.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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ abstract class EmergePluginExtension @Inject constructor(objects: ObjectFactory)
action.execute(reaperOptions)
}

@get:Nested
abstract val distributionOptions: DistributionOptions

fun distribution(action: Action<DistributionOptions>) {
action.execute(distributionOptions)
}

@get:Nested
abstract val vcsOptions: VCSOptions

Expand Down Expand Up @@ -223,6 +230,22 @@ abstract class ReaperOptions : ProductOptions() {
abstract val publishableApiKey: Property<String>
}

abstract class DistributionOptions : ProductOptions() {
/**
* The list of build variants Distribution is enabled for.
*/
abstract val enabledVariants: ListProperty<String>

/**
* 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<String>

}

abstract class DebugOptions : ProductOptions() {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Boolean>

@get:Input
@get:Optional
abstract val distributionApiKey: Property<String>

@get:Input
@get:Optional
abstract val distributionTag: Property<String>

@get:Input
@get:Optional
abstract val distributionEnabled: Property<Boolean>

@get:Input
abstract val variantName: Property<String>

@get:Input
abstract val hasDistributionImplementationDependency: Property<Boolean>

@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)
}
}
Original file line number Diff line number Diff line change
@@ -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.publishableApiKey.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.publishableApiKey)
it.distributionApiKey.set(extension.distributionOptions.tag)
it.hasDistributionImplementationDependency.set(
hasDependency(appProject, variant, DISTRIBUTION_DEP_GROUP, DISTRIBUTION_DEP_NAME)
)
it.setPreflightTaskInputs(extension)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ 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,
extension: EmergePluginExtension,
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)
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 566216e

Please sign in to comment.