Files

146 lines
6.8 KiB
Python

"""Explicit Linux author SDK leaves for offline Python/npm build acceptance.
This prepares test inputs, not a trusted public distribution. System files are
declared here and hashed; fixed Python/Node headers and npm come from the same
verified upstream archives used by prepare.py. No PATH lookup or downloads.
"""
import argparse
import json
import os
from pathlib import Path
import re
import shutil
from prepare import SOURCES, digest, materialize
TARGET = "linux-x86_64"
MULTIARCH = "x86_64-linux-gnu"
GCC_VERSION = "13"
NPM_NODE_GYP_SHIM = "npm/frontend/node_modules/npm/node_modules/@npmcli/run-script/lib/node-gyp-bin/node-gyp"
TOOLS = {
"sh": "dash", "cc": "x86_64-linux-gnu-gcc-13", "gcc": "x86_64-linux-gnu-gcc-13",
"c++": "x86_64-linux-gnu-g++-13", "g++": "x86_64-linux-gnu-g++-13",
"as": "x86_64-linux-gnu-as", "ld": "x86_64-linux-gnu-ld.bfd",
"ar": "x86_64-linux-gnu-ar", "ranlib": "x86_64-linux-gnu-ranlib",
"nm": "x86_64-linux-gnu-nm", "strip": "x86_64-linux-gnu-strip",
**{name: name for name in (
"make", "env", "basename", "dirname", "cat", "cp", "mv", "rm", "mkdir",
"rmdir", "ln", "touch", "chmod", "pwd", "printf", "test", "true", "false",
"sed", "grep", "find", "sort", "tr", "cut", "head", "tail", "wc", "uname",
)},
}
LIBRARIES = (
"ld-linux-x86-64.so.2",
"libc.so.6", "libm.so.6", "libdl.so.2", "libpthread.so.0", "librt.so.1",
"libgcc_s.so.1", "libstdc++.so.6", "libisl.so.23", "libmpc.so.3",
"libmpfr.so.6", "libgmp.so.10", "libz.so.1", "libzstd.so.1",
"libbfd-2.42-system.so", "libctf.so.0", "libjansson.so.4",
"libsframe.so.1", "libpcre2-8.so.0", "libselinux.so.1", "libacl.so.1",
"libattr.so.1", "libtinfo.so.6", "libcrypto.so.3", "libssl.so.3",
"libc.so", "libc_nonshared.a", "libm.so", "libm-2.39.a", "libmvec.so.1",
"libpthread.a", "libdl.a", "librt.a", "libutil.a",
"crt1.o", "Scrt1.o", "crti.o", "crtn.o",
)
def signed_library_paths(content):
# 仅迁移以 /lib 开头的绝对 token,不能再次改写已有的 /usr/lib。
rewritten = re.sub(r"(?<!\S)/lib/" + re.escape(MULTIARCH) + "/",
f"/usr/lib/{MULTIARCH}/", content)
return re.sub(r"(?<!\S)/lib64/ld-linux-x86-64\.so\.2(?=[\s)])",
f"/usr/lib/{MULTIARCH}/ld-linux-x86-64.so.2", rewritten)
def sdk_leaf(source, destination, changes):
original = source
source = source.resolve(strict=True)
if not source.is_file() or not source.is_relative_to("/usr"):
raise ValueError(f"SDK leaf must resolve to an explicit /usr file: {original}")
destination.parent.mkdir(parents=True, exist_ok=True)
if original.name in ("libc.so", "libm.so", "libgcc_s.so"):
content = source.read_text()
rewritten = signed_library_paths(content)
if rewritten != content:
destination.write_text(rewritten)
changes.append({"source": str(original), "source_sha256": digest(source),
"transform": "linker script uses signed /usr/lib multiarch leaves"})
return
# These are read-only author inputs. Never chmod a hardlink to a host file.
os.link(source, destination)
def sdk_tree(source, destination, changes):
for current, directories, names in os.walk(source, followlinks=False):
current = Path(current)
if any((current / name).is_symlink() for name in directories):
raise ValueError(f"unexpected directory alias in explicit SDK input: {current}")
for name in sorted(names):
sdk_leaf(current / name, destination / (current / name).relative_to(source), changes)
def leaves(root, sources):
entries = []
executables = []
for source, prefix in sources:
for path in sorted(source.rglob("*")):
if not path.is_file():
continue
name = (Path(prefix) / path.relative_to(source)).as_posix()
entries.append({"source": path.relative_to(root).as_posix(),
"path": name, "sha256": digest(path)})
if path.stat().st_mode & 0o111:
executables.append(name)
if len(entries) > 65_536 or sum((root / entry["source"]).stat().st_size for entry in entries) > 512 * 1024 * 1024:
raise ValueError("author SDK exceeds the build-only pack budget")
return {"files": entries, "executables": executables}
def prepare(output):
output.mkdir(parents=True, exist_ok=False)
changes = []
try:
rootfs = output / "rootfs"
for name, source in TOOLS.items():
sdk_leaf(Path("/usr/bin") / source, rootfs / "usr/bin" / name, changes)
for source in (Path("/usr/include"),
Path(f"/usr/lib/gcc/{MULTIARCH}/{GCC_VERSION}"),
Path(f"/usr/libexec/gcc/{MULTIARCH}/{GCC_VERSION}")):
sdk_tree(source, rootfs / source.relative_to("/"), changes)
for name in LIBRARIES:
source = Path("/usr/lib") / MULTIARCH / name
sdk_leaf(source, rootfs / source.relative_to("/"), changes)
python = output / "python"
node = output / "node"
materialize("pip", python, selector=lambda name: name.startswith("include/"))
materialize("npm", node, selector=lambda name: name.startswith(("include/node/", "lib/node_modules/npm/")))
npm = leaves(output, ((rootfs, "rootfs"), (node / "include/node", "npm/include/node"),
(node / "lib/node_modules/npm", "npm/frontend/node_modules/npm")))
shim = next(entry for entry in npm["files"] if entry["path"] == NPM_NODE_GYP_SHIM)
if not (output / shim["source"]).read_bytes().startswith(b"#!/usr/bin/env sh\n"):
raise ValueError("pinned npm node-gyp shim changed its interpreter")
# run-script executes this upstream shell entry; the remaining frontend
# JavaScript is loaded by the exact packaged Node interpreter.
npm["executables"] = sorted(set(npm["executables"]) | {NPM_NODE_GYP_SHIM})
manifest = {
"format": "dever-build-author-leaves-v1", "target": TARGET,
"provenance": {"kind": "private Ubuntu GNU author SDK",
"gcc": GCC_VERSION, "system_linker_script_changes": changes,
"upstream_archives": {name: {"path": str(value[0]), "sha256": value[1]}
for name, value in SOURCES.items() if name != "go"}},
"pip": leaves(output, ((rootfs, "rootfs"), (python / "include", "runtime/include"))),
"npm": npm,
}
(output / "inputs.json").write_text(json.dumps(manifest, indent=2) + "\n")
except BaseException:
shutil.rmtree(output)
raise
print(output / "inputs.json")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("output", type=Path)
prepare(parser.parse_args().output.absolute())