← 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
M .github/workflows/build-app.yml
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 →