Files
templates/c/protocan-transport/tools/build_host.py

90 lines
3.1 KiB
Python

"""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())