From 7840e3c2b0c54c1d49bd2aa7c717892e8a3669e7 Mon Sep 17 00:00:00 2001 From: Max Vilimpoc Date: Sun, 6 Sep 2026 08:32:10 +0200 Subject: [PATCH 1/1] dotfiles: split provisioning between x64 and ARM64 The ARM64 machine is a small VM, and nearly everything it installed without a native build ran under Prism emulation. Each exception was defensible on its own; together they were a VM full of x64 binaries. So the two architectures now provision different things on purpose. x64 is the full workstation. ARM64 gets MSVC and nothing that duplicates it or exists only as an x64 binary: Brave, WinMerge, upstream LLVM, NASM, Android GPU Inspector, OpenCppCoverage, BinSkim, Visual Studio's clang-cl, and the WDK are all dropped there. :x64only in the batch file is the single place the policy is applied, so every skip prints its reason into the run log instead of leaving a silent gap. Two of those are dropped by choice rather than limitation, and the comments say so: a native ARM64 clang-cl is offered on Arm and worked here before this change. The WDK could not have installed anyway, which is worth recording. It requires the matching Windows SDK revision, and revisions are not side-by-side - Include\10.0.26100.0 is one directory whichever wrote it. VS 2026 installs SDK 26100.7705; linkid=2335869 is WDK 26100.6584, the kit paired with VS 2022. wdksetup.exe /quiet therefore aborts with exit 15605 and WER "WDK / 10.1.26100.6584 / Install / 0x80070642" - 0x642 is 1602, ERROR_INSTALL_USEREXIT, a silent install cancelling itself on a failed prerequisite. Nothing to do with Arm; the same pairing fails on x64. Fix the architecture detection, which this change makes load-bearing. Both sources the scripts trusted report the EMULATED architecture inside an emulated x64 process - which is what `powershell` resolves to when launched from an x64 shell on an ARM64 box. Measured there: [RuntimeInformation]::OSArchitecture X64 wrong %PROCESSOR_ARCHITECTURE% AMD64 wrong HKLM\...\Session Manager\Environment ARM64 right OSArchitecture is documented as the OS's architecture, and on .NET Core it is; under .NET Framework on Prism it is not. All three scripts now read the registry and keep the old sources as a fallback. This was a mislabelled log line before; now it decides which packages install at all. The batch parses reg.exe without piping to find/findstr - both are shadowable, and a POSIX find turns the detection into a silent no-op that leaves the emulated answer standing. Stop invoking `py`, which failed with "'py' is not recognized" for two independent reasons that look identical at the prompt. The session inherits its PATH before winget installs Python, so nothing winget adds is visible - the same staleness the DOTNET_EXE lookup already handles. And the launcher may not exist: on this box every other Python component registered while no launcher did, and the Launcher directory is on the user PATH but absent. Resolve an interpreter by full path instead, preferring the launcher only when it is real, and require python.exe inside the directory - a half-removed version leaves the folder behind with only Doc and Lib, which is what this box has. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Ak6VzfGyaocrykX1LhNMwt --- README.md | 172 ++++++++++++++++++++++--------- setup-windows-no-uac.ps1 | 85 ++++++++++++---- setup-windows-with-uac.ps1 | 146 +++++++++++++++++++-------- setup-windows.bat | 201 ++++++++++++++++++++++++++++--------- 4 files changed, 447 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 794657d..ed5d49d 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,16 @@ BinSkim, the Windows Performance Toolkit). It also sets the box up to be driven remotely: OpenSSH Server plus an rsync build for Windows, which is what makes a throwaway VM reachable from a Linux host. -Runs on **x64 and on ARM64** (Windows 11 on Arm). See -[Running on ARM64](#running-on-arm64) for what differs. +Runs on **x64 and on ARM64** (Windows 11 on Arm), and installs *different things* +on each: x64 is the full workstation, ARM64 a lean single-compiler build box. See +[Running on ARM64](#running-on-arm64) for what differs and why. ## Files | File | Purpose | | --- | --- | | `setup-windows.bat` | Entry point. Runs the winget installs, then launches the elevated half and prints its log, then runs the non-elevated script. | -| `setup-windows-no-uac.ps1` | The non-elevated, per-user half: WinMerge and BinSkim on the user `PATH`, the global git config (identity, plus `core.sshCommand`), the native `ninja` pinned ahead of the one Visual Studio bundles, and an architecture audit of everything provisioned. Can also be run directly from an ordinary prompt. | +| `setup-windows-no-uac.ps1` | The non-elevated, per-user half: WinMerge, BinSkim and upstream LLVM on the user `PATH` (x64 only), the global git config (identity, plus `core.sshCommand`), the native `ninja` pinned ahead of the one Visual Studio bundles, and an architecture audit of everything provisioned. Can also be run directly from an ordinary prompt. | | `setup-windows-with-uac.ps1` | The elevated half, started via UAC by the batch file. Enables `ssh-agent`, installs the OpenSSH Client and Server capabilities and starts `sshd`, unpacks the `rsync-windows` release zip for this architecture (`rsync.exe` plus the `ssh.exe` it runs) into `C:\Tools\rsync` on the machine `PATH`, then installs Visual Studio Community with the required components, the WDK, and the Windows Performance Toolkit, and reports whether Intel VTune Profiler is present. Can also be run directly from an Administrator prompt. | | `setup-windows-7-test-env.bat` | Prepares a **Windows 7 VM** as a test target driven from the host by `VBoxManage guestcontrol`. Copy it into the guest and run it there; it is idempotent, so re-run it after any snapshot restore. The per-user half needs no UAC (crash-dialog suppression, no screen blanking, a staging directory, the shared folder on `Z:`); the machine-wide half is skipped with a notice unless run elevated inside the guest. It then reports what the box can actually test: DWM composition, printers, audio capture devices. | @@ -122,8 +123,9 @@ Runs on **x64 and on ARM64** (Windows 11 on Arm). See a box on a known build, pin the tag in `$RsyncUrl` (`.../releases/download//`) instead. - Visual Studio is installed in three labelled passes (base workload, Clang/LLVM, - XP toolset) so a failure identifies which component group is responsible. The - last two are optional passes: a failure there warns and provisioning continues, + XP toolset) so a failure identifies which component group is responsible. On + ARM64 only the first runs — the other two are skipped there, each printing why. + The last two are optional passes: a failure there warns and provisioning continues, because neither is needed to build with MSVC and losing the whole toolchain over a component you can add later from the installer UI is the worse outcome. - **Which Visual Studio generation.** `$VsChannel` near the VS step picks it — @@ -238,14 +240,41 @@ Runs on **x64 and on ARM64** (Windows 11 on Arm). See ## Running on ARM64 All three scripts detect the host architecture and adapt. Nothing needs a flag — -run `setup-windows.bat` exactly as on x64. Both PowerShell halves use -`RuntimeInformation.OSArchitecture` rather than `%PROCESSOR_ARCHITECTURE%`, -because an emulated PowerShell reports the *emulated* architecture in the -environment variable and the real one through the API. +run `setup-windows.bat` exactly as on x64. -**The build toolchain is fully native.** Visual Studio's ARM64 installer, MSVC -and clang-cl are all ARM64 binaries, and MSVC cross-compiles every target from -an ARM64 host with no emulation in the compiler: +**The two architectures deliberately install different things.** x64 is the full +workstation; ARM64 is provisioned as a lean, single-compiler build box. See +[ARM64 installs less, on purpose](#arm64-installs-less-on-purpose) below for the +list and the reasoning. + +### Detecting the architecture + +All three scripts read the **machine-level registry value**, not the environment +and not `RuntimeInformation.OSArchitecture`. Both of those report the *emulated* +architecture inside an emulated x64 process — which is what you get by launching +`powershell` or `cmd` from an x64 shell (Git Bash, for one) on an ARM64 box. +Measured on this machine, from that emulated shell: + +| source | reports | +| --- | --- | +| `[RuntimeInformation]::OSArchitecture` | `X64` ✗ | +| `%PROCESSOR_ARCHITECTURE%` | `AMD64` ✗ | +| `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment` | `ARM64` ✓ | + +`OSArchitecture` is documented as the *OS's* architecture, and on .NET Core it +is; under .NET Framework on Prism it is not. It is kept only as a fallback. This +used to be a cosmetic mislabel in a log line — now that the architecture decides +which packages are installed at all, reading it wrong provisions the wrong +machine. + +> The batch file calls `reg.exe` by full path and parses it **without** piping to +> `find`/`findstr`. Both are shadowable by anything earlier on the `PATH`, and a +> POSIX `find` turns the whole detection into a silent no-op that leaves the +> emulated answer standing. That is not hypothetical; it is how this was found. + +**The build toolchain is fully native.** Visual Studio's ARM64 installer and MSVC +are ARM64 binaries, and MSVC cross-compiles every target from an ARM64 host with +no emulation in the compiler: ```bat cmake -B build -A ARM64 && cmake --build build --config Release :: native @@ -266,24 +295,36 @@ ARM64-targeting toolchain, and the x64 cross-compiler is *not* implied. Spectre runtimes and ATL are likewise requested for both target families (`VC.ATL.ARM64.Spectre` is a separate component from `VC.ATL.Spectre`). -**Three native compilers, on purpose.** MSVC and `clang-cl` from Visual Studio, -plus **upstream LLVM** (`winget install LLVM.LLVM`, which resolves to -`LLVM--woa64.exe` on ARM64 — "Windows on Arm 64"). Upstream runs on its own -schedule and is usually several major versions ahead of the Clang that VS -bundles, and it installs to `C:\Program Files\LLVM` rather than inside the VS -tree, so the two are genuinely independent. Two Clang majors plus MSVC over the -same sources is what catches the bugs a single toolchain agrees with itself -about. Watch the `PATH` if you use it: a bare `clang-cl` may resolve to upstream -while CMake's `-T ClangCL` keeps using VS's, so pass a full -`-DCMAKE_CXX_COMPILER` when it matters. - -**Clang/LLVM is installed on ARM64 too, and it is native there** — MSVC and -`clang-cl` over the same sources is the compiler diversity this box is after. -Two things establish that it is genuinely native rather than an emulated x64 -compiler: the `VC.Llvm.Clang` VSIX is `productArch=neutral` with no `chip` or +### ARM64 installs less, on purpose + +The ARM64 machine here is a constrained VM, and nearly everything it was +installing without a native build was going to run under Prism emulation anyway. +The exceptions were individually defensible and collectively a VM full of x64 +binaries. So the split is now explicit: **ARM64 gets MSVC and nothing that +duplicates it or that exists only as an x64 binary.** + +| dropped on ARM64 | where | why | +| --- | --- | --- | +| `Brave.Brave` | `setup-windows.bat` | no ARM64 build; an emulated browser is the slowest thing on the box, and Edge is in-box and native | +| `WinMerge.WinMerge` | `setup-windows.bat` | ARM64 ships as a **machine-scope** installer only, so an unelevated `winget` can install nothing but the emulated x64 build | +| `LLVM.LLVM` | `setup-windows.bat` | second Clang, dropped along with VS's `clang-cl` | +| `NASM.NASM` | `setup-windows.bat` | x86/x86-64 assembler with no ARM64 target; MSVC's `armasm64.exe` is the native one | +| `Google.AndroidGPUInspector` | `setup-windows.bat` | x64-only, and it profiles Android devices rather than anything compiled here | +| `OpenCppCoverage` | `setup-windows.bat` | collects coverage by stepping x86/x64 instructions, so it cannot instrument an ARM64 binary at all | +| VS Clang/LLVM component | `setup-windows-with-uac.ps1` | see below | +| Windows Driver Kit | `setup-windows-with-uac.ps1` | see below | +| BinSkim | `setup-windows-no-uac.ps1` | the NuGet package publishes `win-x64` only | + +x64 keeps every one of them. `:x64only` at the bottom of `setup-windows.bat` is +the single place that decision is applied, so each skip prints its reason into +the run log rather than leaving a silent gap. + +**`clang-cl` is dropped by choice, not by limitation.** A native ARM64 `clang-cl` +*is* offered on Arm and did work here before the split. Two things establish +that: the `VC.Llvm.Clang` VSIX is `productArch=neutral` with no `chip` or `machineArch` restriction, so the Arm installer offers it; and MSVC's `VC\Tools\Llvm` tree is partitioned by **host** architecture, with real ARM64 -binaries already in `ARM64\bin`, which is where `clang-cl.exe` lands: +binaries in `ARM64\bin`, which is where `clang-cl.exe` lands: | directory | host | | --- | --- | @@ -291,23 +332,54 @@ binaries already in `ARM64\bin`, which is where `clang-cl.exe` lands: | `VC\Tools\Llvm\x64\bin` | x64 | | `VC\Tools\Llvm\ARM64\bin` | ARM64 | -Unlike the `v141`/XP group, nothing technical is in the way. Upstream LLVM ships -a Windows-on-Arm build independently too — `winget install LLVM.LLVM` resolves to -`LLVM--woa64.exe` on ARM64 — if you want a Clang outside Visual Studio. - > Watch for a false positive when checking by hand: `VC\Tools\Llvm\*\bin` holds > `clang-format.exe` and `clang-tidy.exe` on **any** host, installed or not — > those ship with the NativeDesktop workload. Their presence does not mean the -> compiler is there; look for `clang-cl.exe`. The verification step does exactly -> that, and warns separately if only a non-native `clang-cl` landed on Arm. +> compiler is there; look for `clang-cl.exe`. + +Note that the VS installer never uninstalls a component you simply stopped +asking for, so a box provisioned before this split keeps its `clang-cl` until it +is explicitly removed. The verification step says so and prints the +`--remove` line. + +**The WDK is dropped for two reasons, and the second one bites on x64 too.** The +ARM64 box builds user-mode software, so it does not need a driver kit — but this +particular WDK *cannot* install here regardless. The kit requires the **matching +Windows SDK revision**, and revisions are not side-by-side: `Include\10.0.26100.0` +is one directory, whichever revision wrote it. VS 2026 installs SDK +**26100.7705**; `linkid=2335869` is WDK **26100.6584**, the kit paired with VS +2022. `wdksetup.exe /quiet` therefore aborts: + +``` +WDK installer exit code: 15605 +WER: WDK / 10.1.26100.6584 / Install / 0x80070642 +``` + +`0x642` is `1602`, `ERROR_INSTALL_USEREXIT` — a silent install cancelling itself +on a failed prerequisite check. Nothing to do with Arm or emulation; the same +pairing fails on x64. If a driver ever does need building against VS 2026, use +the kit Microsoft's [supported WDK downloads](https://learn.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads) +table pairs with it — the 28000.x one — not this link. x64 with VS 2022 has the +matched pair, so it installs there. + +**Compiler diversity now lives on the x64 box**, which has room for three +toolchains: MSVC, VS's `clang-cl`, and **upstream LLVM** (`winget install +LLVM.LLVM`). Upstream runs on its own schedule, is usually several major versions +ahead of the Clang VS bundles, and installs to `C:\Program Files\LLVM` rather +than inside the VS tree, so the two are genuinely independent. Two Clang majors +plus MSVC over the same sources is what catches the bugs a single toolchain +agrees with itself about. Watch the `PATH` there: a bare `clang-cl` may resolve +to upstream while CMake's `-T ClangCL` keeps using VS's, so pass a full +`-DCMAKE_CXX_COMPILER` when it matters. -What changes, and why: +What you end up with on ARM64: | | On ARM64 | | --- | --- | -| **Native ARM64** | Git, Python, .NET SDK, PowerShell, VS Code, Windows Terminal, WinMerge, Brave, Sysinternals, Claude Code, CMake, Ninja, the VS installer, **MSVC, VS clang-cl and upstream LLVM**, MSBuild, the SDK tools (`rc`, `signtool`), the in-box OpenSSH client, and the Windows Performance Toolkit. | -| **Emulated x64** | `iperf3`, NASM, OpenCppCoverage, BinSkim, `rsync.exe`, Android GPU Inspector, and the WDK/ADK *installers* (the kits they lay down are native). | +| **Native ARM64** | Git, Python, .NET SDK, PowerShell, VS Code, Windows Terminal, Sysinternals, Claude Code, CMake, Ninja, the VS installer, **MSVC**, MSBuild, the SDK tools (`rc`, `signtool`), the in-box OpenSSH client, and the Windows Performance Toolkit. | +| **Emulated x64** | `iperf3` and `rsync.exe` — both network-bound — and the ADK *installer* (the toolkit it lays down is native). | | **Emulated, minor** | `py.exe` (python.org ships the launcher shim as x86; it execs the native `python.exe`) and `vswhere.exe` (Microsoft ships x86 only; runs once). | +| **Not installed** | Brave, WinMerge, upstream LLVM, NASM, Android GPU Inspector, OpenCppCoverage, BinSkim, VS `clang-cl`, and the WDK — see the table above. | | **Unavailable** | VirtualBox, the Windows 7 x86 test VM, Intel VTune, and the `v141` / Windows XP targeting toolset. | ### The architecture audit @@ -426,18 +498,20 @@ Two consequences worth knowing rather than rediscovering: Windows XP never ran on ARM64, so the only point of an XP-targeting build here would be producing x86 binaries, which the current toolset does natively via `-A Win32`. -- **OpenCppCoverage is installed but limited.** Unlike the rest of the emulated - row, this is a real ceiling rather than a slowdown: it collects coverage by - debugging the process under test and stepping x86/x64 instructions, so it - covers the x86/x64 binaries this box cross-compiles but **not** an ARM64 one. - Run coverage against the x64 build. The script prints this rather than leaving - you to discover it. -- **BinSkim is emulated and that is fine.** The NuGet package publishes `win-x64` - only (its other RIDs are `linux-x64`, `linux-arm64`, `osx-x64`). BinSkim *reads* - PE headers and load configs, so its own architecture is independent of the - binaries it analyses — an emulated x64 BinSkim checks ARM64 binaries perfectly - well. The RID list is ordered preference, so a future `win-arm64` build is - picked up with no further change. +- **OpenCppCoverage is no longer installed on ARM64.** Unlike the rest of the + emulated row this was a real ceiling rather than a slowdown: it collects + coverage by debugging the process under test and stepping x86/x64 + instructions, so it can cover cross-compiled x86/x64 binaries but **not** an + ARM64 one. A coverage tool that cannot cover this machine's own output belongs + on the x64 box, which is where it still installs. Run coverage there. +- **BinSkim is no longer installed on ARM64 either**, though the reason is + weaker: the NuGet package publishes `win-x64` only (its other RIDs are + `linux-x64`, `linux-arm64`, `osx-x64`), but BinSkim *reads* PE headers and load + configs, so its own architecture is independent of the binaries it analyses — + an emulated x64 BinSkim checks ARM64 binaries perfectly well. It went with the + rest of the x64-only set to keep the rule simple, not because it was broken. + The RID list is still ordered preference on x64, so a future `win-arm64` build + would be picked up with no further change. - **`rsync` gets the x64 zip, and its `ssh.exe` is verified by running it.** The release publishes x64 and x86 assets and no ARM64 one. x64 is still the right pick — a transfer is bounded by the socket, not by emulated CPU, so lifting the diff --git a/setup-windows-no-uac.ps1 b/setup-windows-no-uac.ps1 index 1fd2c1f..f0679f1 100644 --- a/setup-windows-no-uac.ps1 +++ b/setup-windows-no-uac.ps1 @@ -11,15 +11,13 @@ elevated would configure the *administrator's* profile instead of yours. What this installs / configures: - - WinMerge on the user PATH + - WinMerge on the user PATH (x64 only) - vswhere.exe on the user PATH: the Visual Studio installer puts it in %ProgramFiles(x86)%\Microsoft Visual Studio\Installer, which nothing adds to the PATH, so build scripts (and VsDevCmd.bat itself) complain that 'vswhere.exe' is not recognized - BinSkim (binary hardening analyzer) in %LOCALAPPDATA%\Programs\BinSkim, - on the user PATH. The package has no win-arm64 build, so on ARM64 this is - the x64 tool under emulation - which analyses ARM64 binaries fine, since it - only reads their headers. + on the user PATH (x64 only - the NuGet package publishes win-x64 alone) - Global git identity, and core.sshCommand pointed at a Win32-OpenSSH client so git shares the Windows ssh-agent: the fast ssh.exe the elevated half unpacks beside rsync.exe if it is there, the in-box one otherwise @@ -46,11 +44,36 @@ param( $ErrorActionPreference = 'Stop' -# Host architecture. RuntimeInformation rather than PROCESSOR_ARCHITECTURE: an -# emulated PowerShell reports the emulated architecture in the environment -# variable while this API reports the real one. Values: X64, Arm64, X86. -$HostArch = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() -$IsArm64 = ($HostArch -eq 'Arm64') +# Host architecture, from the machine environment in the registry. Both of the +# obvious sources report the EMULATED architecture inside an emulated x64 +# PowerShell - which is what `powershell` resolves to when launched from an x64 +# shell on an ARM64 box. Measured on this machine: +# [RuntimeInformation]::OSArchitecture X64 <- wrong +# $env:PROCESSOR_ARCHITECTURE AMD64 <- wrong +# HKLM\...\Session Manager\Environment ARM64 <- right +# OSArchitecture is documented as the OS's architecture, and on .NET Core it is; +# under .NET Framework on Prism it is not, so it is kept only as a fallback. +# $IsArm64 decides which steps run at all, so this has to be the real one. +$rawArch = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -ErrorAction SilentlyContinue).PROCESSOR_ARCHITECTURE +if (-not $rawArch) { $rawArch = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() } +$IsArm64 = ($rawArch -eq 'ARM64') +$HostArch = if ($IsArm64) { 'Arm64' } elseif ($rawArch -eq 'AMD64') { 'X64' } else { $rawArch } + +# Steps that do not run on ARM64, with the reason printed in place of the step. +# +# The ARM64 machine is provisioned as a single-compiler build box - see the +# x64-only set in setup-windows.bat - so the packages these three steps wire up +# are not installed there. Skipping the step rather than letting it find nothing +# matters: Install-BinSkim would happily download and PATH the x64 build, and +# Add-LlvmToUserPath is the step that put an emulated-adjacent toolchain on the +# PATH ahead of MSVC in the first place. +# +# -Skip still works on top of this; it can only subtract. +$Arm64Dropped = [ordered]@{ + WinMerge = 'not installed on ARM64 - the only build an unelevated winget can fetch is the emulated x64 one' + BinSkim = 'not installed on ARM64 - the NuGet package publishes win-x64 only' + LlvmPath = 'not installed on ARM64 - this box carries MSVC alone' +} # --- Global git identity: FILL THESE IN BEFORE RUNNING --- # Left empty, Set-GlobalGitConfig skips the identity and says so, rather than @@ -538,11 +561,13 @@ function Invoke-ArchAudit { # Tools with no native ARM64 build available anywhere, with the reason. These # print as expected rather than as problems - see the README's ARM64 section. + # + # Shorter than it was: BinSkim, OpenCppCoverage and NASM used to be listed + # here as accepted emulation, and are now simply not installed on ARM64. That + # is the whole shape of the change - the exceptions that were tolerable one at + # a time added up to a VM full of x64 binaries. $KnownEmulated = @{ - 'BinSkim.exe' = 'NuGet package publishes win-x64 only; it reads PE headers, so it still analyses ARM64 binaries' 'rsync.exe' = 'no ARM64 asset published; transfer is socket-bound, not CPU-bound' - 'OpenCppCoverage.exe' = 'x86/x64 only, and cannot instrument ARM64 binaries - run coverage against the x64 build' - 'nasm.exe' = 'x86/x86-64 assembler by definition; ARM64 uses armasm64.exe from MSVC' 'iperf3.exe' = 'no ARM64 build published; network-bound anyway' 'py.exe' = 'python.org ships the launcher shim as x86; it execs the native python.exe' 'vswhere.exe' = 'Microsoft ships x86 only; runs once per script' @@ -556,11 +581,14 @@ function Invoke-ArchAudit { # even on ARM64, and it is re-invoked for every edge in the build graph, so # an emulated one is paid over and over rather than once. The NinjaPath step # puts the native copy first; this is the check that it worked. + # + # Only two left, and both are for tools ARM64 still installs. The clang-cl and + # WinMerge remedies were removed with their rows: advising an install that the + # x64-only set has just declined to do would be the audit arguing with the + # provisioning. $Remedies = @{ - 'ninja.exe' = 'Visual Studio bundles an x64 ninja. Install the native one (winget install Ninja-build.Ninja) and re-run - the NinjaPath step puts its directory at the front of the user PATH.' - 'cmake.exe' = 'Install the native build with: winget install Kitware.CMake (its MSI is machine-scope, so it needs an administrator).' - 'clang-cl.exe' = 'Install the upstream native LLVM with: winget install LLVM.LLVM' - 'WinMergeU.exe' = 'Upstream ships ARM64 as a MACHINE-scope installer and x64 as per-user, so an unelevated winget picks x64. Re-run as an administrator to get: winget install WinMerge.WinMerge --architecture arm64' + 'ninja.exe' = 'Visual Studio bundles an x64 ninja. Install the native one (winget install Ninja-build.Ninja) and re-run - the NinjaPath step puts its directory at the front of the user PATH.' + 'cmake.exe' = 'Install the native build with: winget install Kitware.CMake (its MSI is machine-scope, so it needs an administrator).' } $vs = $null @@ -573,7 +601,11 @@ function Invoke-ArchAudit { $targets = [ordered]@{ 'git.exe' = @("$env:LOCALAPPDATA\Programs\Git\cmd\git.exe", (Join-Path $env:ProgramFiles 'Git\cmd\git.exe')) 'python.exe' = @() - 'py.exe' = @("$env:WINDIR\py.exe") + # Both homes of the launcher: C:\Windows for an all-users install, the + # per-user Launcher directory otherwise. Often neither - it is a separate + # component from the interpreter, and "- not installed" here next to a + # native python.exe is the normal result of an unelevated Python install. + 'py.exe' = @("$env:WINDIR\py.exe", "$env:LOCALAPPDATA\Programs\Python\Launcher\py.exe") 'pwsh.exe' = @() 'cmake.exe' = @((Join-Path $env:ProgramFiles 'CMake\bin\cmake.exe')) 'ninja.exe' = @() @@ -608,6 +640,21 @@ function Invoke-ArchAudit { $targets['clang-cl.exe (VS)'] = @((Join-Path $vs "VC\Tools\Llvm\$llvmHost\clang-cl.exe")) } + # Tools this box does not provision on ARM64. Dropped from the audit rather + # than left to print "- not installed", because that line reads as a gap in + # the provisioning when it is the provisioning working as intended - and a + # dull audit is one you keep reading. + # + # The rows survive on x64, where all of these are installed and expected. + # Anything still on disk from before the split shows up in the winget list, + # not here. + if ($IsArm64) { + foreach ($gone in 'WinMergeU.exe', 'BinSkim.exe', 'nasm.exe', + 'OpenCppCoverage.exe', 'clang-cl.exe', 'clang-cl.exe (VS)') { + $targets.Remove($gone) + } + } + $native = if ($IsArm64) { 'ARM64' } else { 'x64' } $unexpected = @() foreach ($name in $targets.Keys) { @@ -679,6 +726,10 @@ foreach ($name in $steps.Keys) { Write-Host "`n==> $name (skipped)" -ForegroundColor DarkGray continue } + if ($IsArm64 -and $Arm64Dropped.Contains($name)) { + Write-Host "`n==> $name (skipped: $($Arm64Dropped[$name]))" -ForegroundColor DarkGray + continue + } try { & $steps[$name] } catch { diff --git a/setup-windows-with-uac.ps1 b/setup-windows-with-uac.ps1 index c935e2d..52384d4 100644 --- a/setup-windows-with-uac.ps1 +++ b/setup-windows-with-uac.ps1 @@ -14,10 +14,10 @@ architecture (there is no ARM64 zip; ARM64 gets the x64 one, and the ssh.exe is kept only if it actually starts) - Visual Studio Community (C++ desktop workload, x86/x64 AND ARM64 build - tools, Spectre libs, WDK VSIX, Win11 SDK 26100, Clang/LLVM, and - on x64 - only - the v141 + Windows XP targeting toolset). VS 2022 on x64, VS 2026 - on ARM64; see $VsChannel. - - Windows Driver Kit 10.0.26100 + tools, Spectre libs, Win11 SDK 26100, and - on x64 only - the WDK VSIX, + Clang/LLVM, and the v141 + Windows XP targeting toolset). VS 2022 on x64, + VS 2026 on ARM64; see $VsChannel. + - Windows Driver Kit 10.0.26100 (x64 only) - Windows Performance Toolkit - xperf, wpr and Windows Performance Analyzer (wpa.exe) - on the machine PATH @@ -27,9 +27,14 @@ 18 (VS 2026) on ARM64, where that toolset cannot exist anyway. ARCHITECTURE. Runs on x64 and on ARM64 (Windows 11 on Arm). On ARM64 the - Visual Studio installer, MSVC and clang-cl are all native ARM64 and - cross-compile ARM64/x64/x86 targets; what changes is called out at each step, - and $HostArch below is what drives it. + Visual Studio installer and MSVC are native ARM64 and cross-compile + ARM64/x64/x86 targets. + + The two architectures deliberately provision DIFFERENT things. x64 is the full + workstation; ARM64 is a constrained VM set up as a single-compiler build box - + MSVC only, with clang-cl and the WDK dropped there. Neither is dropped for lack + of an ARM64 build; see the comments on $ClangComponents and the WDK step. + What changes is called out at each step, and $HostArch below is what drives it. #> $ErrorActionPreference = 'Stop' @@ -37,22 +42,30 @@ $ErrorActionPreference = 'Stop' # --------------------------------------------------------------------------- # Host architecture # -# RuntimeInformation.OSArchitecture rather than PROCESSOR_ARCHITECTURE: this -# script can be launched by a 32-bit or an emulated x64 PowerShell, either of -# which reports the emulated architecture in the environment variable while this -# API still reports the real one. Values seen here: X64, Arm64, X86. +# Read from the machine environment in the registry, because this script can be +# launched by a 32-bit or an emulated x64 PowerShell and BOTH of the obvious +# sources report the emulated architecture in that case. Measured on this box, +# from the x64 PowerShell an x64 shell resolves to: +# [RuntimeInformation]::OSArchitecture X64 <- wrong +# $env:PROCESSOR_ARCHITECTURE AMD64 <- wrong +# HKLM\...\Session Manager\Environment ARM64 <- right +# OSArchitecture is documented as the OS's architecture, and on .NET Core it is; +# under .NET Framework on Prism it is not, so it is kept only as a fallback. # # $IsArm64 gates: # - which rsync-windows release zip is fetched (there is no ARM64 one) # - which Visual Studio generation is driven ($VsChannel: 18 on Arm, 17 on x64) -# - the Visual Studio component groups (ARM64 build tools in; the v141/XP -# toolset out, because it has no ARM64-hosted compiler and Microsoft does not -# ship Windows XP targeting for Arm hosts. Clang/LLVM is installed on every -# architecture and is native on Arm, so it is NOT gated here) +# - the Visual Studio component groups: ARM64 build tools in; the v141/XP +# toolset out because it has no ARM64-hosted compiler and Microsoft does not +# ship Windows XP targeting for Arm hosts; Clang/LLVM and the WDK VSIX out by +# policy rather than by limitation - both are available and native on Arm +# - whether the Windows Driver Kit is installed at all # - what the VTune and WPT steps report # --------------------------------------------------------------------------- -$HostArch = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() -$IsArm64 = ($HostArch -eq 'Arm64') +$rawArch = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -ErrorAction SilentlyContinue).PROCESSOR_ARCHITECTURE +if (-not $rawArch) { $rawArch = [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() } +$IsArm64 = ($rawArch -eq 'ARM64') +$HostArch = if ($IsArm64) { 'Arm64' } elseif ($rawArch -eq 'AMD64') { 'X64' } else { $rawArch } function Write-Step([string]$Msg) { Write-Host "`n==> $Msg" -ForegroundColor Cyan @@ -501,29 +514,39 @@ $BaseComponents = @( # Windows 11 SDK — build number must match the WDK below 'Microsoft.VisualStudio.Component.Windows11SDK.26100' - - # WDK Visual Studio extension (VSIX). The silent wdksetup.exe /quiet does NOT - # install this (it only prompts interactively), so it must be added here. - 'Component.Microsoft.Windows.DriverKit' ) +# WDK Visual Studio extension (VSIX). The silent wdksetup.exe /quiet does NOT +# install this (it only prompts interactively), so it has to be asked for here. +# +# Follows the WDK itself: x64 only. On ARM64 the kit is not installed (see the +# WDK step for why), and this VSIX on its own is just the driver project +# templates and property pages with no headers, libs or tools behind them. +if (-not $IsArm64) { + $BaseComponents += 'Component.Microsoft.Windows.DriverKit' +} + # Clang/LLVM toolset (ClangCL, used in CMakePresets.json). Two parts: the Clang # compiler itself, plus the MSBuild integration providing the "ClangCL" toolset. # -# Installed on every architecture, ARM64 included, and native there - not an -# emulated x64 compiler. Two things establish that: the VSIX is +# x64 ONLY, and NOT because ARM64 cannot have it - it can, natively. The VSIX is # productArch=neutral with no chip/machineArch restriction, so the Arm installer -# offers it; and MSVC's VC\Tools\Llvm tree is partitioned by HOST architecture -# (bin = x86, x64\bin = x64, ARM64\bin = ARM64) with genuine ARM64 binaries -# already in ARM64\bin, which is where clang-cl.exe lands. Unlike the v141/XP -# group below, nothing technical is in the way. +# offers it, and MSVC's VC\Tools\Llvm tree is partitioned by HOST architecture +# (bin = x86, x64\bin = x64, ARM64\bin = ARM64) with genuine ARM64 binaries in +# ARM64\bin, which is where clang-cl.exe lands. It was installed and working here +# before this split. # -# This is the compiler diversity the box is after: MSVC and clang-cl over the -# same sources, both native. -$ClangComponents = @( - 'Microsoft.VisualStudio.Component.VC.Llvm.Clang' - 'Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset' -) +# It is dropped on ARM64 because that machine is a constrained VM provisioned as +# a single-compiler build box: MSVC and nothing else. Compiler diversity - MSVC, +# VS clang-cl and upstream LLVM over the same sources - now lives entirely on the +# x64 machine, which is the one with room for three toolchains. Empty here means +# Invoke-VsModify is never called for this group. +$ClangComponents = if ($IsArm64) { @() } else { + @( + 'Microsoft.VisualStudio.Component.VC.Llvm.Clang' + 'Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset' + ) +} # Windows XP targeting (v141_xp toolset, used in CMakePresets.json). The v141 # (VS2017) build tools provide the 14.16 compiler that the XP toolset wraps; @@ -619,7 +642,14 @@ Write-Host " OK: bootstrapper is a PE ($([math]::Round((Get-Item $VsBootstrap # offered a hard failure here would cost you the whole toolchain over a component # you can add later from the installer UI. Invoke-VsModify -Label 'base toolset + workload' -Ids $BaseComponents -Invoke-VsModify -Label 'Clang / LLVM' -Ids $ClangComponents -Optional +if ($IsArm64) { + Write-Step 'Visual Studio: Clang / LLVM' + Write-Host ' Skipped on ARM64 by choice, not by limitation: a native ARM64 clang-cl is' -ForegroundColor Yellow + Write-Host ' offered here and works. This box is provisioned with MSVC alone; the' -ForegroundColor Yellow + Write-Host ' multi-compiler builds belong on the x64 machine.' -ForegroundColor Yellow +} else { + Invoke-VsModify -Label 'Clang / LLVM' -Ids $ClangComponents -Optional +} if ($IsArm64) { Write-Step 'Visual Studio: Windows XP (v141 + WinXP)' Write-Host ' Skipped on ARM64: the v141 (14.16) toolset ships x86/x64-hosted compilers only,' -ForegroundColor Yellow @@ -680,13 +710,21 @@ if (-not $InstallPath) { $clangArm = Test-Path (Join-Path $llvmRoot 'ARM64\bin\clang-cl.exe') $clangX64 = Test-Path (Join-Path $llvmRoot 'x64\bin\clang-cl.exe') $clangX86 = Test-Path (Join-Path $llvmRoot 'bin\clang-cl.exe') - if ($clangArm -or $clangX64 -or $clangX86) { - Write-Host " clang-cl: $(@(if ($clangArm) {'ARM64'}; if ($clangX64) {'x64'}; if ($clangX86) {'x86'}) -join ', ')" -ForegroundColor Green - # On ARM64 the x64 build would still run, under emulation - so say plainly - # whether the NATIVE one is the one that landed. - if ($IsArm64 -and -not $clangArm) { - Write-Warning 'No ARM64-hosted clang-cl - the x64 one would run under emulation. Re-run the Clang/LLVM pass, or add "C++ Clang tools for Windows" in the installer.' + $clangAny = $clangArm -or $clangX64 -or $clangX86 + if ($IsArm64) { + # Not installed here by design, so its absence is the expected result and + # must not read as a fault. Its PRESENCE is the thing worth a line: a box + # provisioned before this split still carries it, and the component stays + # until it is explicitly removed - the installer never uninstalls what you + # simply stopped asking for. + if ($clangAny) { + Write-Host ' clang-cl: present but no longer provisioned on ARM64 - left over from an earlier run.' -ForegroundColor Yellow + Write-Host ' Remove it with: vs_installer.exe modify --installPath "" --remove Microsoft.VisualStudio.Component.VC.Llvm.Clang --remove Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --quiet' -ForegroundColor Yellow + } else { + Write-Host ' clang-cl: n/a on ARM64 (MSVC only by design; use the x64 box for ClangCL).' -ForegroundColor DarkGray } + } elseif ($clangAny) { + Write-Host " clang-cl: $(@(if ($clangArm) {'ARM64'}; if ($clangX64) {'x64'}; if ($clangX86) {'x86'}) -join ', ')" -ForegroundColor Green } else { Write-Warning 'clang-cl not found - the ClangCL presets will fail. Add the "C++ Clang tools for Windows" component.' } @@ -715,6 +753,26 @@ if (-not $InstallPath) { # Build 26100 matches the Windows 11 SDK installed above. # Provides IddCx (iddcx.h / iddcx.lib) and UMDF 2.x for Indirect Display Drivers. # linkid=2335869 -> WDK 26100.6584 (per Microsoft "Other WDK Downloads"). +# +# x64 ONLY. Two reasons, and the second is the one that decided it: +# +# 1. The ARM64 box builds user-mode software; no driver is developed on it. +# +# 2. This exact WDK cannot install next to the SDK that VS 2026 brings. The +# WDK requires the MATCHING Windows SDK revision, and revisions are not +# side-by-side - Include\10.0.26100.0 is one directory whichever revision +# wrote it. VS 2026 installs SDK 26100.7705; linkid=2335869 is WDK +# 26100.6584, which is the kit paired with VS 2022. So wdksetup.exe aborts: +# exit 15605, WER signature "WDK / 10.1.26100.6584 / Install / 0x80070642" +# (0x642 = 1602, ERROR_INSTALL_USEREXIT - a silent install cancelling +# itself on a failed prerequisite check). It is not an ARM64 or emulation +# problem; the same pairing fails on x64. +# +# If a driver ever does need building here, the fix is the WDK that matches +# this generation - Microsoft's "Supported and other WDK downloads" table +# pairs VS 2026 with the 28000.x kit - not this link. +# +# On x64 with VS 2022 the SDK and this WDK are the matched pair, so it installs. # --------------------------------------------------------------------------- $WdkVersion = '10.0.26100' # Both hives. The WDK installer is a 32-bit program, so on x64 it writes under @@ -728,7 +786,13 @@ $WdkInstalledRoot = @( ) | ForEach-Object { (Get-ItemProperty $_ -ErrorAction SilentlyContinue).WdkBinRootVersioned } | Where-Object { $_ } | Select-Object -First 1 -if ($WdkInstalledRoot -and $WdkInstalledRoot -match [regex]::Escape($WdkVersion)) { +if ($IsArm64) { + Write-Step 'Windows Driver Kit' + Write-Host ' Skipped on ARM64: this box builds user-mode software only, and WDK 26100.6584' -ForegroundColor Yellow + Write-Host ' cannot install beside the 26100.7705 SDK that Visual Studio 2026 brings - the' -ForegroundColor Yellow + Write-Host ' kit needs the matching SDK revision and they are not side-by-side. See the' -ForegroundColor Yellow + Write-Host ' comment above this step for the exit code and what to use instead.' -ForegroundColor Yellow +} elseif ($WdkInstalledRoot -and $WdkInstalledRoot -match [regex]::Escape($WdkVersion)) { # Re-running wdksetup.exe for an already-present version returns exit code # 2008 (maintenance mode / nothing to do), which is not a real failure. Write-Step "WDK $WdkVersion already installed - skipping ($WdkInstalledRoot)" diff --git a/setup-windows.bat b/setup-windows.bat index bd248a9..0a69956 100644 --- a/setup-windows.bat +++ b/setup-windows.bat @@ -10,18 +10,34 @@ @rem --------------------------------------------------------------------------- @rem Host architecture @rem -@rem PROCESSOR_ARCHITECTURE alone is not enough: a 32-bit cmd.exe under emulation -@rem reports x86 and stashes the real one in PROCESSOR_ARCHITEW6432. Check both, -@rem so this is right however the script was launched. +@rem The environment variables are not enough, and the failure is silent. A 32-bit +@rem cmd.exe under emulation reports x86 and stashes the real one in +@rem PROCESSOR_ARCHITEW6432 - that pair is handled below - but an EMULATED x64 +@rem cmd.exe on an ARM64 box reports AMD64 with PROCESSOR_ARCHITEW6432 unset, and +@rem nothing in the environment contradicts it. Launching this script from an x64 +@rem shell (Git Bash, for one) is enough to hit that, and measured here it gives: +@rem PROCESSOR_ARCHITECTURE AMD64 +@rem HKLM\...\Session Manager\Environment ARM64 <- the real one @rem -@rem The value drives two things below, and nothing else - winget resolves the +@rem So take the machine-level registry value as the answer and keep the +@rem environment pair only as the fallback. The registry key is per-machine and +@rem not subject to per-process emulation, and it is not one of the redirected +@rem hives. This is load-bearing now: %IS_ARM64% decides which packages are +@rem installed at all, so reading it wrong provisions the wrong machine. +@rem +@rem The value drives three things below, and nothing else - winget resolves the @rem installer architecture on its own (native first, then whatever the box can @rem emulate), so the ordinary installs need no help: @rem - VirtualBox is skipped on ARM64 (see below) +@rem - the x64-only package set is skipped on ARM64 (see :x64only) @rem - the summary tells you which tools you got as emulated x64 @rem --------------------------------------------------------------------------- set "HOST_ARCH=%PROCESSOR_ARCHITECTURE%" if defined PROCESSOR_ARCHITEW6432 set "HOST_ARCH=%PROCESSOR_ARCHITEW6432%" +@rem reg.exe by full path, and NOT through a pipe to find/findstr: both of those +@rem are shadowable by anything earlier on the PATH, and a POSIX `find` on the +@rem PATH turns this into a silent no-op that leaves the emulated answer standing. +for /f "tokens=1,2,*" %%A in ('%SystemRoot%\System32\reg.exe query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v PROCESSOR_ARCHITECTURE 2^>nul') do if /i "%%A"=="PROCESSOR_ARCHITECTURE" set "HOST_ARCH=%%C" set "IS_ARM64=0" if /i "%HOST_ARCH%"=="ARM64" set "IS_ARM64=1" echo [setup-windows] Host architecture: %HOST_ARCH% @@ -51,6 +67,30 @@ if "%ELEV%"=="NOLUA" ( echo [setup-windows] at each one, and a summary before the elevated half. ) +@rem --------------------------------------------------------------------------- +@rem ARM64 is a lean build box, x64 is the full workstation +@rem +@rem The ARM64 machine here is a small VM, and everything it was installing +@rem without a native build was going to run under Prism emulation anyway. So the +@rem two architectures now provision different things on purpose: x64 keeps the +@rem full set, ARM64 installs ONE compiler - MSVC from Visual Studio - and +@rem nothing that merely duplicates it or that exists only as an x64 binary. +@rem +@rem Dropped on ARM64, and where each one is dropped: +@rem Brave.Brave here no ARM64 build; Edge is in-box and native +@rem WinMerge.WinMerge here ARM64 build is machine-scope only +@rem LLVM.LLVM here second Clang, dropped with VS's clang-cl +@rem NASM.NASM here x86/x64 assembler; ARM64 uses MSVC's armasm64 +@rem Google.AndroidGPUInspector here x64-only, profiles Android devices +@rem OpenCppCoverage.OpenCppCoverage here x86/x64 only; cannot instrument ARM64 binaries +@rem VS Clang/LLVM component with-uac see $ClangComponents +@rem Windows Driver Kit with-uac see the WDK step +@rem BinSkim no-uac NuGet publishes win-x64 only +@rem +@rem :x64only at the bottom of this file is the one place that decision is +@rem applied, so the skip and its reason land in the log together. +@rem --------------------------------------------------------------------------- + @rem --- Non-admin (per-user) winget installs --- @rem @rem No --architecture anywhere on purpose. winget already picks the best @@ -67,7 +107,7 @@ if "%ELEV%"=="NOLUA" ( @rem (ARM64). On ARM64, run the *64a.exe variants - plain procexp.exe is the x86 @rem one and will run emulated. winget install Anthropic.ClaudeCode -winget install Brave.Brave +call :x64only Brave.Brave "no ARM64 build - the emulated x64 browser is the slowest thing on the box, and Edge is in-box and native" winget install Git.Git winget install Microsoft.PowerShell Microsoft.Sysinternals.ProcessExplorer Microsoft.Sysinternals.ProcessMonitor Microsoft.Sysinternals.SDelete Microsoft.VisualStudioCode Microsoft.WindowsTerminal winget install Python.Python.3.13 @@ -79,17 +119,11 @@ winget install Python.Python.3.13 @rem unelevated, winget's scope preference beats its architecture preference and @rem you silently get the emulated x64 build. @rem -@rem So ask for arm64 explicitly, and fall back to the default when that cannot be -@rem installed - which is exactly the standard-user case, where the machine-scope -@rem installer has nowhere to go. The fallback is the x64 build under emulation: -@rem a diff tool is not a hot path, and a working WinMerge beats none. The -@rem architecture audit at the end of setup-windows-no-uac.ps1 reports which one -@rem you ended up with. -if "%IS_ARM64%"=="1" ( - winget install WinMerge.WinMerge --architecture arm64 || winget install WinMerge.WinMerge -) else ( - winget install WinMerge.WinMerge -) +@rem That asymmetry is why ARM64 no longer installs it at all: the only build a +@rem normal user can install there is the emulated one, and asking for --architecture +@rem arm64 just fails unless the run is elevated. VS Code's diff view and +@rem `git difftool` cover the same ground natively. +call :x64only WinMerge.WinMerge "upstream ships ARM64 as a machine-scope installer only, so an unelevated winget can install nothing but the emulated x64 build" @rem --- Build drivers, standalone and native --- @rem @@ -117,24 +151,27 @@ if "%IS_ARM64%"=="1" ( @rem whole machine. winget install Ninja-build.Ninja -@rem --- Second, independent Clang: compiler diversity --- +@rem --- Second, independent Clang: compiler diversity, on x64 only --- @rem -@rem The elevated half installs Visual Studio's Clang component (clang-cl, native -@rem ARM64). This is a DIFFERENT LLVM: the upstream release, on its own schedule -@rem and usually several major versions ahead of the one VS bundles, installed to +@rem On x64 the elevated half installs Visual Studio's Clang component (clang-cl). +@rem This is a DIFFERENT LLVM: the upstream release, on its own schedule and +@rem usually several major versions ahead of the one VS bundles, installed to @rem C:\Program Files\LLVM rather than inside the VS tree. Two Clang majors plus @rem MSVC over the same sources is the point - it is what catches the bugs a @rem single toolchain agrees with itself about. @rem -@rem Native on ARM64: the manifest resolves to LLVM--woa64.exe ("Windows on -@rem Arm 64"), not the x64 build. +@rem NOT ON ARM64, and not for lack of a native build - the manifest does resolve +@rem to LLVM--woa64.exe there. It is dropped because the ARM64 box carries +@rem MSVC alone: with VS's clang-cl component gone too, a third compiler with +@rem nothing to disagree with is just disk. Do the multi-compiler build on the +@rem x64 machine, which keeps all three. @rem -@rem PATH ORDER MATTERS if you use it. Should this installer put its bin on the -@rem PATH, a bare `clang-cl` resolves to the upstream one while CMake's +@rem PATH ORDER MATTERS on x64 if you use it. Should this installer put its bin on +@rem the PATH, a bare `clang-cl` resolves to the upstream one while CMake's @rem `-T ClangCL` toolset keeps using the VS copy - so the two can be selected @rem independently, but only if you know which one you are asking for. Prefer @rem -DCMAKE_CXX_COMPILER with a full path when you care. -winget install LLVM.LLVM +call :x64only LLVM.LLVM "ARM64 carries MSVC alone - VS's clang-cl is dropped there too, so a second Clang has nothing to cross-check against" @rem x64-only manifest: winget falls back to the x64 installer on ARM64 and it runs @rem under Prism emulation. Harmless for what it does here - iperf3 is a network @@ -142,11 +179,15 @@ winget install LLVM.LLVM winget install ar51an.iPerf3 @rem NASM assembles x86/x86-64 only - there is no ARM64 target in it and no ARM64 -@rem build of it. Installed on ARM64 anyway (as emulated x64) because this box -@rem still cross-compiles x86/x64 binaries, where NASM is the assembler that -@rem builds them. If you only ever target ARM64, nothing here uses it: the ARM64 -@rem assembler is armasm64.exe, which ships with MSVC. -winget install NASM.NASM +@rem build of it. x64 keeps it; ARM64 does not, because an emulated assembler is +@rem only worth carrying if you cross-compile x86/x64 here, and that work belongs +@rem on the x64 box. The ARM64 assembler is armasm64.exe, which ships with MSVC. +@rem +@rem Its installer is also one of the two here that fall back to a per-user +@rem directory when it cannot write to Program Files - C:\Users\\AppData\ +@rem Local\bin\NASM, registered nowhere, so `winget uninstall` cannot find it +@rem afterwards. Uninstall.exe in that directory is the only way back out. +call :x64only NASM.NASM "x86/x86-64 assembler with no ARM64 target and no ARM64 build - MSVC's armasm64.exe is the native one" @rem VirtualBox has no ARM64 Windows host build, and the x64 one cannot be made to @rem work by emulation: a hypervisor is kernel-mode, and its x64 driver will not @@ -181,11 +222,15 @@ if "%IS_ARM64%"=="1" ( @rem NOTE none of these is a build-blocker: the .NET SDK is only here for the WiX @rem MSI tooling, CMake also ships inside Visual Studio, AGI profiles Android @rem devices, and OpenCppCoverage cannot instrument ARM64 binaries anyway. +@rem +@rem On ARM64 the group is just the first two - AGI and OpenCppCoverage are in the +@rem x64-only set, so there they are skipped before elevation is even consulted. @rem --------------------------------------------------------------------------- if "%ELEV%"=="NOLUA" ( echo. echo [setup-windows] Skipping the machine-wide installers - this account cannot elevate: - echo [setup-windows] Microsoft.DotNet.SDK.10, Kitware.CMake, Google.AndroidGPUInspector, OpenCppCoverage. + echo [setup-windows] Microsoft.DotNet.SDK.10 and Kitware.CMake, plus - on x64 only - + echo [setup-windows] Google.AndroidGPUInspector and OpenCppCoverage. echo [setup-windows] None blocks a build. Re-run from an administrator account to get them. echo. goto :after_admin_pkgs @@ -193,7 +238,7 @@ if "%ELEV%"=="NOLUA" ( winget install Microsoft.DotNet.SDK.10 winget install Kitware.CMake -winget install Google.AndroidGPUInspector +call :x64only Google.AndroidGPUInspector "x64-only build, and it profiles Android devices rather than anything compiled here" @rem OpenCppCoverage: native (PE) line coverage for the C++ binaries. run-coverage-occ.py drives the @rem pytest suite under it to produce an HTML report (the binaries under test build with PDBs, @@ -201,13 +246,12 @@ winget install Google.AndroidGPUInspector @rem @rem x86/x64 only, and unlike the other emulated tools that is a real limit rather @rem than a slowdown: it collects coverage by debugging the process under test and -@rem stepping x86/x64 instructions, so it can cover the x86/x64 binaries this box -@rem cross-compiles but NOT an ARM64 one. For ARM64 coverage, build the ARM64 -@rem binaries with /fsanitize-coverage or use the x64 build for the coverage run. -winget install OpenCppCoverage.OpenCppCoverage +@rem stepping x86/x64 instructions, so it can cover x86/x64 binaries but NOT an +@rem ARM64 one. That is what puts it in the x64-only set: on ARM64 it could only +@rem ever cover the cross-compiled x64 build, which is a job for the x64 box. +call :x64only OpenCppCoverage.OpenCppCoverage "it collects coverage by stepping x86/x64 instructions, so it cannot instrument an ARM64 binary at all" :after_admin_pkgs -if "%IS_ARM64%"=="1" echo [setup-windows] NOTE: OpenCppCoverage is x86/x64-only - it cannot instrument ARM64 binaries. Run coverage against the x64 build. @rem --- WiX 5.0.2, pinned on purpose --- @rem WiX packages our proprietary software into MSIs, and the version is pinned to keep that @@ -298,8 +342,8 @@ echo [setup-windows] so Windows offers no way to elevate - there is no prompt echo [setup-windows] 'Start-Process -Verb RunAs' is silently ignored and reports success, which is why echo [setup-windows] this used to look like a cancelled prompt. echo [setup-windows] -echo [setup-windows] Not installed: Visual Studio components ^(including clang-cl^), the WDK, -echo [setup-windows] rsync, and the OpenSSH server. Everything per-user above is unaffected. +echo [setup-windows] Not installed: Visual Studio and its MSVC toolchain, rsync, and the OpenSSH +echo [setup-windows] server. Everything per-user above is unaffected. echo [setup-windows] echo [setup-windows] To finish the box, either sign in to an administrator account and re-run this echo [setup-windows] script, or have an administrator run setup-windows-with-uac.ps1 there. Then @@ -374,21 +418,57 @@ if "%UAC_RC%"=="SKIPPED" echo [setup-windows] Per-user setup complete; the eleva @rem py -m pip install "headroom-ai[all]" @rem npm install headroom-ai -@rem Pillow publishes win_arm64 wheels alongside win_amd64, so `py` - the ARM64 -@rem Python winget just installed - gets a native wheel and never falls back to -@rem building from source. Any package WITHOUT an ARM64 wheel does fall back, and -@rem that source build needs the MSVC toolchain the elevated half installs. -py -m pip install Pillow +@rem --- Find an interpreter by path, rather than trusting `py` --- +@rem +@rem `py -m pip install Pillow` used to be this line, and it failed with +@rem 'py' is not recognized as an internal or external command +@rem for two independent reasons, which look identical at the prompt: +@rem +@rem 1. PATH staleness. winget installed Python a few lines above, but this cmd +@rem session inherited its environment before that, so nothing winget added +@rem is visible here. Same problem the DOTNET_EXE lookup solves above, and it +@rem would bite even where the launcher IS installed. +@rem +@rem 2. The launcher may simply not be there. Measured on the ARM64 box: every +@rem other Python component registered - Core Interpreter, Executables, +@rem Standard Library, pip Bootstrap, Tcl/Tk, Add to Path - and there is no +@rem launcher entry and no py.exe anywhere on disk, while the Launcher +@rem directory it would occupy is on the user PATH but does not exist. +@rem +@rem So resolve an interpreter by full path and prefer the launcher only when it +@rem is real. python.exe is the thing that actually has to exist; `py` is a +@rem convenience shim that picks between several of them, and there is exactly one +@rem here. Reverse name order so the newest Python wins, and require python.exe +@rem inside the directory - a half-removed version leaves the folder behind with +@rem only Doc and Lib in it, which is exactly what this box has. +set "PY_EXE=" +if exist "%LOCALAPPDATA%\Programs\Python\Launcher\py.exe" set "PY_EXE=%LOCALAPPDATA%\Programs\Python\Launcher\py.exe" +if not defined PY_EXE if exist "%WINDIR%\py.exe" set "PY_EXE=%WINDIR%\py.exe" +if not defined PY_EXE for /f "delims=" %%P in ('dir /b /ad /o-n "%LOCALAPPDATA%\Programs\Python\Python3*" 2^>nul') do if not defined PY_EXE if exist "%LOCALAPPDATA%\Programs\Python\%%P\python.exe" set "PY_EXE=%LOCALAPPDATA%\Programs\Python\%%P\python.exe" + +@rem Pillow publishes win_arm64 wheels alongside win_amd64, so the ARM64 Python +@rem winget just installed gets a native wheel and never falls back to building +@rem from source. Any package WITHOUT an ARM64 wheel does fall back, and that +@rem source build needs the MSVC toolchain the elevated half installs. +if not defined PY_EXE ( + echo [setup-windows] WARNING: no Python interpreter found; skipping the Pillow install. + echo [setup-windows] Looked for the py launcher, then %LOCALAPPDATA%\Programs\Python\Python3*\python.exe. +) else ( + echo [setup-windows] Python: %PY_EXE% + "%PY_EXE%" -m pip install Pillow +) if "%IS_ARM64%"=="1" ( echo. echo [setup-windows] ARM64 notes: echo [setup-windows] native ARM64: Git, Python, .NET SDK, PowerShell, VS Code, Windows Terminal, - echo [setup-windows] WinMerge, Brave, Sysinternals ^(the *64a.exe variants^), - echo [setup-windows] Claude Code, CMake, Ninja, and the SDK/WPT tools. - echo [setup-windows] compilers : MSVC and clang-cl from Visual Studio 2026, plus upstream - echo [setup-windows] LLVM - all three native ARM64, two different Clang majors. - echo [setup-windows] emulated x64: iperf3, NASM, OpenCppCoverage, BinSkim, rsync.exe, AGI. + echo [setup-windows] Sysinternals ^(the *64a.exe variants^), Claude Code, CMake, + echo [setup-windows] Ninja, and the SDK/WPT tools. + echo [setup-windows] compiler : MSVC from Visual Studio 2026, native ARM64. One compiler by + echo [setup-windows] design - clang-cl and upstream LLVM are x64-box only. + echo [setup-windows] emulated x64: iperf3 and rsync.exe, both network-bound. + echo [setup-windows] not here : Brave, WinMerge, LLVM, NASM, AGI, OpenCppCoverage, BinSkim + echo [setup-windows] and the WDK - dropped on ARM64, see the x64-only set above. echo [setup-windows] unavailable : VirtualBox, the Windows 7 x86 test VM, Intel VTune, echo [setup-windows] and the v141 / Windows XP targeting toolset. ) @@ -396,6 +476,27 @@ if "%IS_ARM64%"=="1" ( @rem landed on this run - so point at the audit, which reports the actual state. if "%ELEV%"=="NOLUA" ( echo [setup-windows] NOT on this box: everything needing elevation was skipped, including - echo [setup-windows] Visual Studio's clang-cl, the .NET SDK and CMake. The + echo [setup-windows] Visual Studio's MSVC, the .NET SDK and CMake. The echo [setup-windows] architecture audit above lists what is really installed. ) + +exit /b 0 + +@rem --------------------------------------------------------------------------- +@rem :x64only "" +@rem +@rem Install a package on x64 and skip it on ARM64, saying why. One subroutine +@rem rather than a gate at each call site so that the reason is never separated +@rem from the skip: the log line a future reader sees is the same text this file +@rem carries, and there is exactly one place to change the policy. +@rem +@rem The reason is a real argument, not a comment, because the interesting case is +@rem reading the run log six months later and wondering where NASM went. +@rem --------------------------------------------------------------------------- +:x64only +if not "%IS_ARM64%"=="1" ( + winget install %1 + goto :eof +) +echo [setup-windows] Skipping %1 on ARM64: %~2 +goto :eof -- 2.48.2