development environment: editors and shells, Python, the Visual Studio
toolchain (including the Clang and Windows XP targeting toolsets), the Windows
Driver Kit, Rust (rustup, with the stable MSVC toolchain for the host as the
-default), Android's `adb`, and a handful of analysis tools (Sysinternals,
-OpenCppCoverage, 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.
+default), Android Studio and a per-user Android SDK, Android's `adb`, and a
+handful of analysis tools (Sysinternals, OpenCppCoverage, 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.
**Nothing happens until you have seen the plan.** It first lists every step it
can take on this machine and lets you choose which ones to run — see
| `setup-windows.bat` | Entry point. Shows the plan and lets you choose, then runs the winget installs, launches the elevated half and prints its log, then runs the non-elevated script. |
| `setup-windows-plan.ps1` | The plan. Run by the batch file before anything else: lists every step for this machine, grouped and numbered, and takes your choice. Run it on its own with `-WhatIf` to preview; add `-HostArch ARM64 -Elevation NOLUA` to preview another kind of box. |
| `setup-windows-steps.ps1` | The catalog the other four read: every step's name, what it does, and where it cannot run and why. Defines functions only; it is dot-sourced, never run. |
-| `setup-windows-current.ps1` | Asked by the batch file before the Claude Code and NASM installs: is what is on the box already current? Those are the two installs that would otherwise run in full every time — see [Re-running](#re-running). |
+| `setup-windows-current.ps1` | Asked by the batch file before the Claude Code, NASM and Android Studio installs: is what is on the box already current? Those are the installs that would otherwise run in full every time — see [Re-running](#re-running). |
| `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; it lists its steps and asks first. |
| `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; it lists its steps and asks first. |
| `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. |
[x] 2 Brave Brave.Brave
...
E Elevated setup (setup-windows-with-uac.ps1) - one UAC prompt, runs in its own window
- [x] 24 OpenSshClient OpenSSH Client capability (ssh.exe, and the libcrypto.dll rsync's ssh.exe needs)
+ [x] 25 OpenSshClient OpenSSH Client capability (ssh.exe, and the libcrypto.dll rsync's ssh.exe needs)
...
- [x] 28 VisualStudio Visual Studio 2022 Community, C++: MSVC for x86/x64 + ARM64, SDK 26100 (several GB)
+ [x] 29 VisualStudio Visual Studio 2022 Community, C++: MSVC for x86/x64 + ARM64, SDK 26100 (several GB)
...
- 42 of 42 steps selected.
+ 44 of 44 steps selected.
Enter runs the selected steps. To change them, type step numbers or ranges
(3 5-7), step names, or a group letter (U M E C) to switch them on or off;
| `ClaudeCode` | Anthropic's installer always downloads and reinstalls, so `setup-windows-current.ps1` asks first: is `%USERPROFILE%\.local\bin\claude.exe` the latest release — the version the installer's own endpoint names — and the build for this host? Only both skip it. |
| `NASM` | Its per-user fallback install registers nothing, so winget cannot see it and would rerun the installer every time. `setup-windows-current.ps1` compares that `nasm.exe` with the version winget offers. A machine-wide NASM is left to winget. |
| `Rustup` | Needs no help: rustup registers itself under the manifest's product code and rewrites its version when it updates itself, so winget sees what is really there. |
+| `AndroidStudio` | Installed only when it is missing. Studio updates itself in the app, and winget's package is marked never to upgrade it; the check keeps winget from installing over a Studio it cannot match to its package, as on a box where Studio was installed some other way. |
| `WiX` | Skipped when 5.0.2 is already in the .NET global tool store. |
| `OpenSshClient`, `OpenSshServer` | Installed only when the capability is not. |
| `Rsync` | The release's `.sha256` is fetched first and compared with the hash recorded when the files were installed (`rsync-windows.sha256`). The zip is downloaded only when they differ or a file is missing. |
| `Wdk`, `Wpt` | Skipped when the kit, or the toolkit, is already there. |
| `BinSkim` | Asks NuGet for the newest stable version before downloading a package well over 100 MB; `nupkg-version.txt` beside the tool records what is installed. |
| `RustToolchain` | `rustup toolchain install` updates stable when there is a newer one and otherwise reports it unchanged. |
+| `AndroidSdk` | The command-line tools are re-downloaded only when Google's package index has a newer revision than the installed one; `android sdk install` updates the platform, build-tools and platform-tools only when they are out of date. A re-run with nothing new takes seconds. |
| `Pillow` | `pip install` leaves an installed Pillow alone. It does not upgrade it either. |
| the rest | Configuration: `PATH` entries are added only when missing, and settings are written with the values they already have. |
emulation on ARM64, which every Windows on Arm has; the audit lists it as
expected. It stays on ARM64 for the same reason `iperf3` does: it is bound by
the link to the device, not by the CPU.
+- **Android Studio and the Android SDK** are separate steps. Studio
+ (`Google.AndroidStudio`) is machine-wide — its installer elevates itself — and
+ is installed only when missing, since it updates itself. The SDK is per-user,
+ in Studio's default `%LOCALAPPDATA%\Android\Sdk` (or wherever `ANDROID_HOME`
+ already points): the command-line tools, platform-tools, and the newest
+ stable platform and build-tools from Google's package index, read at run
+ time. That is what Studio's first-run wizard would set up, and it is enough
+ to build: Gradle fetches whatever else a project asks for. The step sets
+ `ANDROID_HOME`, puts the command-line tools on the user `PATH`, and points
+ `JAVA_HOME` at Studio's bundled JDK for a command-line `gradlew` when there is
+ no `JAVA_HOME` already. The SDK licences are accepted on your behalf — the
+ plan says so before anything runs. Both are x64-only: Studio has no ARM64
+ build, and the SDK's Windows build tools are x86/x64. Three things about
+ the current command-line tools (23.0), found when this step was written:
+ - `sdkmanager` is deprecated. It is now a shim over the new **Android CLI**
+ (`android.exe`), which on first use installs itself into
+ `%USERPROFILE%\.android` — about 90 MB in `bin\`, and a `cli\` folder with
+ its own Java runtime. It also keeps a spool of **usage metrics** there and
+ uploads them; no opt-out was found. The step calls the CLI directly.
+ - **CLI 1.0.16406183 crashes on the way out of every `sdk` command**
+ (`0xC0000409`) on this Windows 10, after the work is done. So the step does
+ not trust its exit code: each package must be present afterwards, and a
+ crash with everything in place is printed as a note, not a failure.
+ - Windows PowerShell 5.1's progress bar made the 148 MB command-line tools
+ download take about ten minutes; with it off, the whole first install took
+ under a minute. The BinSkim download now skips the progress bar for the
+ same reason.
- The scripts were extracted from a native Windows project, so the component
selection is tuned for that: Spectre-mitigated runtimes, the v141/XP toolset,
and driver-kit headers. Trim the component lists in the `.ps1` if you don't
| 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 |
+| Android Studio (`Google.AndroidStudio`) | `setup-windows.bat` | Google publishes no Windows ARM64 build |
+| Android SDK | `setup-windows-no-uac.ps1` | its Windows build tools are x86/x64 only, as is Android Studio |
x64 keeps every one of them. The decision is made in one place,
`setup-windows-steps.ps1` (`-X64Only`, with the reason): the plan shows each as
| **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`), rustup and **Rust** (`stable-aarch64-pc-windows-msvc`), the in-box OpenSSH client, and the Windows Performance Toolkit. |
| **Emulated** | `iperf3` (x64; network-bound), `adb` and `fastboot` (x86 — Google ships no other Windows build; bound by the USB/TCP link to the device), and the ADK *installer* (x64; 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. |
+| **Not installed** | Brave, WinMerge, upstream LLVM, NASM, Android GPU Inspector, OpenCppCoverage, BinSkim, VS `clang-cl`, the WDK, and the Android SDK — see the table above. |
+| **Unavailable** | VirtualBox, the Windows 7 x86 test VM, Intel VTune, Android Studio, and the `v141` / Windows XP targeting toolset. |
### The architecture audit
<#
setup-windows-current.ps1 - is this step's program already current?
- setup-windows.bat asks this before the two installs that would otherwise run
- in full on every re-run, because neither the installer nor winget can tell
- that what is on the box is already the latest:
+ setup-windows.bat asks this before the installs that would otherwise run in
+ full on every re-run, because neither the installer nor winget can tell that
+ what is on the box is already the latest:
- ClaudeCode Anthropic's installer script always downloads the release
- (a couple of hundred MB) and reinstalls it; it has no "already
- current" check of its own.
- NASM its installer falls back to a per-user directory when it cannot
- write to Program Files, and registers nothing there - so winget
- cannot see it, and reruns the installer every time.
+ ClaudeCode Anthropic's installer script always downloads the release
+ (a couple of hundred MB) and reinstalls it; it has no
+ "already current" check of its own.
+ NASM its installer falls back to a per-user directory when it
+ cannot write to Program Files, and registers nothing there -
+ so winget cannot see it, and reruns the installer every time.
+ AndroidStudio an install that did not come from winget is one winget
+ cannot match to its package, so it would install over it.
+ And Android Studio updates itself; winget never upgrades it.
Exit code 0 means already current: skip the install. Anything else means go
ahead as before - and that includes this script failing (no network, a version
"Re-running" note lists how each one decides.
#>
param(
- [Parameter(Mandatory)] [ValidateSet('ClaudeCode', 'NASM')] [string] $Step,
+ [Parameter(Mandatory)] [ValidateSet('ClaudeCode', 'NASM', 'AndroidStudio')] [string] $Step,
# As setup-windows.bat worked it out, from the registry: AMD64, ARM64 or x86.
[string] $HostArch
)
$true
}
+function Test-AndroidStudioCurrent {
+ # Installed at all counts as current, because Android Studio keeps itself
+ # current: it updates in the app (Help > Check for Updates), and the winget
+ # package is published with UpgradeBehavior: deny and RequireExplicitUpgrade,
+ # so winget would not upgrade it anyway. The check matters for an install
+ # that did not come from winget - measured on the x64 laptop, where winget
+ # lists Studio only as "ARP\Machine\X64\Android Studio", unmatched to
+ # Google.AndroidStudio - since `winget install` would then run the installer
+ # over it. Looked for both ways: the Add/Remove Programs entry its installer
+ # writes, and studio64.exe where that installer puts it.
+ $arp = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
+ 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
+ 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue |
+ Where-Object { $_.DisplayName -eq 'Android Studio' }
+ $exe = @((Join-Path $env:ProgramFiles 'Android\Android Studio\bin\studio64.exe'),
+ (Join-Path $env:LOCALAPPDATA 'Programs\Android Studio\bin\studio64.exe')) | Where-Object { Test-Path $_ }
+ if (-not $arp -and -not $exe) { Say 'AndroidStudio: not installed yet.'; return $false }
+ $ver = @($arp)[0].DisplayVersion
+ Say "AndroidStudio: $(if ($ver) { $ver } else { 'an existing install' }) is already here, and it updates itself (Help > Check for Updates); not reinstalling."
+ $true
+}
+
try {
$current = switch ($Step) {
- 'ClaudeCode' { Test-ClaudeCodeCurrent }
- 'NASM' { Test-NasmCurrent }
+ 'ClaudeCode' { Test-ClaudeCodeCurrent }
+ 'NASM' { Test-NasmCurrent }
+ 'AndroidStudio' { Test-AndroidStudioCurrent }
}
} catch {
Say "${Step}: the version check failed ($($_.Exception.Message)); installing as usual."
half unpacks beside rsync.exe if it is there, the in-box one otherwise
- Rust's stable MSVC toolchain for this host - x86_64 on x64, aarch64 on
ARM64 - installed through rustup and made the default
+ - The Android SDK, per-user in %LOCALAPPDATA%\Android\Sdk (x64 only): the
+ command-line tools, platform-tools and the newest stable platform and
+ build-tools, SDK licences accepted, ANDROID_HOME set - and JAVA_HOME, to
+ Android Studio's bundled JDK, when there is none
- An architecture audit: the real PE machine type of every tool this box
provisions, resolved the way a shell would. Informational, never fatal.
Anything running emulated without a listed reason is called out.
default { $null }
}
+# The Android SDK: where Android Studio puts it by default, unless ANDROID_HOME
+# already names one. "Latest" is read from Google's package index at run time,
+# the same index sdkmanager and Android Studio's SDK Manager read.
+$AndroidSdkRoot = if ($env:ANDROID_HOME) { $env:ANDROID_HOME } else { Join-Path $env:LOCALAPPDATA 'Android\Sdk' }
+$AndroidRepo = 'https://dl.google.com/android/repository/'
+$AndroidIndex = $AndroidRepo + 'repository2-3.xml'
+
function Write-Step([string]$Msg) {
Write-Host "`n==> $Msg" -ForegroundColor Cyan
}
# newest at the moment of the request.
Write-Step 'BinSkim'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+ # No progress bar: Windows PowerShell 5.1 redraws it per chunk, and on a
+ # download this size that, not the network, is the wait - see
+ # Install-AndroidSdk for the measurement. Local to this function.
+ $ProgressPreference = 'SilentlyContinue'
$exe = Join-Path $BinSkimDir 'BinSkim.exe'
$marker = Join-Path $BinSkimDir 'nupkg-version.txt'
}
}
+function Set-UserEnvironmentVariable([string] $Name, [string] $Value) {
+ # HKCU, like Add-ToUserPath, so it outlives the script; written only when it
+ # differs, so a re-run changes nothing.
+ if ([Environment]::GetEnvironmentVariable($Name, 'User') -eq $Value) {
+ Write-Host " $Name is already $Value"
+ return
+ }
+ [Environment]::SetEnvironmentVariable($Name, $Value, 'User')
+ Write-Host " Set $Name = $Value (restart your shell to pick it up)."
+}
+
+function Get-AndroidRevision($Package) {
+ # A package's revision from the index, as a [version]: <major>, optional
+ # <minor> and <micro>. [int] of a missing element is 0.
+ $r = $Package.revision
+ [version]('{0}.{1}.{2}' -f [int]$r.major, [int]$r.minor, [int]$r.micro)
+}
+
+function ConvertTo-Revision([string] $Text) {
+ # "23.0" as the same three-part [version] the index gives, so the two
+ # compare. [version]'23.0' has no third part - it reads as -1 - and so
+ # counted as OLDER than 23.0.0: an installed, current copy looked stale.
+ $parts = @($Text.Trim() -split '\.' | Where-Object { $_ -ne '' })
+ while ($parts.Count -lt 3) { $parts += '0' }
+ [version]($parts[0..2] -join '.')
+}
+
+function Invoke-AndroidCli {
+ # The Android CLI (cmdline-tools\latest\bin\android.exe) against this SDK,
+ # its output shown as it comes - a first install downloads well over 100 MB
+ # and should not look stalled. Returns the exit code, $null if it would not
+ # start. EAP Continue for the call, as for the other native tools here.
+ #
+ # stdin is one empty line, so it is closed rather than the console: a
+ # prompt nobody expected gets "no" instead of an unattended run waiting on
+ # it forever.
+ param([string[]] $Arguments)
+ $cli = Join-Path $AndroidSdkRoot 'cmdline-tools\latest\bin\android.exe'
+ $prevEap = $ErrorActionPreference
+ $ErrorActionPreference = 'Continue'
+ try {
+ $global:LASTEXITCODE = $null
+ # Its stderr becomes error records under 2>&1 in Windows PowerShell,
+ # which would print as red "NativeCommandError" noise; back to plain
+ # lines first. The one JVM warning dropped is the bundled Java noting,
+ # on every call, a flag this Windows 10 does not support.
+ '' | & $cli "--sdk=$AndroidSdkRoot" @Arguments 2>&1 | ForEach-Object { "$_" } |
+ Where-Object { $_ -notmatch 'UseAllWindowsProcessorGroups flag is not supported' } | Out-Host
+ $LASTEXITCODE
+ } finally {
+ $ErrorActionPreference = $prevEap
+ }
+}
+
+function Install-AndroidSdk {
+ # The Android SDK for building apps, per-user, in Android Studio's default
+ # location (%LOCALAPPDATA%\Android\Sdk) unless ANDROID_HOME names another:
+ # the command-line tools, platform-tools, and the newest stable platform and
+ # build-tools. That is what Android Studio's first-run wizard would set up,
+ # done without the wizard - on the x64 laptop Studio was installed and that
+ # wizard had never run, so there was no SDK at all. Gradle fetches any other
+ # platform or build-tools a project asks for by itself, once the licences are
+ # accepted, so this is the floor rather than the whole set.
+ #
+ # "Newest stable" comes from Google's package index, read here each run:
+ # channel-0 (stable) and no <preview> revision, the platform by API level
+ # (android-37.2 beats android-37.1 beats android-36), build-tools by version.
+ #
+ # THE COMMAND-LINE TOOLS ARE NOT WHAT THEY WERE. From 23.0, sdkmanager is
+ # deprecated and sdkmanager.bat is a shim over the new Android CLI
+ # (cmdline-tools\latest\bin\android.exe, `android sdk install ...`), which on
+ # first use installs itself into %USERPROFILE%\.android (bin\android-cli.exe,
+ # about 90 MB, and cli\ - a bundled Java runtime, and a metrics spool). So
+ # the CLI is called directly here, with its own "platforms/android-37.2"
+ # package paths: no deprecated shim, and no cmd parsing to split the old
+ # "platforms;android-37.2" at its semicolon. `sdk install` installs a
+ # package or, if it is there, updates it to the latest - so it is also the
+ # re-run path, downloading nothing when everything is current.
+ #
+ # ITS EXIT CODE IS NOT TRUSTED. CLI 1.0.16406183 finishes every `sdk`
+ # command and then crashes on the way out, 0xC0000409 (a fail-fast in
+ # ucrtbase) - on the x64 laptop's Windows 10, every time, in a console or
+ # not, while `--version` and `info` exit 0. So success is judged by the
+ # result: each package asked for must have the package.xml the CLI writes
+ # once a package is complete. A nonzero exit with everything present is a
+ # note, not a failure; anything missing is a failure, whatever the exit.
+ #
+ # The command-line tools themselves are bootstrapped from the index's own
+ # "cmdline-tools;latest" archive (SHA-1 from the index), and replaced only
+ # when the index has a newer revision than the one in source.properties.
+ # The CLI cannot do that for them: the archive calls itself
+ # cmdline-tools;23.0, a fixed version, so it would never "update" it.
+ #
+ # The SDK licences are accepted on your behalf; the plan says so before
+ # anything runs. The CLI does it as it installs - sdkmanager --licenses is
+ # now a no-op - writing licenses\android-sdk-license. Gradle needs it to
+ # build, and to fetch what a project asks for.
+ #
+ # The CLI brings its own Java, so the SDK needs no JDK. Gradle does: when
+ # there is no JAVA_HOME, the one Android Studio bundles (jbr) becomes the
+ # user's, so a command-line gradlew works. An existing JAVA_HOME is never
+ # changed.
+ Write-Step "Android SDK in $AndroidSdkRoot"
+
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+ # No progress bar for the downloads. Windows PowerShell 5.1's
+ # Invoke-WebRequest redraws it per chunk, and that - not the network - is
+ # what a large download then waits on: measured on the x64 laptop, the
+ # 148 MB command-line tools took about ten minutes and five minutes of CPU
+ # with it on. Local to this function, so nothing else changes.
+ $ProgressPreference = 'SilentlyContinue'
+ [xml]$index = (Invoke-WebRequest -UseBasicParsing -Uri $AndroidIndex).Content
+ $stable = @($index.DocumentElement.remotePackage |
+ Where-Object { $_.channelRef.ref -eq 'channel-0' -and -not $_.revision.preview })
+
+ # --- the command-line tools: bootstrapped, and kept current, from the index ---
+ $ct = @($stable | Where-Object { $_.path -eq 'cmdline-tools;latest' })[0]
+ if (-not $ct) { throw "No stable cmdline-tools;latest in $AndroidIndex." }
+ $ctWant = Get-AndroidRevision $ct
+ $ctDir = Join-Path $AndroidSdkRoot 'cmdline-tools\latest'
+ $ctHave = $null
+ $props = Join-Path $ctDir 'source.properties'
+ if (Test-Path $props) {
+ $m = [regex]::Match((Get-Content $props -Raw), '(?m)^Pkg\.Revision=(\d+(\.\d+){0,2})')
+ if ($m.Success) { $ctHave = ConvertTo-Revision $m.Groups[1].Value }
+ }
+ if ($ctHave -and $ctHave -ge $ctWant) {
+ Write-Host " command-line tools $ctHave are current."
+ } else {
+ $archive = @($ct.archives.archive | Where-Object { $_.'host-os' -eq 'windows' })[0]
+ $sum = $archive.complete.checksum
+ $sha1 = if ($sum -is [string]) { $sum } else { $sum.'#text' }
+ $url = $AndroidRepo + $archive.complete.url
+ Write-Host " command-line tools $(if ($ctHave) { "$ctHave -> " })$ctWant; downloading $($archive.complete.url)."
+ # PATH LENGTH. The archive nests deep - a guava jar in it sits well over
+ # 150 characters below its top - and Windows PowerShell 5.1 is bound by
+ # the 260-character MAX_PATH. So the temp directory's name is 8
+ # characters: %TEMP%\xxxxxxxx\cmdline-tools\... is then no deeper than
+ # %LOCALAPPDATA%\Android\Sdk\cmdline-tools\latest\..., and whenever the
+ # SDK's own path fits, unpacking it does too. (A 43-character name here
+ # made the unpack deeper than the SDK it was for.)
+ $tmp = Join-Path $env:TEMP ([guid]::NewGuid().ToString('N').Substring(0, 8))
+ New-Item -ItemType Directory -Force -Path $tmp | Out-Null
+ try {
+ $zip = Join-Path $tmp 'ct.zip'
+ Invoke-WebRequest -UseBasicParsing -Uri $url -OutFile $zip
+ $got = (Get-FileHash $zip -Algorithm SHA1).Hash.ToLower()
+ if ($sha1 -and $got -ne $sha1.ToLower()) { throw "SHA-1 mismatch for $url`: expected $sha1, got $got" }
+ Expand-Archive -Path $zip -DestinationPath $tmp -Force
+ # The archive holds a top-level cmdline-tools\ directory; the SDK
+ # layout wants it at cmdline-tools\latest. Replaced wholesale, as
+ # BinSkim is, so files dropped between releases do not linger - but
+ # the old copy is RENAMED aside first and deleted last. A rename is
+ # one operation whatever the depth below it; deleting walks every
+ # file and can trip over a path that is too long. Done this way, a
+ # delete that fails leaves a stray folder to report, not an SDK
+ # with no command-line tools in it. The aside name is six
+ # characters, like "latest", so the delete is no deeper either.
+ New-Item -ItemType Directory -Force -Path (Split-Path $ctDir) | Out-Null
+ $old = $null
+ if (Test-Path $ctDir) {
+ $old = Join-Path (Split-Path $ctDir) ('old' + [guid]::NewGuid().ToString('N').Substring(0, 3))
+ Rename-Item -Path $ctDir -NewName (Split-Path $old -Leaf)
+ }
+ Move-Item -Path (Join-Path $tmp 'cmdline-tools') -Destination $ctDir
+ if ($old) {
+ try { Remove-Item -Recurse -Force $old }
+ catch { Write-Warning "The new command-line tools are in place, but the old copy could not be removed: $old ($($_.Exception.Message))" }
+ }
+ } finally {
+ Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
+ }
+ }
+
+ # --- the newest stable platform and build-tools, by the index ---
+ $platform = @($stable | ForEach-Object {
+ $m = [regex]::Match($_.path, '^platforms;android-(\d+)(?:\.(\d+))?$')
+ if ($m.Success) {
+ [pscustomobject]@{ Path = $_.path; Api = [version]('{0}.{1}' -f $m.Groups[1].Value, [int]$m.Groups[2].Value) }
+ }
+ } | Sort-Object Api)[-1].Path
+ $buildTools = @($stable | Where-Object { $_.path -match '^build-tools;\d+(\.\d+)*$' } |
+ Sort-Object { [version]($_.path -replace '^build-tools;', '') })[-1].path
+ if (-not $platform -or -not $buildTools) { throw "Could not find a stable platform and build-tools in $AndroidIndex." }
+
+ # The index names packages "platforms;android-37.2"; the CLI, and the SDK's
+ # directories, use "platforms/android-37.2".
+ $packages = @('platform-tools', ($platform -replace ';', '/'), ($buildTools -replace ';', '/'))
+ Write-Host " installing or updating: $($packages -join ', ')"
+ $rc = Invoke-AndroidCli -Arguments (@('sdk', 'install') + $packages)
+ $missing = @($packages | Where-Object { -not (Test-Path (Join-Path $AndroidSdkRoot (($_ -replace '/', '\') + '\package.xml'))) })
+ if ($missing) {
+ $why = if ($null -eq $rc) { 'it would not start' } else { "exit $rc" }
+ throw "android sdk install left out $($missing -join ', ') ($why)"
+ }
+ if ($null -eq $rc) {
+ Write-Warning 'The Android CLI would not start; the packages already installed were left as they were, not updated.'
+ } elseif ($rc -ne 0) {
+ Write-Host (" note: the Android CLI exited {0} (0x{0:X8}) after finishing - its crash on the way out, not a failed" -f $rc) -ForegroundColor DarkGray
+ Write-Host ' install: every package asked for is in place.' -ForegroundColor DarkGray
+ }
+
+ # Report what is there now, from each package's own source.properties.
+ foreach ($pkg in $packages) {
+ $p = Join-Path $AndroidSdkRoot (($pkg -replace '/', '\') + '\source.properties')
+ $rev = if (Test-Path $p) { [regex]::Match((Get-Content $p -Raw), '(?m)^Pkg\.Revision=(.*)$').Groups[1].Value.Trim() } else { '?' }
+ Write-Host (' {0,-28} {1}' -f $pkg, $rev)
+ }
+
+ Set-UserEnvironmentVariable 'ANDROID_HOME' $AndroidSdkRoot
+
+ # JAVA_HOME, for Gradle - only when there is none, and only to Android
+ # Studio's bundled JDK. Without Studio this just says what is missing.
+ if (-not [Environment]::GetEnvironmentVariable('JAVA_HOME', 'User') -and
+ -not [Environment]::GetEnvironmentVariable('JAVA_HOME', 'Machine')) {
+ $jbr = @((Join-Path $env:ProgramFiles 'Android\Android Studio\jbr'),
+ (Join-Path $env:LOCALAPPDATA 'Programs\Android Studio\jbr') |
+ Where-Object { Test-Path (Join-Path $_ 'bin\java.exe') })[0]
+ if ($jbr) {
+ Set-UserEnvironmentVariable 'JAVA_HOME' $jbr
+ } else {
+ Write-Warning 'No JAVA_HOME, and no Android Studio JDK to point it at: a command-line gradlew will need one. Install Android Studio (the AndroidStudio step) or a JDK.'
+ }
+ }
+
+ # The Android CLI (android), and the tools beside it (lint, d8, r8,
+ # apkanalyzer, the sdkmanager/avdmanager shims), in any shell.
+ # platform-tools is NOT added: the PlatformTools step already puts adb on the
+ # PATH, and two adb.exe on it would only make which one answers depend on
+ # the order.
+ Add-ToUserPath (Join-Path $ctDir 'bin')
+}
+
function Get-PEMachine {
# Architecture of a PE, read straight from the COFF header: the 2 bytes at
# the e_lfanew offset + 4. Cheap, and it answers the only question that
NinjaPath = { Set-NativeNinjaFirst }
LlvmPath = { Add-LlvmToUserPath }
RustToolchain = { Set-RustDefaultToolchain }
+ AndroidSdk = { Install-AndroidSdk }
ArchAudit = { Invoke-ArchAudit }
}
New-SetupStep CMake M bat -Admin -Package Kitware.CMake -Text 'standalone, so a plain cmake works in any shell'
New-SetupStep AndroidGPUInspector M bat -Admin -Package Google.AndroidGPUInspector `
-X64Only 'x64-only build, and it profiles Android devices, not this box'
+ New-SetupStep AndroidStudio M bat -Admin -Package Google.AndroidStudio -Text 'only if missing; it updates itself' `
+ -X64Only 'Google publishes no Windows ARM64 build of Android Studio'
New-SetupStep OpenCppCoverage M bat -Admin -Package OpenCppCoverage.OpenCppCoverage -Text 'native line coverage' `
-X64Only 'it steps x86/x64 instructions, so it cannot cover an ARM64 binary'
New-SetupStep WiX M bat -Needs DotNetSdk `
New-SetupStep LlvmPath C user -Needs LLVM -Text 'upstream LLVM on the user PATH' `
-X64Only 'LLVM is not installed on ARM64'
New-SetupStep RustToolchain C user -Needs Rustup, VisualStudio -Text $rust
+ New-SetupStep AndroidSdk C user -Needs AndroidStudio `
+ -Text 'Android SDK, per-user: latest platform + build-tools; accepts the SDK licences' `
+ -X64Only 'its Windows build tools are x86/x64 only, as is Android Studio'
New-SetupStep ArchAudit C user -Text 'architecture audit: native or emulated, for every tool (read-only)'
# --- setup-windows.bat, after both halves ---
@rem VS Clang/LLVM component with-uac see $ClangComponents\r
@rem Windows Driver Kit with-uac see the WDK step\r
@rem BinSkim no-uac NuGet publishes win-x64 only\r
+@rem Google.AndroidStudio here no Windows ARM64 build\r
+@rem Android SDK no-uac Windows build tools are x86/x64 only\r
@rem\r
@rem The decision itself is made in ONE place, setup-windows-steps.ps1, which\r
@rem marks each of these -X64Only with its reason. The plan shows them as [-]\r
call :winget CMake Kitware.CMake\r
call :winget AndroidGPUInspector Google.AndroidGPUInspector\r
\r
+@rem --- Android Studio ---\r
+@rem\r
+@rem Machine-wide: the manifest's NSIS installer elevates itself and installs to\r
+@rem %ProgramFiles%\Android\Android Studio. x64 only - there is no ARM64 build -\r
+@rem so the plan keeps it off ARM64.\r
+@rem\r
+@rem Installed only when it is missing, which setup-windows-current.ps1 decides.\r
+@rem Android Studio updates itself, in the app, and the winget package is published\r
+@rem with UpgradeBehavior: deny and RequireExplicitUpgrade, so winget would never\r
+@rem upgrade it anyway. What the check prevents is the other case: a Studio that\r
+@rem was installed some other way - as on the x64 laptop, where winget lists it\r
+@rem only by its Add/Remove Programs key - is one winget cannot match to the\r
+@rem package, so `winget install` would run the installer over it.\r
+@rem\r
+@rem It is also where the per-user Android SDK step gets its JDK: Studio bundles\r
+@rem one (jbr), and the SDK's sdkmanager and Gradle both need Java.\r
+call :want AndroidStudio || goto :after_studio\r
+"%PS64%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0setup-windows-current.ps1" -Step AndroidStudio -HostArch "%HOST_ARCH%"\r
+if not "%ERRORLEVEL%"=="0" call :winget AndroidStudio Google.AndroidStudio\r
+:after_studio\r
+\r
@rem OpenCppCoverage: native (PE) line coverage for the C++ binaries. run-coverage-occ.py drives the\r
@rem pytest suite under it to produce an HTML report (the binaries under test build with PDBs,\r
@rem which it reads). The installer elevates via UAC.\r
echo [setup-windows] compiler by design; clang-cl and upstream LLVM are x64-box only.\r
echo [setup-windows] Rust stable-aarch64-pc-windows-msvc, native, linking through MSVC.\r
echo [setup-windows] emulated : iperf3 ^(x64^) and adb ^(x86^), both bound by their link, not the CPU.\r
- echo [setup-windows] not here : Brave, WinMerge, LLVM, NASM, AGI, OpenCppCoverage, BinSkim\r
- echo [setup-windows] and the WDK - dropped on ARM64; the plan above says why.\r
- echo [setup-windows] unavailable : VirtualBox, the Windows 7 x86 test VM, Intel VTune,\r
+ echo [setup-windows] not here : Brave, WinMerge, LLVM, NASM, AGI, OpenCppCoverage, BinSkim,\r
+ echo [setup-windows] the WDK and the Android SDK - dropped on ARM64; the plan above says why.\r
+ echo [setup-windows] unavailable : VirtualBox, the Windows 7 x86 test VM, Intel VTune, Android Studio,\r
echo [setup-windows] and the v141 / Windows XP targeting toolset.\r
)\r
@rem The list above is what this script PROVIDES on ARM64, not necessarily what\r