"""Build the SETCore shared library without imposing one host toolchain. CMake remains the primary build. On a Windows workstation where CMake is absent, the script locates Visual Studio and invokes MSVC directly. Unix-like hosts fall back to the system C compiler. """ from __future__ import annotations import argparse import os import platform import shutil import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parent.parent INCLUDE = ROOT / "include" SOURCES = [ ROOT / "src" / name for name in ( "gui_frame.c", "pcan_abi.c", "pcan_crc.c", "pcan_frame.c", "pcan_id.c", "pcan_link.c", "pcan_ring.c", "pcan_gas.c", ) ] def _visual_studio_vcvars() -> Path | None: installer = Path(os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)")) vswhere = installer / "Microsoft Visual Studio" / "Installer" / "vswhere.exe" if vswhere.exists(): result = subprocess.run( [str(vswhere), "-latest", "-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath"], text=True, capture_output=True, check=False, ) if result.returncode == 0 and result.stdout.strip(): candidate = (Path(result.stdout.strip()) / "VC" / "Auxiliary" / "Build" / "vcvars64.bat") if candidate.exists(): return candidate return None def _build_msvc(output: Path, build_dir: Path) -> None: vcvars = _visual_studio_vcvars() if vcvars is None: raise SystemExit("Visual Studio C++ tools were not found") quoted_sources = " ".join(f'"{source}"' for source in SOURCES) import_library = build_dir / "setcore.lib" command = ( f'call "{vcvars}" && cl /nologo /W4 /std:c11 ' f'/DPCAN_ABI_BUILD_DLL /I"{INCLUDE}" /LD {quoted_sources} ' f'/Fe"{output}" /link /IMPLIB:"{import_library}"' ) # shell=True is intentional on Windows: ``call`` must run the batch file # in the same cmd.exe process that subsequently launches cl.exe. subprocess.run(command, cwd=build_dir, check=True, shell=True) def _build_cc(output: Path, build_dir: Path) -> None: compiler = shutil.which("cc") or shutil.which("clang") or shutil.which("gcc") if compiler is None: raise SystemExit("C compiler was not found") command = [compiler, "-std=c99", "-Wall", "-Wextra", "-Wpedantic", "-fPIC", "-shared", f"-I{INCLUDE}", *map(str, SOURCES), "-o", str(output)] subprocess.run(command, cwd=build_dir, check=True) def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() output = args.output.resolve() output.parent.mkdir(parents=True, exist_ok=True) build_dir = output.parent / ".setcore-build" build_dir.mkdir(parents=True, exist_ok=True) if platform.system() == "Windows": _build_msvc(output, build_dir) else: _build_cc(output, build_dir) print(output) return 0 if __name__ == "__main__": raise SystemExit(main())