From d5dede24a17850e874fafe0536c554815fc5e333 Mon Sep 17 00:00:00 2001 From: Moustachauve <2206577+Moustachauve@users.noreply.github.com> Date: Thu, 5 Nov 2026 17:50:51 -0500 Subject: [PATCH 1/2] Use committed version metadata in release builds for reproducible APKs Computing VERSION_CODE from github.run_number and passing it via -PversionCode in CI caused the release APK's AndroidManifest.xml and BuildConfig.VERSION_CODE to diverge from what IzzyOnDroid builds from the git tag (#309). Sync app-versionCode and CURRENT_PROJECT_VERSION to 108 (matching the published v8.0.0 store builds), update /prepare-release to bump the committed version metadata before every beta and production release, and have release.yml validate and build directly from gradle/libs.versions.toml without CLI overrides or workspace changelog mutations. --- .agents/skills/prepare-release/SKILL.md | 15 ++- .github/workflows/check.yml | 4 + .github/workflows/release.yml | 126 ++++++++++-------------- app/build.gradle.kts | 14 +-- docs/RELEASING.md | 17 ++-- gradle/libs.versions.toml | 2 +- iosApp/wled.xcodeproj/project.pbxproj | 8 +- 7 files changed, 84 insertions(+), 102 deletions(-) diff --git a/.agents/skills/prepare-release/SKILL.md b/.agents/skills/prepare-release/SKILL.md index 9fd2f8e6..99532967 100644 --- a/.agents/skills/prepare-release/SKILL.md +++ b/.agents/skills/prepare-release/SKILL.md @@ -62,14 +62,18 @@ Use this skill to prepare either a **Beta (`beta`)** or a **Full / Stable (`prod 1. **Update Cumulative `dev.md` Files (If Needed):** - `dev.md` is **cumulative**: it contains everything new since the last **stable** release. - If the Phase 2 audit found missing user-facing items, update the affected platform `dev.md` file(s) following the changelog style rules in `AGENTS.md` and each changelog directory's `README.md`. Keep `dev.md` in English only and do not rename it (for Beta builds on Google Play, `fastlane/Fastfile` automatically summarizes `app/src/main/assets/changelog/dev.md` to `<= 500` characters, so do not commit a standing `fastlane/changelogs/dev.txt` on `dev`). - - If any `dev.md` file was modified, create a branch off `dev`, run verification checks, ask the user for confirmation, and open a PR targeting `dev`. -2. **Prepare the Incremental GitHub Beta Draft Release:** +2. **Bump Version Metadata (`gradle/libs.versions.toml` & `project.pbxproj`):** + - Increment `app-versionCode` by `+1` and set `app-versionName = "-beta."` in `gradle/libs.versions.toml` (committed in git so Android builds are 100% reproducible from the release tag). + - Set `MARKETING_VERSION = ;` (numeric `X.Y.Z` without `-beta.`) and `CURRENT_PROJECT_VERSION = ;` on the `WLED` and `WLEDWidgets` targets in `iosApp/wled.xcodeproj/project.pbxproj`. +3. **Open the Beta Preparation PR to `dev`:** + - Create a branch off `dev` (e.g., `chore/prepare-release--beta.`), run verification checks (`./gradlew spotlessApply detekt`), ask the user for confirmation, and open a PR targeting `dev`. +4. **Prepare the Incremental GitHub Beta Draft Release:** - Unlike `dev.md`, a Beta GitHub Release description is **incremental**: - For `beta.1`: includes all PRs since `` (which matches the existing Release Drafter draft body from `gh release view --json body -q .body`). - For `beta.2+`: includes **only** PRs merged into `dev` since the previous beta release's `publishedAt` timestamp (`gh pr list --state merged --base dev --search "merged:>=" --limit 500 --json number,title,labels,author,mergedAt`). - Format the release body and contributors list following the **Shared Guideline** above. - Present the proposed tag and release body to the user for confirmation. - - Once confirmed (and after any `dev.md` PR on `dev` is merged), create or update the draft pre-release targeting `dev`: + - Once confirmed (and after the Beta Preparation PR on `dev` is merged and Release Drafter finishes), create or update the draft pre-release targeting `dev`: ```bash gh release create v-beta. --draft --prerelease --target dev --title "v-beta." --notes-file - <<'EOF' @@ -94,8 +98,9 @@ Use this skill to prepare either a **Beta (`beta`)** or a **Full / Stable (`prod - Present the proposed English `.md` files for all platforms **and** `fastlane/changelogs/.txt` (with its character count) to the user and wait for approval before translating. 2. **Translate `.md` and `fastlane/changelogs/.txt` into Supported Languages:** - Read `.agents/skills/translate-changelog/SKILL.md` and execute Steps 2–5 (skipping Step 1 since `.md` and `.txt` were already prepared above) to generate `..md` translations across all supported platforms and `..txt` translations in `fastlane/changelogs/` (each verified to be `<= 500` characters). -3. **Bump Version Metadata:** - - Update each platform's version configuration files to `` as documented in `docs/RELEASING.md` (Section 3, Step 1). +3. **Bump Version Metadata (`gradle/libs.versions.toml` & `project.pbxproj`):** + - Increment `app-versionCode` by `+1` and set `app-versionName = ""` in `gradle/libs.versions.toml` (committed in git so Android builds are 100% reproducible from the release tag). + - Set `MARKETING_VERSION = ;` and `CURRENT_PROJECT_VERSION = ;` on the `WLED` and `WLEDWidgets` targets in `iosApp/wled.xcodeproj/project.pbxproj`. 4. **Open the Release Preparation PR to `dev`:** - Run repository lint/verification checks (`./gradlew spotlessApply detekt`). - Present the summary and proposed commit/PR description to the user for confirmation, then commit on a release-prep branch (e.g., `chore/prepare-release-`), push, and open a PR targeting `dev`. diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d1ec7213..5a75d1ac 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -229,6 +229,10 @@ jobs: run: | set -euo pipefail TOML_VERSION=$(grep -E '^app-versionName\s*=' gradle/libs.versions.toml | sed -E 's/.*"([^"]+)".*/\1/') + if [[ "$TOML_VERSION" == *-* ]]; then + echo "::error file=gradle/libs.versions.toml::app-versionName is \"$TOML_VERSION\" (pre-release). Update it to the stable X.Y.Z version before merging to main." + exit 1 + fi if [ -f "app/src/main/assets/changelog/dev.md" ] || [ -f "iosApp/wled/changelog/dev.md" ] || [ -f "fastlane/changelogs/dev.txt" ]; then echo "::error::dev changelog still exists! Before merging to main, rename app/src/main/assets/changelog/dev.md and iosApp/wled/changelog/dev.md to ${TOML_VERSION}.md and create fastlane/changelogs/${TOML_VERSION}.txt." exit 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 074689ab..67985d2b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,8 +15,8 @@ on: - beta - production version_name: - description: 'Version name to build (e.g. 8.1.0-beta.1 or 8.1.0)' - required: true + description: 'Optional version name override to validate against gradle/libs.versions.toml (e.g. 8.1.0-beta.1 or 8.1.0)' + required: false type: string dry_run: description: 'Dry run: build, sign, and validate Play Store / iOS archive without publishing' @@ -57,7 +57,6 @@ jobs: INPUT_DRY_RUN: ${{ inputs.dry_run }} RELEASE_TAG: ${{ github.event.release.tag_name }} RELEASE_PRERELEASE: ${{ github.event.release.prerelease }} - RUN_NUMBER: ${{ github.run_number }} ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} @@ -70,10 +69,18 @@ jobs: run: | set -euo pipefail - # 1. Determine track (beta vs production) + # 1. Read committed version metadata from gradle/libs.versions.toml (single source of truth for Reproducible Builds) + TOML_VERSION_NAME=$(grep -E '^app-versionName\s*=' gradle/libs.versions.toml | sed -E 's/.*"([^"]+)".*/\1/') + VERSION_CODE=$(grep -E '^app-versionCode\s*=' gradle/libs.versions.toml | sed -E 's/.*"([^"]+)".*/\1/') + if [ -z "$TOML_VERSION_NAME" ] || ! [[ "$VERSION_CODE" =~ ^[0-9]+$ ]]; then + echo "::error::Invalid app-versionName (\"$TOML_VERSION_NAME\") or app-versionCode (\"$VERSION_CODE\") in gradle/libs.versions.toml." + exit 1 + fi + + # 2. Determine track (beta vs production) and validate tag / input version matches gradle/libs.versions.toml if [ "$EVENT_NAME" = "workflow_dispatch" ]; then TRACK="$INPUT_TRACK" - RAW_VERSION="$INPUT_VERSION_NAME" + RAW_VERSION="${INPUT_VERSION_NAME:-$TOML_VERSION_NAME}" if [ "$INPUT_DRY_RUN" = "true" ]; then DRY_RUN="true" else @@ -96,13 +103,18 @@ jobs: fi VERSION_NAME="${RAW_VERSION#v}" - if [ "$TRACK" = "beta" ] && [[ "$VERSION_NAME" != *-* ]]; then - VERSION_NAME="${VERSION_NAME}-beta.${RUN_NUMBER}" + if [ "$VERSION_NAME" != "$TOML_VERSION_NAME" ]; then + echo "::error::Release version \"$VERSION_NAME\" does not match app-versionName=\"$TOML_VERSION_NAME\" in gradle/libs.versions.toml. Run /prepare-release so builds are reproducible." + exit 1 + fi + if [ "$IS_BETA" = "false" ] && [[ "$VERSION_NAME" == *-* ]]; then + echo "::error::Production release cannot use a pre-release version ($VERSION_NAME)." + exit 1 fi TAG_NAME="${RELEASE_TAG:-v${VERSION_NAME}}" IOS_MARKETING_VERSION="${VERSION_NAME%%-*}" - # 2. Pre-flight check: prevent workflow_dispatch (non-dry-run) from colliding with an existing release or draft + # 3. Pre-flight check: prevent workflow_dispatch (non-dry-run) from colliding with an existing release or draft if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$DRY_RUN" = "false" ]; then EXISTING_MATCH=$(gh release list --limit 50 --json tagName,isDraft --jq ".[] | select(.tagName == \"$TAG_NAME\") | .tagName" || true) if [ -n "$EXISTING_MATCH" ]; then @@ -111,7 +123,7 @@ jobs: fi fi - # 3. Pre-flight check: verify all required secrets are configured before any platform job starts + # 4. Pre-flight check: verify all required secrets are configured before any platform job starts MISSING_SECRETS=() for SECRET_VAR in \ ANDROID_KEYSTORE_BASE64 \ @@ -132,23 +144,31 @@ jobs: exit 1 fi - # 4. Pre-flight check: verify non-empty changelog exists for both Android and iOS - # Keep candidate filenames in sync with `read_plain_changelog` in `fastlane/Fastfile`. - for DIR in "app/src/main/assets/changelog" "iosApp/wled/changelog"; do - if [ ! -s "${DIR}/${VERSION_NAME}.md" ] && \ - [ ! -s "${DIR}/v${VERSION_NAME}.md" ] && \ - [ ! -s "${DIR}/${IOS_MARKETING_VERSION}.md" ] && \ - [ ! -s "${DIR}/v${IOS_MARKETING_VERSION}.md" ] && \ - [ ! -s "${DIR}/dev.md" ]; then - echo "::error::No non-empty changelog found in ${DIR} for ${VERSION_NAME} (or dev.md)." + # 5. Pre-flight check: verify non-empty changelog exists for both Android and iOS + if [ "$IS_BETA" = "false" ]; then + for DIR in "app/src/main/assets/changelog" "iosApp/wled/changelog"; do + if [ -f "${DIR}/dev.md" ] || [ ! -s "${DIR}/${VERSION_NAME}.md" ]; then + echo "::error::Production release requires committed ${DIR}/${VERSION_NAME}.md (and no dev.md) so builds remain reproducible." + exit 1 + fi + done + if [ -f "fastlane/changelogs/dev.txt" ] || [ ! -s "fastlane/changelogs/${VERSION_NAME}.txt" ]; then + echo "::error::Production release requires committed fastlane/changelogs/${VERSION_NAME}.txt (and no dev.txt)." exit 1 fi - done - - # 5. Compute monotonically increasing versionCode / CFBundleVersion (base offset 100 + workflow run_number). - # Note: github.run_number is tied to this workflow file ID; do not rename/delete release.yml without - # updating the base offset, and use "Re-run failed jobs" rather than "Re-run all jobs" on partial failures. - VERSION_CODE=$((100 + RUN_NUMBER)) + else + # Keep candidate filenames in sync with `read_plain_changelog` in `fastlane/Fastfile`. + for DIR in "app/src/main/assets/changelog" "iosApp/wled/changelog"; do + if [ ! -s "${DIR}/${VERSION_NAME}.md" ] && \ + [ ! -s "${DIR}/v${VERSION_NAME}.md" ] && \ + [ ! -s "${DIR}/${IOS_MARKETING_VERSION}.md" ] && \ + [ ! -s "${DIR}/v${IOS_MARKETING_VERSION}.md" ] && \ + [ ! -s "${DIR}/dev.md" ]; then + echo "::error::No non-empty changelog found in ${DIR} for ${VERSION_NAME} (or dev.md)." + exit 1 + fi + done + fi echo "track=$TRACK" >> "$GITHUB_OUTPUT" echo "is_beta=$IS_BETA" >> "$GITHUB_OUTPUT" @@ -194,32 +214,6 @@ jobs: ruby-version: '3.3' bundler-cache: true - - name: Promote dev.md changelog in build workspace if needed - if: needs.prepare.outputs.is_beta == 'false' - env: - MARKETING_VERSION: ${{ needs.prepare.outputs.ios_marketing_version }} - run: | - CHANGELOG_DIR="app/src/main/assets/changelog" - if [ -f "$CHANGELOG_DIR/dev.md" ] && [ ! -f "$CHANGELOG_DIR/${MARKETING_VERSION}.md" ]; then - echo "::warning::dev.md was not renamed before tagging; promoting dev.md -> ${MARKETING_VERSION}.md for this build." - mv "$CHANGELOG_DIR/dev.md" "$CHANGELOG_DIR/${MARKETING_VERSION}.md" - for DEV_LOCALE_FILE in "$CHANGELOG_DIR"/dev.*.md; do - [ -e "$DEV_LOCALE_FILE" ] || continue - SUFFIX="${DEV_LOCALE_FILE#"$CHANGELOG_DIR/dev"}" - mv "$DEV_LOCALE_FILE" "$CHANGELOG_DIR/${MARKETING_VERSION}${SUFFIX}" - done - fi - PLAY_CHANGELOG_DIR="fastlane/changelogs" - if [ -f "$PLAY_CHANGELOG_DIR/dev.txt" ] && [ ! -f "$PLAY_CHANGELOG_DIR/${MARKETING_VERSION}.txt" ]; then - echo "::warning::fastlane/changelogs/dev.txt was not renamed before tagging; promoting dev.txt -> ${MARKETING_VERSION}.txt for this build." - mv "$PLAY_CHANGELOG_DIR/dev.txt" "$PLAY_CHANGELOG_DIR/${MARKETING_VERSION}.txt" - for DEV_LOCALE_FILE in "$PLAY_CHANGELOG_DIR"/dev.*.txt; do - [ -e "$DEV_LOCALE_FILE" ] || continue - SUFFIX="${DEV_LOCALE_FILE#"$PLAY_CHANGELOG_DIR/dev"}" - mv "$DEV_LOCALE_FILE" "$PLAY_CHANGELOG_DIR/${MARKETING_VERSION}${SUFFIX}" - done - fi - - name: Decode Android Keystore id: keystore env: @@ -236,12 +230,8 @@ jobs: ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} - VERSION_CODE: ${{ needs.prepare.outputs.version_code }} - VERSION_NAME: ${{ needs.prepare.outputs.version_name }} run: | - ./gradlew :app:bundleRelease :app:assembleRelease \ - -PversionCode="$VERSION_CODE" \ - -PversionName="$VERSION_NAME" + ./gradlew :app:bundleRelease :app:assembleRelease - name: Deploy to Google Play Store via Fastlane env: @@ -342,31 +332,21 @@ jobs: ruby-version: '3.3' bundler-cache: true - - name: Promote dev.md changelog in build workspace if needed + - name: Copy dev.md changelog in build workspace for TestFlight beta if needed + if: needs.prepare.outputs.is_beta == 'true' env: MARKETING_VERSION: ${{ needs.prepare.outputs.ios_marketing_version }} - IS_BETA: ${{ needs.prepare.outputs.is_beta }} run: | # iOS CFBundleShortVersionString is strictly numeric (e.g. 8.1.0 without -beta.1), # so copy dev.md -> .md when missing so in-app "What's New" works on TestFlight too. CHANGELOG_DIR="iosApp/wled/changelog" if [ -f "$CHANGELOG_DIR/dev.md" ] && [ ! -f "$CHANGELOG_DIR/${MARKETING_VERSION}.md" ]; then - if [ "$IS_BETA" = "false" ]; then - echo "::warning::dev.md was not renamed before tagging; promoting dev.md -> ${MARKETING_VERSION}.md for this build." - mv "$CHANGELOG_DIR/dev.md" "$CHANGELOG_DIR/${MARKETING_VERSION}.md" - for DEV_LOCALE_FILE in "$CHANGELOG_DIR"/dev.*.md; do - [ -e "$DEV_LOCALE_FILE" ] || continue - SUFFIX="${DEV_LOCALE_FILE#"$CHANGELOG_DIR/dev"}" - mv "$DEV_LOCALE_FILE" "$CHANGELOG_DIR/${MARKETING_VERSION}${SUFFIX}" - done - else - cp "$CHANGELOG_DIR/dev.md" "$CHANGELOG_DIR/${MARKETING_VERSION}.md" - for DEV_LOCALE_FILE in "$CHANGELOG_DIR"/dev.*.md; do - [ -e "$DEV_LOCALE_FILE" ] || continue - SUFFIX="${DEV_LOCALE_FILE#"$CHANGELOG_DIR/dev"}" - cp "$DEV_LOCALE_FILE" "$CHANGELOG_DIR/${MARKETING_VERSION}${SUFFIX}" - done - fi + cp "$CHANGELOG_DIR/dev.md" "$CHANGELOG_DIR/${MARKETING_VERSION}.md" + for DEV_LOCALE_FILE in "$CHANGELOG_DIR"/dev.*.md; do + [ -e "$DEV_LOCALE_FILE" ] || continue + SUFFIX="${DEV_LOCALE_FILE#"$CHANGELOG_DIR/dev"}" + cp "$DEV_LOCALE_FILE" "$CHANGELOG_DIR/${MARKETING_VERSION}${SUFFIX}" + done fi - name: Assemble Shared Release XCFramework diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d455cfa6..1ac59867 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,17 +16,11 @@ android { minSdk = 24 targetSdk = 37 versionCode = - providers - .gradleProperty("versionCode") - .map { it.toInt() } - .orElse( - libs.versions.app.versionCode - .map { it.toInt() }, - ).get() + libs.versions.app.versionCode + .get() + .toInt() versionName = - providers - .gradleProperty("versionName") - .orElse(libs.versions.app.versionName) + libs.versions.app.versionName .get() testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 58f1a5b2..85536840 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -11,24 +11,23 @@ WLED-App uses GitHub Actions (`.github/workflows/release.yml`) and Fastlane (`fa ## 1. How It Works ### Version Numbers & Build Codes -- **Version Name (`versionName` / `MARKETING_VERSION`)**: - - Derived automatically from the GitHub Release tag (e.g., `v8.1.0-beta.1` sets Android `versionName = 8.1.0-beta.1` and iOS `MARKETING_VERSION = 8.1.0`, since App Store Connect requires numeric `X.Y.Z` version strings). -- **Build Code (`versionCode` / `CURRENT_PROJECT_VERSION`)**: - - Computed automatically in CI as `100 + github.run_number` so every Beta and Production upload has a strictly increasing integer without needing manual bumps for each test build (`app-versionCode` in `gradle/libs.versions.toml` and `CURRENT_PROJECT_VERSION` in `project.pbxproj` are only used for local builds). - - *Note:* If a workflow run partially fails (e.g., Android succeeds but iOS fails), use **Re-run failed jobs** rather than **Re-run all jobs** so the succeeded store isn't re-sent the same build number. +- **Single Source of Truth (`gradle/libs.versions.toml` & `project.pbxproj`)**: + - `app-versionName` and `app-versionCode` in `gradle/libs.versions.toml` (and `MARKETING_VERSION` / `CURRENT_PROJECT_VERSION` in `iosApp/wled.xcodeproj/project.pbxproj`) are bumped by `/prepare-release` before every Beta and Production release. + - Because `release.yml` reads `app-versionName` and `app-versionCode` directly from `gradle/libs.versions.toml` without overriding them via `-P` flags or mutating tracked files, any third party (such as **IzzyOnDroid / F-Droid**) building from the release git tag gets a byte-for-byte **Reproducible Build**. ### IzzyOnDroid / F-Droid Compliance - The codebase contains zero proprietary Google Play Services or Firebase SDKs. - `app/build.gradle.kts` sets `dependenciesInfo.includeInApk = false`, stripping Google's encrypted dependency metadata block from the release `.apk` while keeping `.aab` metadata intact for Google Play Console SDK warnings. +- `release.yml` builds `:app:assembleRelease` directly from the committed `gradle/libs.versions.toml` values without build-time `-PversionCode` overrides, ensuring 100% **Reproducible Builds**. - Every release attaches `WLED-Native-v.apk` to the GitHub Release after the Play Store upload succeeds, which IzzyOnDroid automatically polls and publishes. --- ## 2. Publishing a Beta / TestFlight Build (from `dev`) -*(Tip: Run the `/prepare-release` skill to automatically audit commits/PRs since the last stable release, verify `dev.md` on both platforms, and prepare the incremental Beta GitHub Release draft.)* +*(Tip: Run the `/prepare-release` skill to automatically audit commits/PRs since the last stable release, verify `dev.md` on both platforms, bump `app-versionCode` and `app-versionName` in a release-prep PR to `dev`, and prepare the incremental Beta GitHub Release draft.)* -You can send a Beta build to Google Play Open Testing (`beta`) and the TestFlight `"Beta"` external group in any of three ways: +Once the Beta release-prep PR (bumping `app-versionCode` by `+1` and `app-versionName` to e.g. `8.1.0-beta.1`) is merged into `dev`, you can trigger the Beta build to Google Play Open Testing (`beta`) and the TestFlight `"Beta"` external group in any of three ways: ### Option A: Publish the Release Drafter Draft as a Pre-Release (GitHub UI) 1. Go to **GitHub Releases** and open the draft created by Release Drafter. @@ -55,8 +54,8 @@ gh release create v8.1.0-beta.1 --prerelease --target dev --title "v8.1.0-beta.1 - Rename `iosApp/wled/changelog/dev.md` → `.md` (e.g. `8.1.0.md`). - Create `fastlane/changelogs/.txt` and curate the top highlights so it stays within Google Play's **500-character limit** (`fastlane/changelogs/README.md`), while keeping `app/src/main/assets/changelog/.md` full-length for the in-app "What's New" sheet. - Run the `/translate-changelog` skill to generate localized `..md` release notes (e.g. `8.1.0.fr.md`, `8.1.0.de.md`, `8.1.0.zh.md`) and localized `fastlane/changelogs/..txt` summaries (`<= 500` chars each). Both the in-app "What's New" sheet and Fastlane store uploads automatically pick up localized files when present and fall back to English. - - Update `app-versionName` in `gradle/libs.versions.toml` and `MARKETING_VERSION` in `iosApp/wled.xcodeproj/project.pbxproj`. - - *(Safety net)*: The `Check` workflow on PRs targeting `main` will block merging if `dev.md` hasn't been renamed or `fastlane/changelogs/.txt` is missing—and if you ever publish a release tag directly without renaming `dev.md`, `release.yml` automatically promotes `dev` → `` inside the build workspace so your tagged release still succeeds without needing a version bump. + - Increment `app-versionCode` by `+1` and update `app-versionName` in `gradle/libs.versions.toml`, and update `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION` in `iosApp/wled.xcodeproj/project.pbxproj`. + - *(Safety net)*: The `Check` workflow on PRs targeting `main` will block merging if `dev.md` hasn't been renamed, if `fastlane/changelogs/.txt` is missing, or if `app-versionName` still has a `-beta` suffix, and `release.yml` verifies in `prepare` that the tag matches `gradle/libs.versions.toml`. 2. **Merge `dev` into `main`**: - Open and merge a PR from `dev` → `main`. 3. **Publish the GitHub Release**: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2208440d..2716dbd3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # App version - Update these when releasing -app-versionCode = "53" +app-versionCode = "108" app-versionName = "8.0.0" agp = "9.5.0" diff --git a/iosApp/wled.xcodeproj/project.pbxproj b/iosApp/wled.xcodeproj/project.pbxproj index 7e08c3e9..ced5b054 100644 --- a/iosApp/wled.xcodeproj/project.pbxproj +++ b/iosApp/wled.xcodeproj/project.pbxproj @@ -428,7 +428,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = WLEDWidgets/WLEDWidgets.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 108; DEVELOPMENT_TEAM = RWLNCUSDCJ; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = WLEDWidgets/Info.plist; @@ -460,7 +460,7 @@ buildSettings = { CODE_SIGN_ENTITLEMENTS = WLEDWidgets/WLEDWidgets.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 108; DEVELOPMENT_TEAM = RWLNCUSDCJ; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = WLEDWidgets/Info.plist; @@ -628,7 +628,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = wled/WLED.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 108; DEVELOPMENT_ASSET_PATHS = "\"wled/View/Preview\""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = wled/Info.plist; @@ -664,7 +664,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = wled/WLED.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 108; DEVELOPMENT_ASSET_PATHS = "\"wled/View/Preview\""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = wled/Info.plist; From 0e47399e94af796e3e70df208cb58d33c44d9ecc Mon Sep 17 00:00:00 2001 From: Moustachauve <2206577+Moustachauve@users.noreply.github.com> Date: Fri, 6 Nov 2026 00:03:20 -0500 Subject: [PATCH 2/2] Rename app-versionCode/Name keys to use underscores for yq compatibility IzzyOnDroid's Debian yq parser cannot evaluate dashed keys like .versions.app-versionCode in gradle/libs.versions.toml (#309). Using underscores (app_versionCode and app_versionName) allows yq to read them directly while generating the same libs.versions.app.versionCode Kotlin accessors in Gradle. --- .agents/skills/prepare-release/SKILL.md | 4 ++-- .github/workflows/check.yml | 4 ++-- .github/workflows/release.yml | 8 ++++---- docs/RELEASING.md | 12 ++++++------ gradle/libs.versions.toml | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.agents/skills/prepare-release/SKILL.md b/.agents/skills/prepare-release/SKILL.md index 99532967..c7d31182 100644 --- a/.agents/skills/prepare-release/SKILL.md +++ b/.agents/skills/prepare-release/SKILL.md @@ -63,7 +63,7 @@ Use this skill to prepare either a **Beta (`beta`)** or a **Full / Stable (`prod - `dev.md` is **cumulative**: it contains everything new since the last **stable** release. - If the Phase 2 audit found missing user-facing items, update the affected platform `dev.md` file(s) following the changelog style rules in `AGENTS.md` and each changelog directory's `README.md`. Keep `dev.md` in English only and do not rename it (for Beta builds on Google Play, `fastlane/Fastfile` automatically summarizes `app/src/main/assets/changelog/dev.md` to `<= 500` characters, so do not commit a standing `fastlane/changelogs/dev.txt` on `dev`). 2. **Bump Version Metadata (`gradle/libs.versions.toml` & `project.pbxproj`):** - - Increment `app-versionCode` by `+1` and set `app-versionName = "-beta."` in `gradle/libs.versions.toml` (committed in git so Android builds are 100% reproducible from the release tag). + - Increment `app_versionCode` by `+1` and set `app_versionName = "-beta."` in `gradle/libs.versions.toml` (committed in git so Android builds are 100% reproducible from the release tag). - Set `MARKETING_VERSION = ;` (numeric `X.Y.Z` without `-beta.`) and `CURRENT_PROJECT_VERSION = ;` on the `WLED` and `WLEDWidgets` targets in `iosApp/wled.xcodeproj/project.pbxproj`. 3. **Open the Beta Preparation PR to `dev`:** - Create a branch off `dev` (e.g., `chore/prepare-release--beta.`), run verification checks (`./gradlew spotlessApply detekt`), ask the user for confirmation, and open a PR targeting `dev`. @@ -99,7 +99,7 @@ Use this skill to prepare either a **Beta (`beta`)** or a **Full / Stable (`prod 2. **Translate `.md` and `fastlane/changelogs/.txt` into Supported Languages:** - Read `.agents/skills/translate-changelog/SKILL.md` and execute Steps 2–5 (skipping Step 1 since `.md` and `.txt` were already prepared above) to generate `..md` translations across all supported platforms and `..txt` translations in `fastlane/changelogs/` (each verified to be `<= 500` characters). 3. **Bump Version Metadata (`gradle/libs.versions.toml` & `project.pbxproj`):** - - Increment `app-versionCode` by `+1` and set `app-versionName = ""` in `gradle/libs.versions.toml` (committed in git so Android builds are 100% reproducible from the release tag). + - Increment `app_versionCode` by `+1` and set `app_versionName = ""` in `gradle/libs.versions.toml` (committed in git so Android builds are 100% reproducible from the release tag). - Set `MARKETING_VERSION = ;` and `CURRENT_PROJECT_VERSION = ;` on the `WLED` and `WLEDWidgets` targets in `iosApp/wled.xcodeproj/project.pbxproj`. 4. **Open the Release Preparation PR to `dev`:** - Run repository lint/verification checks (`./gradlew spotlessApply detekt`). diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5a75d1ac..9ddb8c2e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -228,9 +228,9 @@ jobs: if: github.event_name == 'pull_request' && github.base_ref == 'main' run: | set -euo pipefail - TOML_VERSION=$(grep -E '^app-versionName\s*=' gradle/libs.versions.toml | sed -E 's/.*"([^"]+)".*/\1/') + TOML_VERSION=$(grep -E '^app_versionName\s*=' gradle/libs.versions.toml | sed -E 's/.*"([^"]+)".*/\1/') if [[ "$TOML_VERSION" == *-* ]]; then - echo "::error file=gradle/libs.versions.toml::app-versionName is \"$TOML_VERSION\" (pre-release). Update it to the stable X.Y.Z version before merging to main." + echo "::error file=gradle/libs.versions.toml::app_versionName is \"$TOML_VERSION\" (pre-release). Update it to the stable X.Y.Z version before merging to main." exit 1 fi if [ -f "app/src/main/assets/changelog/dev.md" ] || [ -f "iosApp/wled/changelog/dev.md" ] || [ -f "fastlane/changelogs/dev.txt" ]; then diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 67985d2b..abfe88bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -70,10 +70,10 @@ jobs: set -euo pipefail # 1. Read committed version metadata from gradle/libs.versions.toml (single source of truth for Reproducible Builds) - TOML_VERSION_NAME=$(grep -E '^app-versionName\s*=' gradle/libs.versions.toml | sed -E 's/.*"([^"]+)".*/\1/') - VERSION_CODE=$(grep -E '^app-versionCode\s*=' gradle/libs.versions.toml | sed -E 's/.*"([^"]+)".*/\1/') + TOML_VERSION_NAME=$(grep -E '^app_versionName\s*=' gradle/libs.versions.toml | sed -E 's/.*"([^"]+)".*/\1/') + VERSION_CODE=$(grep -E '^app_versionCode\s*=' gradle/libs.versions.toml | sed -E 's/.*"([^"]+)".*/\1/') if [ -z "$TOML_VERSION_NAME" ] || ! [[ "$VERSION_CODE" =~ ^[0-9]+$ ]]; then - echo "::error::Invalid app-versionName (\"$TOML_VERSION_NAME\") or app-versionCode (\"$VERSION_CODE\") in gradle/libs.versions.toml." + echo "::error::Invalid app_versionName (\"$TOML_VERSION_NAME\") or app_versionCode (\"$VERSION_CODE\") in gradle/libs.versions.toml." exit 1 fi @@ -104,7 +104,7 @@ jobs: VERSION_NAME="${RAW_VERSION#v}" if [ "$VERSION_NAME" != "$TOML_VERSION_NAME" ]; then - echo "::error::Release version \"$VERSION_NAME\" does not match app-versionName=\"$TOML_VERSION_NAME\" in gradle/libs.versions.toml. Run /prepare-release so builds are reproducible." + echo "::error::Release version \"$VERSION_NAME\" does not match app_versionName=\"$TOML_VERSION_NAME\" in gradle/libs.versions.toml. Run /prepare-release so builds are reproducible." exit 1 fi if [ "$IS_BETA" = "false" ] && [[ "$VERSION_NAME" == *-* ]]; then diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 85536840..07bc5e3f 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -12,8 +12,8 @@ WLED-App uses GitHub Actions (`.github/workflows/release.yml`) and Fastlane (`fa ### Version Numbers & Build Codes - **Single Source of Truth (`gradle/libs.versions.toml` & `project.pbxproj`)**: - - `app-versionName` and `app-versionCode` in `gradle/libs.versions.toml` (and `MARKETING_VERSION` / `CURRENT_PROJECT_VERSION` in `iosApp/wled.xcodeproj/project.pbxproj`) are bumped by `/prepare-release` before every Beta and Production release. - - Because `release.yml` reads `app-versionName` and `app-versionCode` directly from `gradle/libs.versions.toml` without overriding them via `-P` flags or mutating tracked files, any third party (such as **IzzyOnDroid / F-Droid**) building from the release git tag gets a byte-for-byte **Reproducible Build**. + - `app_versionName` and `app_versionCode` in `gradle/libs.versions.toml` (and `MARKETING_VERSION` / `CURRENT_PROJECT_VERSION` in `iosApp/wled.xcodeproj/project.pbxproj`) are bumped by `/prepare-release` before every Beta and Production release. + - Because `release.yml` reads `app_versionName` and `app_versionCode` directly from `gradle/libs.versions.toml` without overriding them via `-P` flags or mutating tracked files, any third party (such as **IzzyOnDroid / F-Droid**) building from the release git tag gets a byte-for-byte **Reproducible Build**. ### IzzyOnDroid / F-Droid Compliance - The codebase contains zero proprietary Google Play Services or Firebase SDKs. @@ -25,9 +25,9 @@ WLED-App uses GitHub Actions (`.github/workflows/release.yml`) and Fastlane (`fa ## 2. Publishing a Beta / TestFlight Build (from `dev`) -*(Tip: Run the `/prepare-release` skill to automatically audit commits/PRs since the last stable release, verify `dev.md` on both platforms, bump `app-versionCode` and `app-versionName` in a release-prep PR to `dev`, and prepare the incremental Beta GitHub Release draft.)* +*(Tip: Run the `/prepare-release` skill to automatically audit commits/PRs since the last stable release, verify `dev.md` on both platforms, bump `app_versionCode` and `app_versionName` in a release-prep PR to `dev`, and prepare the incremental Beta GitHub Release draft.)* -Once the Beta release-prep PR (bumping `app-versionCode` by `+1` and `app-versionName` to e.g. `8.1.0-beta.1`) is merged into `dev`, you can trigger the Beta build to Google Play Open Testing (`beta`) and the TestFlight `"Beta"` external group in any of three ways: +Once the Beta release-prep PR (bumping `app_versionCode` by `+1` and `app_versionName` to e.g. `8.1.0-beta.1`) is merged into `dev`, you can trigger the Beta build to Google Play Open Testing (`beta`) and the TestFlight `"Beta"` external group in any of three ways: ### Option A: Publish the Release Drafter Draft as a Pre-Release (GitHub UI) 1. Go to **GitHub Releases** and open the draft created by Release Drafter. @@ -54,8 +54,8 @@ gh release create v8.1.0-beta.1 --prerelease --target dev --title "v8.1.0-beta.1 - Rename `iosApp/wled/changelog/dev.md` → `.md` (e.g. `8.1.0.md`). - Create `fastlane/changelogs/.txt` and curate the top highlights so it stays within Google Play's **500-character limit** (`fastlane/changelogs/README.md`), while keeping `app/src/main/assets/changelog/.md` full-length for the in-app "What's New" sheet. - Run the `/translate-changelog` skill to generate localized `..md` release notes (e.g. `8.1.0.fr.md`, `8.1.0.de.md`, `8.1.0.zh.md`) and localized `fastlane/changelogs/..txt` summaries (`<= 500` chars each). Both the in-app "What's New" sheet and Fastlane store uploads automatically pick up localized files when present and fall back to English. - - Increment `app-versionCode` by `+1` and update `app-versionName` in `gradle/libs.versions.toml`, and update `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION` in `iosApp/wled.xcodeproj/project.pbxproj`. - - *(Safety net)*: The `Check` workflow on PRs targeting `main` will block merging if `dev.md` hasn't been renamed, if `fastlane/changelogs/.txt` is missing, or if `app-versionName` still has a `-beta` suffix, and `release.yml` verifies in `prepare` that the tag matches `gradle/libs.versions.toml`. + - Increment `app_versionCode` by `+1` and update `app_versionName` in `gradle/libs.versions.toml`, and update `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION` in `iosApp/wled.xcodeproj/project.pbxproj`. + - *(Safety net)*: The `Check` workflow on PRs targeting `main` will block merging if `dev.md` hasn't been renamed, if `fastlane/changelogs/.txt` is missing, or if `app_versionName` still has a `-beta` suffix, and `release.yml` verifies in `prepare` that the tag matches `gradle/libs.versions.toml`. 2. **Merge `dev` into `main`**: - Open and merge a PR from `dev` → `main`. 3. **Publish the GitHub Release**: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2716dbd3..3150b0a5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] -# App version - Update these when releasing -app-versionCode = "108" -app-versionName = "8.0.0" +# App version - Update these when releasing (underscores used instead of dashes for IzzyOnDroid's yq parser) +app_versionCode = "108" +app_versionName = "8.0.0" agp = "9.5.0" activityCompose = "1.13.0"