mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
7400179b5b
## 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. **CI** — `windows-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. --------- Co-authored-by: Ignacy Łątka <claude-hubert.gancarczyk@swmansion.com> Co-authored-by: Ignacy Łątka <latekvo@users.noreply.github.com>
70 lines
2.8 KiB
JavaScript
Executable File
70 lines
2.8 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
// Thin dispatcher exposed as the `argent-simulator-server` bin entry in
|
|
// package.json. Picks the platform-specific simulator-server binary at
|
|
// invocation time and execs it with the caller's args. Required because
|
|
// npm's `bin` field resolves to a single file regardless of host platform,
|
|
// but the binary itself is platform-specific (Mach-O for darwin, ELF for
|
|
// linux). The native-devtools-ios resolver uses the same per-platform
|
|
// subdirectory layout, so a stable layout lives in exactly one place.
|
|
|
|
const { spawn } = require("node:child_process");
|
|
const path = require("node:path");
|
|
const fs = require("node:fs");
|
|
|
|
// Mirrors hostPlatformKey() in @argent/native-devtools-ios: darwin ships a
|
|
// universal (lipo) binary, but Linux binaries are single-arch ELFs, so arm64
|
|
// Linux resolves to its own "linux-arm64" directory next to the x86_64 one
|
|
// ("linux"). Duplicated here because the dispatcher must stay a standalone
|
|
// file — it ships verbatim as the npm `bin` entry.
|
|
const platformKey =
|
|
process.platform === "linux" && process.arch === "arm64" ? "linux-arm64" : process.platform;
|
|
|
|
// PE `.exe` on Windows, extensionless ELF/Mach-O elsewhere. Mirrors
|
|
// simulatorServerBinaryName() in @argent/native-devtools-ios; inlined because
|
|
// this file ships verbatim as the npm `bin` entry and can't import.
|
|
const binaryName = process.platform === "win32" ? "simulator-server.exe" : "simulator-server";
|
|
|
|
const binary = path.join(__dirname, platformKey, binaryName);
|
|
if (!fs.existsSync(binary)) {
|
|
console.error(
|
|
`argent-simulator-server: no binary for platform "${platformKey}" at ${binary}.\n` +
|
|
`Supported hosts today: darwin, linux (x86_64 and arm64), win32.`
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const child = spawn(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
|
|
// Forward termination signals so a supervisor that signals only the dispatcher
|
|
// PID (systemd, `kill -TERM <pid>`, container stop) doesn't orphan the child.
|
|
// Ctrl+C in a TTY already broadcasts to the whole process group so the child
|
|
// receives it too — these handlers cover the non-TTY case where the parent
|
|
// would otherwise exit alone and leave the binary reparented to init.
|
|
/** @type {NodeJS.Signals[]} */
|
|
const FORWARDED_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
|
|
for (const sig of FORWARDED_SIGNALS) {
|
|
process.on(sig, () => {
|
|
if (!child.killed) {
|
|
try {
|
|
child.kill(sig);
|
|
} catch {
|
|
// Already exited between the signal arriving and us forwarding it.
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
child.on("exit", (code, signal) => {
|
|
if (signal) {
|
|
process.kill(process.pid, signal);
|
|
} else {
|
|
process.exit(code ?? 1);
|
|
}
|
|
});
|
|
child.on("error", (err) => {
|
|
console.error(`argent-simulator-server: failed to spawn ${binary}: ${err.message}`);
|
|
process.exit(1);
|
|
});
|