Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add distribution support to gradle-plugin #297

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tag doesn't seem to be relevant currently - I'd suggest removing it until we have an upload-related task around distro to avoid confusion

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tag is used to define the release 'channel' (when the SDK checks for an update we send the tag and only consider builds a with matching tag). Possibly we should have a distribution upload task but I was worried that just makes it more confusing.

// 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
Loading