Skip to content

Commit 3284255

Browse files
committed
Add Windows ARM64 release packaging
1 parent 1d0121c commit 3284255

10 files changed

Lines changed: 252 additions & 32 deletions

File tree

.github/workflows/release-windows.yml

Lines changed: 79 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ jobs:
9797
run: npm run build
9898
working-directory: apps/desktop
9999

100-
windows-x64:
101-
name: Build and publish Windows x64
100+
windows:
101+
name: Build and publish Windows x64 and ARM64
102102
needs: release-preflight
103103
runs-on: windows-latest
104104
env:
@@ -158,6 +158,9 @@ jobs:
158158
- name: Select stable Rust
159159
uses: dtolnay/rust-toolchain@stable
160160

161+
- name: Install Windows ARM64 Rust target
162+
run: rustup target add aarch64-pc-windows-msvc
163+
161164
- name: Cache Rust build
162165
uses: Swatinem/rust-cache@v2
163166

@@ -228,13 +231,13 @@ jobs:
228231
tenant-id: ${{ env.AZURE_TENANT_ID }}
229232
subscription-id: ${{ env.AZURE_SUBSCRIPTION_ID }}
230233

231-
- name: Prepare unsigned Windows sidecars
234+
- name: Prepare unsigned Windows x64 sidecars
232235
shell: pwsh
233236
env:
234237
LOCALITY_WINDOWS_EXTERNAL_CODESIGN: ""
235238
run: ./apps/desktop/scripts/prepare-windows-bundle.ps1
236239

237-
- name: Sign Windows sidecars
240+
- name: Sign Windows x64 sidecars
238241
if: steps.windows-signing.outputs.enabled == 'true'
239242
uses: azure/artifact-signing-action@v2
240243
with:
@@ -246,7 +249,7 @@ jobs:
246249
timestamp-rfc3161: http://timestamp.acs.microsoft.com
247250
timestamp-digest: SHA256
248251

249-
- name: Validate signed Windows sidecars
252+
- name: Validate signed Windows x64 sidecars
250253
if: steps.windows-signing.outputs.enabled == 'true'
251254
shell: pwsh
252255
run: |
@@ -255,13 +258,49 @@ jobs:
255258
Assert-LocalityWindowsSigned -Path (Join-Path "apps/desktop/src-tauri/windows" $name)
256259
}
257260
258-
- name: Build, sign, and validate Windows installer
261+
- name: Build, sign, and validate Windows x64 installer
259262
shell: pwsh
260263
env:
261264
LOCALITY_WINDOWS_BUNDLE_PREPARED: "1"
262265
PUBLISH_ALLOW_DIRTY: "1"
263266
run: ./scripts/publish-windows.ps1
264267

