mirror of
https://github.com/vercel-labs/fx.git
synced 2026-10-28 10:15:17 +08:00
Build fx with Zig 0.17.0. build.zig.zon requires it, every workflow installs it, and PGSO qualification uses LLVM 22.1.8 to match. Zig 0.17 removes `**`, `errdefer |err|`, `@cImport`, and the old type info field lists. The source moves to `@splat`, a comptime `text_utils.repeat` helper, split cleanup functions, and the new field_names and field_types arrays. The Node-API addon translates node_api.h with a build step. Test fakes checked with `@hasDecl` become pub, because `@hasDecl` no longer sees private declarations. `zig build test` gives the test binary its fx and source paths through a generated test_paths module, since the build no longer exposes install paths for environment variables. Zig 0.17's SafeAllocator grows a small allocation in place only when it sits at the end of a bucket, and buckets are never reused, so an allocation-failure sweep could count a different number of allocations on each run. Sweeps and allocation-count tests now use testing_allocator.no_resize, which never resizes in place. allocRemaining now treats its limit as inclusive. readFileToEnd keeps rejecting a file of exactly max_bytes by reading through appendRemaining. Direct allocRemaining callers for remote responses now accept a body exactly at their limit. Zig 0.17's memset stores a full vector per iteration, so the Linux memset replacement is removed. macOS release binaries now require macOS 15, Zig 0.17's default minimum.
243 lines
8.5 KiB
Python
243 lines
8.5 KiB
Python
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import platform
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
|
|
from scripts.pgso.model import PgsoError
|
|
|
|
|
|
REQUIRED_ZIG_VERSION = "0.17.0"
|
|
REQUIRED_LLVM_VERSION = "22.1.8"
|
|
SUPPORTED_TARGET = "aarch64-macos"
|
|
|
|
|
|
def _resolve_executable(command: str, display_name: str) -> pathlib.Path:
|
|
resolved = shutil.which(command)
|
|
if resolved is None:
|
|
raise PgsoError(f"missing executable: {display_name}")
|
|
return pathlib.Path(resolved).resolve()
|
|
|
|
|
|
def _capture(argv: tuple[str, ...], stage: str) -> str:
|
|
try:
|
|
result = subprocess.run(
|
|
argv,
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
timeout=10,
|
|
check=False,
|
|
)
|
|
except (OSError, subprocess.TimeoutExpired) as error:
|
|
raise PgsoError(f"could not query {stage}: {error}") from error
|
|
if result.returncode != 0:
|
|
raise PgsoError(
|
|
f"could not query {stage}: exit code {result.returncode}: "
|
|
f"{result.stderr.strip()}"
|
|
)
|
|
return result.stdout.strip()
|
|
|
|
|
|
def _llvm_version(tool: pathlib.Path, display_name: str) -> str:
|
|
output = _capture((str(tool), "--version"), f"{display_name} version")
|
|
match = re.search(r"\b\d+\.\d+\.\d+\b", output)
|
|
if match is None:
|
|
raise PgsoError(f"could not parse LLVM version from {display_name}: {output}")
|
|
version = match.group(0)
|
|
if version != REQUIRED_LLVM_VERSION:
|
|
raise PgsoError(
|
|
f"PGSO requires LLVM {REQUIRED_LLVM_VERSION}; "
|
|
f"{display_name} reported {version}"
|
|
)
|
|
return version
|
|
|
|
|
|
def _zig_darwin_sdk(zig: pathlib.Path) -> tuple[pathlib.Path, str]:
|
|
output = _capture((str(zig), "env"), "Zig environment")
|
|
matches = re.findall(r'^\s*\.lib_dir = ("(?:[^"\\]|\\.)*"),\s*$', output, re.MULTILINE)
|
|
if len(matches) != 1:
|
|
raise PgsoError("Zig environment must contain one lib_dir")
|
|
try:
|
|
lib_dir = pathlib.Path(json.loads(matches[0]))
|
|
except (ValueError, TypeError) as error:
|
|
raise PgsoError("invalid Zig library directory") from error
|
|
if not lib_dir.is_absolute():
|
|
raise PgsoError("Zig library directory must be absolute")
|
|
sdk = (lib_dir / "libc" / "darwin").resolve()
|
|
stub = sdk / "libSystem.tbd"
|
|
if not stub.is_file() or stub.stat().st_size == 0:
|
|
raise PgsoError(f"missing Zig libSystem stub: {stub}")
|
|
try:
|
|
settings = json.loads((sdk / "SDKSettings.json").read_text(encoding="utf-8"))
|
|
except (OSError, ValueError) as error:
|
|
raise PgsoError("could not read Zig Darwin SDK settings") from error
|
|
version = settings.get("MinimalDisplayName") if isinstance(settings, dict) else None
|
|
if not isinstance(version, str) or re.fullmatch(r"\d+\.\d+(?:\.\d+)?", version) is None:
|
|
raise PgsoError("invalid Zig Darwin SDK version")
|
|
return sdk, version
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class Toolchain:
|
|
zig: pathlib.Path
|
|
llvm_bin: pathlib.Path
|
|
opt: pathlib.Path
|
|
llc: pathlib.Path
|
|
llvm_profdata: pathlib.Path
|
|
llvm_ar: pathlib.Path
|
|
llvm_nm: pathlib.Path
|
|
llvm_link: pathlib.Path
|
|
llvm_split: pathlib.Path
|
|
clang: pathlib.Path
|
|
apple_ld: pathlib.Path
|
|
apple_ld_version: str
|
|
strip: pathlib.Path
|
|
codesign: pathlib.Path
|
|
otool: pathlib.Path
|
|
xcrun: pathlib.Path
|
|
sdk: pathlib.Path
|
|
sdk_version: str
|
|
zig_darwin_sdk: pathlib.Path
|
|
zig_sdk_version: str
|
|
profile_runtime: pathlib.Path
|
|
zig_version: str
|
|
llvm_version: str
|
|
target: str
|
|
host_arch: str
|
|
|
|
@classmethod
|
|
def discover(
|
|
cls,
|
|
zig: str,
|
|
llvm_bin: str,
|
|
target: str,
|
|
) -> "Toolchain":
|
|
if target != SUPPORTED_TARGET:
|
|
raise PgsoError(f"unsupported target: {target}")
|
|
if platform.system() != "Darwin":
|
|
raise PgsoError("PGSO requires a Darwin host")
|
|
host_arch = platform.machine()
|
|
if host_arch != "arm64":
|
|
raise PgsoError("PGSO requires an arm64 host")
|
|
|
|
zig_path = _resolve_executable(zig, "zig")
|
|
zig_version = _capture((str(zig_path), "version"), "Zig version")
|
|
if zig_version != REQUIRED_ZIG_VERSION:
|
|
raise PgsoError(
|
|
f"PGSO requires Zig {REQUIRED_ZIG_VERSION}; "
|
|
f"zig reported {zig_version}"
|
|
)
|
|
|
|
llvm_root = pathlib.Path(llvm_bin).expanduser().resolve()
|
|
if not llvm_root.is_dir():
|
|
raise PgsoError(f"LLVM bin root does not exist: {llvm_root}")
|
|
|
|
llvm_tools: dict[str, pathlib.Path] = {}
|
|
for name in (
|
|
"opt",
|
|
"llc",
|
|
"llvm-profdata",
|
|
"llvm-ar",
|
|
"llvm-nm",
|
|
"llvm-link",
|
|
"llvm-split",
|
|
"clang",
|
|
):
|
|
candidate = llvm_root / name
|
|
if not candidate.is_file() or not os.access(candidate, os.X_OK):
|
|
raise PgsoError(f"missing executable: {name}")
|
|
resolved = candidate.resolve()
|
|
if resolved.parent != llvm_root:
|
|
raise PgsoError(
|
|
f"LLVM tool escapes configured root: {name}: {resolved}"
|
|
)
|
|
llvm_tools[name] = resolved
|
|
|
|
versions = {
|
|
name: _llvm_version(path, name)
|
|
for name, path in llvm_tools.items()
|
|
}
|
|
llvm_version = versions["opt"]
|
|
|
|
strip = _resolve_executable("strip", "strip")
|
|
codesign = _resolve_executable("codesign", "codesign")
|
|
otool = _resolve_executable("otool", "otool")
|
|
xcrun = _resolve_executable("xcrun", "xcrun")
|
|
apple_ld = _resolve_executable(
|
|
_capture((str(xcrun), "--find", "ld"), "Apple linker path"),
|
|
"Apple linker",
|
|
)
|
|
try:
|
|
details = json.loads(_capture((str(apple_ld), "-version_details"), "Apple linker version"))
|
|
except ValueError as error:
|
|
raise PgsoError("invalid Apple linker version details") from error
|
|
apple_ld_version = details.get("version") if isinstance(details, dict) else None
|
|
if not isinstance(apple_ld_version, str) or re.fullmatch(r"\d+(?:\.\d+)*", apple_ld_version) is None:
|
|
raise PgsoError("invalid Apple linker version")
|
|
architectures = details.get("architectures")
|
|
if not isinstance(architectures, list) or "arm64" not in architectures:
|
|
raise PgsoError("Apple linker does not support arm64")
|
|
zig_darwin_sdk, zig_sdk_version = _zig_darwin_sdk(zig_path)
|
|
|
|
sdk_output = _capture(
|
|
(str(xcrun), "--sdk", "macosx", "--show-sdk-path"),
|
|
"macOS SDK path",
|
|
)
|
|
sdk = pathlib.Path(sdk_output).expanduser().resolve()
|
|
if not sdk.is_dir():
|
|
raise PgsoError(f"macOS SDK does not exist: {sdk}")
|
|
sdk_version = _capture(
|
|
(str(xcrun), "--sdk", "macosx", "--show-sdk-version"),
|
|
"macOS SDK version",
|
|
)
|
|
if re.fullmatch(r"\d+\.\d+(?:\.\d+)?", sdk_version) is None:
|
|
raise PgsoError(f"invalid macOS SDK version: {sdk_version}")
|
|
|
|
resource_output = _capture(
|
|
(str(llvm_tools["clang"]), "--print-resource-dir"),
|
|
"clang resource directory",
|
|
)
|
|
resource_dir = pathlib.Path(resource_output).expanduser().resolve()
|
|
profile_runtime = (
|
|
resource_dir / "lib" / "darwin" / "libclang_rt.profile_osx.a"
|
|
)
|
|
if not profile_runtime.is_file() or profile_runtime.stat().st_size == 0:
|
|
raise PgsoError(
|
|
f"missing LLVM profile runtime: {profile_runtime}"
|
|
)
|
|
|
|
return cls(
|
|
zig=zig_path,
|
|
llvm_bin=llvm_root,
|
|
opt=llvm_tools["opt"],
|
|
llc=llvm_tools["llc"],
|
|
llvm_profdata=llvm_tools["llvm-profdata"],
|
|
llvm_ar=llvm_tools["llvm-ar"],
|
|
llvm_nm=llvm_tools["llvm-nm"],
|
|
llvm_link=llvm_tools["llvm-link"],
|
|
llvm_split=llvm_tools["llvm-split"],
|
|
clang=llvm_tools["clang"],
|
|
apple_ld=apple_ld,
|
|
apple_ld_version=apple_ld_version,
|
|
strip=strip,
|
|
codesign=codesign,
|
|
otool=otool,
|
|
xcrun=xcrun,
|
|
sdk=sdk,
|
|
sdk_version=sdk_version,
|
|
zig_darwin_sdk=zig_darwin_sdk,
|
|
zig_sdk_version=zig_sdk_version,
|
|
profile_runtime=profile_runtime.resolve(),
|
|
zig_version=zig_version,
|
|
llvm_version=llvm_version,
|
|
target=target,
|
|
host_arch=host_arch,
|
|
)
|