diff --git a/.github/workflows/update_flutter.yaml b/.github/workflows/update_flutter.yaml new file mode 100644 index 000000000..f5d375214 --- /dev/null +++ b/.github/workflows/update_flutter.yaml @@ -0,0 +1,61 @@ +# Copyright 2026 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +name: Update Flutter SDK Pin + +on: + schedule: + - cron: '0 0 * * 1' # Run every Monday at midnight UTC + workflow_dispatch: # Allow manual execution from the Actions tab + +permissions: + contents: write + pull-requests: write + +jobs: + check-and-update: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Check for New Flutter stable version + id: check-version + run: | + # 1. Retrieve the current pinned version from the shared script + CURRENT_VERSION=$(grep -E 'FLUTTER_VERSION="[0-9.]+"' tool/provision_flutter.sh | head -n 1 | cut -d'"' -f2) + echo "Current pinned version: $CURRENT_VERSION" + + # 2. Retrieve the latest stable version from Flutter's official releases manifest + LATEST_VERSION=$(curl -s https://storage.googleapis.com/flutter_infra_release/releases/releases_linux.json | jq -r '.releases | map(select(.channel == "stable"))[0].version') + echo "Latest stable version: $LATEST_VERSION" + + # 3. Compare and update if a newer version is available + if [ "$CURRENT_VERSION" != "$LATEST_VERSION" ]; then + echo "New Flutter stable version detected: $LATEST_VERSION" + sed -i -e "s/FLUTTER_VERSION=\"$CURRENT_VERSION\"/FLUTTER_VERSION=\"$LATEST_VERSION\"/g" tool/provision_flutter.sh + echo "updated=true" >> $GITHUB_OUTPUT + echo "latest_version=$LATEST_VERSION" >> $GITHUB_OUTPUT + else + echo "Flutter SDK is already up to date." + echo "updated=false" >> $GITHUB_OUTPUT + fi + + - name: Create Pull Request for Version Bump + if: steps.check-version.outputs.updated == 'true' + uses: peter-evans/create-pull-request@v6 + with: + commit-message: "ci: bump pinned Flutter SDK to version ${{ steps.check-version.outputs.latest_version }}" + title: "ci: bump pinned Flutter SDK to version ${{ steps.check-version.outputs.latest_version }}" + body: | + An automated check has detected a new stable release of the Flutter SDK. + + * **New Pinned Version**: `${{ steps.check-version.outputs.latest_version }}` + + This Pull Request updates our shared provisioning script (`tool/provision_flutter.sh`) to target this version. All presubmit tests will be run against this version to verify compatibility. + branch: "auto-update-flutter-sdk" + delete-branch: true + labels: | + dependencies + ci diff --git a/testSrc/unit/io/flutter/CIIntegrityTest.java b/testSrc/unit/io/flutter/CIIntegrityTest.java new file mode 100644 index 000000000..0071205d4 --- /dev/null +++ b/testSrc/unit/io/flutter/CIIntegrityTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2026 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter; + +import org.junit.Test; +import java.io.File; +import java.nio.file.Files; +import java.util.regex.Pattern; +import static org.junit.Assert.assertTrue; + +/** + * Integrity tests for the CI/CD automation pipeline. + *
+ * Why this exists: + * Our CI/CD scripts rely on a shared bash script to provision the Flutter SDK, and a + * scheduled GitHub Actions workflow to automatically check for new Flutter stable releases + * and submit version-bumping Pull Requests. + *
+ * If a developer modifies the provisioning script (e.g., renaming the constant FLUTTER_VERSION) + * or alters the regex search format without updating the GitHub Actions workflow, the automation + * will fail silently in production. + *
+ * This class acts as a "meta-test" or "integrity-check" that runs during standard project + * compilations (`./gradlew test`) to give developers an immediate early warning of synchronization + * failures before code is merged. + */ +public class CIIntegrityTest { + + /** + * Verifies that the Flutter SDK provisioning script exists at the expected path + * and defines the `FLUTTER_VERSION` constant in the exact semver format required. + *
+ * If this test fails, it means `tool/provision_flutter.sh` was moved, deleted, or + * the `FLUTTER_VERSION` constant definition was formatted incorrectly (or renamed). + */ + @Test + public void testFlutterProvisioningScriptIntegrity() throws Exception { + // The JVM's Current Working Directory (CWD) during unit tests is the repository root. + File scriptFile = new File("tool/provision_flutter.sh"); + assertTrue("provision_flutter.sh must exist at the repository root 'tool/' directory", scriptFile.exists()); + + String content = Files.readString(scriptFile.toPath()); + + // Ensure the shell script declares the version constant in a predictable pattern: + // E.g. FLUTTER_VERSION="3.41.0" + Pattern versionPattern = Pattern.compile("FLUTTER_VERSION=\"[0-9.]+\""); + assertTrue( + "provision_flutter.sh must define a constant FLUTTER_VERSION matching the expected format (e.g., FLUTTER_VERSION=\"3.41.0\"). " + + "This constant is critical because the automated GitHub Actions updater searches for this exact pattern to bump the version.", + versionPattern.matcher(content).find() + ); + } + + /** + * Verifies that the automated GitHub Actions updater workflow is synchronized + * with the provisioning script. + *
+ * Specifically, it ensures the workflow searches for the exact constant pattern + * `FLUTTER_VERSION="[0-9.]+"` to perform its automated string replacement. If a developer + * renames the constant in the script, they MUST also update the workflow, otherwise this test fails. + */ + @Test + public void testGitHubWorkflowRegexSync() throws Exception { + File workflowFile = new File(".github/workflows/update_flutter.yaml"); + assertTrue("update_flutter.yaml workflow must exist in the '.github/workflows/' directory", workflowFile.exists()); + + String content = Files.readString(workflowFile.toPath()); + + // The workflow must use the constant name followed by a flexible regex query in single or double quotes. + // E.g., matches 'FLUTTER_VERSION="[0-9.]+"' or 'FLUTTER_VERSION="[0-9.]*"' or "FLUTTER_VERSION='[0-9.]+'" + Pattern regexPattern = Pattern.compile("FLUTTER_VERSION=['\"].+?['\"]"); + assertTrue( + "update_flutter.yaml must reference the 'FLUTTER_VERSION' constant with a regex pattern (e.g. 'FLUTTER_VERSION=\"[0-9.]+\"') " + + "in its search-and-replace logic.", + regexPattern.matcher(content).find() + ); + } +} diff --git a/tool/provision_flutter.sh b/tool/provision_flutter.sh index 537841ac5..a809bbd56 100755 --- a/tool/provision_flutter.sh +++ b/tool/provision_flutter.sh @@ -9,6 +9,8 @@ set -e # Provision the pinned Flutter SDK if not present if [ ! -d "../flutter" ]; then OS_NAME=$(uname -s | tr '[:upper:]' '[:lower:]') + # Pinned Flutter SDK version. This constant is automatically checked and updated weekly + # by the .github/workflows/update_flutter.yaml GitHub Actions workflow. FLUTTER_VERSION="3.41.0" echo "Provisioning Flutter SDK version ${FLUTTER_VERSION} for ${OS_NAME}..."