diff --git a/.github/scripts/bundle-windows.ps1 b/.github/scripts/bundle-windows.ps1 index 46bee01a..a1263f26 100644 --- a/.github/scripts/bundle-windows.ps1 +++ b/.github/scripts/bundle-windows.ps1 @@ -50,7 +50,7 @@ Copy-Item README.md "$Stage/README.md" # swallows a pane's OSC 11 background query (#345); this pair forwards it. The # two files are one unit, so a missing half is an error rather than a warning. # See assets/windows/conpty/README.md. -$ConptyArch = @{ 'x86_64-pc-windows-msvc' = 'x64' }[$Target] +$ConptyArch = @{ 'x86_64-pc-windows-msvc' = 'x64'; 'aarch64-pc-windows-msvc' = 'arm64' }[$Target] if (-not $ConptyArch) { throw "no vendored ConPTY for $Target - see assets/windows/conpty/README.md" } foreach ($File in 'conpty.dll', 'OpenConsole.exe') { $Source = "assets/windows/conpty/$ConptyArch/$File" @@ -68,9 +68,14 @@ Copy-Item "assets/windows/conpty/LICENSE.txt" "$Stage/LICENSE-ConPTY.txt" # naming the directories it searched. # # The *filename* is a contract too, not just the directory: `wsl.rs` looks for -# `/`, so this string has to stay whatever -# `install::asset::ASSET_X86_64` says it is. -$ServerAsset = "tty7-server-linux-x86_64-musl" +# `/`, so these strings have to stay whatever +# `install::asset::ASSET_X86_64` / `ASSET_AARCH64` say they are. A WSL distro +# runs the host's architecture, so each package carries the one that matches it. +$ServerAsset = @{ + 'x86_64-pc-windows-msvc' = 'tty7-server-linux-x86_64-musl' + 'aarch64-pc-windows-msvc' = 'tty7-server-linux-aarch64-musl' +}[$Target] +if (-not $ServerAsset) { throw "no bundled WSL server asset known for $Target" } $ServerSrc = "bundled-server/$ServerAsset" if (Test-Path $ServerSrc) { New-Item -ItemType Directory -Force -Path "$Stage/server" | Out-Null @@ -94,6 +99,15 @@ if ($PackageUpdater) { Set-Content -Path "$Stage/.tty7-inno-install" -Value 'inno-v1' -NoNewline -Encoding ascii } +# Inno's architecture identifier for this payload. `x64compatible` also admits +# ARM64 Windows, which runs the x64 build under emulation — so an ARM64 machine +# can take either installer, and both upgrade the same install (one AppId). +$InstallArch = @{ + 'x86_64-pc-windows-msvc' = 'x64compatible' + 'aarch64-pc-windows-msvc' = 'arm64' +}[$Target] +if (-not $InstallArch) { throw "no Inno architecture known for $Target" } + # Installer, built from the same staged payload. ISCC is on PATH on GitHub's # windows-latest image; fall back to the default install location. $Iscc = (Get-Command ISCC.exe -ErrorAction SilentlyContinue).Source @@ -104,6 +118,7 @@ if (-not $Iscc) { $Iscc = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" } "/DStageDir=$((Resolve-Path $Stage).Path)" ` "/DOutputDir=$((Resolve-Path dist).Path)" ` "/DOutputName=$Name-setup" ` + "/DArchitectures=$InstallArch" ` .github/scripts/windows-installer.iss if ($LASTEXITCODE -ne 0) { throw "ISCC exited with $LASTEXITCODE" } diff --git a/.github/scripts/windows-installer.iss b/.github/scripts/windows-installer.iss index 8c610b10..70afae0b 100644 --- a/.github/scripts/windows-installer.iss +++ b/.github/scripts/windows-installer.iss @@ -7,6 +7,7 @@ ; /DStageDir= staged payload (app, CLI, updater, marker, resources) ; /DOutputDir= where the setup exe is written ; /DOutputName= setup exe filename, without ".exe" +; /DArchitectures= Inno architecture: x64compatible or arm64 ; ; Defaults to a per-user install ({localappdata}\Programs\tty7 — no UAC ; prompt), with an "install for all users" escape hatch in the dialog. The @@ -19,6 +20,9 @@ #ifndef VersionInfoVersion #error Missing /DVersionInfoVersion — this script is meant to be compiled via bundle-windows.ps1 #endif +#ifndef Architectures + #error Missing /DArchitectures — this script is meant to be compiled via bundle-windows.ps1 +#endif [Setup] ; Never change AppId: it is how Windows ties upgrades + the uninstall entry @@ -35,8 +39,8 @@ DefaultDirName={autopf}\tty7 DisableProgramGroupPage=yes PrivilegesRequired=lowest PrivilegesRequiredOverridesAllowed=dialog -ArchitecturesAllowed=x64compatible -ArchitecturesInstallIn64BitMode=x64compatible +ArchitecturesAllowed={#Architectures} +ArchitecturesInstallIn64BitMode={#Architectures} MinVersion=10.0 LicenseFile={#StageDir}\LICENSE.txt SetupIconFile=..\..\assets\favicon.ico diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 341d973c..9bff9042 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -84,11 +84,23 @@ jobs: os: windows arch: x86_64 target: x86_64-pc-windows-msvc + wsl-server: tty7-server-linux-x86_64-musl + # Cross-compiled from the x64 runner, like `server-windows`'s ARM64 + # leg. Experimental until it has shipped once: a failure here drops + # the ARM64 packages from the release instead of holding it up, and + # ARM64 machines still have the x64 build under emulation. + - runner: windows-latest + os: windows + arch: arm64 + target: aarch64-pc-windows-msvc + wsl-server: tty7-server-linux-aarch64-musl + experimental: true - runner: ubuntu-latest os: linux arch: x86_64 target: x86_64-unknown-linux-gnu runs-on: ${{ matrix.runner }} + continue-on-error: ${{ matrix.experimental == true }} steps: - name: Checkout tty7 uses: actions/checkout@v4 @@ -121,6 +133,9 @@ jobs: - uses: Swatinem/rust-cache@v2 with: workspaces: tty7 + # Two Windows legs share a runner OS; without the target in the key + # they would overwrite each other's cache. + key: ${{ matrix.target }} # The updater ships on every GUI platform now: inside the macOS bundle, # beside the Windows app, and inside the Linux AppImage. @@ -200,7 +215,7 @@ jobs: continue-on-error: true uses: actions/download-artifact@v8 with: - name: nightly-tty7-server-linux-x86_64-musl + name: nightly-${{ matrix.wsl-server }} path: tty7/bundled-server - name: Package Windows installer + zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 384a72f1..fea9f31b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,11 +35,23 @@ jobs: os: windows arch: x86_64 target: x86_64-pc-windows-msvc + wsl-server: tty7-server-linux-x86_64-musl + # Cross-compiled from the x64 runner, like `server-windows`'s ARM64 + # leg. Experimental until it has shipped once: a failure here drops + # the ARM64 packages from the release instead of holding it up, and + # ARM64 machines still have the x64 build under emulation. + - runner: windows-latest + os: windows + arch: arm64 + target: aarch64-pc-windows-msvc + wsl-server: tty7-server-linux-aarch64-musl + experimental: true - runner: ubuntu-latest os: linux arch: x86_64 target: x86_64-unknown-linux-gnu runs-on: ${{ matrix.runner }} + continue-on-error: ${{ matrix.experimental == true }} steps: - name: Checkout tty7 uses: actions/checkout@v4 @@ -69,6 +81,9 @@ jobs: - uses: Swatinem/rust-cache@v2 with: workspaces: tty7 + # Two Windows legs share a runner OS; without the target in the key + # they would overwrite each other's cache. + key: ${{ matrix.target }} # `--locked` because a release must ship the dependency set the tag # recorded, not whatever cargo would re-resolve at build time. Safe here @@ -147,7 +162,7 @@ jobs: continue-on-error: true uses: actions/download-artifact@v8 with: - name: release-tty7-server-linux-x86_64-musl + name: release-${{ matrix.wsl-server }} path: tty7/bundled-server - name: Package Windows installer + zip @@ -388,9 +403,10 @@ jobs: # redistributable — which a headless server is the likeliest machine to lack. # Symbols are stripped through the profile instead. # - # ARM64 is new ground for this repository's Windows builds (the app itself - # ships x64 only), so its leg may fail without holding up the release; an - # ARM64 host then gets a clear "could not download" instead of a server. + # ARM64 is new ground for this repository's Windows builds (the app's own + # ARM64 leg in `build` is experimental too), so its leg may fail without + # holding up the release; an ARM64 host then gets a clear "could not + # download" instead of a server. server-windows: strategy: fail-fast: false diff --git a/assets/windows/conpty/README.md b/assets/windows/conpty/README.md index 9e9f6911..8492aa2e 100644 --- a/assets/windows/conpty/README.md +++ b/assets/windows/conpty/README.md @@ -46,8 +46,11 @@ curl.exe -sL -o conpty.zip "https://api.nuget.org/v3-flatcontainer/microsoft.win Expand-Archive conpty.zip -DestinationPath conpty-pkg Copy-Item conpty-pkg/runtimes/win-x64/native/conpty.dll x64/conpty.dll Copy-Item conpty-pkg/build/native/runtimes/x64/OpenConsole.exe x64/OpenConsole.exe +Copy-Item conpty-pkg/runtimes/win-arm64/native/conpty.dll arm64/conpty.dll +Copy-Item conpty-pkg/build/native/runtimes/arm64/OpenConsole.exe arm64/OpenConsole.exe ``` -Only `x86_64-pc-windows-msvc` is released today, so only `x64/` is vendored. The -package also carries `arm64` and `x86`; adding an architecture means adding the -directory here and one `Copy-Item` in `.github/scripts/bundle-windows.ps1`. +`x64/` and `arm64/` match the two released Windows targets +(`x86_64-pc-windows-msvc`, `aarch64-pc-windows-msvc`). The package also carries +`x86`; adding an architecture means adding the directory here, one arm in +`build.rs`, and one entry in `.github/scripts/bundle-windows.ps1`'s `$ConptyArch`. diff --git a/assets/windows/conpty/arm64/OpenConsole.exe b/assets/windows/conpty/arm64/OpenConsole.exe new file mode 100644 index 00000000..7693efea Binary files /dev/null and b/assets/windows/conpty/arm64/OpenConsole.exe differ diff --git a/assets/windows/conpty/arm64/conpty.dll b/assets/windows/conpty/arm64/conpty.dll new file mode 100644 index 00000000..369a2553 Binary files /dev/null and b/assets/windows/conpty/arm64/conpty.dll differ diff --git a/build.rs b/build.rs index 581d8394..71fadedf 100644 --- a/build.rs +++ b/build.rs @@ -31,8 +31,9 @@ fn stage_bundled_conpty() { let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); let vendored = match arch.as_str() { "x86_64" => "x64", - // Every other Windows architecture is unreleased, so nothing is - // vendored for it and the in-box conhost stays in charge. + "aarch64" => "arm64", + // 32-bit Windows is unreleased, so nothing is vendored for it and the + // in-box conhost stays in charge. _ => return, }; let source = PathBuf::from("assets/windows/conpty").join(vendored); diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx index f1e7c366..8efec900 100644 --- a/docs/getting-started/installation.mdx +++ b/docs/getting-started/installation.mdx @@ -27,12 +27,17 @@ AppImage bundles its own X11/Wayland/font libraries. - Two shapes, both x86-64: + Two shapes, each built for x86-64 (`x86_64`) and ARM64 (`arm64`, e.g. + Snapdragon laptops): | File | Use it when | |---|---| - | `tty7--windows-x86_64-setup.exe` | You want a normal install with Start-menu entries and an uninstaller. | - | `tty7--windows-x86_64.zip` | You want it portable — unzip anywhere and run `tty7-app.exe`. | + | `tty7--windows--setup.exe` | You want a normal install with Start-menu entries and an uninstaller. | + | `tty7--windows-.zip` | You want it portable — unzip anywhere and run `tty7-app.exe`. | + + The x86-64 build also runs on ARM64 Windows under emulation, but the + `arm64` one is native. Installing either over the other upgrades the same + install. The installer offers one optional setup task, off by default: **Add "Open in tty7" to the folder context menu**. It writes shell verbs under `HKCU`, so diff --git a/src/core/update.rs b/src/core/update.rs index 35316fd3..789d3cc5 100644 --- a/src/core/update.rs +++ b/src/core/update.rs @@ -2687,8 +2687,14 @@ impl WindowsUpdateLayout { #[cfg(target_os = "windows")] fn windows_package_for_layout(version: &str, layout: &WindowsUpdateLayout) -> Option { + // Labelled like the macOS packages (`arm64`), not like the server assets + // (`aarch64`): these are the desktop app's names. An x64 build running + // under emulation on ARM64 keeps taking x64 packages — switching it to + // the native build is the user's reinstall, not something an update does. let arch = if cfg!(target_arch = "x86_64") { "x86_64" + } else if cfg!(target_arch = "aarch64") { + "arm64" } else { return None; }; @@ -3732,12 +3738,20 @@ mod tests { assert_eq!( windows_package_for_layout("26.8.2", &WindowsUpdateLayout::Inno(directory.clone())) .as_deref(), - Some("tty7-26.8.2-windows-x86_64-setup.exe") + Some(if cfg!(target_arch = "aarch64") { + "tty7-26.8.2-windows-arm64-setup.exe" + } else { + "tty7-26.8.2-windows-x86_64-setup.exe" + }) ); assert_eq!( windows_package_for_layout("26.8.2", &WindowsUpdateLayout::Portable(directory)) .as_deref(), - Some("tty7-26.8.2-windows-x86_64.zip") + Some(if cfg!(target_arch = "aarch64") { + "tty7-26.8.2-windows-arm64.zip" + } else { + "tty7-26.8.2-windows-x86_64.zip" + }) ); }