*: Switch to python

This commit is contained in:
2026-05-26 03:06:26 +02:00
parent 2e6704516a
commit b6e18c474e
62 changed files with 15663 additions and 3441 deletions
+240
View File
@@ -0,0 +1,240 @@
import argparse
import os
import sys
from pathlib import Path
from src import (
builder,
container,
log,
plan,
profile as profile_mod,
recipe as recipe_mod,
)
from src.layout import Layout, find_repo_root
def _resolve_build_dir(p: str | None) -> Path:
if p:
return Path(p).resolve()
env = os.environ.get("ORCHID_BUILD")
if env:
return Path(env).resolve()
cwd = Path.cwd()
if (cwd / "profile").is_symlink() or (cwd / "profile").exists():
return cwd
raise SystemExit("error: -C <build-dir> required (or run inside a build dir)")
def _layout(build_dir: Path) -> Layout:
repo = find_repo_root(build_dir)
return Layout(repo=repo, build=build_dir)
def _load(layout: Layout):
prof = profile_mod.load_profile(layout)
rs = recipe_mod.load_recipes(layout, prof)
return prof, rs
def cmd_init(args) -> int:
target = Path(args.build_dir).resolve()
repo = find_repo_root(Path.cwd())
profile_mod.init_build_dir(target, repo, args.profile)
log.ok(f"initialized {target} (profile: {args.profile})")
return 0
def cmd_image(args) -> int:
layout = _layout(_resolve_build_dir(args.build_dir))
layout.ensure()
prof = profile_mod.load_profile(layout)
container.ensure_image(
layout.dockerfile, prof["container_image"], layout.image_hash_file
)
return 0
def _recipe_version(r) -> str:
return f"{r.version}-r{r.revision}"
def _print_plan(p: plan.Plan) -> None:
out = sys.stdout
if not p.order:
print(
f"{log.tag('info', stream=out)} "
f"{log.field('plan', 'nothing to do', stream=out)}"
)
return
count = len(p.order)
suffix = "" if count == 1 else "s"
print(
f"{log.tag('info', stream=out)} "
f"{log.field('plan', f'{count} recipe{suffix}', stream=out)}"
)
rows: list[tuple[str, str, str, str]] = []
for i, k in enumerate(p.order, start=1):
r = p.recipes[k]
rows.append((str(i), k, _recipe_version(r), ", ".join(p.stages.get(k, ()))))
widths = [
max(len(row[0]) for row in rows),
max(len("recipe"), *(len(row[1]) for row in rows)),
max(len("version"), *(len(row[2]) for row in rows)),
]
header = (
f" {'#':>{widths[0]}} "
f"{'recipe':<{widths[1]}} "
f"{'version':<{widths[2]}} "
"stages"
)
print(log.bold(header, stream=out))
for num, name, version, stages in rows:
print(
f" {num:>{widths[0]}} "
f"{name:<{widths[1]}} "
f"{version:<{widths[2]}} "
f"{stages}"
)
def cmd_plan(args) -> int:
layout = _layout(_resolve_build_dir(args.build_dir))
layout.ensure()
prof, rs = _load(layout)
p = plan.build_plan(rs, layout, args.recipes or None, rebuild=args.rebuild)
_print_plan(p)
return 0
def cmd_build(args) -> int:
layout = _layout(_resolve_build_dir(args.build_dir))
layout.ensure()
prof, rs = _load(layout)
container.ensure_image(
layout.dockerfile, prof["container_image"], layout.image_hash_file
)
p = plan.build_plan(rs, layout, args.recipes or None, rebuild=args.rebuild)
if args.dry_run:
return cmd_plan(args)
builder.execute(p, rs, layout, prof)
return 0
def cmd_install(args) -> int:
layout = _layout(_resolve_build_dir(args.build_dir))
layout.ensure()
prof, rs = _load(layout)
container.ensure_image(
layout.dockerfile, prof["container_image"], layout.image_hash_file
)
dest = Path(args.dest).resolve()
dest.mkdir(parents=True, exist_ok=True)
if args.recipes:
pkgs: list[str] = []
for k in args.recipes:
r = rs.get(k)
if r.kind != "target":
raise SystemExit(f"error: cannot install host recipe {k!r}")
pkgs.extend(r.outputs)
else:
pkgs = [r.name for r in rs.target.values() if r.enabled]
builder.install_to(layout, prof, dest, pkgs, initdb=args.initdb)
log.ok(f"installed {len(pkgs)} package(s) to {dest}")
return 0
def cmd_fetch(args) -> int:
layout = _layout(_resolve_build_dir(args.build_dir))
layout.ensure()
prof, rs = _load(layout)
from . import fetch as fetch_mod
targets = args.recipes or [r.key for r in rs.all() if r.enabled]
for k in targets:
r = rs.get(k)
for key, src in r.sources.items():
fetch_mod.fetch(layout, src)
return 0
def _common(p: argparse.ArgumentParser, with_recipes: bool = True) -> None:
p.add_argument(
"-C", "--build-dir", help="build directory (defaults to $ORCHID_BUILD or cwd)"
)
if with_recipes:
p.add_argument(
"recipes", nargs="*", help="recipe keys (target name or host:<name>)"
)
def make_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="orchid", description="Orchid distribution builder"
)
sub = parser.add_subparsers(dest="cmd", required=True)
p_init = sub.add_parser("init", help="create a build directory")
p_init.add_argument("build_dir")
p_init.add_argument("--profile", required=True)
p_init.set_defaults(func=cmd_init)
p_img = sub.add_parser("image", help="build/refresh the container image")
_common(p_img, with_recipes=False)
p_img.set_defaults(func=cmd_image)
p_plan = sub.add_parser("plan", help="print build plan")
_common(p_plan)
p_plan.add_argument("--rebuild", action="store_true")
p_plan.set_defaults(func=cmd_plan)
p_build = sub.add_parser("build", help="build recipes")
_common(p_build)
p_build.add_argument("--rebuild", action="store_true")
p_build.add_argument("-n", "--dry-run", action="store_true")
p_build.set_defaults(func=cmd_build)
p_inst = sub.add_parser(
"install", help="install built packages into a sysroot directory"
)
p_inst.add_argument(
"-C", "--build-dir", help="build directory (defaults to $ORCHID_BUILD or cwd)"
)
p_inst.add_argument("dest", help="destination sysroot directory")
p_inst.add_argument(
"recipes",
nargs="*",
help="target recipes to install (defaults to all enabled targets)",
)
p_inst.add_argument(
"--no-initdb",
dest="initdb",
action="store_false",
help="do not initialize the apk database (use for incremental installs)",
)
p_inst.set_defaults(func=cmd_install, initdb=True)
p_fetch = sub.add_parser("fetch", help="fetch sources only")
_common(p_fetch)
p_fetch.set_defaults(func=cmd_fetch)
return parser
def main(argv: list[str] | None = None) -> int:
parser = make_parser()
args = parser.parse_args(argv)
try:
return args.func(args)
except (SystemExit, KeyboardInterrupt):
raise
except Exception as e:
log.error(f"{type(e).__name__}: {e}")
if os.environ.get("ORCHID_DEBUG"):
raise
return 1