ha(H1): pyfglair — cffi-ядро (очередь событий), wheel, CLI discover/monitor
- CMake: shared-таргет fgl-aircon-shared (FGL_BUILD_SHARED, PIC, SOVERSION) - C-API: fgl_prop_raw_range + кольцевые очереди событий/логов (fgl_session_poll/wait_events, fgl_log_poll/wait_events): прямые колбэки из потоков ядра со стеком 8 КБ небезопасны для интерпретатора Python - pyfglair: _corebuild (cmake->wheel), _cffi (ABI), Session (reader-поток -> loop.call_soon_threadsafe), templates (интроспекция C-таблиц), provision (aiohttp), CLI discover/monitor + esphome-secrets - setup.py/pyproject: платформенный wheel py3-none-linux_x86_64 с .so - tests/pyfglair: templates, provision (мок-облако), session + CLI против mock_ac.py; scripts/py-ci.sh; ci.sh запускает python-часть
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""Сборка 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,
|
||||
) -> Path:
|
||||
"""Собирает shared-ядро и (при dest) копирует его в каталог пакета.
|
||||
|
||||
Возвращает путь к скопированной/собранной библиотеке.
|
||||
"""
|
||||
src = Path(source) if source else source_root()
|
||||
if src is None:
|
||||
raise RuntimeError(
|
||||
"не найден корень fgl-aircon (CMakeLists.txt); задайте "
|
||||
"FGLAIR_SOURCE_ROOT"
|
||||
)
|
||||
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",
|
||||
])
|
||||
_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())
|
||||
Reference in New Issue
Block a user