mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-25 02:30:15 -06:00
Retain the complete reviewed change, including prior integration resolutions, on current upstream. #736: deliver Thai UI fonts in desktop and web releases Use the merged craft-fonts input containing Noto Sans Thai across release, FreeBSD and Nix builds. Retain the small Thai face in the wasm subset and reject required release inputs that lack it. Exercise the reported Thai folder name through all UI families and include original font licence texts in Windows installers as well as portable packages. Refs #736; completes delivery of the fallback added in #750. Validation: UI tests, UI-only strict Clippy, 28 wasm checks, HEIF checks, assets, layers, parity and formatting pass. All 21 unrelated workspace failures reproduce with upstream Rust sources and the old font input; existing Rust 1.95 metadata/engine Clippy findings remain unchanged.
177 lines
9.5 KiB
PowerShell
177 lines
9.5 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Build, sign and package LightCraft for Windows.
|
|
|
|
.DESCRIPTION
|
|
Produces, in $env:DIST (default: dist/release):
|
|
lightcraft-<version>-windows-<arch>.msi per-machine installer (WiX v5)
|
|
lightcraft-<version>-windows-<arch>-portable.zip lightcraft.exe + lightcraft-cli.exe
|
|
|
|
The binaries link the C runtime statically (+crt-static), so neither the MSI nor the portable
|
|
zip needs the Visual C++ redistributable. Signing is delegated to sign.ps1 (skipped with a
|
|
warning when no signing secrets are set).
|
|
|
|
Needs: Rust (MSVC toolchain + the target), the Windows SDK (rc.exe, signtool.exe),
|
|
and WiX v5: dotnet tool install --global wix --version 5.0.2
|
|
The WiX UI and Util extensions (installer dialogs, "Launch LightCraft" on Finish) are added to
|
|
the global WiX extension cache by this script (wix extension add -g ..., needs network once).
|
|
|
|
.EXAMPLE
|
|
pwsh packaging/windows/package.ps1 -Arch x64
|
|
pwsh packaging/windows/package.ps1 -Arch x86 -SkipBuild
|
|
pwsh packaging/windows/package.ps1 -Arch arm64 # cross-compiled; needs the MSVC ARM64 build tools
|
|
#>
|
|
param(
|
|
[ValidateSet('x64', 'x86', 'arm64')] [string] $Arch = 'x64',
|
|
[switch] $SkipBuild
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
$Root = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
|
|
|
function Invoke-Native([string] $What, [scriptblock] $Block) {
|
|
Write-Output "==> $What"
|
|
& $Block
|
|
if ($LASTEXITCODE -ne 0) { throw "$What failed with exit code $LASTEXITCODE" }
|
|
}
|
|
|
|
# A 24-bit BMP of two flat colours: $SplitX pixels of $Left, then $Right (colours as R,G,B).
|
|
# Used for the installer's side/banner bitmaps so the MSI ships no third-party artwork.
|
|
function New-PanelBitmap([string] $Path, [int] $Width, [int] $Height, [int] $SplitX, [byte[]] $Left, [byte[]] $Right) {
|
|
$stride = [int][Math]::Ceiling($Width * 3 / 4) * 4
|
|
$row = New-Object byte[] $stride
|
|
for ($x = 0; $x -lt $Width; $x++) {
|
|
$c = if ($x -lt $SplitX) { $Left } else { $Right }
|
|
$row[$x * 3] = $c[2]; $row[$x * 3 + 1] = $c[1]; $row[$x * 3 + 2] = $c[0] # BMP pixels are B,G,R
|
|
}
|
|
$pixels = $stride * $Height
|
|
$ms = [IO.MemoryStream]::new()
|
|
$w = [IO.BinaryWriter]::new($ms)
|
|
$w.Write([byte]0x42); $w.Write([byte]0x4D) # 'BM'
|
|
$w.Write([uint32](54 + $pixels)); $w.Write([uint32]0); $w.Write([uint32]54)
|
|
$w.Write([uint32]40); $w.Write([int32]$Width); $w.Write([int32]$Height) # BITMAPINFOHEADER
|
|
$w.Write([uint16]1); $w.Write([uint16]24); $w.Write([uint32]0); $w.Write([uint32]$pixels)
|
|
$w.Write([int32]2835); $w.Write([int32]2835); $w.Write([uint32]0); $w.Write([uint32]0)
|
|
for ($y = 0; $y -lt $Height; $y++) { $w.Write($row) }
|
|
$w.Flush()
|
|
[IO.File]::WriteAllBytes($Path, $ms.ToArray())
|
|
$w.Dispose()
|
|
}
|
|
|
|
# The version lives in one place: [workspace.package] version in the root Cargo.toml.
|
|
$Version = $env:LIGHTCRAFT_VERSION
|
|
if (-not $Version) {
|
|
$inPkg = $false
|
|
foreach ($line in Get-Content (Join-Path $Root 'Cargo.toml')) {
|
|
if ($line -match '^\s*\[') { $inPkg = ($line.Trim() -eq '[workspace.package]'); continue }
|
|
if ($inPkg -and $line -match '^\s*version\s*=\s*"([^"]+)"') { $Version = $Matches[1]; break }
|
|
}
|
|
}
|
|
if (-not $Version) { throw 'could not read [workspace.package] version from Cargo.toml' }
|
|
# MSI ProductVersion is numeric (major.minor.build); pre-release tags are dropped there.
|
|
$MsiVersion = ($Version -split '-')[0]
|
|
|
|
$Target = switch ($Arch) { 'x64' { 'x86_64-pc-windows-msvc' } 'x86' { 'i686-pc-windows-msvc' } 'arm64' { 'aarch64-pc-windows-msvc' } }
|
|
$Dist = if ($env:DIST) { $env:DIST } else { Join-Path $Root 'dist\release' }
|
|
$TargetDir = if ($env:CARGO_TARGET_DIR) { $env:CARGO_TARGET_DIR } else { Join-Path $Root 'target' }
|
|
New-Item -ItemType Directory -Force -Path $Dist | Out-Null
|
|
|
|
if (-not $env:LIGHTCRAFT_BUILD_SHA) { $env:LIGHTCRAFT_BUILD_SHA = (git -C $Root rev-parse HEAD 2>$null) }
|
|
if (-not $env:LIGHTCRAFT_BUILD_DATE) { $env:LIGHTCRAFT_BUILD_DATE = (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd') }
|
|
|
|
Write-Output "LightCraft $Version for Windows $Arch ($Target)"
|
|
|
|
if (-not $SkipBuild) {
|
|
# Static CRT: no VC++ redistributable needed. Scoped to the target so host build scripts and
|
|
# proc-macros are unaffected.
|
|
$flagVar = 'CARGO_TARGET_' + ($Target.ToUpper() -replace '-', '_') + '_RUSTFLAGS'
|
|
[Environment]::SetEnvironmentVariable($flagVar, '-C target-feature=+crt-static')
|
|
# Fail the build (rather than warn) if the icon/VERSIONINFO can't be embedded.
|
|
$env:LIGHTCRAFT_REQUIRE_WINRES = '1'
|
|
Invoke-Native "cargo build ($Target)" { cargo build --release --locked -p lightcraft -p lightcraft-cli --features lightcraft/heif,lightcraft-cli/heif --target $Target }
|
|
}
|
|
|
|
$Bin = Join-Path $TargetDir "$Target\release"
|
|
|
|
# Check both binaries' PE headers before packaging. Machine (COFF header) must match -Arch, so an
|
|
# x64 build can never ship labelled arm64. Subsystem (optional header): 2 = Windows GUI, 3 = console.
|
|
# The app must be GUI (no console window opens with it); the CLI must stay console so its output
|
|
# reaches the terminal.
|
|
function Get-PeHeader([string] $Path) {
|
|
$bytes = [System.IO.File]::ReadAllBytes($Path)
|
|
$pe = [BitConverter]::ToInt32($bytes, 0x3C)
|
|
return @{ Machine = [BitConverter]::ToUInt16($bytes, $pe + 4); Subsystem = [BitConverter]::ToUInt16($bytes, $pe + 0x5C) }
|
|
}
|
|
$Machine = switch ($Arch) { 'x64' { 0x8664 } 'x86' { 0x14C } 'arm64' { 0xAA64 } }
|
|
foreach ($check in @(@('lightcraft.exe', 2), @('lightcraft-cli.exe', 3))) {
|
|
$h = Get-PeHeader (Join-Path $Bin $check[0])
|
|
if ($h.Machine -ne $Machine) { throw "$($check[0]) is for machine 0x$('{0:X}' -f $h.Machine), expected 0x$('{0:X}' -f $Machine) ($Arch)" }
|
|
if ($h.Subsystem -ne $check[1]) { throw "$($check[0]) has PE subsystem $($h.Subsystem), expected $($check[1])" }
|
|
Write-Output "ok $($check[0]): $Arch, PE subsystem $($h.Subsystem)"
|
|
}
|
|
$Stage = Join-Path $TargetDir "windows-package\$Arch"
|
|
Remove-Item -Recurse -Force $Stage -ErrorAction SilentlyContinue
|
|
New-Item -ItemType Directory -Force -Path $Stage | Out-Null
|
|
Copy-Item (Join-Path $Bin 'lightcraft.exe'), (Join-Path $Bin 'lightcraft-cli.exe') $Stage
|
|
# Include the embedded fonts' unmodified OFL texts in both installer and portable releases.
|
|
. (Join-Path $PSScriptRoot 'font-licences.ps1')
|
|
Write-FontLicences (Join-Path $Stage 'FONT-LICENCES.txt') (Join-Path $Root 'assets\fonts\OFL-Inter.txt') $env:CRAFT_FONTS_DIR
|
|
|
|
& (Join-Path $PSScriptRoot 'sign.ps1') (Join-Path $Stage 'lightcraft.exe') (Join-Path $Stage 'lightcraft-cli.exe')
|
|
|
|
# ---- MSI ---------------------------------------------------------------------------------------
|
|
# Extensions must match the WiX tool version (5.0.2, see release.yml). Re-adding is harmless.
|
|
$WixVersion = '5.0.2'
|
|
$WixExtensions = 'WixToolset.UI.wixext', 'WixToolset.Util.wixext'
|
|
foreach ($ext in $WixExtensions) {
|
|
Invoke-Native "wix extension add $ext/$WixVersion" { wix extension add -g "$ext/$WixVersion" }
|
|
}
|
|
$ExtArgs = foreach ($ext in $WixExtensions) { '-ext'; "$ext/$WixVersion" }
|
|
|
|
# Installer dialog bitmaps (WixUI sizes): a brand-blue side panel on the welcome/finish pages and
|
|
# a plain white banner on the inner pages. Generated here, never committed.
|
|
$UiDialogBmp = Join-Path $Stage 'ui-dialog.bmp'
|
|
$UiBannerBmp = Join-Path $Stage 'ui-banner.bmp'
|
|
New-PanelBitmap $UiDialogBmp 493 312 164 ([byte[]](0x4E, 0x7B, 0xFB)) ([byte[]](0xFF, 0xFF, 0xFF))
|
|
New-PanelBitmap $UiBannerBmp 493 58 0 ([byte[]](0xFF, 0xFF, 0xFF)) ([byte[]](0xFF, 0xFF, 0xFF))
|
|
|
|
$Msi = Join-Path $Dist "lightcraft-$Version-windows-$Arch.msi"
|
|
Invoke-Native 'wix build' {
|
|
wix build (Join-Path $PSScriptRoot 'lightcraft.wxs') -arch $Arch -culture en-us @ExtArgs `
|
|
-d "Version=$MsiVersion" -d "BinDir=$Stage" -d "IconPath=$(Join-Path $Root 'assets\app-icon\lightcraft.ico')" `
|
|
-d "UiDialogBmp=$UiDialogBmp" -d "UiBannerBmp=$UiBannerBmp" `
|
|
-o $Msi
|
|
}
|
|
# wix writes its debug symbols (.wixpdb) next to the MSI; keep them out of the release assets.
|
|
Remove-Item -Force -ErrorAction SilentlyContinue ([IO.Path]::ChangeExtension($Msi, '.wixpdb'))
|
|
& (Join-Path $PSScriptRoot 'sign.ps1') $Msi
|
|
|
|
# ---- portable zip ------------------------------------------------------------------------------
|
|
$Portable = Join-Path $TargetDir "windows-package\lightcraft-$Version-windows-$Arch-portable"
|
|
Remove-Item -Recurse -Force $Portable -ErrorAction SilentlyContinue
|
|
New-Item -ItemType Directory -Force -Path $Portable | Out-Null
|
|
Copy-Item (Join-Path $Stage '*.exe') $Portable
|
|
Copy-Item (Join-Path $Stage 'FONT-LICENCES.txt') $Portable
|
|
foreach ($f in 'README.md', 'LICENSE', 'LICENSE-MIT', 'LICENSE-APACHE', 'NOTICE') {
|
|
$p = Join-Path $Root $f
|
|
if (Test-Path $p) { Copy-Item $p $Portable }
|
|
}
|
|
# Builds made with CRAFT_FONTS_DIR (storytold/craft-fonts) embed its fonts: ship their OFL licences.
|
|
if ($env:CRAFT_FONTS_DIR) {
|
|
Get-ChildItem -Path (Join-Path $env:CRAFT_FONTS_DIR 'fonts\*\OFL.txt') -ErrorAction SilentlyContinue | ForEach-Object {
|
|
Copy-Item $_.FullName (Join-Path $Portable "OFL-$($_.Directory.Name).txt")
|
|
}
|
|
}
|
|
$Zip = Join-Path $Dist "lightcraft-$Version-windows-$Arch-portable.zip"
|
|
Remove-Item -Force $Zip -ErrorAction SilentlyContinue
|
|
Compress-Archive -Path $Portable -DestinationPath $Zip
|
|
|
|
# Smoke-test the CLI when this machine can run it. An ARM64 build made on an x64 runner can't run
|
|
# here; .github/workflows/windows-arm64.yml installs and runs it on ARM64 instead.
|
|
$HostArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()
|
|
if ($Arch -ne 'arm64' -or $HostArch -eq 'arm64') {
|
|
Invoke-Native 'lightcraft-cli --version' { & (Join-Path $Stage 'lightcraft-cli.exe') --version }
|
|
} else {
|
|
Write-Output "skipping lightcraft-cli --version: an $Arch build doesn't run on this $HostArch machine"
|
|
}
|
|
Get-Item $Msi, $Zip | Format-Table Name, Length
|