- C1: единственный владелец stop+destroy — reader-поток; close() только выставляет флаг и ждёт; повторный close и close из колбэка безопасны (регресс-тесты; TSan на close с мёртвым модулем — 0 гонок) - M1: wheel собирается с bundled mbedtls (нет внешней soname mbedcrypto); попутно исправлена bundled-ветка CMake (-lSTATIC) - C-API: счётчики потерянных событий/логов, лог-кольцо без union-балласта, атомарные fn/ctx лог-синка - provision: валидация формата облачных ответов; CLI: человеческие ошибки (exit 2), --out с правами 0600, console_script pyfglair - упаковка: MANIFEST.in (sdist с C++-исходниками), очистка stale .so, readme в метаданных; find_library без ложных .bak - тесты: close-сценарии, доставка логов, ошибки CLI/provision, C-API очередь
143 lines
4.6 KiB
Python
143 lines
4.6 KiB
Python
"""Сборка libfgl-aircon.so из корня монорепо (cmake).
|
|
|
|
Используется при упаковке wheel (setup.py) и для локальной разработки:
|
|
|
|
python -m pyfglair._corebuild --inplace
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
LIB_GLOBS = (
|
|
"libfgl-aircon.so",
|
|
"libfgl-aircon.so.*",
|
|
"libfgl-aircon.*.dylib",
|
|
"libfgl-aircon.dylib",
|
|
"fgl-aircon.dll",
|
|
)
|
|
|
|
|
|
def source_root() -> Path | None:
|
|
"""Корень монорепо (CMakeLists.txt + include/fgl-aircon)."""
|
|
env = os.environ.get("FGLAIR_SOURCE_ROOT")
|
|
if env:
|
|
root = Path(env).resolve()
|
|
if (root / "CMakeLists.txt").exists():
|
|
return root
|
|
for parent in Path(__file__).resolve().parents:
|
|
if (parent / "include" / "fgl-aircon").is_dir() and (
|
|
parent / "CMakeLists.txt"
|
|
).exists():
|
|
return parent
|
|
return None
|
|
|
|
|
|
def _run(argv: list[str], cwd: Path | None = None) -> None:
|
|
print("+", " ".join(argv), flush=True)
|
|
subprocess.run(argv, cwd=cwd, check=True)
|
|
|
|
|
|
def find_built_library(build_dir: Path) -> Path:
|
|
found: list[Path] = []
|
|
for pattern in LIB_GLOBS:
|
|
found.extend(build_dir.rglob(pattern))
|
|
real = {p.resolve() for p in found if p.resolve().is_file()}
|
|
if not real:
|
|
raise FileNotFoundError(
|
|
f"libfgl-aircon не найдена после сборки в {build_dir}"
|
|
)
|
|
return max(real, key=lambda p: p.stat().st_mtime)
|
|
|
|
|
|
def build_core(
|
|
dest: Path | None = None,
|
|
*,
|
|
source: Path | None = None,
|
|
build_dir: Path | None = None,
|
|
jobs: int | None = None,
|
|
clean: bool = False,
|
|
bundled_mbedtls: bool | None = None,
|
|
) -> Path:
|
|
"""Собирает shared-ядро и (при dest) копирует его в каталог пакета.
|
|
|
|
Для wheel mbedtls по умолчанию встраивается (bundled), чтобы .so не
|
|
зависел от системной soname; FGL_BUNDLED_MBEDTLS=0 — использовать
|
|
системный.
|
|
"""
|
|
src = Path(source) if source else source_root()
|
|
if src is None:
|
|
raise RuntimeError(
|
|
"не найден корень fgl-aircon (CMakeLists.txt); задайте "
|
|
"FGLAIR_SOURCE_ROOT"
|
|
)
|
|
if bundled_mbedtls is None:
|
|
env = os.environ.get("FGL_BUNDLED_MBEDTLS")
|
|
bundled_mbedtls = env != "0"
|
|
src = Path(src).resolve()
|
|
build = Path(build_dir) if build_dir else src / "build-pyfglair"
|
|
if clean and build.exists():
|
|
shutil.rmtree(build)
|
|
|
|
generator = ["-G", "Ninja"] if shutil.which("ninja") else []
|
|
_run([
|
|
"cmake", "-S", str(src), "-B", str(build), *generator,
|
|
"-DCMAKE_BUILD_TYPE=Release",
|
|
"-DFGL_BUILD_TESTS=OFF",
|
|
"-DFGL_BUILD_EXAMPLES=OFF",
|
|
"-DFGL_BUILD_SHARED=ON",
|
|
"-DFGL_BUNDLED_MBEDTLS=" + ("ON" if bundled_mbedtls else "OFF"),
|
|
])
|
|
_run([
|
|
"cmake", "--build", str(build), "--target", "fgl-aircon-shared",
|
|
"--parallel", str(jobs or os.cpu_count() or 2),
|
|
])
|
|
|
|
lib = find_built_library(build)
|
|
if dest is None:
|
|
return lib
|
|
dest = Path(dest)
|
|
dest.mkdir(parents=True, exist_ok=True)
|
|
target = dest / lib.name
|
|
shutil.copy2(lib, target)
|
|
return target
|
|
|
|
|
|
def ensure_core(dest: Path | None = None) -> Path:
|
|
"""Собирает ядро, если рядом с пакетом нет готовой библиотеки."""
|
|
pkg = Path(__file__).resolve().parent
|
|
for pattern in LIB_GLOBS:
|
|
for candidate in sorted(pkg.glob(pattern)):
|
|
if candidate.is_file():
|
|
return candidate
|
|
return build_core(dest=dest or pkg)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
ap = argparse.ArgumentParser(description="Сборка libfgl-aircon (pyfglair)")
|
|
ap.add_argument("--inplace", action="store_true",
|
|
help="скопировать .so в каталог пакета pyfglair")
|
|
ap.add_argument("--dest", type=Path, help="каталог для копии .so")
|
|
ap.add_argument("--source", type=Path, help="корень монорепо")
|
|
ap.add_argument("--build-dir", type=Path, help="каталог сборки cmake")
|
|
ap.add_argument("--clean", action="store_true")
|
|
args = ap.parse_args(argv)
|
|
|
|
dest = args.dest
|
|
if args.inplace and dest is None:
|
|
dest = Path(__file__).resolve().parent
|
|
lib = build_core(
|
|
dest=dest, source=args.source, build_dir=args.build_dir,
|
|
clean=args.clean,
|
|
)
|
|
print(f"ядро: {lib}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|