diff --git a/.github/workflows/build_and_sign.yml b/.github/workflows/build_and_sign.yml index ccec0c78ec5..f4a865b365c 100644 --- a/.github/workflows/build_and_sign.yml +++ b/.github/workflows/build_and_sign.yml @@ -81,7 +81,7 @@ jobs: fetch-depth: 0 - name: Set up Bazel - uses: bazel-contrib/setup-bazel@8cb04a772ab4c1eb984e9c1b493a182e96c5e425 + uses: bazel-contrib/setup-bazel@0.19.0 with: bazelisk-cache: true diff --git a/.github/workflows/deploy_to_play_console.yml b/.github/workflows/deploy_to_play_console.yml index 1a910b07920..c5d4d5f0af9 100644 --- a/.github/workflows/deploy_to_play_console.yml +++ b/.github/workflows/deploy_to_play_console.yml @@ -91,9 +91,9 @@ jobs: echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> "$GITHUB_ENV" - name: Set up Bazel - uses: abhinavsingh/setup-bazel@v3 + uses: bazel-contrib/setup-bazel@0.19.0 with: - version: 6.5.0 + bazelisk-cache: true - name: Set up build environment uses: ./.github/actions/set-up-android-bazel-build-environment diff --git a/.github/workflows/deploy_updated_changelog.yml b/.github/workflows/deploy_updated_changelog.yml index fbaff722711..e478516b44a 100644 --- a/.github/workflows/deploy_updated_changelog.yml +++ b/.github/workflows/deploy_updated_changelog.yml @@ -155,9 +155,9 @@ jobs: echo "VERSION=$VERSION" >> $GITHUB_ENV - name: Set up Bazel - uses: abhinavsingh/setup-bazel@v3 + uses: bazel-contrib/setup-bazel@0.19.0 with: - version: 6.5.0 + bazelisk-cache: true - name: Set up build environment uses: ./.github/actions/set-up-android-bazel-build-environment diff --git a/.github/workflows/generate_changelog.yml b/.github/workflows/generate_changelog.yml new file mode 100644 index 00000000000..ffb2f90be02 --- /dev/null +++ b/.github/workflows/generate_changelog.yml @@ -0,0 +1,91 @@ +# Triggered when a version-bump PR merges to develop (path filter: version.bzl). +# Runs GenerateChangelogs, which reads the new version, collects PRs between the +# two most recent release branches, calls Vertex AI for a summary, and opens a PR +# with the generated config/changelogs/.md file. +# Can also be triggered manually via workflow_dispatch. + +name: Generate Changelog + +on: + push: + branches: + - develop + paths: + - 'version.bzl' + workflow_dispatch: + inputs: + target_version: + description: >- + Version to generate changelog for (e.g. "0.17"). If empty, the version + is read from version.bzl at HEAD. Use this to regenerate a changelog + for an older release without modifying version.bzl. + required: false + default: '' + +# Only one changelog generation may run at a time; cancel-in-progress: false so that +# a rapid double-push to version.bzl does not leave an inconsistent changelog branch. +concurrency: + group: generate-changelog + cancel-in-progress: false + +jobs: + generate_changelog: + name: Generate Changelog + runs-on: ubuntu-24.04 + # oppia-android-automation-env (not release-env) because this workflow opens a PR + # for human review and never deploys directly. GCP_CHANGELOG_SERVICE_ACCOUNT is + # a dedicated, minimally-scoped SA separate from GCP_RELEASE_SERVICE_ACCOUNT so + # that a compromised changelog workflow cannot reach signing/Play Store resources. + environment: oppia-android-automation-env + permissions: + id-token: write + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Configure git remote credentials + run: | + git remote set-url origin \ + https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} + + - name: Authenticate to GCP via Workload Identity Federation + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ secrets.WIF_PROVIDER }} + service_account: ${{ secrets.GCP_CHANGELOG_SERVICE_ACCOUNT }} + + - name: Set up Google Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + + - name: Get and mask GCP access token + run: | + ACCESS_TOKEN="$(gcloud auth print-access-token)" + echo "::add-mask::$ACCESS_TOKEN" + echo "GCP_ACCESS_TOKEN=$ACCESS_TOKEN" >> "$GITHUB_ENV" + + - name: Set up Bazel + uses: bazel-contrib/setup-bazel@0.19.0 + with: + bazelisk-cache: true + + - name: Set up Android + Bazel build environment + uses: ./.github/actions/set-up-android-bazel-build-environment + + - name: Generate changelog and open PR + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TARGET_VERSION: ${{ inputs.target_version }} + run: | + bazel run //scripts:generate_changelogs -- \ + "$(pwd)" \ + "${{ secrets.GCP_PROJECT_ID }}" \ + "$GCP_ACCESS_TOKEN" diff --git a/.github/workflows/update_rollout.yml b/.github/workflows/update_rollout.yml index ed3e93ff058..0de6a9551d5 100644 --- a/.github/workflows/update_rollout.yml +++ b/.github/workflows/update_rollout.yml @@ -69,9 +69,9 @@ jobs: id: validate - name: Set up Bazel - uses: abhinavsingh/setup-bazel@v3 + uses: bazel-contrib/setup-bazel@0.19.0 with: - version: 6.5.0 + bazelisk-cache: true - name: Set up build environment uses: ./.github/actions/set-up-android-bazel-build-environment diff --git a/config/changelogs/0.18.md b/config/changelogs/0.18.md index 72f5a9afdc6..ea9a634ca49 100644 --- a/config/changelogs/0.18.md +++ b/config/changelogs/0.18.md @@ -1 +1 @@ -Placeholder changelog for 0.18 pre-releases. +This release warns users on Android 5/5.1 of retiring support for their OS versions. It also adds full support for Android 16, and a variety of bugfixes. \ No newline at end of file diff --git a/config/changelogs/0.18_alpha.md b/config/changelogs/0.18_alpha.md new file mode 100644 index 00000000000..ab381096764 --- /dev/null +++ b/config/changelogs/0.18_alpha.md @@ -0,0 +1 @@ +This release warns users on Android 5/5.1 of retiring support for their OS versions. It also adds full support for Android 16, a variety of bugfixes and adds an alpha-specific worked examples feature to study guides. \ No newline at end of file diff --git a/scripts/src/java/org/oppia/android/scripts/release/GenerateChangelogs.kt b/scripts/src/java/org/oppia/android/scripts/release/GenerateChangelogs.kt index 3ac475476c9..ff179cd7991 100644 --- a/scripts/src/java/org/oppia/android/scripts/release/GenerateChangelogs.kt +++ b/scripts/src/java/org/oppia/android/scripts/release/GenerateChangelogs.kt @@ -5,6 +5,9 @@ import org.oppia.android.scripts.common.CommandExecutorImpl import org.oppia.android.scripts.common.ScriptBackgroundCoroutineDispatcher import java.io.File +private const val GCP_LOCATION = "us-central1" +private const val VERTEX_MODEL = "gemini-1.5-flash" + /** * Script that automatically generates a changelog for the previous app version whenever the minor * version is bumped in `version.bzl`, and proposes it as a pull request on `develop`. @@ -26,32 +29,29 @@ import java.io.File * ### Usage (called by `generate_changelog.yml` via Bazel) * ``` * bazel run //scripts:generate_changelogs -- \ - * + * * ``` * * ### Arguments (positional) * 0. `workspace_root` — absolute path to the local repository root * 1. `gcp_project` — GCP project ID that has Vertex AI enabled - * 2. `gcp_location` — Vertex AI region (e.g. "us-central1") - * 3. `vertex_model` — Vertex AI model ID (e.g. "gemini-1.5-flash") - * 4. `gcp_access_token`— GCP Bearer token for authenticating with Vertex AI + * 2. `gcp_access_token`— GCP Bearer token for authenticating with Vertex AI * - * An optional 6th argument overrides the Vertex AI API base URL; this is used in integration + * An optional 4th argument overrides the Vertex AI API base URL; this is used in integration * tests to route HTTP calls through a local mock server. */ + fun main(args: Array) { - require(args.size in 5..6) { - "Usage: generate_changelogs " + - " \nGot ${args.size} argument(s): ${args.toList()}" + require(args.size in 3..4) { + "Usage: generate_changelogs " + + "\nGot ${args.size} argument(s): ${args.toList()}" } val workspaceRoot = args[0] val gcpProject = args[1] - val gcpLocation = args[2] - val vertexModel = args[3] - val gcpAccessToken = args[4] + val gcpAccessToken = args[2] - val overrideApiBaseUrl = if (args.size == 6) args[5] else null + val overrideApiBaseUrl = if (args.size == 4) args[3] else null // TARGET_VERSION is set by the workflow when the user triggers workflow_dispatch with a // specific version (e.g. "0.17"). When empty or absent, version is derived from version.bzl. @@ -60,9 +60,11 @@ fun main(args: Array) { ScriptBackgroundCoroutineDispatcher().use { scriptBgDispatcher -> val commandExecutor = CommandExecutorImpl(scriptBgDispatcher) val vertexAiClient = if (overrideApiBaseUrl != null) { - GoogleVertexAiClient(gcpProject, gcpLocation, vertexModel, gcpAccessToken, overrideApiBaseUrl) + GoogleVertexAiClient( + gcpProject, GCP_LOCATION, VERTEX_MODEL, gcpAccessToken, overrideApiBaseUrl + ) } else { - GoogleVertexAiClient(gcpProject, gcpLocation, vertexModel, gcpAccessToken) + GoogleVertexAiClient(gcpProject, GCP_LOCATION, VERTEX_MODEL, gcpAccessToken) } generateChangelogs( workspaceRoot = File(workspaceRoot), @@ -218,12 +220,17 @@ fun parseVersionBzl(workspaceRoot: File): Pair { /** * Computes the `fromSha..toSha` range for the changelog commit collection. * - * The **toSha** is the merge-base of [releaseBranch] and `develop` — the point where the current - * release branched off (i.e. all commits up to and including the version bump commit). + * The **toSha** is the merge-base of `origin/`[releaseBranch] and `origin/develop` — the point + * where the current release branched off (i.e. all commits up to and including the version bump + * commit). * - * The **fromSha** is the merge-base of [prevReleaseBranch] and `develop` — the point where the - * *previous* release branched off. If the previous release branch doesn't exist (first release), - * falls back to the very first commit on `develop`. + * The **fromSha** is the merge-base of `origin/`[prevReleaseBranch] and `origin/develop` — the + * point where the *previous* release branched off. If the previous release branch doesn't exist on + * the remote, falls back to the very first commit on `develop`. + * + * Both release branch refs are referenced as remote tracking refs (`origin/release-X.Y`) rather + * than local branch names. `actions/checkout` with `fetch-depth: 0` fetches all remote tracking + * refs, so no explicit `git fetch` step is required in the workflow. * * @param prevMinor the previous minor version number, used to detect the first-release edge case * @return a (fromSha, toSha) pair of full commit SHAs @@ -235,13 +242,18 @@ fun findCommitRange( prevReleaseBranch: String, prevMinor: Int ): Pair { - val toSha = gitMergeBase(workspaceRoot, commandExecutor, releaseBranch, "$REMOTE/$DEVELOP_BRANCH") + val toSha = + gitMergeBase( + workspaceRoot, commandExecutor, "$REMOTE/$releaseBranch", "$REMOTE/$DEVELOP_BRANCH" + ) val fromSha = if (prevMinor <= 0) { // First-ever release: include all commits from the beginning of develop. gitFirstCommit(workspaceRoot, commandExecutor) } else { try { - gitMergeBase(workspaceRoot, commandExecutor, prevReleaseBranch, "$REMOTE/$DEVELOP_BRANCH") + gitMergeBase( + workspaceRoot, commandExecutor, "$REMOTE/$prevReleaseBranch", "$REMOTE/$DEVELOP_BRANCH" + ) } catch (e: IllegalStateException) { // Re-throw if this isn't a "branch not found" failure — don't mask unrelated errors. if ("unknown revision" !in (e.message ?: "") && @@ -532,8 +544,7 @@ fun createOrUpdateChangelogPr( "--base", "develop", "--head", branchName, "--title", prTitle, - "--body", prBody, - "--label", "automated-changelog" + "--body", prBody ) if (result.exitCode == 0) { val prUrl = result.output.lastOrNull { it.startsWith("https://") } ?: "(URL not found)" diff --git a/scripts/src/javatests/org/oppia/android/scripts/release/GenerateChangelogsTest.kt b/scripts/src/javatests/org/oppia/android/scripts/release/GenerateChangelogsTest.kt index 375ebac4c88..1a7fd754aee 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/release/GenerateChangelogsTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/release/GenerateChangelogsTest.kt @@ -45,18 +45,18 @@ class GenerateChangelogsTest { } @Test - fun testMain_fourArguments_throwsWithUsageMessage() { + fun testMain_twoArguments_throwsWithUsageMessage() { val exception = - assertThrows { main(arrayOf("a", "b", "c", "d")) } + assertThrows { main(arrayOf("a", "b")) } assertThat(exception).hasMessageThat().contains("Usage:") } @Test - fun testMain_sevenArguments_throwsWithUsageMessage() { + fun testMain_fiveArguments_throwsWithUsageMessage() { val exception = assertThrows { - main(arrayOf("a", "b", "c", "d", "e", "f", "g")) + main(arrayOf("a", "b", "c", "d", "e")) } assertThat(exception).hasMessageThat().contains("Usage:") @@ -593,7 +593,7 @@ class GenerateChangelogsTest { @Test fun testGenerateChangelogs_prevBranchAmbiguousArgument_fallsBackToFirstCommit() { // Simulates the second git error phrase that indicates a missing branch: - // "ambiguous argument" (e.g. git merge-base release-0.16 origin/develop). + // "ambiguous argument" (e.g. git merge-base origin/release-0.16 origin/develop). writeVersionBzl(major = 0, minor = 18) val firstCommit = "firstcommitsha" val toSha = "toshasha456"