fix(pm): prepare the Windows ARM64 compiler environment for every source dependency build

cryptography ships no win_arm64 wheel, so every Windows ARM64 venv sync
compiles it from the sdist and needs MSVC, Clang, Rust and static OpenSSL.
Only setup-hermes.ps1 (and so activate.ps1) prepared that environment,
between a `pm install --tools-only` and the real sync. install.ps1,
`hermes update` and repair ran the same sync without it and failed in
openssl-sys.

PM owns the sync, so PM prepares it. pm/native_build.py holds the adapter
(moved from scripts/build/windows_deps.py) plus source_build_environment(),
which prepares only on win32-arm64 when the synced project carries the
provider script. A payload has prebuilt dependencies and needs no compiler.
VenvPackage.apply and build_environment pass the result to uv children
only. It carries the bridged pip index settings, which managed_environment
applies only to the ambient environment. The state root stays the store
parent, so existing vcpkg/OpenSSL builds are reused.

setup-hermes.ps1 collapses to one `pm install`: the tools-only split existed
only for this preparation, and pm install already puts its tools on PATH
before the venv sync (pm/cli.py activate check).

Not yet verified live on Windows ARM64.
This commit is contained in:
ethernet
2026-09-23 16:07:47 -04:00
parent fd49308813
commit c98bdb77f5
13 changed files with 94 additions and 77 deletions

View File

@@ -288,8 +288,10 @@ save. The packaged `uv-cache/` is a copy, not the writable build cache.
### Windows ARM64 build prerequisites
`scripts/windows-build-deps.ps1` owns Visual Studio ARM64, Clang, Rust, and
static OpenSSL preparation. Source setup calls its initializer. Native build
adapters use `scripts/build/windows-deps.ps1` through `windows_deps.py`, before
static OpenSSL preparation. PM calls it through `pm/native_build.py` before any
dependency build from a checkout, so every source install path gets it. Native
build adapters use the same `scripts/build/windows-deps.ps1` entrypoint through
`pm.native_build`, before
isolating HOME or compiling Node/Python dependencies. CI uses the same script
through `setup-windows-build-deps`, with an OpenSSL cache outside the product.
The product compilers and assembler do not install these prerequisites.

View File

@@ -1,29 +0,0 @@
"""Prepare native Windows build inputs without changing the caller's process."""
from __future__ import annotations
from collections.abc import Mapping
import json
from pathlib import Path
import shutil
import subprocess
import tempfile
def prepare_windows_environment(*, source: Path, state: Path, env: Mapping[str, str]) -> dict[str, str]:
"""The distribution adapter decides whether this target needs ARM64 tools."""
shell = shutil.which("powershell", path=env.get("PATH")) or shutil.which("pwsh", path=env.get("PATH"))
if shell is None:
raise FileNotFoundError("PowerShell is required to prepare Windows ARM64 build dependencies")
state.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="environment-", dir=state) as scratch:
output = Path(scratch) / "environment.json"
subprocess.run(
[shell, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File",
str(source / "scripts/build/windows-deps.ps1"), "-StateRoot", str(state),
"-EnvironmentFile", str(output)],
cwd=source, env=dict(env), check=True,
)
prepared = json.loads(output.read_text(encoding="utf-8-sig"))
if not isinstance(prepared, dict) or any(not isinstance(k, str) or not isinstance(v, str) for k, v in prepared.items()):
raise ValueError("Windows build dependency provider returned an invalid environment")
return prepared

View File

@@ -100,7 +100,7 @@ def prepare_tools(source: Path, work: Path, cache: Path,
raise ValueError("desktop tools require the isolated preparation worker")
prepared = dict(env)
if pm.current_target() == "win32-arm64":
from scripts.build.windows_deps import prepare_windows_environment
from pm.native_build import prepare_windows_environment
prepared = prepare_windows_environment(source=source, state=cache / "native/prerequisites", env=prepared)
if pm.current_target().startswith("darwin"):

View File

@@ -106,7 +106,7 @@ def stage_native(args) -> int:
cache = Path(getattr(args, "cache", None) or os.environ.get("UV_CACHE_DIR") or uv_cache_dir()).resolve()
base_env = dict(os.environ)
if current_target() == "win32-arm64":
from scripts.build.windows_deps import prepare_windows_environment
from pm.native_build import prepare_windows_environment
base_env = prepare_windows_environment(source=root, state=out.parent / ".build-deps", env=base_env)
# Rustup resolves its installed toolchain under HOME unless these are explicit.

View File

@@ -29,7 +29,7 @@ _INPUT_FILES = (
"scripts/bundles/desktop_inputs.py", "scripts/bundles/native.py", "scripts/bundles/native_prepared.py",
"scripts/bundles/native_build.py",
"scripts/build/node-deps.mjs", "scripts/build/icon_environment.py",
"scripts/build/windows_deps.py", "scripts/windows-build-deps.ps1",
"pm/native_build.py", "scripts/windows-build-deps.ps1",
"apps/desktop/scripts/stage-native-deps.mjs", "apps/desktop/scripts/prepare-packaging-tools.mjs",
"apps/desktop/scripts/build-command-screenshot-monitor.mjs",
"apps/desktop/scripts/build-hud-modifier-monitor.mjs",