Skip to content

Windows support: Android + Chromium host control plane#421

Open
latekvo wants to merge 11 commits into
mainfrom
windows-support
Open

Windows support: Android + Chromium host control plane#421
latekvo wants to merge 11 commits into
mainfrom
windows-support

Conversation

@latekvo

@latekvo latekvo commented Jun 26, 2026

Copy link
Copy Markdown
Member

What

Makes Argent run on Windows. iOS Simulator is macOS-only (Apple), so on Windows that means the Android + Chromium host control plane. The blocker was twofold: (1) no Windows simulator-server binary, and (2) POSIX-only assumptions in the tool-server.

Companion radon PR builds the binary and runs the Android emulator E2E on Windows: software-mansion/radon#134.

Changes

Windows-aware tool-server / resolver / bundling

  • New shared commandOnPath() helper — where on Windows, command -v via /bin/sh on POSIX. android-binary, check-deps, and vega-cli previously hardcoded /bin/sh, which never matches on Windows.
  • android-binary: append .exe to adb/emulator in the $ANDROID_HOME fallback; probe %LOCALAPPDATA%\Android\Sdk (Android Studio's Windows default).
  • simulator-server resolver + dispatcher + bundle-tools + download-simulator-server.sh: ship and resolve simulator-server.exe on win32 (new simulatorServerBinaryName()), add win32 to the bundled host keys, add the simulator-server-argent-windows.exe download target with PE arch validation.
  • stop-metro: resolve listening PIDs via netstat -ano on Windows (lsof is POSIX-only and threw ENOENT).
  • adb.ts: AVD root resolution used $HOME + startsWith("/") (rejects C:\…); now os.homedir() + path.isAbsolute().
  • boot-device: the emulator was spawned detached with stdio:"ignore"; on Windows a detached emulator with NUL stdout/stderr never reaches sys.boot_completed (verified on windows-latest: the identical flag set boots fine with real handles, hangs with NUL). Redirect the detached emulator's output to a temp log file on win32. POSIX keeps "ignore".

Tests — cross-platform unit coverage for commandOnPath (both branches), win32 .exe Android resolution, win32 .exe simulator-server resolution, and a win32 AVD drive-path case; dep-gate tests now mock at the commandOnPath boundary so they're platform-agnostic.

CIwindows-e2e.yml (windows-chromium on windows-latest): runs the resolver/dep unit tests natively on Windows, confirms adb.exe resolution via the SDK root, then drives a real headless Chrome over CDP through the tool-server (discover → screenshot → describe → tap → observe the DOM mutation). Flow in scripts/ci/windows-chromium-e2e.mjs (also runnable locally / in Docker). The simulator-server.exe build + the full Android-emulator E2E on Windows live in radon CI (the binary's source is the private radon repo).

Verification (all green)

  • Chromium control plane on real Windows (windows-latest): ✓ discovered chromium-cdp-9222 → ✓ screenshot → ✓ describe → ✓ gesture-tap → ✓ DOM mutation observed. Resolver/dep unit tests run natively; adb.exe resolves via the SDK root (it's not on PATH on the runner — the gap the resolver closes).
  • Android emulator on real Windows (radon#134, windows-latest / WHPX): the real Argent path — boot-device{ booted: true } → screenshot via simulator-server.exe → gesture-tap — all green. (The boot-device fix above is what makes the detached emulator reach boot_completed.)
  • Local clean-room E2E: the Chromium flow runs green in a headless Linux Docker container (fresh npm ci, no host pollution).
  • No regressions: full tool-server suite (1624) + native-devtools-ios (16); ESLint/Prettier/typecheck clean; Wayland E2E (Linux AVD boot) and Vega VVD E2E still pass (they exercise the changed adb.ts / boot-device.ts / vega-cli.ts paths).

Notes

Android-emulator E2E needs WHPX nested virtualization — confirmed available on the current hosted windows-latest image (Server 2025); the emulator boots hardware-accelerated in ~3 min.

latekvo added 2 commits June 26, 2026 17:23
Argent's host-side control plane (Android + Chromium) had a handful of
POSIX-only assumptions that crashed or silently no-op'd on Windows. iOS
stays macOS-only (Apple restriction), so this targets the Android +
Chromium paths.

- Add a shared commandOnPath() helper using `where` on Windows and
  `command -v` via /bin/sh on POSIX; route android-binary, check-deps,
  and vega-cli through it (they previously hardcoded /bin/sh, which never
  matches on Windows).
- android-binary: append `.exe` to adb/emulator in the $ANDROID_HOME
  fallback and probe %LOCALAPPDATA%\\Android\\Sdk (Studio's Windows default).
- simulator-server resolver + dispatcher + bundle-tools + the
  download script: ship and resolve simulator-server.exe on win32
  (new simulatorServerBinaryName()); add win32 to the bundled host keys.
- stop-metro: resolve listening PIDs via `netstat -ano` on Windows
  (`lsof` is POSIX-only and threw ENOENT there).

Tests: cross-platform unit coverage for commandOnPath (where/command -v
branches), the win32 .exe android resolution, and win32 .exe
simulator-server path; update the dep-gate tests to mock at the
commandOnPath boundary so they're platform-agnostic.
…er.exe)

Two jobs on windows-latest, the substrate that makes 'Argent on Windows'
verifiable without local virtualization:

- windows-chromium: builds the workspace, runs the cross-platform
  resolver unit tests natively on Windows, confirms adb.exe resolution,
  then drives a real headless Chrome over CDP through the tool-server
  (discover → screenshot → describe → tap → observe the DOM mutation the
  tap causes). The flow is in scripts/ci/windows-chromium-e2e.mjs (also
  runnable locally on macOS/Linux) against scripts/ci/e2e-chromium-page.html.
- windows-simulator-server: builds the argent simulator-server.exe from
  radon source (cargo build --features argent,swdec,swenc — the same set
  the radon release ships), installs it into bin/win32, confirms the
  resolver finds it, and smoke-runs it to prove the PE executes natively.

Android-emulator E2E needs WHPX/HAXM (unavailable on hosted Windows
runners), so it's out of scope here; the Android host tooling (adb.exe +
the .exe that talks to it) is covered.
latekvo added 2 commits June 26, 2026 17:35
…don CI

- native-devtools-ios resolver tests: name the simulator-server fixture
  per-platform (simulator-server.exe on win32) so the two host-platform
  tests pass on a Windows runner, where the resolver correctly requires
  the .exe. Unchanged on macOS/Linux.
- windows-e2e.yml: drop the windows-simulator-server job. The .exe is
  built from the private radon repo, which the argent runner can't clone
  without a cross-repo token; its Windows build + smoke now lives in
  radon's simulator-server CI (build_argent_windows). Keeps the
  windows-chromium job — the host control-plane E2E — as the proof here.
windows-latest sets ANDROID_HOME but doesn't put platform-tools on PATH,
so a bare `adb version` fails — which is exactly the gap the resolver
closes. Run the adb.exe the resolver returns instead.
latekvo added 3 commits June 26, 2026 17:46
On Windows runners, Start-Process background processes don't reliably
survive into a later step (the tool-server was up in its own step but
gone by the next), so launch Chrome + the tool-server and run the E2E
within a single step where both stay in scope.
Review follow-ups:
- adb.ts avdRootCandidates/resolveAvdPath used process.env.HOME (unset on
  Windows) and a startsWith('/') gate that rejects C:\ / UNC paths, so
  resolveAvdPath always returned null on Windows and the default_boot
  snapshot pre-check silently fell back to cold boot. Use os.homedir() and
  path.isAbsolute(); add a win32-guarded test (POSIX behaviour unchanged,
  verified by the existing cases + full suite).
- download-simulator-server.sh: drop the loose '|PE32+ executable' arch-check
  fallback that would pass a mislabeled non-x86-64 PE; keep the x86-64 match.

Not changed: artifacts.ts tar (Windows 10 1803+/Server 2019+ ship tar.exe).
…og file

On Windows, boot-device spawned the emulator detached with stdio:"ignore";
the guest never reached sys.boot_completed (verified on windows-latest: the
identical flag set boots fine when given real stdout/stderr handles, but
hangs with NUL handles). Redirect the detached emulator's output to a
throwaway temp log on win32 so it has valid write handles. POSIX keeps
"ignore" (works there, and detaching outlives a tool-server restart).
@latekvo latekvo marked this pull request as ready for review June 30, 2026 13:20
latekvo added 4 commits June 30, 2026 15:36
…ggers

Extract the win32 `netstat -ano` -> listening-PID parsing out of
`listeningPids` into an exported pure `parseNetstatListeningPids` so the
Windows-only row matching (TCP/LISTENING filter, IPv4+IPv6 `:<port>`
suffix match with the colon guard against `:18081`, dedup) can be
unit-tested on any host. Runtime behavior is unchanged and the POSIX
lsof branch is untouched.

Add a unit test feeding realistic `netstat -ano` output (banner +
header, IPv4 and IPv6 LISTENING rows, ESTABLISHED rows, a UDP row, and a
neighbouring `:18081` row) and asserting the deduped PID set.

Add the Windows-relevant sources this PR changed (`adb.ts`,
`boot-device.ts`) to the windows-e2e `paths:` trigger so future edits
re-run the job, and reconcile the workflow header comment: WHPX is
available on windows-latest (Server 2025) and the emulator boots there
(matching boot-device.ts's "verified on windows-latest"); the full
Android E2E lives in radon CI only because simulator-server.exe can't be
built in this repo.
The netstat parser keyed off the literal State column value "LISTENING", but
Windows localizes that column (German "ABHÖREN", French "À L'ÉCOUTE"), so on a
non-English host the parser matched nothing and stop-metro silently no-opped
while Metro kept running. Identify a listener by its wildcard foreign endpoint
(0.0.0.0:0 / [::]:0 / *:*) instead — that is locale-independent, and an
ESTABLISHED connection (the tool-server's own CDP socket, which must not be
killed) always has a real remote endpoint there. Also read the PID from the
trailing column so a multi-word localized State can't shift it. Add a localized
(German/French) regression test.
…m dep check

Merging main surfaced launch-restart-tvos.test.ts, whose execFile mock returns
empty stdout on success. That satisfied the old inline `command -v` probe (which
only checked for no-throw), but this branch routes dependency probes through
`commandOnPath`, which treats empty stdout as "not on PATH" — so the xcrun
preflight threw DependencyMissingError on the CI runner. Echo a path from the
`command -v` / `where` probe (as the real tools do) so the dep resolves.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant