[object Object]

← back to Exo

ci: compute CURRENT_PROJECT_VERSION from semver

b9a78f6f3aa119624e9bfeac1038071de6d68e59 · 2026-01-08 18:43:36 +0000 · Jake Hillion

Previous Sparkle builds were cut from a different repo with different
build numbers, breaking version ordering. Users aren't receiving updates
because CFBundleVersion values don't reflect the actual version sequence.

Added a step to compute the build version deterministically from semver:
PRERELEASE + (1000 * PATCH) + (1_000_000 * MINOR) + (1_000_000_000 * MAJOR).
Release versions use prerelease=999 to ensure they're always higher than
their prereleases (e.g., 1.0.61 > 1.0.61-alpha.3).

This ensures consistent version ordering across repos, allowing Sparkle
to correctly identify and deliver updates to users.

Test plan:
- Verified formula with test script:

```sh
compute_version() {
  VERSION="$1"
  BASE_VERSION="${VERSION%%-*}"
  MAJOR=$(echo "$BASE_VERSION" | cut -d. -f1)
  MINOR=$(echo "$BASE_VERSION" | cut -d. -f2)
  PATCH=$(echo "$BASE_VERSION" | cut -d. -f3)

  if [[ "$VERSION" == *-* ]]; then
    PRERELEASE_PART="${VERSION#*-}"
    PRERELEASE_NUM="${PRERELEASE_PART##*.}"
    if ! [[ "$PRERELEASE_NUM" =~ ^[0-9]+$ ]]; then
      PRERELEASE_NUM=0
    fi
  else
    PRERELEASE_NUM=999
  fi

  BUILD_VERSION=$((PRERELEASE_NUM + 1000 * PATCH + 1000000 * MINOR + 1000000000 * MAJOR))
  printf "%-20s -> %12s\n" "$VERSION" "$BUILD_VERSION"
}

compute_version "1.0.61-alpha.2"
compute_version "1.0.61-alpha.3"
compute_version "1.0.61"
compute_version "1.0.62-alpha.1"
compute_version "1.1.0-alpha.1"
compute_version "2.0.0-alpha.1"
compute_version "0.0.0-alpha.0"
compute_version "0.0.1-alpha.1"
compute_version "1.2.3"
compute_version "1.2.3-beta.5"
```

- Output:

```sh
Version              -> Build Number
----------------------------------------
1.0.61-alpha.2       ->   1000061002
1.0.61-alpha.3       ->   1000061003
1.0.61               ->   1000061999
1.0.62-alpha.1       ->   1000062001
1.1.0-alpha.1        ->   1001000001
2.0.0-alpha.1        ->   2000000001
0.0.0-alpha.0        ->            0
0.0.1-alpha.1        ->         1001
1.2.3                ->   1002003999
1.2.3-beta.5         ->   1002003005
```

- Confirmed ordering: alpha.2 < alpha.3 < release < next-alpha

Files touched

Diff

