X-Git-Url: https://vilimpoc.org/repos/dotfiles/blobdiff_plain/0aec6243fab52a10889bd5052a4cf1496be80b0d..f51da37a77ac790110def9697b57f49b821c4c75:/setup-windows.bat?ds=sidebyside diff --git a/setup-windows.bat b/setup-windows.bat index 9dda32e..c9c7efb 100644 --- a/setup-windows.bat +++ b/setup-windows.bat @@ -2,22 +2,256 @@ @rem --------------------------------------------------------------------------- @rem setup-windows.bat - provision a fresh Windows box for native development +@rem +@rem Runs on x64 and on ARM64 (Windows 11 on Arm). See the ARM64 block below for +@rem what changes on Arm silicon. +@rem --------------------------------------------------------------------------- + +@rem --------------------------------------------------------------------------- +@rem Host architecture +@rem +@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 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% + +@rem --------------------------------------------------------------------------- +@rem Can this account elevate? Worked out ONCE, here, because it gates two very +@rem different things: the elevated half far below, and a handful of the "per-user" +@rem winget installs just after this, which are per-user in name only. +@rem +@rem ALREADY - already elevated. IsInRole(Administrator) is false for an admin +@rem running unelevated under UAC, so this means actually elevated, +@rem not merely capable of it. +@rem PROMPT - not elevated, UAC on, so elevation can be requested. +@rem NOLUA - UAC is off machine-wide (EnableLUA = 0) AND this is not an +@rem administrator. Elevation is impossible, not merely declined: +@rem Windows has no prompt to offer. Note that with UAC off, +@rem `Start-Process -Verb RunAs` does not fail - it is silently +@rem ignored, runs the child with the caller's own token, and reports +@rem success, which is why this needs detecting rather than trying. +@rem --------------------------------------------------------------------------- +set "ELEV=PROMPT" +for /f "usebackq tokens=*" %%A in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$id=[Security.Principal.WindowsIdentity]::GetCurrent(); if (([Security.Principal.WindowsPrincipal]$id).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { 'ALREADY' } elseif ((Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -ErrorAction SilentlyContinue).EnableLUA -eq 0) { 'NOLUA' } else { 'PROMPT' }"`) do set "ELEV=%%A" +echo [setup-windows] Elevation: %ELEV% +if "%ELEV%"=="NOLUA" ( + echo [setup-windows] This account cannot elevate ^(UAC off, not an administrator^). Packages that + echo [setup-windows] need a machine-wide install will be SKIPPED rather than left to fail; details + 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 +@rem installer the manifest offers for this machine - arm64, else x64, else x86 - +@rem and pinning it would turn "no native build, use the emulated one" into a +@rem hard failure. +@rem +@rem These all publish real ARM64 installers, so on an ARM64 box every one of +@rem them lands as a native ARM64 binary with no emulation involved. +@rem +@rem The Sysinternals packages are a multi-architecture ZIP rather than an +@rem installer, so the ARM64 build is IN there but is not the default-named exe: +@rem the zip holds procexp.exe (x86), procexp64.exe (x64) and procexp64a.exe +@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.DotNet.SDK.10 winget install Microsoft.PowerShell Microsoft.Sysinternals.ProcessExplorer Microsoft.Sysinternals.ProcessMonitor Microsoft.Sysinternals.SDelete Microsoft.VisualStudioCode Microsoft.WindowsTerminal -winget install Oracle.VirtualBox winget install Python.Python.3.13 -winget install WinMerge.WinMerge + +@rem WinMerge is the one package here where winget's own preference order picks +@rem the WRONG architecture, and it is worth knowing why: upstream publishes the +@rem ARM64 build as a MACHINE-scope installer (WinMerge--ARM64-Setup.exe) and +@rem the x64 build as a PER-USER one (WinMerge--x64-PerUser-Setup.exe). Run +@rem unelevated, winget's scope preference beats its architecture preference and +@rem you silently get the emulated x64 build. +@rem +@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 +@rem CMake and Ninja BOTH ship with Visual Studio, so this is not about making +@rem them exist - it is about which binary you get and where it is on the PATH: +@rem +@rem - VS's cmake.exe is already ARM64, but it only lands on the PATH inside a +@rem Developer Command Prompt. The build line further down assumes a plain +@rem `cmake`, so install the standalone one to make that true in any shell. +@rem - VS's bundled ninja.exe is x64 EVEN ON ARM64 (verified on this box), so it +@rem runs under emulation. Ninja is re-invoked for every edge in the build +@rem graph, which makes it the one tool here where emulation is actually paid +@rem over and over, and ninja-winarm64.zip is a native build. Worth having. +@rem +@rem Which ninja wins inside a VS Developer Command Prompt: the native one. VS +@rem adds its copy from Common7\Tools\vsdevcmd\ext\cmake.bat with +@rem set "PATH=%PATH%;...\CMake\bin;...\CMake\Ninja" +@rem which APPENDS, so the VS directories sit at the very end of the composed +@rem PATH, behind every machine and user entry. setup-windows-no-uac.ps1 then +@rem pins the native ninja's directory to the FRONT of the user PATH so the +@rem margin does not depend on two append orders staying as they are. +@rem +@rem Ninja here, CMake in the machine-wide group below: Ninja is a portable zip +@rem winget unpacks into the user profile, CMake is an MSI that installs for the +@rem whole machine. +winget install Ninja-build.Ninja + +@rem --- Second, independent Clang: compiler diversity, on x64 only --- +@rem +@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 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 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. +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 +@rem benchmark bounded by the link, not by CPU. +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. 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 +@rem load on an ARM64 kernel. Installing it on ARM64 gets you a broken product and +@rem a failed driver install, so skip it and say so. +@rem +@rem This is also what makes setup-windows-7-test-env.bat unreachable from an +@rem ARM64 host: it prepares a Windows 7 *x86* guest, and neither VirtualBox nor +@rem Hyper-V on ARM64 can run one. +if "%IS_ARM64%"=="1" ( + echo [setup-windows] Skipping Oracle.VirtualBox: no ARM64 Windows host build ^(x64 hypervisor drivers cannot load on an ARM64 kernel^). + echo [setup-windows] The Windows 7 x86 test-VM workflow ^(setup-windows-7-test-env.bat^) is therefore unavailable on this box. +) else ( + winget install Oracle.VirtualBox +) + +@rem --------------------------------------------------------------------------- +@rem Machine-wide installers, despite sitting in the "per-user" section +@rem +@rem These four are not per-user at all. On an account that cannot elevate they +@rem fail on every run, with installer exit codes that say nothing useful: +@rem Microsoft.DotNet.SDK.10 exit 5 (ERROR_ACCESS_DENIED) +@rem Kitware.CMake exit 1603 (generic MSI failure) +@rem Google.AndroidGPUInspector exit 1603 +@rem OpenCppCoverage exit 1 (its installer self-elevates) +@rem +@rem Grouped and skipped outright where elevation is impossible. Attempting a +@rem guaranteed failure four times per run - and paying the download for it - +@rem teaches nobody anything, and the four opaque error codes bury the one line +@rem that matters. On an account that can elevate they run exactly as before. +@rem +@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 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 +) + +winget install Microsoft.DotNet.SDK.10 +winget install Kitware.CMake +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, @rem which it reads). The installer elevates via UAC. -winget install OpenCppCoverage.OpenCppCoverage +@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 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 @rem --- WiX 5.0.2, pinned on purpose --- @rem WiX packages our proprietary software into MSIs, and the version is pinned to keep that @@ -40,45 +274,94 @@ winget install OpenCppCoverage.OpenCppCoverage @rem ago, but this cmd session inherited its environment before that and cannot see it. @rem install-then-update is for re-runs - install fails once the tool is there, and update @rem then holds it at exactly 5.0.2 - which keeps this script idempotent like the rest. +@rem +@rem %ProgramFiles%\dotnet is the NATIVE SDK's root on every architecture, ARM64 +@rem included - what winget just installed. An x64 SDK installed alongside it on +@rem an ARM64 box goes to %ProgramFiles%\dotnet\x64 instead, so fall back there +@rem for a machine that only ever got the x64 one. Prefer the native root: the +@rem ARM64 SDK runs the wix tool natively, and building an MSI is not something +@rem worth doing through emulation. set "DOTNET_EXE=%ProgramFiles%\dotnet\dotnet.exe" +if not exist "%DOTNET_EXE%" set "DOTNET_EXE=%ProgramFiles%\dotnet\x64\dotnet.exe" +if not exist "%DOTNET_EXE%" echo [setup-windows] WARNING: dotnet.exe not found; skipping the WiX 5.0.2 pin. +if not exist "%DOTNET_EXE%" goto :after_wix + "%DOTNET_EXE%" tool install --global wix --version 5.0.2 || "%DOTNET_EXE%" tool update --global wix --version 5.0.2 @rem Report what the pin actually produced. By full path again, and because the shim lands in @rem a directory this session's PATH predates: expect "5.0.2+", not 7.x. "%USERPROFILE%\.dotnet\tools\wix.exe" --version +:after_wix + @rem --------------------------------------------------------------------------- @rem No package manager needed for the Windows build @rem @rem Just: @rem cd windows && cmake -B build -G "Visual Studio 17 2022" -A x64 && cmake --build build --config Release +@rem +@rem -A selects the TARGET, independently of the host. On an ARM64 box the same +@rem generator cross-compiles all three, and MSVC's ARM64-hosted toolchain is used +@rem for each - nothing is emulated: +@rem -A ARM64 native ARM64 binaries +@rem -A x64 x64 binaries (run here under emulation) +@rem -A Win32 x86 binaries +@rem Use the generator matching the Visual Studio the elevated half installed - +@rem "Visual Studio 17 2022" on x64, "Visual Studio 18 2026" on ARM64 - or just +@rem let CMake pick the newest it finds. @rem --------------------------------------------------------------------------- -@rem --- Elevated installs (VS2022, WDK, system tools) --- +@rem --- Elevated installs (Visual Studio, WDK, system tools) --- @rem The elevated script runs in its own window and logs to setup-windows-uac.log. @rem -PassThru + $p.ExitCode propagates its real exit code back through to ERRORLEVEL. -@rem -@rem -TraceUser passes YOU across the UAC boundary. Accepting that prompt with an -@rem administrator's credentials runs the elevated half AS that administrator, so -@rem it cannot see whose box this is; the account named here is the one it grants -@rem non-elevated ETW collection rights to (xperf / wpr without a UAC prompt). -@rem -@rem The two values go through the environment, and the quotes the child needs -@rem around them are built in PowerShell as [char]34, so the command line below -@rem contains no embedded quote characters at all. Writing them inline as "".."" -@rem works for ONE argument and quietly breaks at two: the quote-state parsing -@rem swallows everything after the first into the -File value, and the elevated -@rem PowerShell dies with "failed because the file does not have a '.ps1' -@rem extension" and exit code -196608 (0xFFFD0000) before it can log a thing. set "UAC_LOG=%~dp0setup-windows-uac.log" if exist "%UAC_LOG%" del "%UAC_LOG%" -set "UAC_SCRIPT=%~dp0setup-windows-with-uac.ps1" -set "UAC_TRACE_USER=%USERDOMAIN%\%USERNAME%" -powershell -NoProfile -Command "$q = [char]34; $p = Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File',($q + $env:UAC_SCRIPT + $q),'-TraceUser',($q + $env:UAC_TRACE_USER + $q) -Wait -PassThru; exit $p.ExitCode" +@rem %ELEV% was worked out at the top of this script - see the comment there for +@rem why `Start-Process -Verb RunAs` cannot be trusted to report this itself. +if "%ELEV%"=="NOLUA" goto :elev_impossible +if "%ELEV%"=="ALREADY" goto :elev_direct + +@rem Not elevated, UAC is on: request it. Expect a prompt. +echo [setup-windows] Requesting elevation ^(expect a UAC prompt^)... +powershell -NoProfile -Command "$p = Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','""%~dp0setup-windows-with-uac.ps1""' -Wait -PassThru; exit $p.ExitCode" set "UAC_RC=%ERRORLEVEL%" +goto :elev_done + +:elev_direct +echo [setup-windows] Already running elevated; running the elevated half directly. +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-with-uac.ps1" +set "UAC_RC=%ERRORLEVEL%" +goto :elev_done + +:elev_impossible +echo. +echo [setup-windows] SKIPPING the elevated half: this account cannot elevate. +echo [setup-windows] UAC is disabled machine-wide ^(EnableLUA = 0^) and you are not an administrator, +echo [setup-windows] so Windows offers no way to elevate - there is no prompt to accept. With UAC off, +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 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 +echo [setup-windows] re-run setup-windows-no-uac.ps1 as yourself, so the per-user PATH and .gitconfig +echo [setup-windows] land in YOUR profile rather than the administrator's. +echo. +set "UAC_RC=SKIPPED" + +:elev_done @rem --- Surface the elevated session's output (its window has already closed) --- +@rem +@rem A missing log is NOT self-explanatory, so do not guess at one cause. The +@rem elevated script writes its transcript as almost its first act, so no log +@rem means it never got as far as running: either the prompt was declined, or it +@rem started unelevated and stopped on its own #Requires line. Which of those it +@rem was is already known from %ELEV%, so report that instead of speculating. +if "%UAC_RC%"=="SKIPPED" goto :after_uac_log if exist "%UAC_LOG%" ( echo. echo ===== elevated setup log ^(%UAC_LOG%^) ===== @@ -86,8 +369,10 @@ if exist "%UAC_LOG%" ( echo ===== end of elevated setup log ===== ) else ( echo [setup-windows] WARNING: no elevated log found at "%UAC_LOG%". - echo [setup-windows] The elevated window may have been cancelled at the UAC prompt. + if "%ELEV%"=="PROMPT" echo [setup-windows] The elevated window never started - the UAC prompt was most likely declined. + if "%ELEV%"=="ALREADY" echo [setup-windows] The elevated half exited before writing its transcript; see its output above. ) +:after_uac_log @rem --- Non-elevated PowerShell half --- @rem WinMerge on the user PATH, BinSkim, and the global git config (identity + @@ -110,6 +395,11 @@ if exist "%UAC_LOG%" ( powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-no-uac.ps1" if not "%ERRORLEVEL%"=="0" echo [setup-windows] WARNING: setup-windows-no-uac.ps1 reported a failure ^(see above^); continuing. +@rem Skipping the elevated half is a reported, understood outcome on a box where +@rem elevation is impossible - not a failure to exit non-zero over. The per-user +@rem provisioning above did run, and re-running from an administrator account is +@rem the documented next step. +if "%UAC_RC%"=="SKIPPED" goto :uac_reported if not "%UAC_RC%"=="0" ( echo. echo [setup-windows] ELEVATED SETUP FAILED ^(exit code %UAC_RC%^). See log above. @@ -117,10 +407,96 @@ if not "%UAC_RC%"=="0" ( ) echo. echo [setup-windows] Elevated setup completed successfully. +goto :uac_reported + +:uac_reported +if "%UAC_RC%"=="SKIPPED" echo. +if "%UAC_RC%"=="SKIPPED" echo [setup-windows] Per-user setup complete; the elevated half was skipped ^(see above^). @rem Removed: this doesn't work as well as I hoped, maybe try again later @rem -- Install Headroom --- @rem py -m pip install "headroom-ai[all]" @rem npm install headroom-ai -py -m pip install Pillow \ No newline at end of file +@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] 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, which is network-bound anyway. + 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. +) +@rem The list above is what this script PROVIDES on ARM64, not necessarily what +@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 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