Files
FreeCADWasmDev/tools/web_build_lock.py
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

65 lines
2.7 KiB
Python

"""Serialize writers to an existing CMake build tree, including inherited children."""
from __future__ import annotations
import argparse
import fcntl
import json
import os
from pathlib import Path
import subprocess
import sys
import time
def cmake_build_tree(command: list[str], cwd: Path) -> Path | None:
if len(command) < 3 or Path(command[0]).name != "cmake" or command[1] != "--build":
return None
return (cwd / command[2]).resolve()
def run_build(command: list[str], *, cwd: Path, env: dict | None = None,
timeout: float = 1800, **kwargs) -> subprocess.CompletedProcess:
tree = cmake_build_tree(command, cwd)
if tree is None:
return subprocess.run(command, cwd=cwd, env=env, timeout=timeout, **kwargs)
started = time.monotonic()
# Do not create a misspelled build directory just to acquire its lock.
with (tree / ".freecad-web-build.lock").open("a") as lock:
waited = False
while True:
try:
fcntl.flock(lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
break
except BlockingIOError:
if not waited:
print(json.dumps({"stage": "build-lock-wait", "tree": str(tree)}), file=sys.stderr, flush=True)
waited = True
if time.monotonic() - started >= timeout:
raise subprocess.TimeoutExpired(command, timeout, stderr="timed out waiting for the build-tree writer lock")
time.sleep(min(0.05, max(0.001, timeout - (time.monotonic() - started))))
delay = time.monotonic() - started
if waited:
print(json.dumps({"stage": "build-lock-acquired", "tree": str(tree), "waitSeconds": round(delay, 3)}), file=sys.stderr, flush=True)
descriptors = (*kwargs.pop("pass_fds", ()), lock.fileno())
# Children retain the lock if an outer runner exits before its linker.
return subprocess.run(command, cwd=cwd, env=env, timeout=max(0.001, timeout - delay), pass_fds=descriptors, **kwargs)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--timeout", type=float, default=1800)
parser.add_argument("command", nargs=argparse.REMAINDER)
args = parser.parse_args()
command = args.command[1:] if args.command[:1] == ["--"] else args.command
if cmake_build_tree(command, Path.cwd()) is None:
parser.error("expected: -- cmake --build <existing-tree> [build options]")
try:
return run_build(command, cwd=Path.cwd(), env=os.environ.copy(), timeout=args.timeout).returncode
except subprocess.TimeoutExpired as error:
print(str(error), file=sys.stderr)
return 124
if __name__ == "__main__":
raise SystemExit(main())