mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
Merge pull request #34 from sudosylabs/feat/unified-versioning
feat(release): unify cross-platform versioning
This commit is contained in:
26
.github/workflows/apple-release.yml
vendored
26
.github/workflows/apple-release.yml
vendored
@@ -8,20 +8,14 @@ name: Apple release (macOS DMG)
|
|||||||
# The App Store / TestFlight build is NOT produced here — that goes through Xcode
|
# The App Store / TestFlight build is NOT produced here — that goes through Xcode
|
||||||
# Organizer / App Store Connect. This workflow only covers direct distribution.
|
# Organizer / App Store Connect. This workflow only covers direct distribution.
|
||||||
#
|
#
|
||||||
# Trigger: push a tag vMAJOR.MINOR.PATCH (must point at a commit on master), or
|
# Trigger: push the canonical vMAJOR.MINOR.PATCH tag (must point at a commit on
|
||||||
# run manually with an explicit version (produces artifacts, no Release).
|
# master), or run manually using the version committed in version.properties.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "v*.*.*"
|
- "v*.*.*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: Release version in MAJOR.MINOR.PATCH form
|
|
||||||
required: true
|
|
||||||
default: "0.1.0"
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -58,17 +52,11 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Resolve version
|
- name: Resolve canonical version
|
||||||
id: version
|
id: version
|
||||||
env:
|
|
||||||
REQUESTED_VERSION: ${{ inputs.version || '' }}
|
|
||||||
run: |
|
run: |
|
||||||
if [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
|
packaging/version/resolve-version.sh verify >/dev/null
|
||||||
version="${GITHUB_REF_NAME#v}"
|
version="$(packaging/version/resolve-version.sh product)"
|
||||||
else
|
|
||||||
version="$REQUESTED_VERSION"
|
|
||||||
fi
|
|
||||||
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "bad version '$version'" >&2; exit 1; }
|
|
||||||
echo "app=$version" >> "$GITHUB_OUTPUT"
|
echo "app=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Select Xcode
|
- name: Select Xcode
|
||||||
@@ -147,12 +135,12 @@ jobs:
|
|||||||
echo "SPARKLE_ED_KEY_FILE=$RUNNER_TEMP/sparkle_ed_private_key" >> "$GITHUB_ENV"
|
echo "SPARKLE_ED_KEY_FILE=$RUNNER_TEMP/sparkle_ed_private_key" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Build, sign & notarize DMG
|
- name: Build, sign & notarize DMG
|
||||||
run: apple/scripts/build-dmg.sh "${{ steps.version.outputs.app }}"
|
run: apple/scripts/build-dmg.sh
|
||||||
|
|
||||||
- name: Generate appcast
|
- name: Generate appcast
|
||||||
env:
|
env:
|
||||||
RELEASE_REPO: ${{ github.repository }}
|
RELEASE_REPO: ${{ github.repository }}
|
||||||
run: apple/scripts/generate-appcast.sh "${{ steps.version.outputs.app }}"
|
run: apple/scripts/generate-appcast.sh
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
|
|||||||
4
.github/workflows/apple.yml
vendored
4
.github/workflows/apple.yml
vendored
@@ -4,6 +4,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "apple/**"
|
- "apple/**"
|
||||||
|
- "version.properties"
|
||||||
|
- "packaging/version/**"
|
||||||
- "crates/vnidrop/**"
|
- "crates/vnidrop/**"
|
||||||
- "crates/uniffi-bindgen/**"
|
- "crates/uniffi-bindgen/**"
|
||||||
- "Cargo.toml"
|
- "Cargo.toml"
|
||||||
@@ -18,6 +20,8 @@ on:
|
|||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- "apple/**"
|
- "apple/**"
|
||||||
|
- "version.properties"
|
||||||
|
- "packaging/version/**"
|
||||||
- "crates/vnidrop/**"
|
- "crates/vnidrop/**"
|
||||||
- "crates/uniffi-bindgen/**"
|
- "crates/uniffi-bindgen/**"
|
||||||
- "Cargo.toml"
|
- "Cargo.toml"
|
||||||
|
|||||||
24
.github/workflows/linux-packages.yml
vendored
24
.github/workflows/linux-packages.yml
vendored
@@ -5,6 +5,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- ".github/workflows/linux-packages.yml"
|
- ".github/workflows/linux-packages.yml"
|
||||||
- "packaging/linux/**"
|
- "packaging/linux/**"
|
||||||
|
- "packaging/version/**"
|
||||||
|
- "version.properties"
|
||||||
- "assets/linux/**"
|
- "assets/linux/**"
|
||||||
- "desktopApp/**"
|
- "desktopApp/**"
|
||||||
- "shared/**"
|
- "shared/**"
|
||||||
@@ -24,12 +26,6 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- "v*.*.*"
|
- "v*.*.*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: Release version in MAJOR.MINOR.PATCH form
|
|
||||||
required: true
|
|
||||||
default: "1.0.0"
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -88,16 +84,14 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
linux-deb-x64-cargo-1.91.0-
|
linux-deb-x64-cargo-1.91.0-
|
||||||
|
|
||||||
- name: Resolve version
|
- name: Resolve canonical version
|
||||||
id: version
|
id: version
|
||||||
env:
|
|
||||||
REQUESTED_VERSION: ${{ inputs.version || '1.0.0' }}
|
|
||||||
run: |
|
run: |
|
||||||
version=$(packaging/linux/resolve-version.sh "$REQUESTED_VERSION")
|
version=$(packaging/linux/resolve-version.sh)
|
||||||
echo "app=$version" >> "$GITHUB_OUTPUT"
|
echo "app=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Test and build Debian package
|
- name: Test and build Debian package
|
||||||
run: make package-deb VERSION=${{ steps.version.outputs.app }}
|
run: make package-deb
|
||||||
|
|
||||||
- name: Upload Debian artifact
|
- name: Upload Debian artifact
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
@@ -193,16 +187,14 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
linux-rpm-x64-cargo-1.91.0-
|
linux-rpm-x64-cargo-1.91.0-
|
||||||
|
|
||||||
- name: Resolve version
|
- name: Resolve canonical version
|
||||||
id: version
|
id: version
|
||||||
env:
|
|
||||||
REQUESTED_VERSION: ${{ inputs.version || '1.0.0' }}
|
|
||||||
run: |
|
run: |
|
||||||
version=$(packaging/linux/resolve-version.sh "$REQUESTED_VERSION")
|
version=$(packaging/linux/resolve-version.sh)
|
||||||
echo "app=$version" >> "$GITHUB_OUTPUT"
|
echo "app=$version" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Build RPM package
|
- name: Build RPM package
|
||||||
run: make package-rpm VERSION=${{ steps.version.outputs.app }}
|
run: make package-rpm
|
||||||
|
|
||||||
- name: Upload RPM artifact
|
- name: Upload RPM artifact
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
|
|||||||
4
.github/workflows/rust-core.yml
vendored
4
.github/workflows/rust-core.yml
vendored
@@ -6,6 +6,8 @@ on:
|
|||||||
- "Cargo.toml"
|
- "Cargo.toml"
|
||||||
- "Cargo.lock"
|
- "Cargo.lock"
|
||||||
- "crates/vnidrop/**"
|
- "crates/vnidrop/**"
|
||||||
|
- "version.properties"
|
||||||
|
- "packaging/version/**"
|
||||||
- "Makefile"
|
- "Makefile"
|
||||||
- "config.mk"
|
- "config.mk"
|
||||||
- "make/**"
|
- "make/**"
|
||||||
@@ -19,6 +21,8 @@ on:
|
|||||||
- "Cargo.toml"
|
- "Cargo.toml"
|
||||||
- "Cargo.lock"
|
- "Cargo.lock"
|
||||||
- "crates/vnidrop/**"
|
- "crates/vnidrop/**"
|
||||||
|
- "version.properties"
|
||||||
|
- "packaging/version/**"
|
||||||
- "Makefile"
|
- "Makefile"
|
||||||
- "config.mk"
|
- "config.mk"
|
||||||
- "make/**"
|
- "make/**"
|
||||||
|
|||||||
4
.github/workflows/shared-kmp.yml
vendored
4
.github/workflows/shared-kmp.yml
vendored
@@ -4,6 +4,8 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- "shared/**"
|
- "shared/**"
|
||||||
|
- "version.properties"
|
||||||
|
- "packaging/version/**"
|
||||||
- "crates/vnidrop/**"
|
- "crates/vnidrop/**"
|
||||||
- "Cargo.toml"
|
- "Cargo.toml"
|
||||||
- "Cargo.lock"
|
- "Cargo.lock"
|
||||||
@@ -24,6 +26,8 @@ on:
|
|||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- "shared/**"
|
- "shared/**"
|
||||||
|
- "version.properties"
|
||||||
|
- "packaging/version/**"
|
||||||
- "crates/vnidrop/**"
|
- "crates/vnidrop/**"
|
||||||
- "Cargo.toml"
|
- "Cargo.toml"
|
||||||
- "Cargo.lock"
|
- "Cargo.lock"
|
||||||
|
|||||||
42
.github/workflows/windows-store.yml
vendored
42
.github/workflows/windows-store.yml
vendored
@@ -5,6 +5,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- ".github/workflows/windows-store.yml"
|
- ".github/workflows/windows-store.yml"
|
||||||
- "packaging/windows/**"
|
- "packaging/windows/**"
|
||||||
|
- "packaging/version/**"
|
||||||
|
- "version.properties"
|
||||||
- "assets/windows/**"
|
- "assets/windows/**"
|
||||||
- "desktopApp/**"
|
- "desktopApp/**"
|
||||||
- "shared/**"
|
- "shared/**"
|
||||||
@@ -21,12 +23,6 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- "v*.*.*"
|
- "v*.*.*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: Release version in MAJOR.MINOR.PATCH form
|
|
||||||
required: true
|
|
||||||
default: "1.0.0"
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -77,37 +73,13 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
windows-x64-cargo-1.91.0-
|
windows-x64-cargo-1.91.0-
|
||||||
|
|
||||||
- name: Resolve Store version
|
- name: Resolve canonical version
|
||||||
id: version
|
id: version
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
env:
|
|
||||||
REQUESTED_VERSION: ${{ inputs.version || '1.0.0' }}
|
|
||||||
run: |
|
run: |
|
||||||
$version = $env:REQUESTED_VERSION
|
$version = .\packaging\version\resolve-version.ps1 -Field Json -VerifyTag | ConvertFrom-Json
|
||||||
if ($env:GITHUB_REF_TYPE -eq "tag") {
|
"app=$($version.productVersion)" >> $env:GITHUB_OUTPUT
|
||||||
if ($env:GITHUB_REF_NAME -notmatch "^v[0-9]+\.[0-9]+\.[0-9]+$") {
|
"package=$($version.windowsPackageVersion)" >> $env:GITHUB_OUTPUT
|
||||||
throw "Store release tags must use vMAJOR.MINOR.PATCH"
|
|
||||||
}
|
|
||||||
$version = $env:GITHUB_REF_NAME.Substring(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($version -notmatch "^[0-9]+\.[0-9]+\.[0-9]+$") {
|
|
||||||
throw "Version must use MAJOR.MINOR.PATCH"
|
|
||||||
}
|
|
||||||
$parts = $version.Split(".")
|
|
||||||
for ($index = 0; $index -lt $parts.Count; $index++) {
|
|
||||||
$part = $parts[$index]
|
|
||||||
$number = 0
|
|
||||||
if (-not [int]::TryParse($part, [ref] $number) -or $number.ToString() -ne $part) {
|
|
||||||
throw "Version components must be canonical integers"
|
|
||||||
}
|
|
||||||
if ($number -lt $(if ($index -eq 0) { 1 } else { 0 }) -or $number -gt 65535) {
|
|
||||||
throw "Version components must be between 0 and 65535, with a non-zero major"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"app=$version" >> $env:GITHUB_OUTPUT
|
|
||||||
"package=$version.0" >> $env:GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Test and build release app image
|
- name: Test and build release app image
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -115,7 +87,6 @@ jobs:
|
|||||||
$arguments = @(
|
$arguments = @(
|
||||||
":shared:jvmTest"
|
":shared:jvmTest"
|
||||||
":desktopApp:createReleaseDistributable"
|
":desktopApp:createReleaseDistributable"
|
||||||
"-Pvnidrop.version=${{ steps.version.outputs.app }}"
|
|
||||||
"-Pvnidrop.desktop.rustVariant=release"
|
"-Pvnidrop.desktop.rustVariant=release"
|
||||||
"-Pvnidrop.diagnostics.included=false"
|
"-Pvnidrop.diagnostics.included=false"
|
||||||
"--no-daemon"
|
"--no-daemon"
|
||||||
@@ -131,7 +102,6 @@ jobs:
|
|||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$arguments = @{
|
$arguments = @{
|
||||||
Version = "${{ steps.version.outputs.app }}"
|
|
||||||
AppImage = ".\desktopApp\build\compose\binaries\main-release\app\VniDrop"
|
AppImage = ".\desktopApp\build\compose\binaries\main-release\app\VniDrop"
|
||||||
OutputDirectory = ".\build\release\windows"
|
OutputDirectory = ".\build\release\windows"
|
||||||
}
|
}
|
||||||
|
|||||||
13
Makefile
13
Makefile
@@ -13,13 +13,13 @@ include $(ROOT)/make/release.mk
|
|||||||
.PHONY: test-rust-transfer test-rust-approval test-rust-lifecycle test-rust-output-sink
|
.PHONY: test-rust-transfer test-rust-approval test-rust-lifecycle test-rust-output-sink
|
||||||
.PHONY: check-shared test-shared test-android-host check-android verify-android-libs build-android run-desktop
|
.PHONY: check-shared test-shared test-android-host check-android verify-android-libs build-android run-desktop
|
||||||
.PHONY: apple-core apple-project open-apple-project open-apple build-apple-macos build-apple-ios check-apple
|
.PHONY: apple-core apple-project open-apple-project open-apple build-apple-macos build-apple-ios check-apple
|
||||||
.PHONY: check-localization localization localization-migrate
|
.PHONY: check-version check-localization localization localization-migrate
|
||||||
.PHONY: check-docs run-docs check-diagnostics run-diagnostics diagnostics-db-local diagnostics-db-remote diagnostics-typegen deploy-diagnostics
|
.PHONY: check-docs run-docs check-diagnostics run-diagnostics diagnostics-db-local diagnostics-db-remote diagnostics-typegen deploy-diagnostics
|
||||||
|
|
||||||
help: ## Show available commands and common configuration variables.
|
help: ## Show available commands and common configuration variables.
|
||||||
@grep -hE '^[A-Za-z0-9_.-]+:.*## ' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## "} {printf " %-28s %s\n", $$1, $$2}'
|
@grep -hE '^[A-Za-z0-9_.-]+:.*## ' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*## "} {printf " %-28s %s\n", $$1, $$2}'
|
||||||
@printf '\nCommon variables:\n'
|
@printf '\nCommon variables:\n'
|
||||||
@printf ' %-28s %s\n' 'VERSION=x.y.z' 'Package version (default: $(VERSION))'
|
@printf ' %-28s %s\n' 'version.properties' 'Canonical application version ($(VERSION))'
|
||||||
@printf ' %-28s %s\n' 'APPLE_PROFILE=debug|release' 'Rust profile for the Apple XCFramework'
|
@printf ' %-28s %s\n' 'APPLE_PROFILE=debug|release' 'Rust profile for the Apple XCFramework'
|
||||||
@printf ' %-28s %s\n' 'APPLE_CONFIGURATION=...' 'Xcode configuration (default: $(APPLE_CONFIGURATION))'
|
@printf ' %-28s %s\n' 'APPLE_CONFIGURATION=...' 'Xcode configuration (default: $(APPLE_CONFIGURATION))'
|
||||||
@printf ' %-28s %s\n' 'APPLE_DESTINATION=...' 'Optional xcodebuild destination override'
|
@printf ' %-28s %s\n' 'APPLE_DESTINATION=...' 'Optional xcodebuild destination override'
|
||||||
@@ -59,7 +59,12 @@ format: ## Format Rust sources.
|
|||||||
|
|
||||||
test: test-rust test-shared ## Run the main Rust and shared JVM test suites.
|
test: test-rust test-shared ## Run the main Rust and shared JVM test suites.
|
||||||
|
|
||||||
check: check-rust check-shared check-localization check-docs check-diagnostics ## Run portable pre-PR verification.
|
check: check-version check-rust check-shared check-localization check-docs check-diagnostics ## Run portable pre-PR verification.
|
||||||
|
|
||||||
|
check-version: ## Validate the canonical version and its platform mappings.
|
||||||
|
cd $(ROOT) && packaging/version/test-version.sh
|
||||||
|
cd $(ROOT) && packaging/version/resolve-version.sh verify
|
||||||
|
cd $(ROOT) && $(GRADLE) verifyVersion $(GRADLE_FLAGS)
|
||||||
|
|
||||||
check-rust: ## Run Rust formatting, lint, tests, and documentation checks.
|
check-rust: ## Run Rust formatting, lint, tests, and documentation checks.
|
||||||
cd $(ROOT) && $(CARGO) fmt --all -- --check
|
cd $(ROOT) && $(CARGO) fmt --all -- --check
|
||||||
@@ -126,7 +131,7 @@ build-apple-macos-direct: apple-project ## Build the direct-download macOS targe
|
|||||||
cd $(ROOT)/apple && $(XCODEBUILD) -project VniDrop.xcodeproj -scheme VniDropDirect -configuration Release-Direct -derivedDataPath "$(APPLE_DERIVED_DATA)" -destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO build
|
cd $(ROOT)/apple && $(XCODEBUILD) -project VniDrop.xcodeproj -scheme VniDropDirect -configuration Release-Direct -derivedDataPath "$(APPLE_DERIVED_DATA)" -destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO build
|
||||||
|
|
||||||
build-apple-dmg: ## Build the signed/notarized direct-download .dmg (see apple/RELEASE-MACOS.md for required env).
|
build-apple-dmg: ## Build the signed/notarized direct-download .dmg (see apple/RELEASE-MACOS.md for required env).
|
||||||
cd $(ROOT) && apple/scripts/build-dmg.sh $(VERSION)
|
cd $(ROOT) && apple/scripts/build-dmg.sh
|
||||||
|
|
||||||
open-apple: build-apple-macos ## Build and launch the native macOS app.
|
open-apple: build-apple-macos ## Build and launch the native macOS app.
|
||||||
@test -d "$(APPLE_DERIVED_DATA)/Build/Products/$(APPLE_CONFIGURATION)/VniDrop.app" || { printf 'Built macOS app was not found.\n' >&2; exit 1; }
|
@test -d "$(APPLE_DERIVED_DATA)/Build/Products/$(APPLE_CONFIGURATION)/VniDrop.app" || { printf 'Built macOS app was not found.\n' >&2; exit 1; }
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ plugins {
|
|||||||
alias(libs.plugins.composeCompiler)
|
alias(libs.plugins.composeCompiler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val appVersion = rootProject.extra["vnidrop.productVersion"] as String
|
||||||
|
val androidVersionCode = rootProject.extra["vnidrop.androidVersionCode"] as Int
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget = JvmTarget.JVM_11
|
jvmTarget = JvmTarget.JVM_11
|
||||||
@@ -59,8 +62,8 @@ android {
|
|||||||
applicationId = "com.vnidrop.app"
|
applicationId = "com.vnidrop.app"
|
||||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||||
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||||
versionCode = 1
|
versionCode = androidVersionCode
|
||||||
versionName = "1.0"
|
versionName = appVersion
|
||||||
}
|
}
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
@@ -38,7 +40,7 @@
|
|||||||
<data android:mimeType="application/vnd.vnidrop.transfer"/>
|
<data android:mimeType="application/vnd.vnidrop.transfer"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!-- Fallback: .vnd files often arrive as octet-stream / unknown MIME. -->
|
<!-- Fallback: .vnd files often arrive as octet-stream / unknown MIME. -->
|
||||||
<intent-filter>
|
<intent-filter tools:ignore="AppLinkUrlError">
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ The macOS app ships through two targets that build identical sources:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
make build-apple-macos-direct # unsigned compile-check of the direct target
|
make build-apple-macos-direct # unsigned compile-check of the direct target
|
||||||
make build-apple-dmg VERSION=x.y.z # signed (+ notarized) .dmg
|
make build-apple-dmg # signed (+ notarized) .dmg
|
||||||
```
|
```
|
||||||
|
|
||||||
Full signing, notarization, appcast, and cask flow: see
|
Full signing, notarization, appcast, and cask flow: see
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Product and build versions are shared by every platform.
|
||||||
|
#include "../version.properties"
|
||||||
|
|
||||||
// VniDrop development and CI builds are intentionally unsigned.
|
// VniDrop development and CI builds are intentionally unsigned.
|
||||||
CODE_SIGNING_ALLOWED = NO
|
CODE_SIGNING_ALLOWED = NO
|
||||||
CODE_SIGNING_REQUIRED = NO
|
CODE_SIGNING_REQUIRED = NO
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import UIKit
|
|||||||
@MainActor
|
@MainActor
|
||||||
func makeAppDependencies(externalInvitations: ExternalInvitationController) -> AppDependencies {
|
func makeAppDependencies(externalInvitations: ExternalInvitationController) -> AppDependencies {
|
||||||
let device = UIDevice.current
|
let device = UIDevice.current
|
||||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.1.0"
|
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown"
|
||||||
let env = PlatformEnvironment(
|
let env = PlatformEnvironment(
|
||||||
name: "\(device.systemName) \(device.systemVersion)",
|
name: "\(device.systemName) \(device.systemVersion)",
|
||||||
appVersion: version,
|
appVersion: version,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import AppKit
|
|||||||
/// Builds the macOS dependency graph, mirroring `rememberIosAppDependencies`.
|
/// Builds the macOS dependency graph, mirroring `rememberIosAppDependencies`.
|
||||||
@MainActor
|
@MainActor
|
||||||
func makeAppDependencies(externalInvitations: ExternalInvitationController) -> AppDependencies {
|
func makeAppDependencies(externalInvitations: ExternalInvitationController) -> AppDependencies {
|
||||||
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0.1.0"
|
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown"
|
||||||
let host = Host.current().localizedName ?? "Mac"
|
let host = Host.current().localizedName ?? "Mac"
|
||||||
let env = PlatformEnvironment(
|
let env = PlatformEnvironment(
|
||||||
name: "macOS " + ProcessInfo.processInfo.operatingSystemVersionString,
|
name: "macOS " + ProcessInfo.processInfo.operatingSystemVersionString,
|
||||||
|
|||||||
@@ -65,11 +65,8 @@ targetTemplates:
|
|||||||
base:
|
base:
|
||||||
PRODUCT_NAME: VniDrop
|
PRODUCT_NAME: VniDrop
|
||||||
PRODUCT_BUNDLE_IDENTIFIER: com.vnidrop.app
|
PRODUCT_BUNDLE_IDENTIFIER: com.vnidrop.app
|
||||||
MARKETING_VERSION: "0.1.0"
|
MARKETING_VERSION: "$(PRODUCT_VERSION)"
|
||||||
# Placeholder only — the real CFBundleVersion is stamped at build time as a
|
CURRENT_PROJECT_VERSION: "$(APPLE_BUILD_NUMBER)"
|
||||||
# UTC YYMMDD.HHMM timestamp by the "Stamp build number" phase below, so every
|
|
||||||
# build is monotonic and self-describing (shown as "MARKETING_VERSION (build)").
|
|
||||||
CURRENT_PROJECT_VERSION: "1"
|
|
||||||
GENERATE_INFOPLIST_FILE: NO
|
GENERATE_INFOPLIST_FILE: NO
|
||||||
INFOPLIST_FILE: VniDrop/Resources/Info.plist
|
INFOPLIST_FILE: VniDrop/Resources/Info.plist
|
||||||
CODE_SIGN_ENTITLEMENTS: VniDrop/Resources/VniDrop.entitlements
|
CODE_SIGN_ENTITLEMENTS: VniDrop/Resources/VniDrop.entitlements
|
||||||
@@ -111,23 +108,6 @@ targetTemplates:
|
|||||||
echo "error: SwiftLint not installed — run 'brew install swiftlint'"
|
echo "error: SwiftLint not installed — run 'brew install swiftlint'"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
postBuildScripts:
|
|
||||||
# Stamp CFBundleVersion as a UTC YYMMDD.HHMM timestamp into the built
|
|
||||||
# Info.plist before code signing. Runs for every build (Xcode GUI archive and
|
|
||||||
# CLI alike), so both the App Store and direct-download channels get a
|
|
||||||
# monotonic, meaningful build id. CI/reproducible builds can pin it via the
|
|
||||||
# VNIDROP_BUILD env var. MARKETING_VERSION stays the human X.Y.Z version.
|
|
||||||
- name: Stamp build number (UTC timestamp)
|
|
||||||
basedOnDependencyAnalysis: false
|
|
||||||
script: |
|
|
||||||
build="${VNIDROP_BUILD:-$(date -u +%y%m%d.%H%M)}"
|
|
||||||
plist="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"
|
|
||||||
if [ -f "$plist" ]; then
|
|
||||||
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $build" "$plist"
|
|
||||||
echo "Stamped CFBundleVersion = $build"
|
|
||||||
else
|
|
||||||
echo "warning: Info.plist not found at $plist; CFBundleVersion not stamped"
|
|
||||||
fi
|
|
||||||
|
|
||||||
targets:
|
targets:
|
||||||
# App Store / TestFlight target. iOS + macOS, sandboxed, no self-updater.
|
# App Store / TestFlight target. iOS + macOS, sandboxed, no self-updater.
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
# This is the direct-distribution counterpart to the App Store archive flow; it
|
# This is the direct-distribution counterpart to the App Store archive flow; it
|
||||||
# never touches the App Store `VniDrop` target. The Rust crate is not modified.
|
# never touches the App Store `VniDrop` target. The Rust crate is not modified.
|
||||||
#
|
#
|
||||||
# Usage: apple/scripts/build-dmg.sh [version]
|
# Usage: apple/scripts/build-dmg.sh
|
||||||
# version MAJOR.MINOR.PATCH; defaults to MARKETING_VERSION / the git tag.
|
|
||||||
#
|
#
|
||||||
# Environment:
|
# Environment:
|
||||||
# DEVELOPER_ID_APP Codesign identity, e.g. "Developer ID Application: … (TEAMID)".
|
# DEVELOPER_ID_APP Codesign identity, e.g. "Developer ID Application: … (TEAMID)".
|
||||||
@@ -29,29 +28,11 @@ PROJECT="$APPLE_DIR/VniDrop.xcodeproj"
|
|||||||
SCHEME="VniDropDirect"
|
SCHEME="VniDropDirect"
|
||||||
CONFIG="Release-Direct"
|
CONFIG="Release-Direct"
|
||||||
APP_NAME="VniDrop"
|
APP_NAME="VniDrop"
|
||||||
|
VERSION_RESOLVER="$REPO_ROOT/packaging/version/resolve-version.sh"
|
||||||
|
|
||||||
# --- Resolve version (arg > git tag > project MARKETING_VERSION) -------------
|
VERSION="$("$VERSION_RESOLVER" product)"
|
||||||
resolve_version() {
|
BUILD_NUMBER="$("$VERSION_RESOLVER" apple-build)"
|
||||||
local v="${1:-}"
|
"$VERSION_RESOLVER" verify >/dev/null
|
||||||
if [ -z "$v" ] && [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
|
|
||||||
v="${GITHUB_REF_NAME#v}"
|
|
||||||
fi
|
|
||||||
if [ -z "$v" ]; then
|
|
||||||
v="$(sed -nE 's/.*MARKETING_VERSION: "([0-9.]+)".*/\1/p' "$APPLE_DIR/project.yml" | head -1)"
|
|
||||||
fi
|
|
||||||
if [[ ! "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
||||||
echo "version must be MAJOR.MINOR.PATCH (got '$v')" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
printf '%s' "$v"
|
|
||||||
}
|
|
||||||
VERSION="$(resolve_version "${1:-}")"
|
|
||||||
|
|
||||||
# CFBundleVersion is a UTC YYMMDD.HHMM timestamp stamped by the target's
|
|
||||||
# "Stamp build number" build phase. Pin it here (one value for the whole archive)
|
|
||||||
# so the app, DMG, and appcast all agree; Sparkle compares it to order updates.
|
|
||||||
BUILD_NUMBER="$(date -u +%y%m%d.%H%M)"
|
|
||||||
export VNIDROP_BUILD="$BUILD_NUMBER"
|
|
||||||
|
|
||||||
# --- Resolve signing identity ------------------------------------------------
|
# --- Resolve signing identity ------------------------------------------------
|
||||||
if [ -z "${DEVELOPER_ID_APP:-}" ]; then
|
if [ -z "${DEVELOPER_ID_APP:-}" ]; then
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
# keychain). The resulting appcast.xml is uploaded as a release asset; the app's
|
# keychain). The resulting appcast.xml is uploaded as a release asset; the app's
|
||||||
# SUFeedURL (/releases/latest/download/appcast.xml) always resolves to the newest.
|
# SUFeedURL (/releases/latest/download/appcast.xml) always resolves to the newest.
|
||||||
#
|
#
|
||||||
# Usage: apple/scripts/generate-appcast.sh [version]
|
# Usage: apple/scripts/generate-appcast.sh
|
||||||
#
|
#
|
||||||
# Environment:
|
# Environment:
|
||||||
# DIST_DIR Folder holding the DMG(s). Default: apple/dist
|
# DIST_DIR Folder holding the DMG(s). Default: apple/dist
|
||||||
@@ -24,12 +24,10 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|||||||
APPLE_DIR="$REPO_ROOT/apple"
|
APPLE_DIR="$REPO_ROOT/apple"
|
||||||
DIST_DIR="${DIST_DIR:-$APPLE_DIR/dist}"
|
DIST_DIR="${DIST_DIR:-$APPLE_DIR/dist}"
|
||||||
RELEASE_REPO="${RELEASE_REPO:-sudosylabs/vnidrop}"
|
RELEASE_REPO="${RELEASE_REPO:-sudosylabs/vnidrop}"
|
||||||
|
VERSION_RESOLVER="$REPO_ROOT/packaging/version/resolve-version.sh"
|
||||||
|
|
||||||
VERSION="${1:-}"
|
VERSION="$("$VERSION_RESOLVER" product)"
|
||||||
if [ -z "$VERSION" ] && [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
|
"$VERSION_RESOLVER" verify >/dev/null
|
||||||
VERSION="${GITHUB_REF_NAME#v}"
|
|
||||||
fi
|
|
||||||
[ -n "$VERSION" ] || { echo "error: version required (arg or tag)" >&2; exit 1; }
|
|
||||||
|
|
||||||
# Enclosure URLs resolve to the specific release's assets.
|
# Enclosure URLs resolve to the specific release's assets.
|
||||||
DOWNLOAD_PREFIX="https://github.com/$RELEASE_REPO/releases/download/v$VERSION"
|
DOWNLOAD_PREFIX="https://github.com/$RELEASE_REPO/releases/download/v$VERSION"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import java.util.Properties
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// this is necessary to avoid the plugins to be loaded multiple times
|
// this is necessary to avoid the plugins to be loaded multiple times
|
||||||
// in each subproject's classloader
|
// in each subproject's classloader
|
||||||
@@ -13,3 +15,74 @@ plugins {
|
|||||||
alias(libs.plugins.kotlinJvm) apply false
|
alias(libs.plugins.kotlinJvm) apply false
|
||||||
alias(libs.plugins.kotlinMultiplatform) apply false
|
alias(libs.plugins.kotlinMultiplatform) apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val versionFile = layout.projectDirectory.file("version.properties")
|
||||||
|
val versionProperties = Properties().apply {
|
||||||
|
versionFile.asFile.inputStream().use(::load)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requiredVersionProperty(name: String): String =
|
||||||
|
versionProperties.getProperty(name)?.takeIf { it.isNotBlank() }
|
||||||
|
?: error("Missing $name in ${versionFile.asFile}")
|
||||||
|
|
||||||
|
fun canonicalInteger(name: String, value: String, range: LongRange): Long {
|
||||||
|
require(value.matches(Regex("0|[1-9][0-9]*"))) {
|
||||||
|
"$name must be a canonical non-negative integer"
|
||||||
|
}
|
||||||
|
val number = value.toLongOrNull()
|
||||||
|
require(number != null && number in range) {
|
||||||
|
"$name must be between ${range.first} and ${range.last}"
|
||||||
|
}
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
|
||||||
|
val productVersion = requiredVersionProperty("PRODUCT_VERSION")
|
||||||
|
val productVersionMatch = Regex("(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)")
|
||||||
|
.matchEntire(productVersion)
|
||||||
|
?: error("PRODUCT_VERSION must use canonical MAJOR.MINOR.PATCH integers")
|
||||||
|
val productVersionParts = productVersionMatch.groupValues.drop(1).map(String::toLong)
|
||||||
|
require(productVersionParts[0] <= 65534 && productVersionParts.drop(1).all { it <= 65535 }) {
|
||||||
|
"PRODUCT_VERSION components exceed the supported store ranges"
|
||||||
|
}
|
||||||
|
|
||||||
|
val releaseChannel = requiredVersionProperty("RELEASE_CHANNEL")
|
||||||
|
require(releaseChannel.matches(Regex("[a-z][a-z0-9-]*"))) {
|
||||||
|
"RELEASE_CHANNEL contains unsupported characters"
|
||||||
|
}
|
||||||
|
val androidVersionCode = canonicalInteger(
|
||||||
|
"ANDROID_VERSION_CODE",
|
||||||
|
requiredVersionProperty("ANDROID_VERSION_CODE"),
|
||||||
|
1L..2_100_000_000L,
|
||||||
|
).toInt()
|
||||||
|
val appleBuildNumber = requiredVersionProperty("APPLE_BUILD_NUMBER")
|
||||||
|
require(appleBuildNumber.matches(Regex("[1-9][0-9]*(\\.[0-9]+){0,2}"))) {
|
||||||
|
"APPLE_BUILD_NUMBER must contain one to three period-separated non-negative integers and start above zero"
|
||||||
|
}
|
||||||
|
val windowsVersionEpoch = canonicalInteger(
|
||||||
|
"WINDOWS_VERSION_EPOCH",
|
||||||
|
requiredVersionProperty("WINDOWS_VERSION_EPOCH"),
|
||||||
|
1L..65535L,
|
||||||
|
)
|
||||||
|
val windowsMajor = productVersionParts[0] + windowsVersionEpoch
|
||||||
|
require(windowsMajor <= 65535) {
|
||||||
|
"Derived Windows package major exceeds 65535"
|
||||||
|
}
|
||||||
|
val windowsPackageVersion =
|
||||||
|
"$windowsMajor.${productVersionParts[1]}.${productVersionParts[2]}.0"
|
||||||
|
|
||||||
|
extra["vnidrop.productVersion"] = productVersion
|
||||||
|
extra["vnidrop.releaseChannel"] = releaseChannel
|
||||||
|
extra["vnidrop.androidVersionCode"] = androidVersionCode
|
||||||
|
extra["vnidrop.appleBuildNumber"] = appleBuildNumber
|
||||||
|
extra["vnidrop.windowsPackageVersion"] = windowsPackageVersion
|
||||||
|
|
||||||
|
tasks.register("verifyVersion") {
|
||||||
|
group = "verification"
|
||||||
|
description = "Validates the canonical cross-platform application version."
|
||||||
|
inputs.file(versionFile)
|
||||||
|
inputs.property("productVersion", productVersion)
|
||||||
|
inputs.property("releaseChannel", releaseChannel)
|
||||||
|
inputs.property("androidVersionCode", androidVersionCode)
|
||||||
|
inputs.property("appleBuildNumber", appleBuildNumber)
|
||||||
|
inputs.property("windowsPackageVersion", windowsPackageVersion)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Default command configuration. Override locally in the ignored
|
# Default command configuration. Override local tool paths in the ignored
|
||||||
# config.override.mk or on the command line (for example: make package-deb VERSION=1.2.0).
|
# config.override.mk or on the command line.
|
||||||
|
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
HOST_OS := windows
|
HOST_OS := windows
|
||||||
@@ -24,7 +24,7 @@ XCODEGEN ?= xcodegen
|
|||||||
OPEN ?= open
|
OPEN ?= open
|
||||||
POWERSHELL ?= pwsh
|
POWERSHELL ?= pwsh
|
||||||
|
|
||||||
VERSION ?= $(shell sed -n 's/^vnidrop.version=//p' $(ROOT)/gradle.properties)
|
override VERSION := $(shell $(ROOT)/packaging/version/resolve-version.sh product)
|
||||||
APPLE_PROFILE ?= debug
|
APPLE_PROFILE ?= debug
|
||||||
APPLE_CONFIGURATION ?= Debug
|
APPLE_CONFIGURATION ?= Debug
|
||||||
APPLE_DESTINATION ?=
|
APPLE_DESTINATION ?=
|
||||||
|
|||||||
31
crates/vnidrop/build.rs
Normal file
31
crates/vnidrop/build.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use std::{env, fs, path::PathBuf};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
|
||||||
|
let version_file = manifest_dir.join("../../version.properties");
|
||||||
|
println!("cargo:rerun-if-changed={}", version_file.display());
|
||||||
|
|
||||||
|
let contents = fs::read_to_string(&version_file)
|
||||||
|
.unwrap_or_else(|error| panic!("failed to read {}: {error}", version_file.display()));
|
||||||
|
let versions: Vec<_> = contents
|
||||||
|
.lines()
|
||||||
|
.filter_map(|line| line.strip_prefix("PRODUCT_VERSION="))
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
versions.len(),
|
||||||
|
1,
|
||||||
|
"{} must contain exactly one PRODUCT_VERSION",
|
||||||
|
version_file.display()
|
||||||
|
);
|
||||||
|
let version = versions[0];
|
||||||
|
let components: Vec<_> = version.split('.').collect();
|
||||||
|
assert!(
|
||||||
|
components.len() == 3
|
||||||
|
&& components.iter().all(|component| {
|
||||||
|
component.parse::<u16>().is_ok()
|
||||||
|
&& (component == &"0" || !component.starts_with('0'))
|
||||||
|
}),
|
||||||
|
"PRODUCT_VERSION must use canonical MAJOR.MINOR.PATCH integers"
|
||||||
|
);
|
||||||
|
println!("cargo:rustc-env=VNIDROP_PRODUCT_VERSION={version}");
|
||||||
|
}
|
||||||
@@ -106,7 +106,7 @@ impl HandshakeClient {
|
|||||||
transfer_name: metadata.transfer_name.clone(),
|
transfer_name: metadata.transfer_name.clone(),
|
||||||
receiver_name: receiver_name.map(ToOwned::to_owned),
|
receiver_name: receiver_name.map(ToOwned::to_owned),
|
||||||
receiver_device_name: None,
|
receiver_device_name: None,
|
||||||
app_version: env!("CARGO_PKG_VERSION").to_string(),
|
app_version: env!("VNIDROP_PRODUCT_VERSION").to_string(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,7 @@ plugins {
|
|||||||
alias(libs.plugins.composeCompiler)
|
alias(libs.plugins.composeCompiler)
|
||||||
}
|
}
|
||||||
|
|
||||||
val appVersion = providers.gradleProperty("vnidrop.version").get()
|
val appVersion = rootProject.extra["vnidrop.productVersion"] as String
|
||||||
val appVersionParts = appVersion.split(".")
|
|
||||||
require(
|
|
||||||
appVersionParts.size == 3 &&
|
|
||||||
appVersionParts.mapIndexed { index, part ->
|
|
||||||
val number = part.toIntOrNull()
|
|
||||||
number != null &&
|
|
||||||
number.toString() == part &&
|
|
||||||
number in (if (index == 0) 1 else 0)..65535
|
|
||||||
}.all { it },
|
|
||||||
) {
|
|
||||||
"vnidrop.version must be MAJOR.MINOR.PATCH with numeric components from 0 to 65535 and a non-zero major"
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.shared)
|
implementation(projects.shared)
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
|
|||||||
org.gradle.configuration-cache=true
|
org.gradle.configuration-cache=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
|
||||||
# Product version used by desktop packaging. Release workflows override this
|
|
||||||
# from the vMAJOR.MINOR.PATCH tag.
|
|
||||||
vnidrop.version=1.0.0
|
|
||||||
|
|
||||||
#Android
|
#Android
|
||||||
android.builtInKotlin=false
|
android.builtInKotlin=false
|
||||||
android.newDsl=false
|
android.newDsl=false
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
.PHONY: package-deb package-rpm package-msix
|
.PHONY: package-deb package-rpm package-msix
|
||||||
|
|
||||||
package-deb: ## Build and verify a Debian x64 package (VERSION=x.y.z).
|
package-deb: ## Build and verify a Debian x64 package.
|
||||||
@test "$(HOST_OS)" = linux || { printf 'Debian packaging requires Linux.\n' >&2; exit 1; }
|
@test "$(HOST_OS)" = linux || { printf 'Debian packaging requires Linux.\n' >&2; exit 1; }
|
||||||
@cd $(ROOT); \
|
@cd $(ROOT); \
|
||||||
version="$$(packaging/linux/resolve-version.sh "$(VERSION)")"; \
|
version="$$(packaging/linux/resolve-version.sh)"; \
|
||||||
$(GRADLE) :shared:jvmTest :desktopApp:packageReleaseDeb \
|
$(GRADLE) :shared:jvmTest :desktopApp:packageReleaseDeb \
|
||||||
-Pvnidrop.version="$$version" \
|
|
||||||
-Pvnidrop.desktop.rustVariant=release \
|
-Pvnidrop.desktop.rustVariant=release \
|
||||||
-Pvnidrop.diagnostics.included=false \
|
-Pvnidrop.diagnostics.included=false \
|
||||||
$(GRADLE_RELEASE_FLAGS); \
|
$(GRADLE_RELEASE_FLAGS); \
|
||||||
@@ -19,12 +18,11 @@ package-deb: ## Build and verify a Debian x64 package (VERSION=x.y.z).
|
|||||||
( cd "$$output_directory" && sha256sum "$$output_name" > "$$output_name.sha256" ); \
|
( cd "$$output_directory" && sha256sum "$$output_name" > "$$output_name.sha256" ); \
|
||||||
printf 'Package: %s/%s\n' "$$output_directory" "$$output_name"
|
printf 'Package: %s/%s\n' "$$output_directory" "$$output_name"
|
||||||
|
|
||||||
package-rpm: ## Build and verify an RPM x64 package (VERSION=x.y.z).
|
package-rpm: ## Build and verify an RPM x64 package.
|
||||||
@test "$(HOST_OS)" = linux || { printf 'RPM packaging requires Linux.\n' >&2; exit 1; }
|
@test "$(HOST_OS)" = linux || { printf 'RPM packaging requires Linux.\n' >&2; exit 1; }
|
||||||
@cd $(ROOT); \
|
@cd $(ROOT); \
|
||||||
version="$$(packaging/linux/resolve-version.sh "$(VERSION)")"; \
|
version="$$(packaging/linux/resolve-version.sh)"; \
|
||||||
$(GRADLE) :desktopApp:packageReleaseRpm \
|
$(GRADLE) :desktopApp:packageReleaseRpm \
|
||||||
-Pvnidrop.version="$$version" \
|
|
||||||
-Pvnidrop.desktop.rustVariant=release \
|
-Pvnidrop.desktop.rustVariant=release \
|
||||||
-Pvnidrop.diagnostics.included=false \
|
-Pvnidrop.diagnostics.included=false \
|
||||||
$(GRADLE_RELEASE_FLAGS); \
|
$(GRADLE_RELEASE_FLAGS); \
|
||||||
@@ -38,14 +36,12 @@ package-rpm: ## Build and verify an RPM x64 package (VERSION=x.y.z).
|
|||||||
( cd "$$output_directory" && sha256sum "$$output_name" > "$$output_name.sha256" ); \
|
( cd "$$output_directory" && sha256sum "$$output_name" > "$$output_name.sha256" ); \
|
||||||
printf 'Package: %s/%s\n' "$$output_directory" "$$output_name"
|
printf 'Package: %s/%s\n' "$$output_directory" "$$output_name"
|
||||||
|
|
||||||
package-msix: ## Build and verify an unsigned Windows Store MSIX (VERSION=x.y.z).
|
package-msix: ## Build and verify an unsigned Windows Store MSIX.
|
||||||
@test "$(HOST_OS)" = windows || { printf 'MSIX packaging requires Windows.\n' >&2; exit 1; }
|
@test "$(HOST_OS)" = windows || { printf 'MSIX packaging requires Windows.\n' >&2; exit 1; }
|
||||||
cd $(ROOT) && $(GRADLE) :shared:jvmTest :desktopApp:createReleaseDistributable \
|
cd $(ROOT) && $(GRADLE) :shared:jvmTest :desktopApp:createReleaseDistributable \
|
||||||
-Pvnidrop.version="$(VERSION)" \
|
|
||||||
-Pvnidrop.desktop.rustVariant=release \
|
-Pvnidrop.desktop.rustVariant=release \
|
||||||
-Pvnidrop.diagnostics.included=false \
|
-Pvnidrop.diagnostics.included=false \
|
||||||
$(GRADLE_RELEASE_FLAGS)
|
$(GRADLE_RELEASE_FLAGS)
|
||||||
cd $(ROOT) && $(POWERSHELL) -NoProfile -File packaging/windows/build-msix.ps1 \
|
cd $(ROOT) && $(POWERSHELL) -NoProfile -File packaging/windows/build-msix.ps1 \
|
||||||
-Version "$(VERSION)" \
|
|
||||||
-AppImage desktopApp/build/compose/binaries/main-release/app/VniDrop \
|
-AppImage desktopApp/build/compose/binaries/main-release/app/VniDrop \
|
||||||
-OutputDirectory build/release/windows
|
-OutputDirectory build/release/windows
|
||||||
|
|||||||
@@ -27,9 +27,8 @@ artifacts. Manual runs retain build artifacts for 14 days. A pushed version tag
|
|||||||
whose commit is on `master` creates the matching GitHub Release with the `.deb`,
|
whose commit is on `master` creates the matching GitHub Release with the `.deb`,
|
||||||
`.rpm`, and a combined `SHA256SUMS`.
|
`.rpm`, and a combined `SHA256SUMS`.
|
||||||
|
|
||||||
The existing `v1.0.0` tag predates this workflow and will not run it
|
The legacy `v1.0.0` tag predates canonical versioning and does not define the
|
||||||
retroactively. Use the next version tag after this configuration reaches
|
current product version. New release tags must match `version.properties`.
|
||||||
`master`.
|
|
||||||
|
|
||||||
## Install a downloaded package
|
## Install a downloaded package
|
||||||
|
|
||||||
@@ -61,11 +60,12 @@ Use JDK 21 and Rust 1.91. Build DEB packages on Debian/Ubuntu with `dpkg` and
|
|||||||
`fakeroot`; build RPM packages on Fedora with `rpm-build`. Building an RPM on
|
`fakeroot`; build RPM packages on Fedora with `rpm-build`. Building an RPM on
|
||||||
Ubuntu prevents `jpackage` from discovering normal RPM dependencies.
|
Ubuntu prevents `jpackage` from discovering normal RPM dependencies.
|
||||||
|
|
||||||
From the repository root on the matching Linux family, run one of:
|
Set the release in `version.properties`. From the repository root on the
|
||||||
|
matching Linux family, run one of:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make package-deb VERSION=1.0.0
|
make package-deb
|
||||||
make package-rpm VERSION=1.0.0
|
make package-rpm
|
||||||
```
|
```
|
||||||
|
|
||||||
The Make targets collect the Compose output under `build/release/linux/`, then
|
The Make targets collect the Compose output under `build/release/linux/`, then
|
||||||
|
|||||||
@@ -2,38 +2,14 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
version=${1:-1.0.0}
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
resolver="$script_dir/../version/resolve-version.sh"
|
||||||
|
version="$("$resolver" product)"
|
||||||
|
"$resolver" verify >/dev/null
|
||||||
|
|
||||||
if [[ ${GITHUB_REF_TYPE:-} == "tag" ]]; then
|
if [[ -n ${1:-} && $1 != "$version" ]]; then
|
||||||
if [[ ! ${GITHUB_REF_NAME:-} =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
printf 'Version overrides are not supported; version.properties declares %s\n' "$version" >&2
|
||||||
echo "Linux release tags must use vMAJOR.MINOR.PATCH" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
version=${GITHUB_REF_NAME#v}
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
||||||
echo "Version must use MAJOR.MINOR.PATCH" >&2
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
IFS=. read -r major minor patch <<< "$version"
|
|
||||||
parts=("$major" "$minor" "$patch")
|
|
||||||
|
|
||||||
for index in "${!parts[@]}"; do
|
|
||||||
part=${parts[$index]}
|
|
||||||
if [[ $part != "0" && $part == 0* ]]; then
|
|
||||||
echo "Version components must be canonical integers without leading zeroes" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if (( ${#part} > 5 )) || (( 10#$part > 65535 )); then
|
|
||||||
echo "Version components must be between 0 and 65535" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if (( index == 0 && 10#$part == 0 )); then
|
|
||||||
echo "The major version must be non-zero" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
printf '%s\n' "$version"
|
printf '%s\n' "$version"
|
||||||
|
|||||||
44
packaging/version/README.md
Normal file
44
packaging/version/README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
Keep it as plain `KEY=VALUE` assignments: the same file is parsed by shell,
|
||||||
|
PowerShell, Gradle, Rust, and Xcode.
|
||||||
|
|
||||||
|
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
|
||||||
|
the patch component (`0.2.1`). Release channels belong in
|
||||||
|
`RELEASE_CHANNEL`; they are not appended to store version fields.
|
||||||
|
|
||||||
|
| 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 |
|
||||||
|
| Rust handshake | `PRODUCT_VERSION` | Rust crate version remains independent |
|
||||||
|
| Microsoft Store | `PRODUCT_VERSION` in the app | Derived MSIX dot-quad |
|
||||||
|
|
||||||
|
MSIX requires a non-zero first component and reserves the fourth component for
|
||||||
|
the Store. Its version is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
(product major + WINDOWS_VERSION_EPOCH).product minor.product patch.0
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Before releasing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make check-version
|
||||||
|
```
|
||||||
|
|
||||||
|
Release tags must exactly match `vPRODUCT_VERSION`. Manual workflow dispatches
|
||||||
|
also build the committed version and do not accept free-form version inputs.
|
||||||
97
packaging/version/resolve-version.ps1
Normal file
97
packaging/version/resolve-version.ps1
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[ValidateSet("Product", "Channel", "AndroidCode", "AppleBuild", "WindowsPackage", "Json", "Verify")]
|
||||||
|
[string] $Field = "Verify",
|
||||||
|
|
||||||
|
[switch] $VerifyTag,
|
||||||
|
|
||||||
|
[string] $VersionFile
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($VersionFile)) {
|
||||||
|
$VersionFile = Join-Path $PSScriptRoot "..\..\version.properties"
|
||||||
|
}
|
||||||
|
$VersionFile = (Resolve-Path -LiteralPath $VersionFile).Path
|
||||||
|
|
||||||
|
function Read-VersionProperty {
|
||||||
|
param([string] $Name)
|
||||||
|
|
||||||
|
$prefix = "$Name="
|
||||||
|
$matches = @(Get-Content -LiteralPath $VersionFile | Where-Object { $_.StartsWith($prefix) })
|
||||||
|
if ($matches.Count -ne 1) {
|
||||||
|
throw "Expected exactly one $Name entry in $VersionFile"
|
||||||
|
}
|
||||||
|
return $matches[0].Substring($prefix.Length)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Convert-CanonicalInteger {
|
||||||
|
param(
|
||||||
|
[string] $Name,
|
||||||
|
[string] $Value,
|
||||||
|
[long] $Minimum,
|
||||||
|
[long] $Maximum
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($Value -notmatch "^(0|[1-9][0-9]*)$") {
|
||||||
|
throw "$Name must be a canonical non-negative integer"
|
||||||
|
}
|
||||||
|
$number = 0L
|
||||||
|
if (-not [long]::TryParse($Value, [ref] $number) -or $number -lt $Minimum -or $number -gt $Maximum) {
|
||||||
|
throw "$Name must be between $Minimum and $Maximum"
|
||||||
|
}
|
||||||
|
return $number
|
||||||
|
}
|
||||||
|
|
||||||
|
$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"
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
$productParts = $productVersion.Split(".")
|
||||||
|
$productMajor = Convert-CanonicalInteger "PRODUCT_VERSION major" $productParts[0] 0 65534
|
||||||
|
$null = Convert-CanonicalInteger "PRODUCT_VERSION minor" $productParts[1] 0 65535
|
||||||
|
$null = Convert-CanonicalInteger "PRODUCT_VERSION patch" $productParts[2] 0 65535
|
||||||
|
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) {
|
||||||
|
throw "Derived Windows package major exceeds 65535"
|
||||||
|
}
|
||||||
|
$windowsPackageVersion = "$windowsMajor.$($productParts[1]).$($productParts[2]).0"
|
||||||
|
|
||||||
|
if ($VerifyTag -and $env:GITHUB_REF_TYPE -eq "tag" -and $env:GITHUB_REF_NAME -ne "v$productVersion") {
|
||||||
|
throw "Release tag must be v$productVersion, got $($env:GITHUB_REF_NAME)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$versionInfo = [ordered] @{
|
||||||
|
productVersion = $productVersion
|
||||||
|
releaseChannel = $releaseChannel
|
||||||
|
androidVersionCode = $androidVersionCode
|
||||||
|
appleBuildNumber = $appleBuildNumber
|
||||||
|
windowsPackageVersion = $windowsPackageVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($Field) {
|
||||||
|
"Product" { $productVersion }
|
||||||
|
"Channel" { $releaseChannel }
|
||||||
|
"AndroidCode" { $androidVersionCode }
|
||||||
|
"AppleBuild" { $appleBuildNumber }
|
||||||
|
"WindowsPackage" { $windowsPackageVersion }
|
||||||
|
"Json" { $versionInfo | ConvertTo-Json -Compress }
|
||||||
|
"Verify" {
|
||||||
|
"VniDrop $productVersion ($releaseChannel), Android $androidVersionCode, Apple $appleBuildNumber, MSIX $windowsPackageVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
95
packaging/version/resolve-version.sh
Executable file
95
packaging/version/resolve-version.sh
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
repo_root="$(cd "$script_dir/../.." && pwd)"
|
||||||
|
version_file="${VNIDROP_VERSION_FILE:-$repo_root/version.properties}"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf '%s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
read_property() {
|
||||||
|
local key=$1
|
||||||
|
local matches
|
||||||
|
matches="$(sed -n "s/^${key}=//p" "$version_file")"
|
||||||
|
[[ -n "$matches" ]] || fail "Missing $key in $version_file"
|
||||||
|
[[ $(printf '%s\n' "$matches" | wc -l | tr -d ' ') == 1 ]] ||
|
||||||
|
fail "Duplicate $key in $version_file"
|
||||||
|
printf '%s' "$matches"
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_canonical_integer() {
|
||||||
|
local name=$1
|
||||||
|
local value=$2
|
||||||
|
local minimum=$3
|
||||||
|
local maximum=$4
|
||||||
|
[[ $value =~ ^(0|[1-9][0-9]*)$ ]] ||
|
||||||
|
fail "$name must be a canonical non-negative integer"
|
||||||
|
(( 10#$value >= minimum && 10#$value <= maximum )) ||
|
||||||
|
fail "$name must be between $minimum and $maximum"
|
||||||
|
}
|
||||||
|
|
||||||
|
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)"
|
||||||
|
|
||||||
|
[[ $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"
|
||||||
|
IFS=. read -r product_major product_minor product_patch <<< "$product_version"
|
||||||
|
validate_canonical_integer "PRODUCT_VERSION major" "$product_major" 0 65534
|
||||||
|
validate_canonical_integer "PRODUCT_VERSION minor" "$product_minor" 0 65535
|
||||||
|
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
|
||||||
|
|
||||||
|
windows_major=$((10#$product_major + 10#$windows_version_epoch))
|
||||||
|
(( windows_major <= 65535 )) ||
|
||||||
|
fail "Derived Windows package major exceeds 65535"
|
||||||
|
windows_package_version="$windows_major.$product_minor.$product_patch.0"
|
||||||
|
|
||||||
|
verify_tag() {
|
||||||
|
local tag=${1:-${GITHUB_REF_NAME:-}}
|
||||||
|
if [[ ${GITHUB_REF_TYPE:-} == tag || -n ${1:-} ]]; then
|
||||||
|
[[ $tag == "v$product_version" ]] ||
|
||||||
|
fail "Release tag must be v$product_version, got ${tag:-<empty>}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-verify}" in
|
||||||
|
product)
|
||||||
|
printf '%s\n' "$product_version"
|
||||||
|
;;
|
||||||
|
channel)
|
||||||
|
printf '%s\n' "$release_channel"
|
||||||
|
;;
|
||||||
|
android-code)
|
||||||
|
printf '%s\n' "$android_version_code"
|
||||||
|
;;
|
||||||
|
apple-build)
|
||||||
|
printf '%s\n' "$apple_build_number"
|
||||||
|
;;
|
||||||
|
windows-package)
|
||||||
|
printf '%s\n' "$windows_package_version"
|
||||||
|
;;
|
||||||
|
verify)
|
||||||
|
verify_tag
|
||||||
|
printf 'VniDrop %s (%s), Android %s, Apple %s, MSIX %s\n' \
|
||||||
|
"$product_version" "$release_channel" "$android_version_code" \
|
||||||
|
"$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]}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
51
packaging/version/test-version.sh
Executable file
51
packaging/version/test-version.sh
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
resolver="$script_dir/resolve-version.sh"
|
||||||
|
scratch="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$scratch"' EXIT
|
||||||
|
|
||||||
|
write_version() {
|
||||||
|
printf '%s\n' \
|
||||||
|
"PRODUCT_VERSION=$1" \
|
||||||
|
"RELEASE_CHANNEL=$2" \
|
||||||
|
"ANDROID_VERSION_CODE=$3" \
|
||||||
|
"APPLE_BUILD_NUMBER=$4" \
|
||||||
|
"WINDOWS_VERSION_EPOCH=$5" \
|
||||||
|
> "$scratch/version.properties"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve() {
|
||||||
|
VNIDROP_VERSION_FILE="$scratch/version.properties" "$resolver" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect_failure() {
|
||||||
|
if "$@" >/dev/null 2>&1; then
|
||||||
|
printf 'Expected command to fail: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
write_version 0.2.0 beta 2 2 1
|
||||||
|
[[ $(resolve product) == 0.2.0 ]]
|
||||||
|
[[ $(resolve android-code) == 2 ]]
|
||||||
|
[[ $(resolve apple-build) == 2 ]]
|
||||||
|
[[ $(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
|
||||||
|
[[ $(resolve windows-package) == 2.0.0.0 ]]
|
||||||
|
|
||||||
|
write_version 01.0.0 beta 2 2 1
|
||||||
|
expect_failure resolve verify
|
||||||
|
|
||||||
|
write_version 0.2.0 beta 0 2 1
|
||||||
|
expect_failure resolve verify
|
||||||
|
|
||||||
|
write_version 65535.0.0 stable 2 2 1
|
||||||
|
expect_failure resolve verify
|
||||||
|
|
||||||
|
printf 'Version resolver tests passed.\n'
|
||||||
@@ -24,8 +24,10 @@ after the first release. The manifest display name uses the exact reserved Store
|
|||||||
name; the product's in-app branding and launcher remain `VniDrop`.
|
name; the product's in-app branding and launcher remain `VniDrop`.
|
||||||
|
|
||||||
The initial package targets Windows Desktop x64, Windows 10 version 2004
|
The initial package targets Windows Desktop x64, Windows 10 version 2004
|
||||||
(build 19041) or later. The fourth MSIX version component is reserved by the
|
(build 19041) or later. The product version comes from `version.properties`.
|
||||||
Store, so app version 1.2.3 becomes package version 1.2.3.0.
|
Because MSIX requires a non-zero major and reserves the fourth component, the
|
||||||
|
package version adds `WINDOWS_VERSION_EPOCH` to the product major. With epoch
|
||||||
|
`1`, product version `0.2.0` becomes package version `1.2.0.0`.
|
||||||
|
|
||||||
## GitHub Actions
|
## GitHub Actions
|
||||||
|
|
||||||
@@ -87,9 +89,9 @@ The Store ID is a non-secret variable.
|
|||||||
From the repository root:
|
From the repository root:
|
||||||
|
|
||||||
~~~powershell
|
~~~powershell
|
||||||
.\gradlew.bat :shared:jvmTest :desktopApp:createReleaseDistributable -Pvnidrop.version=1.0.0 -Pvnidrop.desktop.rustVariant=release -Pvnidrop.diagnostics.included=false --no-daemon --no-configuration-cache --stacktrace
|
.\gradlew.bat :shared:jvmTest :desktopApp:createReleaseDistributable -Pvnidrop.desktop.rustVariant=release -Pvnidrop.diagnostics.included=false --no-daemon --no-configuration-cache --stacktrace
|
||||||
|
|
||||||
.\packaging\windows\build-msix.ps1 -Version 1.0.0 -AppImage .\desktopApp\build\compose\binaries\main-release\app\VniDrop -OutputDirectory .\build\release\windows
|
.\packaging\windows\build-msix.ps1 -AppImage .\desktopApp\build\compose\binaries\main-release\app\VniDrop -OutputDirectory .\build\release\windows
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
The packaging script requires Windows SDK 10.0.26100.0. It uses MakePri to
|
The packaging script requires Windows SDK 10.0.26100.0. It uses MakePri to
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
|
||||||
[string] $Version,
|
|
||||||
|
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[string] $AppImage,
|
[string] $AppImage,
|
||||||
|
|
||||||
@@ -87,16 +84,11 @@ if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) {
|
|||||||
throw "MSIX packaging must run on Windows"
|
throw "MSIX packaging must run on Windows"
|
||||||
}
|
}
|
||||||
|
|
||||||
$versionParts = $Version.Split(".")
|
$versionResolver = Join-Path $PSScriptRoot "..\version\resolve-version.ps1"
|
||||||
Assert-Condition ($versionParts.Count -eq 3) "Version must use MAJOR.MINOR.PATCH"
|
$versionInfoJson = & $versionResolver -Field Json -VerifyTag
|
||||||
for ($index = 0; $index -lt $versionParts.Count; $index++) {
|
$versionInfo = $versionInfoJson | ConvertFrom-Json
|
||||||
$part = $versionParts[$index]
|
$Version = [string] $versionInfo.productVersion
|
||||||
$number = 0
|
$packageVersion = [string] $versionInfo.windowsPackageVersion
|
||||||
Assert-Condition ([int]::TryParse($part, [ref] $number)) "Version components must be integers"
|
|
||||||
Assert-Condition ($number.ToString() -eq $part) "Version components must not contain leading zeroes"
|
|
||||||
Assert-Condition ($number -ge $(if ($index -eq 0) { 1 } else { 0 }) -and $number -le 65535) "Version components must be between 0 and 65535, with a non-zero major"
|
|
||||||
}
|
|
||||||
$packageVersion = "$Version.0"
|
|
||||||
|
|
||||||
$appImagePath = (Resolve-Path -LiteralPath $AppImage).Path
|
$appImagePath = (Resolve-Path -LiteralPath $AppImage).Path
|
||||||
Assert-Condition (Test-Path -LiteralPath $appImagePath -PathType Container) "App image not found: $AppImage"
|
Assert-Condition (Test-Path -LiteralPath $appImagePath -PathType Container) "App image not found: $AppImage"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ plugins {
|
|||||||
alias(libs.plugins.kotlinAtomicfu)
|
alias(libs.plugins.kotlinAtomicfu)
|
||||||
}
|
}
|
||||||
|
|
||||||
val appVersion = providers.gradleProperty("vnidrop.version").get()
|
val appVersion = rootProject.extra["vnidrop.productVersion"] as String
|
||||||
val desktopRustVariant = providers.gradleProperty("vnidrop.desktop.rustVariant")
|
val desktopRustVariant = providers.gradleProperty("vnidrop.desktop.rustVariant")
|
||||||
.map { value ->
|
.map { value ->
|
||||||
when (value.trim().lowercase()) {
|
when (value.trim().lowercase()) {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ private class AndroidDeviceInfoProvider(
|
|||||||
|
|
||||||
private fun Context.appVersion(): String = runCatching {
|
private fun Context.appVersion(): String = runCatching {
|
||||||
packageManager.getPackageInfo(packageName, 0).versionName
|
packageManager.getPackageInfo(packageName, 0).versionName
|
||||||
}.getOrNull()?.takeIf(String::isNotBlank) ?: "0.1.0"
|
}.getOrNull()?.takeIf(String::isNotBlank) ?: "unknown"
|
||||||
|
|
||||||
private fun Context.activeNetworkSummary(): String? = runCatching {
|
private fun Context.activeNetworkSummary(): String? = runCatching {
|
||||||
val manager = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
val manager = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ fun rememberJvmAppDependencies(externalInvitations: ExternalInvitationController
|
|||||||
AppDependencies(
|
AppDependencies(
|
||||||
environment = PlatformEnvironment(
|
environment = PlatformEnvironment(
|
||||||
name = "Java ${System.getProperty("java.version")}",
|
name = "Java ${System.getProperty("java.version")}",
|
||||||
appVersion = AppDependencies::class.java.`package`.implementationVersion ?: "0.1.0",
|
appVersion = AppDependencies::class.java.`package`.implementationVersion ?: "unknown",
|
||||||
defaultCoreDataDir = System.getProperty("user.home") + "/.vnidrop",
|
defaultCoreDataDir = System.getProperty("user.home") + "/.vnidrop",
|
||||||
defaultUsername = System.getenv("COMPUTERNAME") ?: System.getenv("HOSTNAME") ?: System.getProperty("user.name") ?: "Receiver",
|
defaultUsername = System.getenv("COMPUTERNAME") ?: System.getenv("HOSTNAME") ?: System.getProperty("user.name") ?: "Receiver",
|
||||||
uiPlatform = uiPlatformForJvm(System.getProperty("os.name")),
|
uiPlatform = uiPlatformForJvm(System.getProperty("os.name")),
|
||||||
|
|||||||
5
version.properties
Normal file
5
version.properties
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
PRODUCT_VERSION=0.2.0
|
||||||
|
RELEASE_CHANNEL=beta
|
||||||
|
ANDROID_VERSION_CODE=2
|
||||||
|
APPLE_BUILD_NUMBER=2
|
||||||
|
WINDOWS_VERSION_EPOCH=1
|
||||||
Reference in New Issue
Block a user