From 3a2cfd22e6575a67b1e8800e9563dd9cbb54a493 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Wed, 20 Jan 2021 20:27:04 +0530 Subject: [PATCH] Migrate versioning to Gradle plugin and automate version bumps (#1282) --- .github/workflows/draft_new_release.yml | 61 +++++++++-- app/build.gradle.kts | 3 +- app/version.properties | 6 ++ buildSrc/build.gradle.kts | 5 + buildSrc/buildDependencies.gradle | 2 + buildSrc/src/main/java/VersioningPlugin.kt | 115 +++++++++++++++++++++ 6 files changed, 180 insertions(+), 12 deletions(-) create mode 100644 app/version.properties create mode 100644 buildSrc/src/main/java/VersioningPlugin.kt diff --git a/.github/workflows/draft_new_release.yml b/.github/workflows/draft_new_release.yml index 74702ee4..57719eb2 100644 --- a/.github/workflows/draft_new_release.yml +++ b/.github/workflows/draft_new_release.yml @@ -9,32 +9,73 @@ jobs: name: "Draft a new release" runs-on: ubuntu-latest steps: - - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f - with: - ref: 'release' - name: Extract version from milestone run: | VERSION="${{ github.event.milestone.title }}" - echo "RELEASE_VERSION=${VERSION/v/}" >> $GITHUB_ENV + RELEASE_VERSION="${VERSION/v/}" + # Transforms 1.13.2 to 1.13 so that we can re-use the same + # branch for patch releases. + BRANCH_VERSION="${RELEASE_VERSION:$i:-2}" + if [[ "${RELEASE_VERSION: -1}" == "0" ]]; then + CHECKOUT_REF="develop" + else + CHECKOUT_REF="release-${BRANCH_VERSION}" + fi + + # Export variables separately so the scripting above is more legible, + # and we can actually use them within this block. Changes to $GITHUB_ENV + # only affect the next step, not the current one. + echo "RELEASE_VERSION=${RELEASE_VERSION}" >> $GITHUB_ENV + echo "CHECKOUT_REF=${CHECKOUT_REF}" >> $GITHUB_ENV + echo "BRANCH_VERSION=${BRANCH_VERSION}" >> $GITHUB_ENV + echo "PR_BASE=release-${BRANCH_VERSION}" >> $GITHUB_ENV + echo "PR_HEAD=release-prep" >> $GITHUB_ENV + + - uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + with: + ref: ${{ env.CHECKOUT_REF }} - name: Update changelog uses: thomaseizinger/keep-a-changelog-new-release@96ebf19f2bddaf72406c84691b9c2d827da53140 with: - version: ${{ env.RELEASE_VERSION }} + version: ${{ github.event.milestone.title }} - name: Initialize git config and commit changes run: | + # Configure name and email for Actions user git config user.name "GitHub Actions" git config user.email noreply@github.com + # It is necessary to create the $PR_BASE branch if it doesn't + # already exist because we want to start a PR against it. + if [[ "${CHECKOUT_REF}" == "develop" ]]; then + git branch -c develop "${PR_BASE}" + git push origin "${PR_BASE}" + fi + + # Stage and commit changes to the changelog + git add CHANGELOG.md + git commit -m "CHANGELOG: bump for ${{ github.event.milestone.title }}" + + # Increment the version as necessary. If we checked out develop it means + # that the version number is already correct, and we only need to drop the + # -SNAPSHOT suffix. + if [[ "${CHECKOUT_REF}" == "develop" ]]; then + ./gradlew clearPreRelease + else + ./gradlew bumpPatch + fi + + # Commit changes to the versioning + git add **/version.properties + git commit -m "build: bump version" + - name: Create Pull Request uses: peter-evans/create-pull-request@45c510e1f68ba052e3cd911f661a799cfb9ba3a3 with: author: GitHub Actions - base: release - body: This is an automated pull request to bump the changelog for the v${{ env.RELEASE_VERSION }} release. - branch: release-${{ env.RELEASE_VERSION }} - commit-message: "CHANGELOG: bump for ${{ env.RELEASE_VERSION }}" - draft: true + body: This is an automated pull request to bump the changelog for the ${{ github.event.milestone.title }} release. + base: ${{ env.PR_BASE }} + branch: ${{ env.PR_HEAD }} title: Release v${{ env.RELEASE_VERSION }} token: ${{ secrets.GITHUB_TOKEN }} diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a1b2583b..cdaca4d7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,6 +7,7 @@ import com.android.build.gradle.internal.api.BaseVariantOutputImpl plugins { id("com.android.application") kotlin("android") + `versioning-plugin` `aps-plugin` `crowdin-plugin` } @@ -26,8 +27,6 @@ android { defaultConfig { applicationId = "dev.msfjarvis.aps" - versionCode = 2_00_00 - versionName = "2.0.0-SNAPSHOT" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/version.properties b/app/version.properties new file mode 100644 index 00000000..c8113cb7 --- /dev/null +++ b/app/version.properties @@ -0,0 +1,6 @@ +# +#This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY. +# +#Sun Jan 17 12:32:03 IST 2021 +versioning-plugin.versionCode=20000 +versioning-plugin.versionName=2.0.0-SNAPSHOT diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 3b719a63..d6b6b779 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -25,6 +25,10 @@ gradlePlugin { id = "crowdin-plugin" implementationClass = "CrowdinDownloadPlugin" } + register("versioning") { + id = "versioning-plugin" + implementationClass = "VersioningPlugin" + } } } @@ -33,4 +37,5 @@ dependencies { implementation(build.getValue("androidGradlePlugin")) implementation(build.getValue("binaryCompatibilityValidator")) implementation(build.getValue("downloadTaskPlugin")) + implementation(build.getValue("jsemver")) } diff --git a/buildSrc/buildDependencies.gradle b/buildSrc/buildDependencies.gradle index f72dce15..12da646a 100644 --- a/buildSrc/buildDependencies.gradle +++ b/buildSrc/buildDependencies.gradle @@ -3,6 +3,7 @@ rootProject.ext.versions = [ kotlin : '1.4.21', binary_compatibility_validator : '0.2.4', download_plugin : '4.1.1', + semver : '0.9.0', ] rootProject.ext.build = [ @@ -10,4 +11,5 @@ rootProject.ext.build = [ kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}", binaryCompatibilityValidator : "org.jetbrains.kotlinx:binary-compatibility-validator:${versions.binary_compatibility_validator}", downloadTaskPlugin : "de.undercouch:gradle-download-task:${versions.download_plugin}", + jsemver : "com.github.zafarkhaja:java-semver:${versions.semver}", ] diff --git a/buildSrc/src/main/java/VersioningPlugin.kt b/buildSrc/src/main/java/VersioningPlugin.kt new file mode 100644 index 00000000..7ff9709f --- /dev/null +++ b/buildSrc/src/main/java/VersioningPlugin.kt @@ -0,0 +1,115 @@ +/* + * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + + +import com.android.build.gradle.internal.plugins.AppPlugin +import com.github.zafarkhaja.semver.Version +import java.io.OutputStream +import java.util.Properties +import org.gradle.api.Plugin +import org.gradle.api.Project + +private const val VERSIONING_PROP_FILE = "version.properties" +private const val VERSIONING_PROP_VERSION_NAME = "versioning-plugin.versionName" +private const val VERSIONING_PROP_VERSION_CODE = "versioning-plugin.versionCode" +private const val VERSIONING_PROP_COMMENT = """ +This file was automatically generated by 'versioning-plugin'. DO NOT EDIT MANUALLY. +""" + +/** + * A Gradle [Plugin] that takes a [Project] with the [AppPlugin] applied and dynamically sets the + * versionCode and versionName properties based on values read from a [VERSIONING_PROP_FILE] file in + * the [Project.getBuildDir] directory. It also adds Gradle tasks to bump the major, minor, and patch + * versions along with one to prepare the next snapshot. + */ +@Suppress( + "UnstableApiUsage", + "NAME_SHADOWING" +) +class VersioningPlugin : Plugin { + + /** + * Generate the Android 'versionCode' property + */ + private fun Version.androidCode(): Int { + return majorVersion * 1_00_00 + + minorVersion * 1_00 + + patchVersion + } + + /** + * Write an Android-specific variant of [this] to [stream] + */ + private fun Version.writeForAndroid(stream: OutputStream) { + val newVersionCode = androidCode() + val props = Properties() + props.setProperty(VERSIONING_PROP_VERSION_CODE, "$newVersionCode") + props.setProperty(VERSIONING_PROP_VERSION_NAME, toString()) + props.store(stream, VERSIONING_PROP_COMMENT) + } + + /** + * Returns the same [Version], but with build metadata stripped. + */ + private fun Version.clearPreRelease(): Version { + return Version.forIntegers(majorVersion, minorVersion, patchVersion) + } + + override fun apply(project: Project) { + with(project) { + val appPlugin = requireNotNull(plugins.findPlugin(AppPlugin::class.java)) { + "Plugin 'com.android.application' must be applied to use this plugin" + } + val propFile = layout.projectDirectory.file(VERSIONING_PROP_FILE) + require(propFile.asFile.exists()) { + "A 'version.properties' file must exist in the project subdirectory to use this plugin" + } + val contents = providers.fileContents(propFile).asText.forUseAtConfigurationTime() + val versionProps = Properties().also { it.load(contents.get().byteInputStream()) } + val versionName = requireNotNull(versionProps.getProperty(VERSIONING_PROP_VERSION_NAME)) { + "version.properties must contain a '$VERSIONING_PROP_VERSION_NAME' property" + } + val versionCode = requireNotNull(versionProps.getProperty(VERSIONING_PROP_VERSION_CODE).toInt()) { + "version.properties must contain a '$VERSIONING_PROP_VERSION_CODE' property" + } + appPlugin.extension.defaultConfig.versionName = versionName + appPlugin.extension.defaultConfig.versionCode = versionCode + afterEvaluate { + val version = Version.valueOf(versionName) + tasks.register("clearPreRelease") { + doLast { + version.clearPreRelease() + .writeForAndroid(propFile.asFile.outputStream()) + } + } + tasks.register("bumpMajor") { + doLast { + version.incrementMajorVersion() + .writeForAndroid(propFile.asFile.outputStream()) + } + } + tasks.register("bumpMinor") { + doLast { + version.incrementMinorVersion() + .writeForAndroid(propFile.asFile.outputStream()) + } + } + tasks.register("bumpPatch") { + doLast { + version.incrementPatchVersion() + .writeForAndroid(propFile.asFile.outputStream()) + } + } + tasks.register("bumpSnapshot") { + doLast { + version.incrementMinorVersion() + .setPreReleaseVersion("SNAPSHOT") + .writeForAndroid(propFile.asFile.outputStream()) + } + } + } + } + } +}