commit b9a78f6f3aa119624e9bfeac1038071de6d68e59
Author: Jake Hillion <jake@hillion.co.uk>
Date:   Thu Jan 8 18:43:36 2026 +0000

    ci: compute CURRENT_PROJECT_VERSION from semver
    
    Previous Sparkle builds were cut from a different repo with different
    build numbers, breaking version ordering. Users aren't receiving updates
    because CFBundleVersion values don't reflect the actual version sequence.
    
    Added a step to compute the build version deterministically from semver:
    PRERELEASE + (1000 * PATCH) + (1_000_000 * MINOR) + (1_000_000_000 * MAJOR).
    Release versions use prerelease=999 to ensure they're always higher than
    their prereleases (e.g., 1.0.61 > 1.0.61-alpha.3).
    
    This ensures consistent version ordering across repos, allowing Sparkle
    to correctly identify and deliver updates to users.
    
    Test plan:
    - Verified formula with test script:
    
    ```sh
    compute_version() {
      VERSION="$1"
      BASE_VERSION="${VERSION%%-*}"
      MAJOR=$(echo "$BASE_VERSION" | cut -d. -f1)
      MINOR=$(echo "$BASE_VERSION" | cut -d. -f2)
      PATCH=$(echo "$BASE_VERSION" | cut -d. -f3)
    
      if [[ "$VERSION" == *-* ]]; then
        PRERELEASE_PART="${VERSION#*-}"
        PRERELEASE_NUM="${PRERELEASE_PART##*.}"
        if ! [[ "$PRERELEASE_NUM" =~ ^[0-9]+$ ]]; then
          PRERELEASE_NUM=0
        fi
      else
        PRERELEASE_NUM=999
      fi
    
      BUILD_VERSION=$((PRERELEASE_NUM + 1000 * PATCH + 1000000 * MINOR + 1000000000 * MAJOR))
      printf "%-20s -> %12s\n" "$VERSION" "$BUILD_VERSION"
    }
    
    compute_version "1.0.61-alpha.2"
    compute_version "1.0.61-alpha.3"
    compute_version "1.0.61"
    compute_version "1.0.62-alpha.1"
    compute_version "1.1.0-alpha.1"
    compute_version "2.0.0-alpha.1"
    compute_version "0.0.0-alpha.0"
    compute_version "0.0.1-alpha.1"
    compute_version "1.2.3"
    compute_version "1.2.3-beta.5"
    ```
    
    - Output:
    
    ```sh
    Version              -> Build Number
    ----------------------------------------
    1.0.61-alpha.2       ->   1000061002
    1.0.61-alpha.3       ->   1000061003
    1.0.61               ->   1000061999
    1.0.62-alpha.1       ->   1000062001
    1.1.0-alpha.1        ->   1001000001
    2.0.0-alpha.1        ->   2000000001
    0.0.0-alpha.0        ->            0
    0.0.1-alpha.1        ->         1001
    1.2.3                ->   1002003999
    1.2.3-beta.5         ->   1002003005
    ```
    
    - Confirmed ordering: alpha.2 < alpha.3 < release < next-alpha
---
 .github/workflows/build-app.yml | 28 +++++++++++++++++++++++++++-
 1 file changed, 27 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml
index d95fabb4..c011e979 100644
--- a/.github/workflows/build-app.yml
+++ b/.github/workflows/build-app.yml
@@ -48,6 +48,32 @@ jobs:
           fi
           echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV
 
+      - name: Compute build version from semver
+        run: |
+          VERSION="$RELEASE_VERSION"
+          # Extract major.minor.patch (strip prerelease suffix)
+          BASE_VERSION="${VERSION%%-*}"
+          MAJOR=$(echo "$BASE_VERSION" | cut -d. -f1)
+          MINOR=$(echo "$BASE_VERSION" | cut -d. -f2)
+          PATCH=$(echo "$BASE_VERSION" | cut -d. -f3)
+
+          # Extract prerelease number (e.g., "alpha.2" -> 2, or 999 for releases)
+          if [[ "$VERSION" == *-* ]]; then
+            PRERELEASE_PART="${VERSION#*-}"
+            PRERELEASE_NUM="${PRERELEASE_PART##*.}"
+            # Default to 0 if not a number
+            if ! [[ "$PRERELEASE_NUM" =~ ^[0-9]+$ ]]; then
+              PRERELEASE_NUM=0
+            fi
+          else
+            PRERELEASE_NUM=999
+          fi
+
+          # Compute: PRERELEASE + (1000 * PATCH) + (1_000_000 * MINOR) + (1_000_000_000 * MAJOR)
+          BUILD_VERSION=$((PRERELEASE_NUM + 1000 * PATCH + 1000000 * MINOR + 1000000000 * MAJOR))
+          echo "EXO_BUILD_VERSION=$BUILD_VERSION" >> $GITHUB_ENV
+          echo "Computed build version: $BUILD_VERSION from $VERSION"
+
       - name: Ensure tag commit is on main
         if: github.ref_type == 'tag'
         run: |
@@ -163,7 +189,7 @@ jobs:
             -configuration Release \
             -derivedDataPath build \
             MARKETING_VERSION="$RELEASE_VERSION" \
-            CURRENT_PROJECT_VERSION="$EXO_BUILD_NUMBER" \
+            CURRENT_PROJECT_VERSION="$EXO_BUILD_VERSION" \
             EXO_BUILD_TAG="$RELEASE_VERSION" \
             EXO_BUILD_COMMIT="$GITHUB_SHA" \
             SPARKLE_FEED_URL="$SPARKLE_FEED_URL" \

← 8f7f0e89 ci: avoid uploading alpha appcasts  ·  back to Exo  ·  Fix mlx seed (#1094) c65320ac →