mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
refactor(version): derive Apple build numbers
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
# Application versioning
|
||||
|
||||
`version.properties` at the repository root is the single source of truth for
|
||||
the VniDrop application version. Platform projects and release workflows read
|
||||
that file rather than accepting independent version overrides.
|
||||
the VniDrop product version and persistent store counters. Platform projects
|
||||
and release workflows use the version resolver rather than accepting
|
||||
independent version overrides.
|
||||
|
||||
Keep it as plain `KEY=VALUE` assignments: the same file is parsed by shell,
|
||||
PowerShell, Gradle, Rust, and Xcode.
|
||||
PowerShell, Gradle, and Rust. Xcode receives resolver-generated xcconfig files.
|
||||
|
||||
The product uses numeric semantic versions. While the app is in beta, feature
|
||||
releases increment the minor component (`0.2.0`, `0.3.0`) and fixes increment
|
||||
@@ -15,8 +16,9 @@ the patch component (`0.2.1`). Release channels belong in
|
||||
| Platform | Product version | Platform build/package version |
|
||||
| --- | --- | --- |
|
||||
| Android | `PRODUCT_VERSION` | `ANDROID_VERSION_CODE` |
|
||||
| Apple | `PRODUCT_VERSION` | `APPLE_BUILD_NUMBER` |
|
||||
| Linux and direct macOS | `PRODUCT_VERSION` | Native package revision |
|
||||
| Apple Store | `PRODUCT_VERSION` | Derived UTC `YYYYMMDD.HHMM.SS` |
|
||||
| Direct macOS | `PRODUCT_VERSION` | Independently derived UTC `YYYYMMDD.HHMM.SS` |
|
||||
| Linux | `PRODUCT_VERSION` | Native package revision |
|
||||
| Rust handshake | `PRODUCT_VERSION` | Rust crate version remains independent |
|
||||
| Microsoft Store | `PRODUCT_VERSION` in the app | Derived MSIX dot-quad |
|
||||
|
||||
@@ -30,9 +32,23 @@ the Store. Its version is:
|
||||
With epoch `1`, product `0.2.0` maps to MSIX `1.2.0.0`, while product `1.0.0`
|
||||
maps to `2.0.0.0`. Do not change the epoch after publishing.
|
||||
|
||||
Every Android or Apple upload must increment its platform build number. Every
|
||||
changed Windows Store package must increment the product version because the
|
||||
Store-reserved fourth component cannot carry a rebuild number.
|
||||
Every Android upload must increment `ANDROID_VERSION_CODE`. Apple build numbers
|
||||
are derived at build time by `apple-store-build` and `apple-direct-build`; they
|
||||
are kept as separate resolver outputs so App Store and Sparkle releases do not
|
||||
consume each other's cadence. Every changed Windows Store package must
|
||||
increment the product version because the Store-reserved fourth component
|
||||
cannot carry a rebuild number.
|
||||
|
||||
Apple projects read generated build settings rather than `version.properties`
|
||||
directly:
|
||||
|
||||
```bash
|
||||
packaging/version/generate-apple-xcconfig.sh all
|
||||
```
|
||||
|
||||
The generated files under `apple/Generated/` are intentionally ignored.
|
||||
`VNIDROP_BUILD_TIME_UTC=YYYYMMDDHHMMSS` provides a deterministic clock for
|
||||
tests; distribution builds normally use the current UTC time.
|
||||
|
||||
Before releasing:
|
||||
|
||||
|
||||
48
packaging/version/generate-apple-xcconfig.sh
Executable file
48
packaging/version/generate-apple-xcconfig.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
repo_root="$(cd "$script_dir/../.." && pwd)"
|
||||
resolver="$script_dir/resolve-version.sh"
|
||||
output_dir="${VNIDROP_APPLE_XCCONFIG_DIR:-$repo_root/apple/Generated}"
|
||||
mode="${1:-all}"
|
||||
|
||||
case "$mode" in
|
||||
store|direct|all) ;;
|
||||
*)
|
||||
printf 'Usage: %s {store|direct|all}\n' "$0" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
export VNIDROP_BUILD_TIME_UTC="${VNIDROP_BUILD_TIME_UTC:-$(date -u +%Y%m%d%H%M%S)}"
|
||||
product_version="$("$resolver" product)"
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
write_config() {
|
||||
local filename=$1
|
||||
local build_field=$2
|
||||
local destination="$output_dir/$filename"
|
||||
local temporary
|
||||
local build_number
|
||||
|
||||
build_number="$("$resolver" "$build_field")"
|
||||
temporary="$(mktemp "$output_dir/.${filename}.XXXXXX")"
|
||||
printf '%s\n' \
|
||||
'// Generated by packaging/version/generate-apple-xcconfig.sh.' \
|
||||
'// Regenerate this file instead of editing it.' \
|
||||
'#include "../Signing.xcconfig"' \
|
||||
'' \
|
||||
"PRODUCT_VERSION = $product_version" \
|
||||
"CURRENT_PROJECT_VERSION = $build_number" \
|
||||
> "$temporary"
|
||||
mv "$temporary" "$destination"
|
||||
}
|
||||
|
||||
if [[ $mode == store || $mode == all ]]; then
|
||||
write_config StoreVersion.xcconfig apple-store-build
|
||||
fi
|
||||
if [[ $mode == direct || $mode == all ]]; then
|
||||
write_config DirectVersion.xcconfig apple-direct-build
|
||||
fi
|
||||
@@ -1,6 +1,6 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet("Product", "Channel", "AndroidCode", "AppleBuild", "WindowsPackage", "Json", "Verify")]
|
||||
[ValidateSet("Product", "Channel", "AndroidCode", "AppleStoreBuild", "AppleDirectBuild", "WindowsPackage", "Json", "Verify")]
|
||||
[string] $Field = "Verify",
|
||||
|
||||
[switch] $VerifyTag,
|
||||
@@ -48,8 +48,31 @@ function Convert-CanonicalInteger {
|
||||
$productVersion = Read-VersionProperty "PRODUCT_VERSION"
|
||||
$releaseChannel = Read-VersionProperty "RELEASE_CHANNEL"
|
||||
$androidVersionCodeText = Read-VersionProperty "ANDROID_VERSION_CODE"
|
||||
$appleBuildNumber = Read-VersionProperty "APPLE_BUILD_NUMBER"
|
||||
$windowsVersionEpochText = Read-VersionProperty "WINDOWS_VERSION_EPOCH"
|
||||
$buildTimeUtc = $env:VNIDROP_BUILD_TIME_UTC
|
||||
if ([string]::IsNullOrWhiteSpace($buildTimeUtc)) {
|
||||
$buildTimeUtc = [DateTime]::UtcNow.ToString(
|
||||
"yyyyMMddHHmmss",
|
||||
[Globalization.CultureInfo]::InvariantCulture
|
||||
)
|
||||
}
|
||||
if ($buildTimeUtc -notmatch "^[0-9]{14}$") {
|
||||
throw "VNIDROP_BUILD_TIME_UTC must use YYYYMMDDHHMMSS"
|
||||
}
|
||||
$parsedBuildTime = [DateTime]::MinValue
|
||||
if (-not [DateTime]::TryParseExact(
|
||||
$buildTimeUtc,
|
||||
"yyyyMMddHHmmss",
|
||||
[Globalization.CultureInfo]::InvariantCulture,
|
||||
[Globalization.DateTimeStyles]::AssumeUniversal -bor [Globalization.DateTimeStyles]::AdjustToUniversal,
|
||||
[ref] $parsedBuildTime
|
||||
)) {
|
||||
throw "VNIDROP_BUILD_TIME_UTC is not a valid UTC timestamp"
|
||||
}
|
||||
$appleBuildNumber = $parsedBuildTime.ToString(
|
||||
"yyyyMMdd.HHmm.ss",
|
||||
[Globalization.CultureInfo]::InvariantCulture
|
||||
)
|
||||
|
||||
if ($productVersion -notmatch "^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$") {
|
||||
throw "PRODUCT_VERSION must use canonical MAJOR.MINOR.PATCH integers"
|
||||
@@ -62,9 +85,6 @@ if ($releaseChannel -notmatch "^[a-z][a-z0-9-]*$") {
|
||||
throw "RELEASE_CHANNEL contains unsupported characters"
|
||||
}
|
||||
$androidVersionCode = Convert-CanonicalInteger "ANDROID_VERSION_CODE" $androidVersionCodeText 1 2100000000
|
||||
if ($appleBuildNumber -notmatch "^[1-9][0-9]*(\.[0-9]+){0,2}$") {
|
||||
throw "APPLE_BUILD_NUMBER must contain one to three period-separated non-negative integers and start above zero"
|
||||
}
|
||||
$windowsVersionEpoch = Convert-CanonicalInteger "WINDOWS_VERSION_EPOCH" $windowsVersionEpochText 1 65535
|
||||
$windowsMajor = $productMajor + $windowsVersionEpoch
|
||||
if ($windowsMajor -gt 65535) {
|
||||
@@ -80,7 +100,8 @@ $versionInfo = [ordered] @{
|
||||
productVersion = $productVersion
|
||||
releaseChannel = $releaseChannel
|
||||
androidVersionCode = $androidVersionCode
|
||||
appleBuildNumber = $appleBuildNumber
|
||||
appleStoreBuildNumber = $appleBuildNumber
|
||||
appleDirectBuildNumber = $appleBuildNumber
|
||||
windowsPackageVersion = $windowsPackageVersion
|
||||
}
|
||||
|
||||
@@ -88,10 +109,11 @@ switch ($Field) {
|
||||
"Product" { $productVersion }
|
||||
"Channel" { $releaseChannel }
|
||||
"AndroidCode" { $androidVersionCode }
|
||||
"AppleBuild" { $appleBuildNumber }
|
||||
"AppleStoreBuild" { $appleBuildNumber }
|
||||
"AppleDirectBuild" { $appleBuildNumber }
|
||||
"WindowsPackage" { $windowsPackageVersion }
|
||||
"Json" { $versionInfo | ConvertTo-Json -Compress }
|
||||
"Verify" {
|
||||
"VniDrop $productVersion ($releaseChannel), Android $androidVersionCode, Apple $appleBuildNumber, MSIX $windowsPackageVersion"
|
||||
"VniDrop $productVersion ($releaseChannel), Android $androidVersionCode, Apple Store $appleBuildNumber, Apple Direct $appleBuildNumber, MSIX $windowsPackageVersion"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ validate_canonical_integer() {
|
||||
product_version="$(read_property PRODUCT_VERSION)"
|
||||
release_channel="$(read_property RELEASE_CHANNEL)"
|
||||
android_version_code="$(read_property ANDROID_VERSION_CODE)"
|
||||
apple_build_number="$(read_property APPLE_BUILD_NUMBER)"
|
||||
windows_version_epoch="$(read_property WINDOWS_VERSION_EPOCH)"
|
||||
build_time_utc="${VNIDROP_BUILD_TIME_UTC:-$(date -u +%Y%m%d%H%M%S)}"
|
||||
|
||||
[[ $product_version =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] ||
|
||||
fail "PRODUCT_VERSION must use canonical MAJOR.MINOR.PATCH integers"
|
||||
@@ -47,9 +47,41 @@ validate_canonical_integer "PRODUCT_VERSION patch" "$product_patch" 0 65535
|
||||
[[ $release_channel =~ ^[a-z][a-z0-9-]*$ ]] ||
|
||||
fail "RELEASE_CHANNEL must start with a lowercase letter and contain only lowercase letters, digits, and hyphens"
|
||||
validate_canonical_integer "ANDROID_VERSION_CODE" "$android_version_code" 1 2100000000
|
||||
[[ $apple_build_number =~ ^[1-9][0-9]*(\.[0-9]+){0,2}$ ]] ||
|
||||
fail "APPLE_BUILD_NUMBER must contain one to three period-separated non-negative integers and start above zero"
|
||||
validate_canonical_integer "WINDOWS_VERSION_EPOCH" "$windows_version_epoch" 1 65535
|
||||
[[ $build_time_utc =~ ^[0-9]{14}$ ]] ||
|
||||
fail "VNIDROP_BUILD_TIME_UTC must use YYYYMMDDHHMMSS"
|
||||
|
||||
build_month="${build_time_utc:4:2}"
|
||||
build_day="${build_time_utc:6:2}"
|
||||
build_hour="${build_time_utc:8:2}"
|
||||
build_minute="${build_time_utc:10:2}"
|
||||
build_second="${build_time_utc:12:2}"
|
||||
build_year="${build_time_utc:0:4}"
|
||||
(( 10#$build_year >= 1 )) ||
|
||||
fail "VNIDROP_BUILD_TIME_UTC contains an invalid year"
|
||||
(( 10#$build_month >= 1 && 10#$build_month <= 12 )) ||
|
||||
fail "VNIDROP_BUILD_TIME_UTC contains an invalid month"
|
||||
|
||||
case $((10#$build_month)) in
|
||||
2)
|
||||
max_build_day=28
|
||||
if (( 10#$build_year % 400 == 0 ||
|
||||
(10#$build_year % 4 == 0 && 10#$build_year % 100 != 0) )); then
|
||||
max_build_day=29
|
||||
fi
|
||||
;;
|
||||
4|6|9|11)
|
||||
max_build_day=30
|
||||
;;
|
||||
*)
|
||||
max_build_day=31
|
||||
;;
|
||||
esac
|
||||
(( 10#$build_day >= 1 && 10#$build_day <= max_build_day )) ||
|
||||
fail "VNIDROP_BUILD_TIME_UTC contains an invalid day"
|
||||
(( 10#$build_hour <= 23 && 10#$build_minute <= 59 && 10#$build_second <= 59 )) ||
|
||||
fail "VNIDROP_BUILD_TIME_UTC contains an invalid time"
|
||||
apple_build_number="${build_time_utc:0:8}.${build_time_utc:8:4}.${build_time_utc:12:2}"
|
||||
|
||||
windows_major=$((10#$product_major + 10#$windows_version_epoch))
|
||||
(( windows_major <= 65535 )) ||
|
||||
@@ -74,7 +106,10 @@ case "${1:-verify}" in
|
||||
android-code)
|
||||
printf '%s\n' "$android_version_code"
|
||||
;;
|
||||
apple-build)
|
||||
apple-store-build)
|
||||
printf '%s\n' "$apple_build_number"
|
||||
;;
|
||||
apple-direct-build)
|
||||
printf '%s\n' "$apple_build_number"
|
||||
;;
|
||||
windows-package)
|
||||
@@ -82,14 +117,14 @@ case "${1:-verify}" in
|
||||
;;
|
||||
verify)
|
||||
verify_tag
|
||||
printf 'VniDrop %s (%s), Android %s, Apple %s, MSIX %s\n' \
|
||||
printf 'VniDrop %s (%s), Android %s, Apple Store %s, Apple Direct %s, MSIX %s\n' \
|
||||
"$product_version" "$release_channel" "$android_version_code" \
|
||||
"$apple_build_number" "$windows_package_version"
|
||||
"$apple_build_number" "$apple_build_number" "$windows_package_version"
|
||||
;;
|
||||
verify-tag)
|
||||
verify_tag "${2:-}"
|
||||
;;
|
||||
*)
|
||||
fail "Usage: $0 {product|channel|android-code|apple-build|windows-package|verify|verify-tag [tag]}"
|
||||
fail "Usage: $0 {product|channel|android-code|apple-store-build|apple-direct-build|windows-package|verify|verify-tag [tag]}"
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -12,8 +12,7 @@ write_version() {
|
||||
"PRODUCT_VERSION=$1" \
|
||||
"RELEASE_CHANNEL=$2" \
|
||||
"ANDROID_VERSION_CODE=$3" \
|
||||
"APPLE_BUILD_NUMBER=$4" \
|
||||
"WINDOWS_VERSION_EPOCH=$5" \
|
||||
"WINDOWS_VERSION_EPOCH=$4" \
|
||||
> "$scratch/version.properties"
|
||||
}
|
||||
|
||||
@@ -28,24 +27,41 @@ expect_failure() {
|
||||
fi
|
||||
}
|
||||
|
||||
write_version 0.2.0 beta 2 2 1
|
||||
export VNIDROP_BUILD_TIME_UTC=20260728143217
|
||||
|
||||
write_version 0.2.0 beta 2 1
|
||||
[[ $(resolve product) == 0.2.0 ]]
|
||||
[[ $(resolve android-code) == 2 ]]
|
||||
[[ $(resolve apple-build) == 2 ]]
|
||||
[[ $(resolve apple-store-build) == 20260728.1432.17 ]]
|
||||
[[ $(resolve apple-direct-build) == 20260728.1432.17 ]]
|
||||
[[ $(resolve windows-package) == 1.2.0.0 ]]
|
||||
resolve verify-tag v0.2.0
|
||||
expect_failure resolve verify-tag v1.0.0
|
||||
|
||||
write_version 1.0.0 stable 42 42 1
|
||||
write_version 1.0.0 stable 42 1
|
||||
[[ $(resolve windows-package) == 2.0.0.0 ]]
|
||||
|
||||
write_version 01.0.0 beta 2 2 1
|
||||
write_version 01.0.0 beta 2 1
|
||||
expect_failure resolve verify
|
||||
|
||||
write_version 0.2.0 beta 0 2 1
|
||||
write_version 0.2.0 beta 0 1
|
||||
expect_failure resolve verify
|
||||
|
||||
write_version 65535.0.0 stable 2 2 1
|
||||
write_version 65535.0.0 stable 2 1
|
||||
expect_failure resolve verify
|
||||
|
||||
VNIDROP_BUILD_TIME_UTC=20260728146000 expect_failure resolve verify
|
||||
VNIDROP_BUILD_TIME_UTC=2026-07-28 expect_failure resolve verify
|
||||
VNIDROP_BUILD_TIME_UTC=20260229080000 expect_failure resolve verify
|
||||
|
||||
write_version 0.2.0 beta 2 1
|
||||
config_dir="$scratch/xcconfig"
|
||||
VNIDROP_APPLE_XCCONFIG_DIR="$config_dir" \
|
||||
"$script_dir/generate-apple-xcconfig.sh" all
|
||||
grep -Fx "PRODUCT_VERSION = 0.2.0" "$config_dir/StoreVersion.xcconfig" >/dev/null
|
||||
grep -Fx "CURRENT_PROJECT_VERSION = 20260728.1432.17" \
|
||||
"$config_dir/StoreVersion.xcconfig" >/dev/null
|
||||
grep -Fx "CURRENT_PROJECT_VERSION = 20260728.1432.17" \
|
||||
"$config_dir/DirectVersion.xcconfig" >/dev/null
|
||||
|
||||
printf 'Version resolver tests passed.\n'
|
||||
|
||||
Reference in New Issue
Block a user