#!/usr/bin/env python3 """ Генерирует control-файл equivs (из Jinja2-шаблона) и собирает .deb метапакет с зависимостями для кастомной сборки PostgreSQL под Astra Linux. Использование: ./build_deb.py versions/14.10-2.1C.yml --os 1.7 ./build_deb.py versions/14.10-2.1C.yml --os 1.8 ./build_deb.py versions/14.10-2.1C.yml --all-os # под все ОС сразу """ import argparse import shutil import subprocess import sys import re from pathlib import Path import yaml # pip install pyyaml from jinja2 import Environment, FileSystemLoader, StrictUndefined # pip install jinja2 SCRIPT_DIR = Path(__file__).resolve().parent TEMPLATES_DIR = SCRIPT_DIR / "templates" PKG_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9+.-]*$") def load_version_file(path: Path) -> dict: with open(path, encoding="utf-8") as f: return yaml.safe_load(f) def render_control(context: dict) -> str: env = Environment( loader=FileSystemLoader(str(TEMPLATES_DIR)), undefined=StrictUndefined, # упадёт с понятной ошибкой, если забыли передать переменную trim_blocks=True, lstrip_blocks=True, ) template = env.get_template("control.j2") return template.render(**context) def sanitize_pkg_token(value: str) -> str: """ Приводит строку к виду, допустимому в имени deb-пакета: только [a-z0-9+.-], первый символ - буква или цифра. """ value = value.lower() value = re.sub(r"[^a-z0-9+.-]", "-", value) # всё запрещённое -> дефис value = re.sub(r"-{2,}", "-", value) # схлопнуть повторяющиеся дефисы value = value.strip("-.") # не должно начинаться/заканчиваться на - или . return value def validate_pkg_name(name: str): if not PKG_NAME_RE.match(name): sys.exit( f"Некорректное имя пакета: '{name}' (должно соответствовать {PKG_NAME_RE.pattern})" ) def build_for_os(spec: dict, os_version: str, build_dir: Path, dist_dir: Path) -> Path: if os_version not in spec.get("per_os", {}): sys.exit( f"Ошибка: в {spec['pg_version']} нет секции per_os для ОС {os_version}" ) depends = spec.get("common", []) + spec["per_os"][os_version] pg_version_safe = sanitize_pkg_token(spec["pg_version"]) os_version_safe = sanitize_pkg_token(os_version) pkg_name = f"pgsql-{pg_version_safe}-deps-astra{os_version_safe}" validate_pkg_name(pkg_name) control_content = render_control( { "pg_version": spec["pg_version"], "pg_version_safe": pg_version_safe, "package_revision": spec.get("package_revision", 1), "maintainer": spec.get("maintainer", "unknown"), "depends": depends, "os_version": os_version, "os_version_safe": os_version_safe, } ) work_dir = build_dir / f"{pg_version_safe}-astra{os_version}" work_dir.mkdir(parents=True, exist_ok=True) control_path = work_dir / "control" control_path.write_text(control_content, encoding="utf-8") print(f"--- Building: {control_path} ---") result = subprocess.run( ["equivs-build", "control"], cwd=work_dir, capture_output=True, text=True, ) print(result.stdout) if result.returncode != 0: print(result.stderr, file=sys.stderr) sys.exit(f"Сборка пакета для ОС {os_version} провалилась") debs = list(work_dir.glob("*.deb")) if not debs: sys.exit(f"equivs-build не создал .deb в {work_dir}") dist_dir.mkdir(parents=True, exist_ok=True) dest = dist_dir / debs[0].name shutil.copy2(debs[0], dest) print(f"OK -> {dest}") return dest def main(): parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawTextHelpFormatter ) parser.add_argument( "version_file", type=Path, help="Путь к YAML-файлу версии, напр. versions/14.10-2.1C.yml", ) parser.add_argument( "--os", dest="os_version", help="Версия ОС Astra, напр. 1.7 или 1.8" ) parser.add_argument( "--all-os", action="store_true", help="Собрать под все ОС, перечисленные в per_os", ) parser.add_argument("--build-dir", type=Path, default=Path("build")) parser.add_argument("--dist-dir", type=Path, default=Path("dist")) args = parser.parse_args() if not args.os_version and not args.all_os: sys.exit("Укажите --os <версия> или --all-os") spec = load_version_file(args.version_file) os_list = list(spec.get("per_os", {}).keys()) if args.all_os else [args.os_version] for os_version in os_list: build_for_os(spec, os_version, args.build_dir, args.dist_dir) if __name__ == "__main__": main()