# Archive portable build (no installer). ASCII only - PS 5.1 reads BOM-less files as ANSI. # # Output: dist-portable\Archive-\ (exe + ffmpeg/ffprobe sidecars + portable.marker) # The app already supports portable mode: src-tauri/src/paths.rs uses \data when a # `data\` folder or `portable.marker` sits next to the exe, instead of %APPDATA%\Archive. # # Usage: .\scripts\build-portable.ps1 [-Arch arm64|x64] [-Zip] # # ARM64 needs the MSVC cross tools ("MSVC ... ARM64 build tools" in the VS installer) and # `rustup target add aarch64-pc-windows-msvc`. The script checks both and says what is missing. param( [ValidateSet("arm64", "x64")] [string]$Arch = "arm64", [switch]$Zip ) $ErrorActionPreference = "Stop" $root = Split-Path $PSScriptRoot -Parent Set-Location $root $triple = if ($Arch -eq "arm64") { "aarch64-pc-windows-msvc" } else { "x86_64-pc-windows-msvc" } # vcvarsall argument: cross builds need "_", native is just the target. $vcArg = if ($Arch -eq "arm64") { "x64_arm64" } else { "x64" } Write-Host "[portable] target $triple" # --- toolchain checks ------------------------------------------------------- if (-not (Get-Command cargo -ErrorAction SilentlyContinue)) { $cargoBin = Join-Path $env:USERPROFILE ".cargo\bin" if (Test-Path (Join-Path $cargoBin "cargo.exe")) { $env:PATH = "$cargoBin;$env:PATH" } else { throw "cargo not found. Install Rust (https://rustup.rs) and reopen the shell." } } $installed = & rustup target list --installed if ($installed -notcontains $triple) { throw "Rust target missing: $triple`n fix: rustup target add $triple" } $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" if (-not (Test-Path $vswhere)) { throw "vswhere.exe not found - is Visual Studio installed?" } $vsPath = & $vswhere -latest -products * -property installationPath if (-not $vsPath) { throw "No Visual Studio installation found." } $vcvars = Join-Path $vsPath "VC\Auxiliary\Build\vcvarsall.bat" if (-not (Test-Path $vcvars)) { throw "vcvarsall.bat not found at $vcvars" } # ARM64 needs a cross toolchain. Prefer the one installed in Visual Studio; fall back to the # local copy laid out by fetch-arm64-toolchain.ps1 (that script explains why it exists). $localTc = $null if ($Arch -eq "arm64") { $hasArm = Get-ChildItem (Join-Path $vsPath "VC\Tools\MSVC") -Directory -ErrorAction SilentlyContinue | ForEach-Object { Test-Path (Join-Path $_.FullName "bin\Hostx64\arm64\cl.exe") } | Where-Object { $_ } if (-not $hasArm) { $localTc = Get-ChildItem (Join-Path $root ".toolchain\arm64\VC\Tools\MSVC") -Directory -ErrorAction SilentlyContinue | Sort-Object Name | Select-Object -Last 1 if (-not $localTc -or -not (Test-Path (Join-Path $localTc.FullName "bin\Hostx64\arm64\cl.exe"))) { throw @" MSVC ARM64 cross tools missing. fix (no admin): .\scripts\fetch-arm64-toolchain.ps1 or (Visual Studio): Installer -> Modify -> Individual components -> "MSVC v14x - VS 20xx C++ ARM64/ARM64EC build tools" "@ } Write-Host "[portable] using local toolchain: $($localTc.FullName)" } } # --- sidecars --------------------------------------------------------------- $ffmpeg = Join-Path $root "src-tauri\binaries\ffmpeg-$triple.exe" $ffprobe = Join-Path $root "src-tauri\binaries\ffprobe-$triple.exe" foreach ($f in @($ffmpeg, $ffprobe)) { if (-not (Test-Path $f)) { throw "sidecar missing: $f" } } # --- build ------------------------------------------------------------------ # vcvarsall must be loaded into THIS process, so run it in cmd and import the env back. # With the local toolchain we ask vcvarsall for the plain x64 env (it has no arm64 target) # and then splice the ARM64 compiler/libs in front of it ourselves. $vcvarsArg = if ($localTc) { "x64" } else { $vcArg } Write-Host "[portable] loading MSVC env ($vcvarsArg)" $envDump = & cmd /c "`"$vcvars`" $vcvarsArg >nul 2>&1 && set" if ($LASTEXITCODE -ne 0) { throw "vcvarsall $vcvarsArg failed" } foreach ($line in $envDump) { if ($line -match '^([^=]+)=(.*)$') { Set-Item -Path "env:$($matches[1])" -Value $matches[2] } } if ($localTc) { # Headers are architecture-neutral and already come from vcvarsall/the SDK. # Only the compiler binaries and the CRT import libs differ, so override just those: # PATH -> Hostx64\arm64 first (cl.exe, link.exe, lib.exe target ARM64) # LIB -> the ARM64 CRT, then the SDK's arm64 libs, dropping the x64 entries $armBin = Join-Path $localTc.FullName "bin\Hostx64\arm64" $armLib = Join-Path $localTc.FullName "lib\arm64" if (-not (Test-Path $armLib)) { throw "ARM64 CRT libs missing: $armLib" } # x64 host tools stay reachable behind the ARM64 ones - some build scripts need them. $env:PATH = "$armBin;$env:PATH" $sdkLibs = ($env:LIB -split ';' | Where-Object { $_ -match '\\Windows Kits\\' } | ForEach-Object { $_ -replace '\\x64$', '\arm64' }) -join ';' $env:LIB = "$armLib;$sdkLibs" $env:VSCMD_ARG_TGT_ARCH = "arm64" # cc-rs (libsqlite3-sys, aws-lc-sys, ...) picks its compiler per target triple. # **clang-cl, not cl.exe**: aws-lc-sys and ring ship their aarch64 assembly as .S, and # cl.exe answers "unrecognized source file type" and silently SKIPS those files - the # build then dies at link time on a missing .o. clang-cl assembles them and stays # MSVC-compatible, so the objects link against the same CRT. $llvmBin = Join-Path $root ".toolchain\llvm\bin" $armClang = Join-Path $llvmBin "clang-cl.exe" if (-not (Test-Path $armClang)) { throw "clang-cl missing - run .\scripts\fetch-arm64-toolchain.ps1" } # On PATH too: some build scripts (ring) look up plain "clang" themselves rather than # honouring CC_, and cc-rs resolves the compiler family by running it. $env:PATH = "$llvmBin;$env:PATH" $env:CC_aarch64_pc_windows_msvc = $armClang $env:CFLAGS_aarch64_pc_windows_msvc = "--target=aarch64-pc-windows-msvc" $env:AR_aarch64_pc_windows_msvc = Join-Path $armBin "lib.exe" $env:CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER = Join-Path $armBin "link.exe" } Write-Host "[portable] tauri build (no bundle)" & npx tauri build --target $triple --no-bundle if ($LASTEXITCODE -ne 0) { throw "build failed" } # --- assemble --------------------------------------------------------------- $exe = Join-Path $root "src-tauri\target\$triple\release\archive.exe" if (-not (Test-Path $exe)) { throw "built exe not found: $exe" } $outDir = Join-Path $root "dist-portable\Archive-$Arch" if (Test-Path $outDir) { Remove-Item $outDir -Recurse -Force } New-Item -ItemType Directory -Path $outDir -Force | Out-Null Copy-Item $exe (Join-Path $outDir "Archive.exe") # Drop the triple suffix: locate() looks for both, and the plain name is what a user expects. Copy-Item $ffmpeg (Join-Path $outDir "ffmpeg.exe") Copy-Item $ffprobe (Join-Path $outDir "ffprobe.exe") # Marker = keep the library DB and thumbnails inside this folder, not in %APPDATA%. Set-Content -Path (Join-Path $outDir "portable.marker") -Value "" -Encoding ascii $size = [math]::Round(((Get-ChildItem $outDir -Recurse | Measure-Object Length -Sum).Sum / 1MB), 1) Write-Host "[portable] $outDir ($size MB)" Get-ChildItem $outDir | ForEach-Object { " {0,-16} {1,10:N0}" -f $_.Name, $_.Length } if ($Zip) { $zipPath = Join-Path $root "dist-portable\Archive-$Arch.zip" if (Test-Path $zipPath) { Remove-Item $zipPath -Force } Compress-Archive -Path $outDir -DestinationPath $zipPath Write-Host "[portable] $zipPath" }