chore(repo): replace app symlink/submodule with in-repo app directory; enable committing app/Dockerfile and tooling\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
1 parent
a12270ad01
commit
ae2c674b81
25 files changed
+3417
-1
No files matched your search
@@ -0,0 +1,185 @@
|
||||
"""Isolated cmake / ctest subprocess helpers.
|
||||
|
||||
Each track gets its own worktree + build directory, so workers never
|
||||
share a build state and async concurrency is safe.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Type alias
|
||||
# ---------------------------------------------------------------------------
|
||||
RunResult = Tuple[bool, str, str] # (success, stdout, stderr)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Low-level sync helpers (run in thread-pool via run_in_executor)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _run(cmd: list[str], cwd: Path | None = None) -> Tuple[int, str, str]:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(cwd) if cwd else None,
|
||||
)
|
||||
return result.returncode, result.stdout, result.stderr
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Async wrappers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def cmake_configure(source_dir: Path, build_dir: Path) -> RunResult:
|
||||
"""Run cmake configure, enabling compile_commands.json export."""
|
||||
build_dir.mkdir(parents=True, exist_ok=True)
|
||||
loop = asyncio.get_running_loop()
|
||||
rc, out, err = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: _run(
|
||||
[
|
||||
"cmake",
|
||||
"-B", str(build_dir),
|
||||
str(source_dir),
|
||||
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
|
||||
"-DCMAKE_BUILD_TYPE=Debug",
|
||||
]
|
||||
),
|
||||
)
|
||||
return rc == 0, out, err
|
||||
|
||||
|
||||
async def cmake_build(build_dir: Path, jobs: int = 4) -> RunResult:
|
||||
"""Full (incremental) build of the tree in *build_dir*."""
|
||||
loop = asyncio.get_running_loop()
|
||||
rc, out, err = await loop.run_in_executor(
|
||||
None,
|
||||
lambda: _run(
|
||||
["cmake", "--build", str(build_dir), "--", f"-j{jobs}"]
|
||||
),
|
||||
)
|
||||
return rc == 0, out, err
|
||||
|
||||
|
||||
async def syntax_check_file(file_path: Path, build_dir: Path) -> RunResult:
|
||||
"""Fast per-file syntax check using the compile command from
|
||||
compile_commands.json. Falls back to a plain clang++ call when the
|
||||
JSON is not available.
|
||||
"""
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
cmd = _compile_cmd_for_file(file_path, build_dir)
|
||||
if cmd is None:
|
||||
# Fallback: bare syntax-only compile with C++20
|
||||
cmd = [
|
||||
"clang++",
|
||||
"-std=c++20",
|
||||
"-fsyntax-only",
|
||||
"-w", # suppress all warnings for the fast check
|
||||
str(file_path),
|
||||
]
|
||||
|
||||
rc, out, err = await loop.run_in_executor(None, lambda: _run(cmd))
|
||||
return rc == 0, out, err
|
||||
|
||||
|
||||
async def run_ctest(
|
||||
build_dir: Path,
|
||||
test_filter: str | None = None,
|
||||
) -> RunResult:
|
||||
"""Execute CTest in *build_dir*.
|
||||
|
||||
Pass *test_filter* to run only a subset (regex forwarded to ``-R``).
|
||||
"""
|
||||
cmd = ["ctest", "--test-dir", str(build_dir), "--output-on-failure"]
|
||||
if test_filter:
|
||||
cmd += ["-R", test_filter]
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
rc, out, err = await loop.run_in_executor(None, lambda: _run(cmd))
|
||||
return rc == 0, out, err
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Warning parsing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_WARNING_RE = re.compile(r":\s*warning\s*:", re.IGNORECASE)
|
||||
|
||||
|
||||
def parse_warnings(text: str) -> list[str]:
|
||||
"""Return all lines that contain a compiler/linker warning."""
|
||||
return [ln.strip() for ln in text.splitlines() if _WARNING_RE.search(ln)]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# compile_commands.json helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _load_compile_commands(build_dir: Path) -> dict[str, dict]:
|
||||
"""Return {absolute_file_path: entry} from compile_commands.json."""
|
||||
cc_path = build_dir / "compile_commands.json"
|
||||
if not cc_path.exists():
|
||||
return {}
|
||||
try:
|
||||
with cc_path.open() as fh:
|
||||
entries = json.load(fh)
|
||||
return {e["file"]: e for e in entries if "file" in e}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _compile_cmd_for_file(
|
||||
file_path: Path,
|
||||
build_dir: Path,
|
||||
) -> list[str] | None:
|
||||
"""Build a ``-fsyntax-only`` command for *file_path* from
|
||||
``compile_commands.json``. Returns None if no entry is found.
|
||||
"""
|
||||
db = _load_compile_commands(build_dir)
|
||||
if not db:
|
||||
return None
|
||||
|
||||
# Try both the resolved absolute path and as-is string.
|
||||
key = str(file_path.resolve())
|
||||
entry = db.get(key) or db.get(str(file_path))
|
||||
if entry is None:
|
||||
return None
|
||||
|
||||
raw_cmd: str = entry.get("command", "")
|
||||
if not raw_cmd:
|
||||
# Some generators use "arguments" (list) instead of "command" (string).
|
||||
args: list[str] = entry.get("arguments", [])
|
||||
if not args:
|
||||
return None
|
||||
raw_cmd = " ".join(args)
|
||||
|
||||
parts = raw_cmd.split()
|
||||
result: list[str] = []
|
||||
skip_next = False
|
||||
|
||||
for part in parts:
|
||||
if skip_next:
|
||||
skip_next = False
|
||||
continue
|
||||
if part == "-c":
|
||||
result.append("-fsyntax-only")
|
||||
elif part == "-o":
|
||||
skip_next = True # drop the output-file argument
|
||||
else:
|
||||
result.append(part)
|
||||
|
||||
# Ensure the entry's source file is replaced with the migrated file path.
|
||||
orig_src = entry.get("file", "")
|
||||
if orig_src and result and result[-1] == orig_src:
|
||||
result[-1] = str(file_path)
|
||||
|
||||
return result
|
||||
Reference in new issue
Block a user