65 lines
2.7 KiB
Python
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())
|