shit
This commit is contained in:
+55
-11
@@ -40,11 +40,11 @@ def _source_tree(layout: Layout, r: Recipe, key: str | None) -> Path:
|
||||
return base / key
|
||||
|
||||
|
||||
def _prepare_all_sources(layout: Layout, r: Recipe) -> None:
|
||||
def _prepare_all_sources(layout: Layout, r: Recipe, *, strict: bool = False) -> None:
|
||||
for key, src in r.sources.items():
|
||||
tree = _source_tree(layout, r, key)
|
||||
tree.parent.mkdir(parents=True, exist_ok=True)
|
||||
fetch.prepare_source(layout, r.dir, src, tree)
|
||||
fetch.prepare_source(layout, r.dir, src, tree, strict=strict)
|
||||
|
||||
|
||||
def _build_path_env(host_deps_order: list[str], layout: Layout) -> str:
|
||||
@@ -204,22 +204,64 @@ def _finalize_host(c: Container, layout: Layout, r: Recipe) -> None:
|
||||
layout.host_pkg_marker(r.name, r.version, r.revision).write_text("ok\n")
|
||||
|
||||
|
||||
def _sysroot_sync(c: Container, r: Recipe) -> None:
|
||||
direct_deps = list(dict.fromkeys((*r.deps, *r.build_deps)))
|
||||
if not direct_deps:
|
||||
def _target_output_index(rs: RecipeSet) -> dict[str, Recipe]:
|
||||
"""Map every installable target package name (incl. subpackages) to its recipe."""
|
||||
idx: dict[str, Recipe] = {}
|
||||
for rec in rs.target.values():
|
||||
for out in rec.outputs:
|
||||
idx[out] = rec
|
||||
return idx
|
||||
|
||||
|
||||
def _transitive_runtime_deps(rs: RecipeSet, r: Recipe) -> list[str]:
|
||||
"""Full closure of `r`'s deps, so the sysroot carries everything its
|
||||
dependencies need (e.g. pkg-config Requires.private / linked SONAMEs).
|
||||
|
||||
Starts from `r`'s direct deps + build_deps and follows each package's own
|
||||
`deps`. Unknown names (external packages, subpackages we can't resolve) are
|
||||
still installed but not recursed into.
|
||||
"""
|
||||
idx = _target_output_index(rs)
|
||||
order: list[str] = []
|
||||
visited: set[str] = set()
|
||||
|
||||
def visit(name: str) -> None:
|
||||
if name in visited:
|
||||
return
|
||||
visited.add(name)
|
||||
order.append(name)
|
||||
dep_recipe = idx.get(name)
|
||||
if dep_recipe is not None and dep_recipe.name != r.name:
|
||||
for d in dep_recipe.deps:
|
||||
visit(d)
|
||||
|
||||
for name in (*r.deps, *r.build_deps):
|
||||
visit(name)
|
||||
return order
|
||||
|
||||
|
||||
def _sysroot_sync(c: Container, rs: RecipeSet, r: Recipe) -> None:
|
||||
pkgs = _transitive_runtime_deps(rs, r)
|
||||
if not pkgs:
|
||||
return
|
||||
initdb = not apk.sysroot_initialized(c)
|
||||
apk.sysroot_install(c, direct_deps, initdb=initdb)
|
||||
apk.sysroot_install(c, pkgs, initdb=initdb)
|
||||
|
||||
|
||||
def build_one(
|
||||
rs: RecipeSet, layout: Layout, profile: Profile, r: Recipe, *, forced: bool = False
|
||||
rs: RecipeSet,
|
||||
layout: Layout,
|
||||
profile: Profile,
|
||||
r: Recipe,
|
||||
*,
|
||||
forced: bool = False,
|
||||
strict: bool = False,
|
||||
) -> None:
|
||||
log.info_field("recipe", _recipe_ref(r))
|
||||
if forced:
|
||||
_clear_stamps(layout, r)
|
||||
_wipe_workdir(layout, r)
|
||||
_prepare_all_sources(layout, r)
|
||||
_prepare_all_sources(layout, r, strict=strict)
|
||||
|
||||
c, _host_order = _container_for(rs, layout, profile, r)
|
||||
c.start()
|
||||
@@ -227,7 +269,7 @@ def build_one(
|
||||
# Ensure base output dest dir exists.
|
||||
c.exec(["mkdir", "-p", f"/dest/{r.name}"])
|
||||
|
||||
_sysroot_sync(c, r)
|
||||
_sysroot_sync(c, rs, r)
|
||||
|
||||
ctx = RecipeContext(
|
||||
recipe=r, profile=profile, container=c, jobs=os.cpu_count() or 1
|
||||
@@ -247,13 +289,15 @@ def build_one(
|
||||
log.ok_field("done", _recipe_ref(r))
|
||||
|
||||
|
||||
def execute(plan: Plan, rs: RecipeSet, layout: Layout, profile: Profile) -> None:
|
||||
def execute(
|
||||
plan: Plan, rs: RecipeSet, layout: Layout, profile: Profile, *, strict: bool = False
|
||||
) -> None:
|
||||
if not plan.order:
|
||||
log.info_field("plan", "nothing to do")
|
||||
return
|
||||
for k in plan.order:
|
||||
r = plan.recipes[k]
|
||||
build_one(rs, layout, profile, r, forced=k in plan.forced)
|
||||
build_one(rs, layout, profile, r, forced=k in plan.forced, strict=strict)
|
||||
|
||||
|
||||
def install_to(
|
||||
|
||||
+12
-2
@@ -120,7 +120,7 @@ def cmd_build(args) -> int:
|
||||
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)
|
||||
builder.execute(p, rs, layout, prof, strict=args.strict)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ def cmd_fetch(args) -> int:
|
||||
for k in targets:
|
||||
r = rs.get(k)
|
||||
for key, src in r.sources.items():
|
||||
fetch_mod.fetch(layout, src)
|
||||
fetch_mod.fetch(layout, src, strict=args.strict)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -197,6 +197,11 @@ def make_parser() -> argparse.ArgumentParser:
|
||||
_common(p_build)
|
||||
p_build.add_argument("--rebuild", action="store_true")
|
||||
p_build.add_argument("-n", "--dry-run", action="store_true")
|
||||
p_build.add_argument(
|
||||
"--strict",
|
||||
action="store_true",
|
||||
help='fail on recipes with a placeholder checksum (sha256="?")',
|
||||
)
|
||||
p_build.set_defaults(func=cmd_build)
|
||||
|
||||
p_inst = sub.add_parser(
|
||||
@@ -221,6 +226,11 @@ def make_parser() -> argparse.ArgumentParser:
|
||||
|
||||
p_fetch = sub.add_parser("fetch", help="fetch sources only")
|
||||
_common(p_fetch)
|
||||
p_fetch.add_argument(
|
||||
"--strict",
|
||||
action="store_true",
|
||||
help='fail on recipes with a placeholder checksum (sha256="?")',
|
||||
)
|
||||
p_fetch.set_defaults(func=cmd_fetch)
|
||||
|
||||
return parser
|
||||
|
||||
@@ -108,3 +108,14 @@ class RecipeContext:
|
||||
merged.update(env)
|
||||
cwd_s = str(cwd) if cwd is not None else "/build"
|
||||
self.container.exec(flat, env=merged, cwd=cwd_s)
|
||||
|
||||
def write_text(self, path, content: str) -> None:
|
||||
"""Write *content* to *path* inside the container.
|
||||
|
||||
Round-tripped through base64 so arbitrary text (quotes, newlines,
|
||||
shell metacharacters) survives the shell without escaping.
|
||||
"""
|
||||
import base64
|
||||
|
||||
data = base64.b64encode(content.encode()).decode()
|
||||
self.run("sh", "-c", f"echo '{data}' | base64 -d > '{path}'")
|
||||
|
||||
+12
-5
@@ -26,11 +26,16 @@ def cache_lock(layout: Layout):
|
||||
f.close()
|
||||
|
||||
|
||||
def fetch_tarball(layout: Layout, src: Tarball) -> Path:
|
||||
def fetch_tarball(layout: Layout, src: Tarball, *, strict: bool = False) -> Path:
|
||||
dest = layout.tarball_cache / src.sha256
|
||||
if dest.is_file():
|
||||
return dest
|
||||
if src.sha256 == "?":
|
||||
if strict:
|
||||
raise RuntimeError(
|
||||
f"{src.url}: missing checksum (sha256=\"?\") is not allowed in "
|
||||
f"strict mode; pin the real sha256 in the recipe"
|
||||
)
|
||||
log.warn(f"fetching {src.url} (sha256 unknown)")
|
||||
else:
|
||||
log.info(f"fetching {src.url}")
|
||||
@@ -82,10 +87,10 @@ def fetch_git(layout: Layout, src: Git) -> Path:
|
||||
return dest
|
||||
|
||||
|
||||
def fetch(layout: Layout, src) -> Path:
|
||||
def fetch(layout: Layout, src, *, strict: bool = False) -> Path:
|
||||
with cache_lock(layout):
|
||||
if isinstance(src, Tarball):
|
||||
return fetch_tarball(layout, src)
|
||||
return fetch_tarball(layout, src, strict=strict)
|
||||
if isinstance(src, Git):
|
||||
return fetch_git(layout, src)
|
||||
raise TypeError(f"unknown source type {type(src).__name__}")
|
||||
@@ -165,9 +170,11 @@ def apply_patches(tree: Path, recipe_dir: Path, patches: tuple[str, ...]) -> Non
|
||||
_patched_marker(tree).write_text("\n".join(patches) + "\n")
|
||||
|
||||
|
||||
def prepare_source(layout: Layout, recipe_dir: Path, src, tree: Path) -> None:
|
||||
def prepare_source(
|
||||
layout: Layout, recipe_dir: Path, src, tree: Path, *, strict: bool = False
|
||||
) -> None:
|
||||
"""Fetch + extract + patch into `tree`. Idempotent via marker files."""
|
||||
cache_path = fetch(layout, src)
|
||||
cache_path = fetch(layout, src, strict=strict)
|
||||
expected_patches = "\n".join(src.patches) + "\n" if src.patches else "\n"
|
||||
if (
|
||||
_patched_marker(tree).is_file()
|
||||
|
||||
@@ -1,5 +1,98 @@
|
||||
# A cross `llvm-config`. The target llvm-config is a glibc binary that can't run
|
||||
# on the (musl) build host, so instead of executing it we emulate the queries
|
||||
# meson/mesa make, reporting paths inside the sysroot. Self-configuring: it reads
|
||||
# the LLVM version, library soname, and built targets from the installed sysroot,
|
||||
# so it works for any LLVM consumer (mesa now, rust/etc. later) with no hardcoding.
|
||||
_LLVM_CONFIG_EMULATOR = r'''#!/usr/bin/env python3
|
||||
import glob, re, sys
|
||||
|
||||
SYSROOT = "/sysroot"
|
||||
LIBDIR = SYSROOT + "/usr/lib"
|
||||
INCDIR = SYSROOT + "/usr/include"
|
||||
|
||||
_major = ""
|
||||
for _p in sorted(glob.glob(LIBDIR + "/libLLVM-*.so*")):
|
||||
_m = re.search(r"libLLVM-(\d+)", _p)
|
||||
if _m:
|
||||
_major = _m.group(1)
|
||||
break
|
||||
|
||||
_version = (_major + ".0.0") if _major else "0.0.0"
|
||||
try:
|
||||
with open(INCDIR + "/llvm/Config/llvm-config.h") as _f:
|
||||
_m = re.search(r'LLVM_VERSION_STRING\s+"([^"]+)"', _f.read())
|
||||
if _m:
|
||||
_version = _m.group(1)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
_targets = []
|
||||
try:
|
||||
with open(INCDIR + "/llvm/Config/Targets.def") as _f:
|
||||
_targets = re.findall(r"LLVM_TARGET\((\w+)\)", _f.read())
|
||||
except OSError:
|
||||
pass
|
||||
if not _targets:
|
||||
_targets = ["X86", "AMDGPU"]
|
||||
|
||||
_GENERIC = (
|
||||
"aggressiveinstcombine all all-targets analysis asmparser asmprinter "
|
||||
"binaryformat bitreader bitstreamreader bitwriter cfguard codegen "
|
||||
"codegentypes core coroutines coverage debuginfocodeview debuginfodwarf "
|
||||
"debuginfomsf debuginfopdb demangle dlltooldriver engine executionengine "
|
||||
"extensions frontenddriver frontendhlsl frontendoffloading frontendopenmp "
|
||||
"fuzzmutate globalisel instcombine instrumentation interpreter ipo irreader "
|
||||
"irprinter jitlink libdriver lineeditor linker lto mc mca mcdisassembler "
|
||||
"mcjit mcparser native nativecodegen object objectyaml option orcjit "
|
||||
"orcshared orctargetprocess passes profiledata remarks runtimedyld "
|
||||
"scalaropts selectiondag support symbolize target targetparser textapi "
|
||||
"transformutils vectorize windowsdriver windowsmanifest"
|
||||
).split()
|
||||
|
||||
|
||||
def _components():
|
||||
comps = list(_GENERIC)
|
||||
for t in _targets:
|
||||
tl = t.lower()
|
||||
comps += [tl, tl + "asmparser", tl + "codegen", tl + "desc",
|
||||
tl + "disassembler", tl + "info", tl + "targetmca", tl + "utils"]
|
||||
return " ".join(sorted(set(comps)))
|
||||
|
||||
|
||||
_H = {
|
||||
"--version": lambda: _version,
|
||||
"--components": _components,
|
||||
"--targets-built": lambda: " ".join(_targets),
|
||||
"--prefix": lambda: SYSROOT + "/usr",
|
||||
"--bindir": lambda: SYSROOT + "/usr/bin",
|
||||
"--includedir": lambda: INCDIR,
|
||||
"--libdir": lambda: LIBDIR,
|
||||
"--cmakedir": lambda: LIBDIR + "/cmake/llvm",
|
||||
"--has-rtti": lambda: "YES",
|
||||
"--shared-mode": lambda: "shared",
|
||||
"--libs": lambda: "-lLLVM-" + _major,
|
||||
"--system-libs": lambda: "",
|
||||
"--cflags": lambda: "-I" + INCDIR,
|
||||
"--cppflags": lambda: ("-I" + INCDIR
|
||||
+ " -D__STDC_CONSTANT_MACROS"
|
||||
+ " -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS"),
|
||||
"--cxxflags": lambda: ("-I" + INCDIR
|
||||
+ " -D__STDC_CONSTANT_MACROS"
|
||||
+ " -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS"),
|
||||
"--ldflags": lambda: "-L" + LIBDIR,
|
||||
}
|
||||
|
||||
for _a in sys.argv[1:]:
|
||||
if _a in _H:
|
||||
print(_H[_a]())
|
||||
'''
|
||||
|
||||
|
||||
def meson_cross_file(self):
|
||||
cross = self.build_dir / "meson-cross.ini"
|
||||
llvm_config = self.build_dir / "llvm-config"
|
||||
self.write_text(llvm_config, _LLVM_CONFIG_EMULATOR)
|
||||
self.run("chmod", "+x", llvm_config)
|
||||
self.write_text(
|
||||
cross,
|
||||
f"""\
|
||||
@@ -12,6 +105,8 @@ objcopy = '{self.triple}-objcopy'
|
||||
ranlib = '{self.triple}-ranlib'
|
||||
strip = '{self.triple}-strip'
|
||||
pkg-config = '{self.triple}-pkg-config'
|
||||
cmake = '/usr/bin/cmake'
|
||||
llvm-config = '{llvm_config}'
|
||||
|
||||
[host_machine]
|
||||
system = 'linux'
|
||||
|
||||
+7
-2
@@ -158,8 +158,8 @@ def _load_one(
|
||||
else:
|
||||
for k, v in multi_items:
|
||||
sources[k] = v
|
||||
else:
|
||||
raise ValueError(f"{name}: 'source' or 'sources' required")
|
||||
# else: no source. Allowed for config-only recipes (e.g. base-files); the
|
||||
# presence of at least one phase is validated below.
|
||||
|
||||
if pure:
|
||||
for s in sources.values():
|
||||
@@ -191,6 +191,11 @@ def _load_one(
|
||||
# Purely declarative pkgs are unusual but allowed
|
||||
pass
|
||||
|
||||
if not sources and not phases:
|
||||
raise ValueError(
|
||||
f"{name}: define a 'source'/'sources' or at least one build phase"
|
||||
)
|
||||
|
||||
enabled = bool(getattr(mod, "build_if", True))
|
||||
|
||||
return Recipe(
|
||||
|
||||
Reference in New Issue
Block a user