126 lines
4.3 KiB
Python
126 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the optional GovOPlaN developer convenience meta-package."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import tomllib
|
|
|
|
|
|
META_ROOT = Path(__file__).resolve().parents[2]
|
|
DIRECT = re.compile(r"^(govoplan-[a-z0-9-]+)(?:\[([^]]+)\])?\s+@\s+.*@v([A-Za-z0-9._+!-]+)$")
|
|
LOCAL_CORE = re.compile(r"^(?:-e\s+)?\.\./govoplan-core(?:\[([^]]+)\])?$")
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--workspace", type=Path, default=META_ROOT.parent)
|
|
parser.add_argument(
|
|
"--requirements",
|
|
type=Path,
|
|
default=META_ROOT / "requirements-release.txt",
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
type=Path,
|
|
default=META_ROOT / "packages" / "govoplan-meta" / "pyproject.toml",
|
|
)
|
|
parser.add_argument("--check", action="store_true")
|
|
return parser
|
|
|
|
|
|
def render(*, workspace: Path, requirements: Path) -> str:
|
|
core = tomllib.loads((workspace / "govoplan-core/pyproject.toml").read_text(encoding="utf-8"))["project"]
|
|
version = str(core["version"])
|
|
base: list[str] = []
|
|
for raw in requirements.read_text(encoding="utf-8").splitlines():
|
|
line = raw.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
local = LOCAL_CORE.fullmatch(line)
|
|
if local:
|
|
extra = f"[{local.group(1)}]" if local.group(1) else ""
|
|
base.append(f"govoplan-core{extra}=={version}")
|
|
continue
|
|
match = DIRECT.fullmatch(line)
|
|
if match is None:
|
|
raise ValueError(f"unsupported release requirement: {line!r}")
|
|
extra = f"[{match.group(2)}]" if match.group(2) else ""
|
|
base.append(f"{match.group(1)}{extra}=={match.group(3)}")
|
|
|
|
base_names = {_requirement_name(item) for item in base}
|
|
full: list[str] = []
|
|
for project_path in sorted(workspace.glob("govoplan-*/pyproject.toml")):
|
|
project = tomllib.loads(project_path.read_text(encoding="utf-8"))["project"]
|
|
name = str(project.get("name") or "")
|
|
package_version = str(project.get("version") or "")
|
|
if name.startswith("govoplan-") and name not in base_names:
|
|
full.append(f"{name}=={package_version}")
|
|
|
|
return "\n".join(
|
|
[
|
|
"[build-system]",
|
|
'requires = ["setuptools>=69", "wheel"]',
|
|
'build-backend = "setuptools.build_meta"',
|
|
"",
|
|
"[project]",
|
|
'name = "govoplan"',
|
|
f'version = {json.dumps(version)}',
|
|
'description = "Developer convenience package for a versioned GovOPlaN composition"',
|
|
'readme = "README.md"',
|
|
'requires-python = ">=3.12"',
|
|
'license = { text = "AGPL-3.0-or-later" }',
|
|
"dependencies = [",
|
|
*[f" {json.dumps(item)}," for item in base],
|
|
"]",
|
|
"",
|
|
"[project.optional-dependencies]",
|
|
"full = [",
|
|
*[f" {json.dumps(item)}," for item in full],
|
|
"]",
|
|
"",
|
|
"[project.urls]",
|
|
'Repository = "https://git.add-ideas.de/GovOPlaN/govoplan"',
|
|
'Documentation = "https://govoplan.add-ideas.de"',
|
|
"",
|
|
"[tool.setuptools.packages.find]",
|
|
'where = ["src"]',
|
|
"",
|
|
]
|
|
)
|
|
|
|
|
|
def _requirement_name(value: str) -> str:
|
|
return value.split("[", 1)[0].split("==", 1)[0]
|
|
|
|
|
|
def main() -> int:
|
|
args = build_parser().parse_args()
|
|
try:
|
|
expected = render(
|
|
workspace=args.workspace.expanduser().resolve(),
|
|
requirements=args.requirements.expanduser().resolve(),
|
|
)
|
|
except (OSError, KeyError, ValueError, tomllib.TOMLDecodeError) as exc:
|
|
print(f"error: {exc}")
|
|
return 1
|
|
output = args.output.expanduser()
|
|
current = output.read_text(encoding="utf-8") if output.is_file() else None
|
|
if args.check:
|
|
if current != expected:
|
|
print(f"error: developer meta-package is stale: {output}")
|
|
return 1
|
|
print("Developer meta-package is synchronized.")
|
|
return 0
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
output.write_text(expected, encoding="utf-8")
|
|
print(f"Developer meta-package written to {output}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|