]> vilimpoc.org git repositories - dotfiles/blob - setup-windows.bat
dotfiles: make the Windows setup work natively on ARM64
[dotfiles] / setup-windows.bat
1 @echo off\r
2 \r
3 @rem ---------------------------------------------------------------------------\r
4 @rem setup-windows.bat - provision a fresh Windows box for native development\r
5 @rem\r
6 @rem Runs on x64 and on ARM64 (Windows 11 on Arm). See the ARM64 block below for\r
7 @rem what changes on Arm silicon.\r
8 @rem ---------------------------------------------------------------------------\r
9 \r
10 @rem ---------------------------------------------------------------------------\r
11 @rem Host architecture\r
12 @rem\r
13 @rem PROCESSOR_ARCHITECTURE alone is not enough: a 32-bit cmd.exe under emulation\r
14 @rem reports x86 and stashes the real one in PROCESSOR_ARCHITEW6432. Check both,\r
15 @rem so this is right however the script was launched.\r
16 @rem\r
17 @rem The value drives two things below, and nothing else - winget resolves the\r
18 @rem installer architecture on its own (native first, then whatever the box can\r
19 @rem emulate), so the ordinary installs need no help:\r
20 @rem   - VirtualBox is skipped on ARM64 (see below)\r
21 @rem   - the summary tells you which tools you got as emulated x64\r
22 @rem ---------------------------------------------------------------------------\r
23 set "HOST_ARCH=%PROCESSOR_ARCHITECTURE%"\r
24 if defined PROCESSOR_ARCHITEW6432 set "HOST_ARCH=%PROCESSOR_ARCHITEW6432%"\r
25 set "IS_ARM64=0"\r
26 if /i "%HOST_ARCH%"=="ARM64" set "IS_ARM64=1"\r
27 echo [setup-windows] Host architecture: %HOST_ARCH%\r
28 \r
29 @rem --- Non-admin (per-user) winget installs ---\r
30 @rem\r
31 @rem No --architecture anywhere on purpose. winget already picks the best\r
32 @rem installer the manifest offers for this machine - arm64, else x64, else x86 -\r
33 @rem and pinning it would turn "no native build, use the emulated one" into a\r
34 @rem hard failure.\r
35 @rem\r
36 @rem These all publish real ARM64 installers, so on an ARM64 box every one of\r
37 @rem them lands as a native ARM64 binary with no emulation involved.\r
38 @rem\r
39 @rem The Sysinternals packages are a multi-architecture ZIP rather than an\r
40 @rem installer, so the ARM64 build is IN there but is not the default-named exe:\r
41 @rem the zip holds procexp.exe (x86), procexp64.exe (x64) and procexp64a.exe\r
42 @rem (ARM64). On ARM64, run the *64a.exe variants - plain procexp.exe is the x86\r
43 @rem one and will run emulated.\r
44 winget install Anthropic.ClaudeCode\r
45 winget install Brave.Brave\r
46 winget install Git.Git\r
47 winget install Microsoft.DotNet.SDK.10\r
48 winget install Microsoft.PowerShell Microsoft.Sysinternals.ProcessExplorer Microsoft.Sysinternals.ProcessMonitor Microsoft.Sysinternals.SDelete Microsoft.VisualStudioCode Microsoft.WindowsTerminal\r
49 winget install Python.Python.3.13\r
50 winget install WinMerge.WinMerge\r
51 \r
52 @rem --- Build drivers, standalone and native ---\r
53 @rem\r
54 @rem CMake and Ninja BOTH ship with Visual Studio, so this is not about making\r
55 @rem them exist - it is about which binary you get and where it is on the PATH:\r
56 @rem\r
57 @rem   - VS's cmake.exe is already ARM64, but it only lands on the PATH inside a\r
58 @rem     Developer Command Prompt. The build line further down assumes a plain\r
59 @rem     `cmake`, so install the standalone one to make that true in any shell.\r
60 @rem   - VS's bundled ninja.exe is x64 EVEN ON ARM64 (verified on this box), so it\r
61 @rem     runs under emulation. Ninja is re-invoked for every edge in the build\r
62 @rem     graph, which makes it the one tool here where emulation is actually paid\r
63 @rem     over and over, and ninja-winarm64.zip is a native build. Worth having.\r
64 @rem\r
65 @rem Both are on the machine/user PATH after install, so a bare `cmake -G Ninja`\r
66 @rem picks these up. Inside a VS Developer prompt the VS copies may come first;\r
67 @rem that is fine for cmake (same architecture) and is the case to watch for ninja.\r
68 winget install Kitware.CMake\r
69 winget install Ninja-build.Ninja\r
70 \r
71 @rem --- Second, independent Clang: compiler diversity ---\r
72 @rem\r
73 @rem The elevated half installs Visual Studio's Clang component (clang-cl, native\r
74 @rem ARM64). This is a DIFFERENT LLVM: the upstream release, on its own schedule\r
75 @rem and usually several major versions ahead of the one VS bundles, installed to\r
76 @rem C:\Program Files\LLVM rather than inside the VS tree. Two Clang majors plus\r
77 @rem MSVC over the same sources is the point - it is what catches the bugs a\r
78 @rem single toolchain agrees with itself about.\r
79 @rem\r
80 @rem Native on ARM64: the manifest resolves to LLVM-<ver>-woa64.exe ("Windows on\r
81 @rem Arm 64"), not the x64 build.\r
82 @rem\r
83 @rem PATH ORDER MATTERS if you use it. Should this installer put its bin on the\r
84 @rem PATH, a bare `clang-cl` resolves to the upstream one while CMake's\r
85 @rem `-T ClangCL` toolset keeps using the VS copy - so the two can be selected\r
86 @rem independently, but only if you know which one you are asking for. Prefer\r
87 @rem -DCMAKE_CXX_COMPILER with a full path when you care.\r
88 winget install LLVM.LLVM\r
89 \r
90 @rem x64-only manifests: winget falls back to the x64 installer on ARM64 and these\r
91 @rem run under Prism emulation. Harmless for what they do here - iperf3 is a\r
92 @rem network benchmark bounded by the link, and AGI is an Android-side GPU\r
93 @rem profiler whose work happens on the phone. Neither is CPU-bound on this box.\r
94 winget install ar51an.iPerf3\r
95 winget install Google.AndroidGPUInspector\r
96 \r
97 @rem NASM assembles x86/x86-64 only - there is no ARM64 target in it and no ARM64\r
98 @rem build of it. Installed on ARM64 anyway (as emulated x64) because this box\r
99 @rem still cross-compiles x86/x64 binaries, where NASM is the assembler that\r
100 @rem builds them. If you only ever target ARM64, nothing here uses it: the ARM64\r
101 @rem assembler is armasm64.exe, which ships with MSVC.\r
102 winget install NASM.NASM\r
103 \r
104 @rem VirtualBox has no ARM64 Windows host build, and the x64 one cannot be made to\r
105 @rem work by emulation: a hypervisor is kernel-mode, and its x64 driver will not\r
106 @rem load on an ARM64 kernel. Installing it on ARM64 gets you a broken product and\r
107 @rem a failed driver install, so skip it and say so.\r
108 @rem\r
109 @rem This is also what makes setup-windows-7-test-env.bat unreachable from an\r
110 @rem ARM64 host: it prepares a Windows 7 *x86* guest, and neither VirtualBox nor\r
111 @rem Hyper-V on ARM64 can run one.\r
112 if "%IS_ARM64%"=="1" (\r
113     echo [setup-windows] Skipping Oracle.VirtualBox: no ARM64 Windows host build ^(x64 hypervisor drivers cannot load on an ARM64 kernel^).\r
114     echo [setup-windows]   The Windows 7 x86 test-VM workflow ^(setup-windows-7-test-env.bat^) is therefore unavailable on this box.\r
115 ) else (\r
116     winget install Oracle.VirtualBox\r
117 )\r
118 \r
119 @rem OpenCppCoverage: native (PE) line coverage for the C++ binaries. run-coverage-occ.py drives the\r
120 @rem pytest suite under it to produce an HTML report (the binaries under test build with PDBs,\r
121 @rem which it reads). The installer elevates via UAC.\r
122 @rem\r
123 @rem x86/x64 only, and unlike the other emulated tools that is a real limit rather\r
124 @rem than a slowdown: it collects coverage by debugging the process under test and\r
125 @rem stepping x86/x64 instructions, so it can cover the x86/x64 binaries this box\r
126 @rem cross-compiles but NOT an ARM64 one. For ARM64 coverage, build the ARM64\r
127 @rem binaries with /fsanitize-coverage or use the x64 build for the coverage run.\r
128 winget install OpenCppCoverage.OpenCppCoverage\r
129 if "%IS_ARM64%"=="1" echo [setup-windows] NOTE: OpenCppCoverage is x86/x64-only - it cannot instrument ARM64 binaries. Run coverage against the x64 build.\r
130 \r
131 @rem --- WiX 5.0.2, pinned on purpose ---\r
132 @rem WiX packages our proprietary software into MSIs, and the version is pinned to keep that\r
133 @rem free of a fee. 5.0.2 is the last release distributed under the Microsoft Reciprocal\r
134 @rem License alone. From 6.0 onward the package also carries OSMFEULA.txt, an Open Source\r
135 @rem Maintenance Fee agreement: a monthly fee owed by anyone who uses the PREBUILT BINARIES\r
136 @rem as part of revenue-generating activity and has annual gross revenue >= US$10,000.\r
137 @rem\r
138 @rem It is a fee for the binaries, not a restriction on what we ship - MS-RL is file-scoped\r
139 @rem and never reached the MSIs WiX builds, under any version - but 5.0.2 owes nothing.\r
140 @rem The 6.x/7.x SOURCE is still MS-RL too, so self-compiling is another way out; a pin is\r
141 @rem the cheaper one. This replaces `winget install WiXToolset.WiXCLI`, which has no version\r
142 @rem selector and so installs the latest (7.0.0 today, EULA and all).\r
143 @rem\r
144 @rem PIN THE MSBUILD SIDE TOO. A .wixproj referencing WixToolset.Sdk without a version\r
145 @rem resolves to the latest - 7.x, same EULA - and nothing here constrains it. Pin it in the\r
146 @rem project: <Project Sdk="WixToolset.Sdk/5.0.2">.\r
147 @rem\r
148 @rem dotnet.exe is called by full path: winget put the SDK on the machine PATH a few lines\r
149 @rem ago, but this cmd session inherited its environment before that and cannot see it.\r
150 @rem install-then-update is for re-runs - install fails once the tool is there, and update\r
151 @rem then holds it at exactly 5.0.2 - which keeps this script idempotent like the rest.\r
152 @rem\r
153 @rem %ProgramFiles%\dotnet is the NATIVE SDK's root on every architecture, ARM64\r
154 @rem included - what winget just installed. An x64 SDK installed alongside it on\r
155 @rem an ARM64 box goes to %ProgramFiles%\dotnet\x64 instead, so fall back there\r
156 @rem for a machine that only ever got the x64 one. Prefer the native root: the\r
157 @rem ARM64 SDK runs the wix tool natively, and building an MSI is not something\r
158 @rem worth doing through emulation.\r
159 set "DOTNET_EXE=%ProgramFiles%\dotnet\dotnet.exe"\r
160 if not exist "%DOTNET_EXE%" set "DOTNET_EXE=%ProgramFiles%\dotnet\x64\dotnet.exe"\r
161 if not exist "%DOTNET_EXE%" echo [setup-windows] WARNING: dotnet.exe not found; skipping the WiX 5.0.2 pin.\r
162 if not exist "%DOTNET_EXE%" goto :after_wix\r
163 \r
164 "%DOTNET_EXE%" tool install --global wix --version 5.0.2 || "%DOTNET_EXE%" tool update --global wix --version 5.0.2\r
165 \r
166 @rem Report what the pin actually produced. By full path again, and because the shim lands in\r
167 @rem a directory this session's PATH predates: expect "5.0.2+<commit>", not 7.x.\r
168 "%USERPROFILE%\.dotnet\tools\wix.exe" --version\r
169 \r
170 :after_wix\r
171 \r
172 @rem ---------------------------------------------------------------------------\r
173 @rem No package manager needed for the Windows build\r
174 @rem\r
175 @rem Just:\r
176 @rem   cd windows && cmake -B build -G "Visual Studio 17 2022" -A x64 && cmake --build build --config Release\r
177 @rem\r
178 @rem -A selects the TARGET, independently of the host. On an ARM64 box the same\r
179 @rem generator cross-compiles all three, and MSVC's ARM64-hosted toolchain is used\r
180 @rem for each - nothing is emulated:\r
181 @rem   -A ARM64    native ARM64 binaries\r
182 @rem   -A x64      x64 binaries (run here under emulation)\r
183 @rem   -A Win32    x86 binaries\r
184 @rem Use the generator matching the Visual Studio the elevated half installed -\r
185 @rem "Visual Studio 17 2022" on x64, "Visual Studio 18 2026" on ARM64 - or just\r
186 @rem let CMake pick the newest it finds.\r
187 @rem ---------------------------------------------------------------------------\r
188 \r
189 @rem --- Elevated installs (Visual Studio, WDK, system tools) ---\r
190 @rem The elevated script runs in its own window and logs to setup-windows-uac.log.\r
191 @rem -PassThru + $p.ExitCode propagates its real exit code back through to ERRORLEVEL.\r
192 set "UAC_LOG=%~dp0setup-windows-uac.log"\r
193 if exist "%UAC_LOG%" del "%UAC_LOG%"\r
194 \r
195 powershell -NoProfile -Command "$p = Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','""%~dp0setup-windows-with-uac.ps1""' -Wait -PassThru; exit $p.ExitCode"\r
196 set "UAC_RC=%ERRORLEVEL%"\r
197 \r
198 @rem --- Surface the elevated session's output (its window has already closed) ---\r
199 if exist "%UAC_LOG%" (\r
200     echo.\r
201     echo ===== elevated setup log ^(%UAC_LOG%^) =====\r
202     type "%UAC_LOG%"\r
203     echo ===== end of elevated setup log =====\r
204 ) else (\r
205     echo [setup-windows] WARNING: no elevated log found at "%UAC_LOG%".\r
206     echo [setup-windows] The elevated window may have been cancelled at the UAC prompt.\r
207 )\r
208 \r
209 @rem --- Non-elevated PowerShell half ---\r
210 @rem WinMerge on the user PATH, BinSkim, and the global git config (identity +\r
211 @rem core.sshCommand -> a Win32-OpenSSH client, so git shares the Windows\r
212 @rem ssh-agent). EDIT THE GIT IDENTITY at the top of setup-windows-no-uac.ps1\r
213 @rem before the first run.\r
214 @rem\r
215 @rem Deliberately NOT elevated: every step writes per-user state (the HKCU PATH,\r
216 @rem the .gitconfig under %USERPROFILE%), which the elevated half would write to\r
217 @rem the administrator profile instead.\r
218 @rem\r
219 @rem AFTER the elevated half on purpose: core.sshCommand prefers the ssh.exe that\r
220 @rem half unpacks beside rsync.exe - a push through the in-box client is capped\r
221 @rem at ~17MB/s - and it can only prefer it once it is on disk. Run either way,\r
222 @rem including when the elevated half failed above: nothing here depends on it,\r
223 @rem and the fallback is the in-box client that Windows already has.\r
224 @rem\r
225 @rem Non-fatal: these are conveniences, and the elevated half is the part worth\r
226 @rem the UAC prompt. A failure warns and provisioning continues.\r
227 powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-no-uac.ps1"\r
228 if not "%ERRORLEVEL%"=="0" echo [setup-windows] WARNING: setup-windows-no-uac.ps1 reported a failure ^(see above^); continuing.\r
229 \r
230 if not "%UAC_RC%"=="0" (\r
231     echo.\r
232     echo [setup-windows] ELEVATED SETUP FAILED ^(exit code %UAC_RC%^). See log above.\r
233     exit /b %UAC_RC%\r
234 )\r
235 echo.\r
236 echo [setup-windows] Elevated setup completed successfully.\r
237 \r
238 @rem Removed: this doesn't work as well as I hoped, maybe try again later\r
239 @rem -- Install Headroom ---\r
240 @rem py -m pip install "headroom-ai[all]"\r
241 @rem npm install headroom-ai\r
242 \r
243 @rem Pillow publishes win_arm64 wheels alongside win_amd64, so `py` - the ARM64\r
244 @rem Python winget just installed - gets a native wheel and never falls back to\r
245 @rem building from source. Any package WITHOUT an ARM64 wheel does fall back, and\r
246 @rem that source build needs the MSVC toolchain the elevated half installs.\r
247 py -m pip install Pillow\r
248 \r
249 if "%IS_ARM64%"=="1" (\r
250     echo.\r
251     echo [setup-windows] ARM64 notes:\r
252     echo [setup-windows]   native ARM64: Git, Python, .NET SDK, PowerShell, VS Code, Windows Terminal,\r
253     echo [setup-windows]                 WinMerge, Brave, Sysinternals ^(the *64a.exe variants^),\r
254     echo [setup-windows]                 Claude Code, CMake, Ninja, and the SDK/WPT tools.\r
255     echo [setup-windows]   compilers   : MSVC and clang-cl from Visual Studio 2026, plus upstream\r
256     echo [setup-windows]                 LLVM - all three native ARM64, two different Clang majors.\r
257     echo [setup-windows]   emulated x64: iperf3, NASM, OpenCppCoverage, BinSkim, rsync.exe, AGI.\r
258     echo [setup-windows]   unavailable : VirtualBox, the Windows 7 x86 test VM, Intel VTune,\r
259     echo [setup-windows]                 and the v141 / Windows XP targeting toolset.\r
260 )\r