268+
- name: Prepare unsigned Windows ARM64 sidecars
269+
shell: pwsh
270+
env:
271+
LOCALITY_WINDOWS_EXTERNAL_CODESIGN: ""
272+
LOCALITY_WINDOWS_TARGET: aarch64-pc-windows-msvc
273+
run: ./apps/desktop/scripts/prepare-windows-bundle.ps1
274+
275+
- name: Sign Windows ARM64 sidecars
276+
if: steps.windows-signing.outputs.enabled == 'true'
277+
uses: azure/artifact-signing-action@v2
278+
with:
279+
endpoint: ${{ env.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
280+
signing-account-name: ${{ env.AZURE_ARTIFACT_SIGNING_ACCOUNT }}
281+
certificate-profile-name: ${{ env.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE }}
282+
files-folder: ${{ github.workspace }}\apps\desktop\src-tauri\windows
283+
files-folder-filter: exe
284+
timestamp-rfc3161: http://timestamp.acs.microsoft.com
285+
timestamp-digest: SHA256
286+
287+
- name: Validate signed Windows ARM64 sidecars
288+
if: steps.windows-signing.outputs.enabled == 'true'
289+
shell: pwsh
290+
run: |
291+
. ./scripts/windows-codesign.ps1
292+
foreach ($name in @("loc.exe", "localityd.exe", "locality-cloud-files.exe")) {
293+
Assert-LocalityWindowsSigned -Path (Join-Path "apps/desktop/src-tauri/windows" $name)
294+
}
295+
296+
- name: Build, sign, and validate Windows ARM64 installer
297+
shell: pwsh
298+
env:
299+
LOCALITY_WINDOWS_BUNDLE_PREPARED: "1"
300+
LOCALITY_WINDOWS_TARGET: aarch64-pc-windows-msvc
301+
PUBLISH_ALLOW_DIRTY: "1"
302+
run: ./scripts/publish-windows.ps1
303+
265304
- name: Sign Windows installers
266305
if: steps.windows-signing.outputs.enabled == 'true'
267306
uses: azure/artifact-signing-action@v2
@@ -299,32 +338,46 @@ jobs:
299338
- name: Create standard GitHub asset names
300339
shell: pwsh
301340
run: |
302-
$source = "target/release/bundle/windows/Locality-release-windows-x86_64-setup.exe"
303-
$destination = "target/release/bundle/windows/Locality_Windows_v$env:APP_VERSION.exe"
304-
$latestDestination = "target/release/bundle/windows/Locality_Windows.exe"
305-
if (-not (Test-Path -LiteralPath $source)) {
306-
Write-Error "Could not find Windows installer $source."
307-
exit 1
308-
}
309-
if (-not (Test-Path -LiteralPath "$source.sig")) {
310-
Write-Error "Could not find Windows updater signature $source.sig."
311-
exit 1
312-
}
313-
Copy-Item -LiteralPath $source -Destination $destination -Force
314-
Copy-Item -LiteralPath "$source.sig" -Destination "$destination.sig" -Force
315-
Copy-Item -LiteralPath $source -Destination $latestDestination -Force
316-
Copy-Item -LiteralPath "$source.sig" -Destination "$latestDestination.sig" -Force
317-
foreach ($path in @($destination, $latestDestination)) {
318-
$hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $path).Hash.ToLowerInvariant()
319-
"$hash $(Split-Path -Leaf $path)" | Set-Content -Encoding ascii -LiteralPath "$path.sha256"
341+
function Copy-InstallerAsset {
342+
param(
343+
[string] $Source,
344+
[string] $VersionedDestination,
345+
[string] $LatestDestination
346+
)
347+
if (-not (Test-Path -LiteralPath $Source)) {
348+
Write-Error "Could not find Windows installer $Source."
349+
exit 1
350+
}
351+
if (-not (Test-Path -LiteralPath "$Source.sig")) {
352+
Write-Error "Could not find Windows updater signature $Source.sig."
353+
exit 1
354+
}
355+
Copy-Item -LiteralPath $Source -Destination $VersionedDestination -Force
356+
Copy-Item -LiteralPath "$Source.sig" -Destination "$VersionedDestination.sig" -Force
357+
Copy-Item -LiteralPath $Source -Destination $LatestDestination -Force
358+
Copy-Item -LiteralPath "$Source.sig" -Destination "$LatestDestination.sig" -Force
359+
foreach ($path in @($VersionedDestination, $LatestDestination)) {
360+
$hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $path).Hash.ToLowerInvariant()
361+
"$hash $(Split-Path -Leaf $path)" | Set-Content -Encoding ascii -LiteralPath "$path.sha256"
362+
}
320363
}
321364
365+
Copy-InstallerAsset `
366+
-Source "target/release/bundle/windows/Locality-release-windows-x86_64-setup.exe" `
367+
-VersionedDestination "target/release/bundle/windows/Locality_Windows_v$env:APP_VERSION.exe" `
368+
-LatestDestination "target/release/bundle/windows/Locality_Windows.exe"
369+
Copy-InstallerAsset `
370+
-Source "target/release/bundle/windows/Locality-release-windows-aarch64-setup.exe" `
371+
-VersionedDestination "target/release/bundle/windows/Locality_Windows_ARM64_v$env:APP_VERSION.exe" `
372+
-LatestDestination "target/release/bundle/windows/Locality_Windows_ARM64.exe"
373+
322374
- name: Render Windows updater manifest
323375
shell: bash
324376
env:
325377
GITHUB_RELEASE_TAG: ${{ env.RELEASE_TAG }}
326378
UPDATER_MANIFEST_OUTPUT: target/release/bundle/updater/latest-windows.json
327379
UPDATER_WINDOWS_X86_64_ARTIFACT: target/release/bundle/windows/Locality_Windows_v${{ env.APP_VERSION }}.exe
380+
UPDATER_WINDOWS_AARCH64_ARTIFACT: target/release/bundle/windows/Locality_Windows_ARM64_v${{ env.APP_VERSION }}.exe
328381
UPDATER_NOTES: Locality ${{ env.APP_VERSION }} Windows update.
329382
run: scripts/render-tauri-updater-manifest.sh
330383

@@ -333,7 +386,9 @@ jobs:
333386
run: |
334387
New-Item -ItemType Directory -Force -Path target/release/github-assets | Out-Null
335388
Copy-Item "target/release/bundle/windows/Locality_Windows_v$env:APP_VERSION.exe*" target/release/github-assets/
389+
Copy-Item "target/release/bundle/windows/Locality_Windows_ARM64_v$env:APP_VERSION.exe*" target/release/github-assets/
336390
Copy-Item target/release/bundle/windows/Locality_Windows.exe* target/release/github-assets/
391+
Copy-Item target/release/bundle/windows/Locality_Windows_ARM64.exe* target/release/github-assets/
337392
Copy-Item target/release/bundle/updater/latest-windows.json target/release/github-assets/
338393
Get-ChildItem target/release/github-assets -File |
339394
Sort-Object Name |

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,18 @@ publish-linux: setup ## Build and validate Linux .deb and .rpm packages.
107107
publish-windows: setup ## Build, sign, and validate a Windows NSIS package.
108108
pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/publish-windows.ps1
109109

110+
.PHONY: publish-windows-arm64
111+
publish-windows-arm64: setup ## Build, sign, and validate a Windows ARM64 NSIS package.
112+
LOCALITY_WINDOWS_TARGET=aarch64-pc-windows-msvc pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/publish-windows.ps1
113+
110114
.PHONY: build-tauri-windows
111115
build-tauri-windows: setup ## Build the Windows Tauri installer.
112116
$(DESKTOP_NPM) run build:windows
113117

118+
.PHONY: build-tauri-windows-arm64
119+
build-tauri-windows-arm64: setup ## Build the Windows ARM64 Tauri installer.
120+
LOCALITY_WINDOWS_TARGET=aarch64-pc-windows-msvc $(DESKTOP_NPM) run build:windows-arm64
121+
114122
.PHONY: render-homebrew-cask
115123
render-homebrew-cask: ## Render a Homebrew cask from published macOS DMG artifacts.
116124
scripts/render-homebrew-cask.sh

apps/desktop/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ Build a local NSIS installer from the repo root on Windows:
6868
make build-tauri-windows
6969
```
7070

71+
For a Windows on Arm installer:
72+
73+
```sh
74+
rustup target add aarch64-pc-windows-msvc
75+
make build-tauri-windows-arm64
76+
```
77+
7178
The build stages `loc.exe`, `localityd.exe`, and `locality-cloud-files.exe` under
7279
`src-tauri/windows` before Tauri bundles the app. The installer copies those
7380
sidecars next to the desktop executable so the app can start the packaged

apps/desktop/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"build:mas": "VITE_LOCALITY_DISTRIBUTION_CHANNEL=mas LOCALITY_DISTRIBUTION_CHANNEL=mas tauri build --bundles app",
1414
"build:linux": "tauri build --bundles deb,rpm",
1515
"build:windows": "tauri build --bundles nsis",
16+
"build:windows-arm64": "tauri build --bundles nsis --target aarch64-pc-windows-msvc",
1617
"preview": "vite preview --host 127.0.0.1 --port 1421",
1718
"tauri": "tauri"
1819
},

apps/desktop/scripts/prepare-windows-bundle.ps1

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ $Out = Join-Path $Root "apps\desktop\src-tauri\windows"
66
$MountLogoSource = Join-Path $Root "apps\desktop\src-tauri\icons\locality-mount-logo.ico"
77
$MountLogoOut = Join-Path $Out "locality-mount-logo.ico"
88
. (Join-Path $Root "scripts\windows-codesign.ps1")
9+
10+
function Get-WindowsBuildTarget {
11+
if (-not [string]::IsNullOrWhiteSpace($env:LOCALITY_WINDOWS_TARGET)) {
12+
return $env:LOCALITY_WINDOWS_TARGET
13+
}
14+
if (-not [string]::IsNullOrWhiteSpace($env:PUBLISH_WINDOWS_TARGET)) {
15+
return $env:PUBLISH_WINDOWS_TARGET
16+
}
17+
if (-not [string]::IsNullOrWhiteSpace($env:TAURI_ENV_TARGET_TRIPLE)) {
18+
return $env:TAURI_ENV_TARGET_TRIPLE
19+
}
20+
if ($env:TAURI_ENV_ARCH -match "^(aarch64|arm64)$") {
21+
return "aarch64-pc-windows-msvc"
22+
}
23+
return ""
24+
}
25+
26+
$TargetTriple = Get-WindowsBuildTarget
27+
$ReleaseDir = if ([string]::IsNullOrWhiteSpace($TargetTriple)) {
28+
Join-Path $Root "target\release"
29+
} else {
30+
Join-Path $Root "target\$TargetTriple\release"
31+
}
32+
933
New-Item -ItemType Directory -Force -Path $Out | Out-Null
1034
$Sidecars = @(
1135
(Join-Path $Out "loc.exe"),
@@ -33,14 +57,18 @@ if ($env:LOCALITY_WINDOWS_BUNDLE_PREPARED -ne "1") {
3357

3458
Push-Location $Root
3559
try {
36-
& $Cargo build -p loc-cli -p localityd -p locality-cloud-files --release
60+
$cargoArgs = @("build", "-p", "loc-cli", "-p", "localityd", "-p", "locality-cloud-files", "--release")
61+
if (-not [string]::IsNullOrWhiteSpace($TargetTriple)) {
62+
$cargoArgs += @("--target", $TargetTriple)
63+
}
64+
& $Cargo @cargoArgs
3765
} finally {
3866
Pop-Location
3967
}
4068

41-
Copy-Item -LiteralPath (Join-Path $Root "target\release\loc.exe") -Destination (Join-Path $Out "loc.exe") -Force
42-
Copy-Item -LiteralPath (Join-Path $Root "target\release\localityd.exe") -Destination (Join-Path $Out "localityd.exe") -Force
43-
Copy-Item -LiteralPath (Join-Path $Root "target\release\locality-cloud-files.exe") -Destination (Join-Path $Out "locality-cloud-files.exe") -Force
69+
Copy-Item -LiteralPath (Join-Path $ReleaseDir "loc.exe") -Destination (Join-Path $Out "loc.exe") -Force
70+
Copy-Item -LiteralPath (Join-Path $ReleaseDir "localityd.exe") -Destination (Join-Path $Out "localityd.exe") -Force
71+
Copy-Item -LiteralPath (Join-Path $ReleaseDir "locality-cloud-files.exe") -Destination (Join-Path $Out "locality-cloud-files.exe") -Force
4472
Copy-Item -LiteralPath $MountLogoSource -Destination $MountLogoOut -Force
4573
} else {
4674
foreach ($Sidecar in $Sidecars) {
@@ -60,6 +88,9 @@ if (Test-LocalityWindowsCodeSigningRequested) {
6088
}
6189
}
6290

91+
if (-not [string]::IsNullOrWhiteSpace($TargetTriple)) {
92+
Write-Host "Prepared Windows target $TargetTriple"
93+
}
6394
Write-Host "Prepared Windows CLI in $(Join-Path $Out 'loc.exe')"
6495
Write-Host "Prepared Windows daemon in $(Join-Path $Out 'localityd.exe')"
6596
Write-Host "Prepared Windows Cloud Files helper in $(Join-Path $Out 'locality-cloud-files.exe')"

docs/windows-distribution.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,27 @@ Build a local NSIS installer from the repo root on Windows:
1212
make build-tauri-windows
1313
```
1414

15+
Build the Windows on Arm installer from a Windows host with the Rust target
16+
installed:
17+
18+
```powershell
19+
rustup target add aarch64-pc-windows-msvc
20+
make build-tauri-windows-arm64
21+
```
22+
1523
For release-like local packaging, use:
1624

1725
```powershell
1826
pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/publish-windows.ps1
1927
```
2028

29+
For Windows on Arm release-like packaging, run:
30+
31+
```powershell
32+
rustup target add aarch64-pc-windows-msvc
33+
make publish-windows-arm64
34+
```
35+
2136
The Tauri pre-bundle hook runs:
2237

2338
```powershell
@@ -26,14 +41,19 @@ apps/desktop/scripts/prepare-windows-bundle.ps1
2641

2742
That script builds `loc-cli`, `localityd`, and `locality-cloud-files` in release mode,
2843
stages the three sidecars under `apps/desktop/src-tauri/windows/`, and lets the
29-
NSIS hook copy them next to the installed desktop executable.
44+
NSIS hook copy them next to the installed desktop executable. Set
45+
`LOCALITY_WINDOWS_TARGET=aarch64-pc-windows-msvc` or use the ARM64 make targets
46+
to build and stage Windows on Arm sidecars from
47+
`target\aarch64-pc-windows-msvc\release`.
3048

3149
Expected local artifacts:
3250

3351
```text
3452
target/release/bundle/nsis/*.exe
3553
target/release/bundle/windows/Locality-beta-windows-x86_64-setup.exe
3654
target/release/bundle/windows/Locality-beta-windows-x86_64-setup.exe.sha256
55+
target/release/bundle/windows/Locality-beta-windows-aarch64-setup.exe
56+
target/release/bundle/windows/Locality-beta-windows-aarch64-setup.exe.sha256
3757
```
3858

3959
The publish script requires a clean git working tree by default because the
@@ -174,6 +194,7 @@ Render the Windows updater manifest with:
174194
```powershell
175195
$env:UPDATER_MANIFEST_OUTPUT = "target/release/bundle/updater/latest-windows.json"
176196
$env:UPDATER_WINDOWS_X86_64_ARTIFACT = "target/release/bundle/windows/Locality-release-windows-x86_64-setup.exe"
197+
$env:UPDATER_WINDOWS_AARCH64_ARTIFACT = "target/release/bundle/windows/Locality-release-windows-aarch64-setup.exe"
177198
bash scripts/render-tauri-updater-manifest.sh
178199
```
179200

@@ -193,6 +214,12 @@ Locality_Windows_v0.1.0.exe.sig
193214
Locality_Windows.exe
194215
Locality_Windows.exe.sha256
195216
Locality_Windows.exe.sig
217+
Locality_Windows_ARM64_v0.1.0.exe
218+
Locality_Windows_ARM64_v0.1.0.exe.sha256
219+
Locality_Windows_ARM64_v0.1.0.exe.sig
220+
Locality_Windows_ARM64.exe
221+
Locality_Windows_ARM64.exe.sha256
222+
Locality_Windows_ARM64.exe.sig
196223
latest-windows.json
197224
SHA256SUMS-windows
198225
```

scripts/finalize-github-release.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ required_assets=(
9292
"Locality_Windows.exe"
9393
"Locality_Windows.exe.sha256"
9494
"Locality_Windows.exe.sig"
95+
"Locality_Windows_ARM64_v${APP_VERSION}.exe"
96+
"Locality_Windows_ARM64_v${APP_VERSION}.exe.sha256"
97+
"Locality_Windows_ARM64_v${APP_VERSION}.exe.sig"
98+
"Locality_Windows_ARM64.exe"
99+
"Locality_Windows_ARM64.exe.sha256"
100+
"Locality_Windows_ARM64.exe.sig"
95101
"latest-windows.json"
96102
"SHA256SUMS-windows"
97103
)

0 commit comments

Comments
 (0)