This commit is contained in:
2026-05-20 00:30:38 +02:00
parent 312750c61b
commit 16d81f509f
36 changed files with 13292 additions and 273 deletions
Generated
+1
View File
@@ -458,6 +458,7 @@ dependencies = [
"anyhow",
"clap",
"hex",
"libc",
"reqwest",
"serde",
"serde_json",
+1
View File
@@ -8,6 +8,7 @@ license = "MIT"
anyhow = "1.0"
clap = { version = "4.5", features = ["derive"] }
hex = "0.4"
libc = "0.2"
reqwest = { version = "0.12", default-features = false, features = [
"blocking",
"rustls-tls",
+5
View File
@@ -16,8 +16,10 @@ RUN apk upgrade --no-cache && apk add --no-cache \
file \
findutils \
flex \
gawk \
gettext-dev \
git \
grep \
gzip \
elfutils-dev \
gmp-dev \
@@ -26,6 +28,9 @@ RUN apk upgrade --no-cache && apk add --no-cache \
libtool \
linux-headers \
meson \
mtools \
nasm \
ncurses \
ninja \
openssl \
openssl-dev \
+9
View File
@@ -38,4 +38,13 @@ options = dict(
ldflags = target_ldflags,
libc = libc,
prefix = "/usr",
bindir = "/usr/bin",
sbindir = "/usr/bin",
libdir = "/usr/lib",
libexecdir = "/usr/libexec",
includedir = "/usr/include",
sysconfdir = "/etc",
localstatedir = "/var",
)
+3 -4
View File
@@ -13,7 +13,7 @@ source = tarball_source(
def configure(ctx):
configure_args = [
ctx.source_dir / "configure",
"--prefix=" + ctx.prefix,
"--prefix=/",
"--target=" + options.target_triple,
"--with-sysroot=" + ctx.sysroot,
"--with-pic",
@@ -28,10 +28,9 @@ def configure(ctx):
"--enable-threads",
"--disable-nls",
"--disable-werror",
]
# gprofng's libcollector relies on glibc-specific internals.
if options.libc == "musl":
configure_args.append("--disable-gprofng")
"--disable-gprofng",
]
ctx.run(configure_args, env = {
"CFLAGS": options.host_cflags,
+1 -1
View File
@@ -15,7 +15,7 @@ def configure(ctx):
ctx.run([
ctx.source_dir / "configure",
"--target=" + options.target_triple,
"--prefix=" + ctx.prefix,
"--prefix=/",
"--with-sysroot=" + ctx.sysroot,
"--without-headers",
"--with-newlib",
+4 -5
View File
@@ -17,7 +17,7 @@ def configure(ctx):
ctx.run([
ctx.source_dir / "configure",
"--target=" + options.target_triple,
"--prefix=" + ctx.prefix,
"--prefix=/",
"--with-sysroot=" + ctx.sysroot,
"--with-build-sysroot=" + ctx.sysroot,
"--enable-languages=c,c++,lto",
@@ -48,7 +48,6 @@ def build(ctx):
def install(ctx, pkg):
ctx.run(["make", "install-strip"], env = {"DESTDIR": pkg.dest_dir})
# Drop libtool archives — they bake build-time paths into target programs.
ctx.run([
"find", pkg.dest_dir + ctx.prefix, "-name", "*.la", "-delete",
])
# Drop libtool archives.
ctx.run(["find", pkg.dest_dir, "-name", "*.la", "-delete"])
+1 -1
View File
@@ -19,7 +19,7 @@ def configure(ctx):
"-B", ctx.build_dir,
"-G", "Ninja",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_PREFIX=" + ctx.prefix,
"-DCMAKE_INSTALL_PREFIX=/",
"-DLLVM_ENABLE_PROJECTS=clang;clang-tools-extra;lld",
"-DLLVM_ENABLE_RUNTIMES=compiler-rt",
"-DLLVM_TARGETS_TO_BUILD=X86;AArch64;RISCV",
+13 -34
View File
@@ -1,4 +1,4 @@
# Commonly used helpers.
# Autotools
def autotools_configure(ctx, extra_args = [], extra_env = {}):
env = {
@@ -11,12 +11,12 @@ def autotools_configure(ctx, extra_args = [], extra_env = {}):
ctx.source_dir / "configure",
"--host=" + options.target_triple,
"--with-sysroot=" + ctx.sysroot,
"--prefix=" + ctx.prefix,
"--sysconfdir=/etc",
"--localstatedir=/var",
"--bindir=" + ctx.prefix / "bin",
"--sbindir=" + ctx.prefix / "bin",
"--libdir=" + ctx.prefix / "lib",
"--prefix=" + options.prefix,
"--sysconfdir=" + options.sysconfdir,
"--localstatedir=" + options.localstatedir,
"--bindir=" + options.bindir,
"--sbindir=" + options.sbindir,
"--libdir=" + options.libdir,
"--disable-static",
"--enable-shared",
] + extra_args, env = env)
@@ -25,7 +25,7 @@ def autotools_build(ctx, extra_args = []):
ctx.run(["make", "-j" + str(ctx.jobs)] + extra_args)
def autotools_install(ctx, pkg, extra_args = []):
ctx.run(["make", "install", "DESTDIR=" + pkg.dest_dir] + extra_args)
ctx.run(["make", "install"] + extra_args, env = { "DESTDIR": pkg.dest_dir })
def autotools(configure_args = [], configure_env = {}, build_args = [], install_args = []):
def _configure(ctx):
@@ -36,29 +36,7 @@ def autotools(configure_args = [], configure_env = {}, build_args = [], install_
autotools_install(ctx, pkg, extra_args = install_args)
return _configure, _build, _install
def host_autotools_configure(ctx, extra_args = [], extra_env = {}):
env = {
"CFLAGS": options.host_cflags,
"CXXFLAGS": options.host_cxxflags,
"LDFLAGS": options.host_ldflags,
}
env.update(extra_env)
ctx.run([
ctx.source_dir / "configure",
"--prefix=" + ctx.prefix,
"--sysconfdir=/etc",
"--localstatedir=/var",
"--disable-nls",
] + extra_args, env = env)
def host_autotools(configure_args = [], configure_env = {}, build_args = [], install_args = []):
def _configure(ctx):
host_autotools_configure(ctx, extra_args = configure_args, extra_env = configure_env)
def _build(ctx):
autotools_build(ctx, extra_args = build_args)
def _install(ctx, pkg):
autotools_install(ctx, pkg, extra_args = install_args)
return _configure, _build, _install
# CMake
def cmake_configure(ctx, extra_args = [], extra_env = {}, host = False):
if host:
@@ -92,9 +70,9 @@ def cmake_configure(ctx, extra_args = [], extra_env = {}, host = False):
"-B", ctx.build_dir,
"-G", "Ninja",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_PREFIX=" + ctx.prefix,
"-DCMAKE_INSTALL_SYSCONFDIR=/etc",
"-DCMAKE_INSTALL_LOCALSTATEDIR=/var",
"-DCMAKE_INSTALL_PREFIX=" + options.prefix,
"-DCMAKE_INSTALL_SYSCONFDIR=" + options.sysconfdir,
"-DCMAKE_INSTALL_LOCALSTATEDIR=" + options.localstatedir,
] + toolchain_args + extra_args, env = env)
def cmake_build(ctx, extra_args = []):
@@ -114,3 +92,4 @@ def cmake(configure_args = [], configure_env = {}, build_args = [], install_args
def _install(ctx, pkg):
cmake_install(ctx, pkg, extra_args = install_args)
return _configure, _build, _install
+2 -2
View File
@@ -11,7 +11,7 @@ source = tarball_source(
strip_components = 1,
)
host_deps = ["binutils", "gcc"]
deps = ["ncurses", "readline"]
deps = [options.libc, "ncurses", "readline"]
configure, build, install = autotools(configure_args = [
"--without-bash-malloc",
@@ -19,4 +19,4 @@ configure, build, install = autotools(configure_args = [
"--enable-readline",
"--enable-history",
"--enable-job-control",
])
], configure_env = {"CFLAGS": "-std=gnu17"})
-39
View File
@@ -1,39 +0,0 @@
version = "1.0.8"
revision = 1
metadata = meta(
description = "Block-sorting file compressor",
license = "bzip2-1.0.6",
website = "https://sourceware.org/bzip2/",
)
source = tarball_source(
url = f"https://sourceware.org/pub/bzip2/bzip2-{version}.tar.gz",
sha256 = "?",
strip_components = 1,
)
host_deps = ["binutils", "gcc"]
# bzip2 ships only a plain Makefile, no configure script.
def build(ctx):
# Copy sources into the build dir so the in-tree Makefile can write here.
ctx.run(["cp", "-rp", ctx.source_dir / ".", ctx.build_dir])
jobs = "-j" + str(ctx.jobs)
common = [
"CC=" + options.target_triple + "-gcc",
"AR=" + options.target_triple + "-ar",
"RANLIB=" + options.target_triple + "-ranlib",
"CFLAGS=" + options.cflags + " -D_FILE_OFFSET_BITS=64",
]
ctx.run(["make", jobs, "-f", "Makefile-libbz2_so"] + common)
ctx.run(["make", jobs, "libbz2.a", "bzip2", "bzip2recover"] + common)
def install(ctx, pkg):
ctx.run(["make", "install", "PREFIX=" + pkg.dest_dir + ctx.prefix])
# Install the shared library that the auxiliary Makefile produced.
libdir = pkg.dest_dir + ctx.prefix / "lib"
bindir = pkg.dest_dir + ctx.prefix / "bin"
ctx.run(["mkdir", "-p", libdir, bindir])
ctx.run(["cp", "-a", "libbz2.so." + version, libdir])
ctx.run(["ln", "-sf", "libbz2.so." + version, libdir + "/libbz2.so.1.0"])
ctx.run(["ln", "-sf", "libbz2.so." + version, libdir + "/libbz2.so.1"])
ctx.run(["ln", "-sf", "libbz2.so." + version, libdir + "/libbz2.so"])
+1 -1
View File
@@ -7,7 +7,7 @@ metadata = meta(
)
source = tarball_source(
url = f"https://ftp.gnu.org/gnu/coreutils/coreutils-{version}.tar.xz",
sha256 = "?",
sha256 = "7a0124327b398fd9eb1a6abde583389821422c744ffa10734b24f557610d3283",
strip_components = 1,
)
host_deps = ["binutils", "gcc"]
+6 -17
View File
@@ -16,12 +16,9 @@ deps = ["linux-headers"]
build_if = options.libc == "glibc"
def configure(ctx):
ctx.run([
ctx.source_dir / "configure",
"--host=" + options.target_triple,
autotools_configure(ctx, [
"--build=" + options.target_triple,
"--prefix=/usr",
"--with-headers=" + ctx.sysroot / "usr/include",
"--with-headers=" + ctx.sysroot / options.prefix / "include",
"--enable-kernel=5.4",
"--enable-bind-now",
"--enable-stack-protector=strong",
@@ -31,17 +28,9 @@ def configure(ctx):
"--disable-nscd",
"--without-selinux",
"--without-gd",
"libc_cv_slibdir=/lib",
"libc_cv_rtlddir=/lib",
"libc_cv_forced_unwind=yes",
"libc_cv_ssp=no",
"libc_cv_ssp_strong=yes",
], env = {
"CC": options.target_triple + "-gcc",
"CXX": options.target_triple + "-g++",
"CFLAGS": options.cflags,
})
])
def build(ctx):
ctx.run(["make", "-j" + str(ctx.jobs)])
def install(ctx, pkg):
ctx.run(["make", "install", "DESTDIR=" + pkg.dest_dir])
_, build, install = autotools()
+34 -6
View File
@@ -15,9 +15,37 @@ deps = [options.libc]
build_if = options.target_arch in ["x86_64", "aarch64", "riscv64", "loongarch64"]
configure, build, install = autotools(configure_env = {
"TOOLCHAIN_FOR_TARGET": options.target_triple + "-",
"LD_FOR_TARGET": options.target_triple + "-" + "ld",
"OBJCOPY_FOR_TARGET": options.target_triple + "-" + "objcopy",
"OBJDUMP_FOR_TARGET": options.target_triple + "-" + "objdump",
})
arch_configure_args = {
"x86_64": ["--enable-uefi-x86-64", "--enable-uefi-ia32", "--enable-bios", "--enable-bios-cd"],
"aarch64": ["--enable-uefi-aarch64"],
"riscv64": ["--enable-uefi-riscv64"],
"loongarch64": ["--enable-uefi-loongarch64"],
}
configure, build, install = autotools(
configure_args = ["--enable-uefi-cd"] + arch_configure_args.get(options.target_arch),
configure_env = {"TOOLCHAIN_FOR_TARGET": options.target_triple + "-"},
)
subpackages = [
subpackage(
"limine-uefi",
meta(description = "UEFI files"),
[
"usr/share/limine/BOOT*.EFI",
"usr/share/limine/limine-uefi-*.bin",
],
),
]
if options.target_arch == "x86_64":
subpackages += [
subpackage(
"limine-bios",
meta(description = "BIOS files"),
[
"usr/share/limine/limine-bios*",
],
)
]
+2 -2
View File
@@ -16,5 +16,5 @@ def build(ctx):
ctx.run(["find", ctx.build_dir / "usr/include", "-type", "f", "!", "-name", "*.h", "-delete"])
def install(ctx, pkg):
ctx.run(["mkdir", "-p", pkg.dest_dir + ctx.prefix])
ctx.run(["cp", "-rp", ctx.build_dir / "usr/include", pkg.dest_dir / ctx.prefix])
ctx.run(["mkdir", "-p", pkg.dest_dir / options.prefix])
ctx.run(["cp", "-rp", ctx.build_dir / "usr/include", pkg.dest_dir / options.prefix])
-1
View File
@@ -1 +0,0 @@
# TODO
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -13,7 +13,7 @@ source = tarball_source(
host_deps = ["binutils", "gcc"]
def _make_args(ctx, *args):
def make_args(ctx, *args):
# Translate arch name
if options.target_arch == "aarch64":
linux_arch = "arm64"
@@ -22,8 +22,6 @@ def _make_args(ctx, *args):
result = [
"make",
"-C", ctx.source_dir,
"O=" + ctx.build_dir,
"ARCH=" + linux_arch,
"CROSS_COMPILE=" + options.target_triple + "-",
"-j" + str(ctx.jobs),
@@ -32,10 +30,12 @@ def _make_args(ctx, *args):
return result
def configure(ctx):
ctx.run(_make_args(ctx, "defconfig"))
ctx.run(["cp", "-rp", ctx.source_dir / ".", ctx.build_dir])
ctx.run(["cp", ctx.files / "config." + options.target_arch, ctx.build_dir / ".config"])
ctx.run(make_args(ctx, "olddefconfig"))
def build(ctx):
ctx.run(_make_args(ctx, "bzImage"))
ctx.run(make_args(ctx))
def install(ctx, pkg):
ctx.install(
+2 -1
View File
@@ -7,10 +7,11 @@ metadata = meta(
)
source = tarball_source(
url = f"https://ftp.gnu.org/gnu/make/make-{version}.tar.gz",
sha256 = "?",
sha256 = "dd16fb1d67bfab79a72f5e8390735c49e3e8e70b4945a15ab1f81ddb78658fb3",
strip_components = 1,
)
host_deps = ["binutils", "gcc"]
deps = [options.libc]
configure, build, install = autotools(configure_args = [
"--without-guile",
+1 -1
View File
@@ -16,7 +16,7 @@ def configure(ctx):
ctx.run([
ctx.source_dir / "configure",
"--target=" + options.target_triple,
"--prefix=" + ctx.prefix,
"--prefix=" + options.prefix,
"--syslibdir=/lib",
], env = {
"CC": options.target_triple + "-gcc",
+6 -1
View File
@@ -13,7 +13,7 @@ source = tarball_source(
host_deps = ["binutils", "gcc"]
deps = [options.libc]
configure, build, install = autotools(configure_args = [
configure, build, _ = autotools(configure_args = [
"--with-shared",
"--without-debug",
"--without-ada",
@@ -31,3 +31,8 @@ configure, build, install = autotools(configure_args = [
"cf_cv_builtin_bool": "1",
"ac_cv_header_stdbool_h": "yes",
})
def install(ctx, pkg):
autotools_install(ctx, pkg, extra_args = [
"DESTDIR=" + pkg.dest_dir
])
+1 -1
View File
@@ -28,7 +28,7 @@ def configure(ctx):
ctx.run([
ctx.source_dir / "Configure",
ossl_target,
"--prefix=" + ctx.prefix,
"--prefix=" + options.prefix,
"--openssldir=/etc/ssl",
"--libdir=lib",
"shared",
+3 -2
View File
@@ -11,8 +11,9 @@ source = tarball_source(
strip_components = 1,
)
host_deps = ["binutils", "gcc"]
deps = [options.libc]
configure, build, install = autotools(configure_args = [
"--with-system-libdir=/usr/lib",
"--with-system-includedir=/usr/include",
"--with-system-libdir=" + options.libdir,
"--with-system-includedir=" + options.includedir,
])
+8 -2
View File
@@ -11,9 +11,9 @@ source = tarball_source(
strip_components = 1,
)
host_deps = ["binutils", "gcc"]
deps = ["ncurses"]
deps = [options.libc, "ncurses"]
configure, build, install = autotools(
configure, build, _ = autotools(
configure_args = ["--with-curses"],
configure_env = {
# Force linking against the system curses; otherwise readline's
@@ -21,3 +21,9 @@ configure, build, install = autotools(
"bash_cv_termcap_lib": "ncursesw",
},
)
# Readline overwrites DESTDIR
def install(ctx, pkg):
autotools_install(ctx, pkg, extra_args = [
"DESTDIR=" + pkg.dest_dir
])
+2 -1
View File
@@ -7,10 +7,11 @@ metadata = meta(
)
source = tarball_source(
url = f"https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.xz",
sha256 = "?",
sha256 = "db0590629b6f0fa36e74aea5f9731dc6f8df068ce7b7bafa45301832a5eebc3a",
strip_components = 1,
)
host_deps = ["binutils", "gcc"]
deps = [options.libc]
configure, build, install = autotools(configure_args = [
"--disable-doc",
+6 -5
View File
@@ -1,4 +1,4 @@
version = "1.3.1"
version = "1.3.2"
revision = 1
metadata = meta(
description = "Lossless data-compression library",
@@ -7,19 +7,20 @@ metadata = meta(
)
source = tarball_source(
url = f"https://zlib.net/zlib-{version}.tar.xz",
sha256 = "?",
sha256 = "d7a0654783a4da529d1bb793b7ad9c3318020af77667bcae35f95d0e42a792f3",
strip_components = 1,
)
host_deps = ["binutils", "gcc"]
deps = [options.libc]
def configure(ctx):
# zlib ships its own ./configure that does not understand the usual
# autoconf flags (no --host, --build, etc.), so it is invoked directly.
ctx.run([
ctx.source_dir / "configure",
"--prefix=" + ctx.prefix,
"--libdir=" + ctx.prefix / "lib",
"--sharedlibdir=" + ctx.prefix / "lib",
"--prefix=" + options.prefix,
"--libdir=" + options.libdir,
"--sharedlibdir=" + options.libdir,
], env = {
"CC": options.target_triple + "-gcc",
"AR": options.target_triple + "-ar",
+172 -29
View File
@@ -26,6 +26,36 @@ use crate::{
recipe::{GitSource, OutputPackage, Recipe, RecipeKind, RecipeSet, Source},
};
const SPLIT_SUBPACKAGE_SCRIPT: &str = r#"
base=$1
dest=$2
package=$3
shift 3
mkdir -p "$dest"
for pattern do
matches=$(mktemp)
find "$base" -depth -path "$base/$pattern" -print > "$matches"
if ! [ -s "$matches" ]; then
echo "subpackage $package: glob '$pattern' matched no files under $base" >&2
rm -f "$matches"
exit 1
fi
while IFS= read -r path; do
[ "$path" != "$base" ] || continue
rel=${path#"$base"/}
target=$dest/$rel
mkdir -p "$(dirname "$target")"
echo "$rel"
mv "$path" "$target"
done < "$matches"
rm -f "$matches"
done
find "$base" -depth -type d -empty -delete
"#;
#[derive(Debug)]
pub struct Builder {
root: PathBuf,
@@ -52,7 +82,7 @@ impl Builder {
let requested = filter_skipped(recipes, requested);
let plan = TaskPlanner::new(&self.root, &self.config.arch, recipes)
.build_plan(&requested, rebuild)?;
self.print_plan(&plan);
self.print_plan(recipes, &plan)?;
if dry_run {
return Ok(());
}
@@ -68,7 +98,7 @@ impl Builder {
let requested = filter_skipped(recipes, requested);
let plan =
TaskPlanner::new(&self.root, &self.config.arch, recipes).fetch_plan(&requested)?;
self.print_plan(&plan);
self.print_plan(recipes, &plan)?;
if dry_run {
return Ok(());
}
@@ -128,6 +158,11 @@ impl Builder {
let recipe = recipes.recipe(key)?;
self.task_prepare_sources(layout, recipe)
}
TaskId::PrepareRecipe(key) => {
let recipe = recipes.recipe(key)?;
let active = active.expect("recipe prepare task requires an active container");
self.task_prepare_recipe(layout, recipe, active)
}
TaskId::ConfigureRecipe(key) => {
let recipe = recipes.recipe(key)?;
let active = active.expect("configure task requires an active container");
@@ -206,7 +241,7 @@ impl Builder {
let mut tmp = tempfile::NamedTempFile::new_in(&cache_dir)
.with_context(|| format!("creating temp file in {}", cache_dir.display()))?;
let mut hasher = Sha256::new();
let mut buf = [0u8; 64 * 1024];
let mut buf: [u8; 65536] = [0u8; 64 * 1024];
loop {
let n = std::io::Read::read(&mut response, &mut buf)
.with_context(|| format!("reading response body for {url}"))?;
@@ -235,12 +270,6 @@ impl Builder {
})?;
}
if unknown {
bail!(
"{}: source sha256 is unknown; update the recipe with sha256 = \"{hash}\"",
recipe.key()
);
}
Ok(())
}
@@ -418,6 +447,22 @@ impl Builder {
Ok(())
}
fn task_prepare_recipe(
&self,
layout: &Layout<'_>,
recipe: &Recipe,
active: &ActiveContainer,
) -> anyhow::Result<()> {
let Some(func) = recipe.phases().prepare() else {
return Ok(());
};
log::step("prepare", &format!("{} (recipe phase)", recipe.key()));
let ctx = phase_context_for(recipe);
self.invoke_with_runtime(active, &[PhaseArg::Ctx(ctx)], func)?;
self.write_recipe_stamp(layout, recipe, "prepare")
}
fn task_configure(
&self,
layout: &Layout<'_>,
@@ -452,6 +497,10 @@ impl Builder {
active: &ActiveContainer,
) -> anyhow::Result<()> {
log::step("install", &output.key());
if !output.is_base() {
return self.task_split_subpackage(layout, recipe, output, active);
}
let dest = format!("/dest/{}", output.name());
active.container.borrow().exec(
&["mkdir".to_owned(), "-p".to_owned(), dest.clone()],
@@ -468,6 +517,36 @@ impl Builder {
self.write_output_stamp(layout, recipe, output, "install")
}
fn task_split_subpackage(
&self,
layout: &Layout<'_>,
recipe: &Recipe,
output: &OutputPackage,
active: &ActiveContainer,
) -> anyhow::Result<()> {
let base = recipe
.base_output()
.ok_or_else(|| anyhow::anyhow!("recipe `{}` has no base output", recipe.key()))?;
let base_dest = format!("/dest/{}", base.name());
let dest = format!("/dest/{}", output.name());
let mut argv: Vec<String> = vec![
"sh".to_owned(),
"-eu".to_owned(),
"-c".to_owned(),
SPLIT_SUBPACKAGE_SCRIPT.to_owned(),
"split-subpackage".to_owned(),
base_dest,
dest,
output.name().to_owned(),
];
argv.extend(output.file_globs().iter().cloned());
active
.container
.borrow()
.exec(&argv, &base_env(&active.base_path), "/")?;
self.write_output_stamp(layout, recipe, output, "install")
}
fn task_install_host(
&self,
layout: &Layout<'_>,
@@ -594,6 +673,10 @@ impl Builder {
}
let env = base_env(&active.base_path);
log::step(
"sysroot",
&format!("{} from {} target apk(s)", recipe.key(), deps.len()),
);
for dep in deps {
let output = recipes.output(dep)?;
let owning = recipes.recipe(output.recipe())?;
@@ -616,6 +699,7 @@ impl Builder {
"apk".to_owned(),
"extract".to_owned(),
"--allow-untrusted".to_owned(),
"--force-overwrite".to_owned(),
"--destination".to_owned(),
"/sysroot".to_owned(),
format!("/pkgs/{file_name}"),
@@ -693,10 +777,10 @@ impl Builder {
let bare = dep_key.strip_prefix("host:").unwrap_or(dep_key);
mounts.push(Mount {
host: install,
container: format!("/tools/{bare}/usr/local"),
container: format!("/tools/{bare}"),
read_only: true,
});
tools_bins.push(format!("/tools/{bare}/usr/local/bin"));
tools_bins.push(format!("/tools/{bare}/bin"));
}
let name = format!(
@@ -837,23 +921,54 @@ impl Builder {
Ok(hex::encode(hasher.finalize()))
}
fn print_plan(&self, plan: &TaskPlan) {
fn print_plan(&self, recipes: &RecipeSet, plan: &TaskPlan) -> anyhow::Result<()> {
if plan.is_empty() {
log::skip("plan", "nothing to do");
return;
return Ok(());
}
let task_count = plan.order().len();
let edge_count = plan.dependency_count();
log::step(
"plan",
&format!(
"{} active task(s), {} edge(s)",
plan.order().len(),
plan.dependency_count()
"{} {}, {} {}",
task_count,
plural(task_count, "task", "tasks"),
edge_count,
plural(edge_count, "dependency edge", "dependency edges")
),
);
for task in plan.order() {
println!("{task}");
let step_width = task_count.to_string().len() * 2 + 1;
let action_width = plan
.order()
.iter()
.map(|task| plan_task_parts(task).0.len())
.max()
.unwrap_or(0);
let mut current_recipe: Option<String> = None;
for (index, task) in plan.order().iter().enumerate() {
let recipe_slug = task_recipe_slug(task, recipes)?;
if current_recipe.as_deref() != Some(recipe_slug.as_str()) {
if current_recipe.is_some() {
println!();
}
println!(" {recipe_slug}");
current_recipe = Some(recipe_slug.clone());
}
let step = format!("{}/{}", index + 1, task_count);
let (action, target) = plan_task_parts(task);
if target == recipe_slug {
println!(" {step:>step_width$} {action}");
} else {
println!(" {step:>step_width$} {action:<action_width$} {target}");
}
}
Ok(())
}
fn abs_config_path(&self, path: &Path) -> PathBuf {
@@ -874,14 +989,7 @@ struct ActiveContainer {
fn filter_skipped(recipes: &RecipeSet, requested: &[String]) -> Vec<String> {
requested
.iter()
.filter(|key| {
if recipes.is_skipped(key) {
log::skip("skip", &format!("{key} (build_if returned false)"));
false
} else {
true
}
})
.filter(|key| !recipes.is_skipped(key))
.cloned()
.collect()
}
@@ -894,9 +1002,27 @@ fn task_needs_container(task: &TaskId) -> bool {
| TaskId::InstallPackageFiles(_)
| TaskId::ProduceApk(_)
| TaskId::InstallHostRecipe(_)
| TaskId::PrepareRecipe(_)
)
}
fn plan_task_parts(task: &TaskId) -> (&'static str, &str) {
match task {
TaskId::FetchSources(recipe) => ("fetch sources", recipe),
TaskId::PrepareSources(recipe) => ("prepare sources", recipe),
TaskId::PrepareRecipe(recipe) => ("recipe prepare", recipe),
TaskId::ConfigureRecipe(recipe) => ("configure", recipe),
TaskId::BuildRecipe(recipe) => ("build", recipe),
TaskId::InstallPackageFiles(output) => ("install files", output),
TaskId::ProduceApk(output) => ("produce apk", output),
TaskId::InstallHostRecipe(recipe) => ("install host", recipe),
}
}
fn plural<'a>(count: usize, singular: &'a str, plural: &'a str) -> &'a str {
if count == 1 { singular } else { plural }
}
fn prefix_for(kind: RecipeKind) -> &'static str {
match kind {
RecipeKind::Package => "/usr",
@@ -937,13 +1063,30 @@ fn default_jobs() -> i32 {
.unwrap_or(1)
}
const TERMINAL_ENV_VARS: &[&str] = &[
"TERM",
"COLORTERM",
"NO_COLOR",
"CLICOLOR",
"CLICOLOR_FORCE",
"FORCE_COLOR",
"TERM_PROGRAM",
"TERM_PROGRAM_VERSION",
];
fn bare_env() -> Vec<(String, String)> {
vec![
let mut env = vec![
("PATH".to_owned(), String::new()),
("HOME".to_owned(), "/tmp".to_owned()),
("TERM".to_owned(), "dumb".to_owned()),
("LC_ALL".to_owned(), "C".to_owned()),
]
];
env.extend(TERMINAL_ENV_VARS.iter().filter_map(|key| {
std::env::var(key)
.ok()
.filter(|value| !value.is_empty())
.map(|value| ((*key).to_owned(), value))
}));
env
}
fn base_env(path: &str) -> Vec<(String, String)> {
+152
View File
@@ -1,6 +1,15 @@
use std::{
fs::File,
io::{self, Read},
os::{fd::FromRawFd, unix::io::RawFd},
path::{Path, PathBuf},
process::{Command, Stdio},
ptr,
sync::{
Mutex,
atomic::{AtomicBool, AtomicI32, Ordering},
},
thread,
};
use anyhow::{Context, bail};
@@ -21,6 +30,41 @@ pub struct Container {
stopped: bool,
}
#[derive(Clone, Debug)]
struct RegisteredContainer {
runtime: &'static str,
id: String,
}
static ACTIVE_CONTAINER: Mutex<Option<RegisteredContainer>> = Mutex::new(None);
static SIGNAL_CLEANUP_INSTALLED: AtomicBool = AtomicBool::new(false);
static SIGNAL_WRITE_FD: AtomicI32 = AtomicI32::new(-1);
pub fn install_signals() -> anyhow::Result<()> {
if SIGNAL_CLEANUP_INSTALLED
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_err()
{
return Ok(());
}
let mut fds = [0; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } == -1 {
SIGNAL_CLEANUP_INSTALLED.store(false, Ordering::SeqCst);
return Err(io::Error::last_os_error()).context("creating signal cleanup pipe");
}
set_close_on_exec(fds[0]);
set_close_on_exec(fds[1]);
SIGNAL_WRITE_FD.store(fds[1], Ordering::SeqCst);
thread::spawn(move || signal_cleanup_loop(fds[0]));
install_signal_handler(libc::SIGINT)?;
install_signal_handler(libc::SIGTERM)?;
Ok(())
}
impl Container {
pub fn start(
runtime: &ContainerRuntime,
@@ -77,6 +121,8 @@ impl Container {
bail!("`{runtime_str} run` returned an empty container id");
}
register_container(runtime_str, &id);
Ok(Self {
runtime: runtime_str,
id,
@@ -135,6 +181,112 @@ impl Drop for Container {
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
self.stopped = true;
}
unregister_container(&self.id);
}
}
fn register_container(runtime: &'static str, id: &str) {
if let Ok(mut active) = ACTIVE_CONTAINER.lock() {
*active = Some(RegisteredContainer {
runtime,
id: id.to_owned(),
});
}
}
fn unregister_container(id: &str) {
if let Ok(mut active) = ACTIVE_CONTAINER.lock()
&& active.as_ref().is_some_and(|container| container.id == id)
{
*active = None;
}
}
fn active_container() -> Option<RegisteredContainer> {
ACTIVE_CONTAINER
.lock()
.ok()
.and_then(|active| active.clone())
}
fn signal_cleanup_loop(read_fd: RawFd) {
let mut signals = unsafe { File::from_raw_fd(read_fd) };
let mut buffer = [0_u8; 16];
loop {
match signals.read(&mut buffer) {
Ok(0) => return,
Ok(len) => {
let signal = buffer[..len]
.iter()
.copied()
.find(|signal| *signal != 0)
.unwrap_or(libc::SIGINT as u8);
cleanup_after_signal(signal as i32);
}
Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
Err(_) => return,
}
}
}
fn cleanup_after_signal(signal: i32) -> ! {
if let Some(container) = active_container() {
eprintln!(
"\nreceived signal {signal}; killing container {}",
container.id
);
kill_container_detached(container.runtime, &container.id);
}
std::process::exit(128 + signal);
}
fn kill_container_detached(runtime: &str, id: &str) {
let _ = Command::new(runtime)
.arg("kill")
.arg(id)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::inherit())
.spawn();
}
fn set_close_on_exec(fd: RawFd) {
unsafe {
let flags = libc::fcntl(fd, libc::F_GETFD);
if flags != -1 {
let _ = libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC);
}
}
}
fn install_signal_handler(signal: i32) -> anyhow::Result<()> {
let mut action = unsafe { std::mem::zeroed::<libc::sigaction>() };
action.sa_sigaction = handle_signal as *const () as usize;
action.sa_flags = 0;
unsafe {
libc::sigemptyset(&mut action.sa_mask);
if libc::sigaction(signal, &action, ptr::null_mut()) == -1 {
return Err(io::Error::last_os_error())
.with_context(|| format!("installing handler for signal {signal}"));
}
}
Ok(())
}
extern "C" fn handle_signal(signal: libc::c_int) {
let fd = SIGNAL_WRITE_FD.load(Ordering::Relaxed);
if fd < 0 {
return;
}
let signal = signal as u8;
unsafe {
libc::write(fd, ptr::addr_of!(signal).cast(), 1);
}
}
+9 -11
View File
@@ -3,7 +3,7 @@ use starlark::{
environment::{FrozenModule, Globals, GlobalsBuilder, Module},
eval::Evaluator,
syntax::{AstModule, Dialect},
values::list::ListRef,
values::list::{ListRef, UnpackList},
};
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
@@ -46,7 +46,7 @@ fn builder_globals(builder: &mut GlobalsBuilder) {
url: String,
sha256: String,
strip_components: Option<u32>,
patches: Option<starlark::values::list::UnpackList<String>>,
patches: Option<UnpackList<String>>,
) -> anyhow::Result<Source> {
Ok(Source::Tarball(TarballSource::new(
url,
@@ -59,7 +59,7 @@ fn builder_globals(builder: &mut GlobalsBuilder) {
fn git_source(
url: String,
commit: String,
patches: Option<starlark::values::list::UnpackList<String>>,
patches: Option<UnpackList<String>>,
) -> anyhow::Result<Source> {
Ok(Source::Git(GitSource::new(
url,
@@ -68,16 +68,14 @@ fn builder_globals(builder: &mut GlobalsBuilder) {
)))
}
fn subpackage(name: String, metadata: Option<&Metadata>) -> anyhow::Result<Subpackage> {
let metadata = metadata
.cloned()
.unwrap_or_else(|| Metadata::new(None, None, None, None));
Ok(Subpackage::new(name, metadata))
fn subpackage(
name: String,
metadata: &Metadata,
files: UnpackList<String>,
) -> anyhow::Result<Subpackage> {
Ok(Subpackage::new(name, files.items, metadata.clone()))
}
/// Wrap a string as a `path`. Paths support the `/` operator for
/// meson-style joining (`path("/usr") / "lib" / "pkgconfig"`), with
/// the same semantics as `pathlib.PurePosixPath`.
fn path(value: String) -> anyhow::Result<StarPath> {
Ok(StarPath::new(value))
}
+50 -2
View File
@@ -15,6 +15,7 @@ use crate::{
pub enum TaskId {
FetchSources(String),
PrepareSources(String),
PrepareRecipe(String),
ConfigureRecipe(String),
BuildRecipe(String),
InstallPackageFiles(String),
@@ -27,6 +28,7 @@ impl fmt::Display for TaskId {
match self {
Self::FetchSources(recipe) => write!(f, "fetch sources {recipe}"),
Self::PrepareSources(recipe) => write!(f, "prepare sources {recipe}"),
Self::PrepareRecipe(recipe) => write!(f, "prepare recipe {recipe}"),
Self::ConfigureRecipe(recipe) => write!(f, "configure {recipe}"),
Self::BuildRecipe(recipe) => write!(f, "build {recipe}"),
Self::InstallPackageFiles(output) => write!(f, "install package files {output}"),
@@ -146,9 +148,13 @@ impl<'a> TaskPlanner<'a> {
match task {
TaskId::FetchSources(_) => Ok(Vec::new()),
TaskId::PrepareSources(recipe) => Ok(vec![TaskId::FetchSources(recipe.clone())]),
TaskId::PrepareRecipe(recipe) => Ok(vec![TaskId::PrepareSources(recipe.clone())]),
TaskId::ConfigureRecipe(recipe) => {
let recipe = self.recipes.recipe(recipe)?;
let mut deps = vec![TaskId::PrepareSources(recipe.key())];
let mut deps = vec![
TaskId::PrepareSources(recipe.key()),
TaskId::PrepareRecipe(recipe.key()),
];
deps.extend(
recipe
.host_deps()
@@ -167,12 +173,29 @@ impl<'a> TaskPlanner<'a> {
TaskId::BuildRecipe(recipe) => Ok(vec![TaskId::ConfigureRecipe(recipe.clone())]),
TaskId::InstallPackageFiles(output) => {
let output = self.recipes.output(output)?;
let recipe = self.recipes.recipe(output.recipe())?;
if output.is_base() {
Ok(vec![TaskId::BuildRecipe(output.recipe().to_owned())])
} else {
let base = recipe.base_output().ok_or_else(|| {
anyhow::anyhow!("recipe `{}` has no base output", recipe.key())
})?;
Ok(vec![TaskId::InstallPackageFiles(base.key())])
}
}
TaskId::ProduceApk(output) => {
let output = self.recipes.output(output)?;
let recipe = self.recipes.recipe(output.recipe())?;
let mut deps = vec![TaskId::InstallPackageFiles(output.key())];
if output.is_base() {
deps.extend(
recipe
.outputs()
.iter()
.filter(|candidate| !candidate.is_base())
.map(|subpackage| TaskId::InstallPackageFiles(subpackage.key())),
);
}
deps.extend(
recipe
.deps()
@@ -195,6 +218,9 @@ impl<'a> TaskPlanner<'a> {
TaskId::PrepareSources(recipe) => {
self.prepare_sources_active(self.recipes.recipe(recipe)?)
}
TaskId::PrepareRecipe(recipe) => {
self.prepare_recipe_active(self.recipes.recipe(recipe)?)
}
TaskId::ConfigureRecipe(recipe) => {
self.recipe_task_active(self.recipes.recipe(recipe)?, "configure")
}
@@ -204,8 +230,19 @@ impl<'a> TaskPlanner<'a> {
TaskId::InstallPackageFiles(output) => {
let output = self.recipes.output(output)?;
let recipe = self.recipes.recipe(output.recipe())?;
Ok(self.output_task_active(recipe, output, "install")?
if output.is_base() {
return recipe.outputs().iter().try_fold(false, |active, output| {
Ok(active
|| self.output_task_active(recipe, output, "install")?
|| self.produce_apk_active(recipe, output)?)
});
}
let base = recipe.base_output().ok_or_else(|| {
anyhow::anyhow!("recipe `{}` has no base output", recipe.key())
})?;
Ok(self.output_task_active(recipe, output, "install")?
|| self.produce_apk_active(recipe, output)?
|| self.produce_apk_active(recipe, base)?)
}
TaskId::ProduceApk(output) => {
let output = self.recipes.output(output)?;
@@ -250,6 +287,16 @@ impl<'a> TaskPlanner<'a> {
Ok(false)
}
fn prepare_recipe_active(&self, recipe: &Recipe) -> anyhow::Result<bool> {
if recipe.phases().prepare().is_none() {
return Ok(false);
}
if self.prepare_sources_active(recipe)? {
return Ok(true);
}
self.recipe_task_active(recipe, "prepare")
}
fn recipe_task_active(&self, recipe: &Recipe, kind: &str) -> anyhow::Result<bool> {
if self.is_recipe_forced(recipe) {
return Ok(true);
@@ -376,6 +423,7 @@ pub fn task_recipe_slug(task: &TaskId, recipes: &RecipeSet) -> anyhow::Result<St
Ok(match task {
TaskId::FetchSources(recipe)
| TaskId::PrepareSources(recipe)
| TaskId::PrepareRecipe(recipe)
| TaskId::ConfigureRecipe(recipe)
| TaskId::BuildRecipe(recipe)
| TaskId::InstallHostRecipe(recipe) => recipe.clone(),
+1 -4
View File
@@ -35,10 +35,7 @@ impl<'a> Layout<'a> {
}
pub fn host_install_dir(&self, recipe: &Recipe) -> PathBuf {
self.root
.join("build/host-pkgs")
.join(recipe.slug())
.join("usr/local")
self.root.join("build/host-pkgs").join(recipe.slug())
}
pub fn host_install_root(&self, recipe: &Recipe) -> PathBuf {
+1
View File
@@ -11,5 +11,6 @@ mod phase;
mod recipe;
fn main() -> anyhow::Result<()> {
container::install_signals()?;
cli::run()
}
+1 -6
View File
@@ -73,9 +73,6 @@ impl Path {
}
fn join_paths(base: &str, rhs: &str) -> String {
if rhs.starts_with('/') {
return rhs.to_owned();
}
let trimmed = base.trim_end_matches('/');
if trimmed.is_empty() {
format!("/{rhs}")
@@ -247,7 +244,6 @@ impl<'v> StarlarkValue<'v> for PhaseContext {
Some(match attr {
"source_dir" => heap.alloc(self.source_dir.clone()),
"build_dir" => heap.alloc(Path::new(self.build_dir.clone())),
"prefix" => heap.alloc(Path::new(self.prefix.clone())),
"sysroot" => heap.alloc(Path::new(self.sysroot.clone())),
"files" => heap.alloc(Path::new(self.files.as_ref()?.clone())),
"jobs" => heap.alloc(self.jobs),
@@ -257,7 +253,7 @@ impl<'v> StarlarkValue<'v> for PhaseContext {
fn has_attr(&self, attr: &str, _heap: &'v Heap) -> bool {
match attr {
"source_dir" | "build_dir" | "prefix" | "sysroot" | "jobs" | "run" => true,
"source_dir" | "build_dir" | "sysroot" | "jobs" | "run" => true,
"files" => self.files.is_some(),
_ => false,
}
@@ -267,7 +263,6 @@ impl<'v> StarlarkValue<'v> for PhaseContext {
let mut attrs = vec![
"source_dir".to_owned(),
"build_dir".to_owned(),
"prefix".to_owned(),
"sysroot".to_owned(),
"jobs".to_owned(),
"run".to_owned(),
+17
View File
@@ -51,4 +51,21 @@ impl Metadata {
pub fn website(&self) -> Option<&str> {
self.website.as_deref()
}
pub fn inherit(&self, from: &Self) -> Self {
Self {
maintainer: self
.maintainer
.as_ref()
.or(from.maintainer.as_ref())
.cloned(),
description: self
.description
.as_ref()
.or(from.description.as_ref())
.cloned(),
license: self.license.as_ref().or(from.license.as_ref()).cloned(),
website: self.website.as_ref().or(from.website.as_ref()).cloned(),
}
}
}
+60 -51
View File
@@ -13,14 +13,12 @@ use starlark::{
};
use std::{
collections::{HashMap, HashSet},
fmt,
path::{Path, PathBuf},
};
use walkdir::WalkDir;
use crate::{
eval::{self, ExtractError},
log,
options::Options,
};
@@ -100,24 +98,6 @@ pub struct Recipe {
phases: RecipePhases,
}
impl fmt::Debug for Recipe {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Recipe")
.field("path", &self.path)
.field("kind", &self.kind)
.field("version", &self.version)
.field("revision", &self.revision)
.field("sources", &self.sources)
.field("outputs", &self.outputs)
.field("host_deps", &self.host_deps)
.field("build_deps", &self.build_deps)
.field("deps", &self.deps)
.field("run_deps", &self.run_deps)
.field("phases", &self.phases)
.finish()
}
}
impl Recipe {
pub fn load(
path: &Path,
@@ -129,7 +109,7 @@ impl Recipe {
let module = eval::eval_file(path, Some(options), lib)
.with_context(|| format!("evaluating recipe {}", path.display()))?;
if !evaluate_build_if(&module)? {
if !Self::eval_build_if(&module)? {
return Ok(None);
}
@@ -176,7 +156,7 @@ impl Recipe {
let key_str = key
.unpack_str()
.ok_or_else(|| anyhow::anyhow!("field `sources`: keys must be strings"))?;
if !is_valid_source_name(key_str) {
if !Self::is_valid_source_name(key_str) {
bail!(
"field `sources`: invalid source name `{key_str}` (allowed: letters, digits, `-`, `_`; must start with a letter or digit)"
);
@@ -192,10 +172,10 @@ impl Recipe {
}
};
let host_deps = optional_string_list(&module, "host_deps")?;
let build_deps = optional_string_list(&module, "build_deps")?;
let deps = optional_string_list(&module, "deps")?;
let run_deps = optional_string_list(&module, "run_deps")?;
let host_deps = Self::optional_string_list(&module, "host_deps")?;
let build_deps = Self::optional_string_list(&module, "build_deps")?;
let deps = Self::optional_string_list(&module, "deps")?;
let run_deps = Self::optional_string_list(&module, "run_deps")?;
let recipe_key = kind.key(name);
let outputs = match kind {
@@ -204,6 +184,8 @@ impl Recipe {
recipe: recipe_key.clone(),
name: name.to_owned(),
metadata: metadata.clone(),
file_globs: Vec::new(),
base: true,
}];
if let Some(value) = module.get("subpackages") {
let list = ListRef::from_value(value)
@@ -214,10 +196,13 @@ impl Recipe {
"field `subpackages`: each entry must be a subpackage value"
)
})?;
Self::check_subpackage_file_globs(sub.name(), sub.file_globs())?;
outputs.push(OutputPackage {
recipe: recipe_key.clone(),
name: sub.name().to_owned(),
metadata: sub.metadata().clone(),
metadata: sub.metadata().inherit(&metadata),
file_globs: sub.file_globs().to_vec(),
base: false,
});
}
}
@@ -306,6 +291,10 @@ impl Recipe {
&self.outputs
}
pub fn base_output(&self) -> Option<&OutputPackage> {
self.outputs.iter().find(|output| output.is_base())
}
pub fn host_deps(&self) -> &[String] {
&self.host_deps
}
@@ -321,7 +310,6 @@ impl Recipe {
pub fn run_deps(&self) -> &[String] {
&self.run_deps
}
}
fn optional_string_list(module: &Module, key: &str) -> anyhow::Result<Vec<String>> {
match eval::extract_string_list(module, key) {
@@ -331,11 +319,8 @@ fn optional_string_list(module: &Module, key: &str) -> anyhow::Result<Vec<String
}
}
/// Evaluate the recipe's optional `build_if` gate. It may be either a bool
/// (or any expression evaluating to one) or a zero-argument callable that
/// returns a bool. Recipes can reach configuration via the `options` global.
/// Recipes without a `build_if` are unconditionally built.
fn evaluate_build_if(module: &Module) -> anyhow::Result<bool> {
/// Evaluates "build_if" functions.
fn eval_build_if(module: &Module) -> anyhow::Result<bool> {
let Some(value) = module.get("build_if") else {
return Ok(true);
};
@@ -369,31 +354,50 @@ fn is_valid_source_name(name: &str) -> bool {
chars.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
}
fn check_subpackage_file_globs(name: &str, file_globs: &[String]) -> anyhow::Result<()> {
if file_globs.is_empty() {
bail!("subpackage '{name}' must declare at least one file glob");
}
for glob in file_globs {
if glob.is_empty() {
bail!("subpackage '{name}' has an empty file glob");
}
if glob.starts_with('/') {
bail!("subpackage '{name}' glob '{glob}' must be relative to the package dest dir");
}
if Path::new(glob)
.components()
.any(|component| matches!(component, std::path::Component::ParentDir))
{
bail!("subpackage '{name}' glob '{glob}' must not contain '..'");
}
}
Ok(())
}
}
pub struct RecipePhases {
prepare: Option<OwnedFrozenValue>,
configure: Option<OwnedFrozenValue>,
build: OwnedFrozenValue,
install: OwnedFrozenValue,
}
impl fmt::Debug for RecipePhases {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RecipePhases")
.field("configure", &self.configure.is_some())
.field("build", &true)
.field("install", &true)
.finish()
}
}
impl RecipePhases {
fn load(module: &FrozenModule) -> anyhow::Result<Self> {
Ok(Self {
prepare: optional_phase_function(module, "prepare")?,
configure: optional_phase_function(module, "configure")?,
build: required_phase_function(module, "build")?,
install: required_phase_function(module, "install")?,
})
}
pub fn prepare(&self) -> Option<&OwnedFrozenValue> {
self.prepare.as_ref()
}
pub fn configure(&self) -> Option<&OwnedFrozenValue> {
self.configure.as_ref()
}
@@ -440,7 +444,7 @@ fn validate_phase_function(name: &str, value: &OwnedFrozenValue) -> anyhow::Resu
Ok(())
}
#[derive(Clone, Debug)]
#[derive(Clone)]
pub struct OutputPackage {
/// Canonical key of the owning recipe.
recipe: String,
@@ -448,6 +452,10 @@ pub struct OutputPackage {
name: String,
/// Metadata attached to the output package.
metadata: Metadata,
/// File globs claimed from the base package dest dir.
file_globs: Vec<String>,
/// Whether this is the recipe's base output.
base: bool,
}
impl OutputPackage {
@@ -466,9 +474,16 @@ impl OutputPackage {
pub fn metadata(&self) -> &Metadata {
&self.metadata
}
pub fn file_globs(&self) -> &[String] {
&self.file_globs
}
pub fn is_base(&self) -> bool {
self.base
}
}
#[derive(Debug)]
pub struct RecipeSet {
recipes: HashMap<String, Recipe>,
outputs: HashMap<String, OutputPackage>,
@@ -500,7 +515,6 @@ impl RecipeSet {
let loaded = Recipe::load(&path, &name, kind, options, lib)
.with_context(|| format!("loading recipe `{name}`"))?;
let Some(recipe) = loaded else {
log::skip("load", &format!("{key} (build_if returned false)"));
skipped.insert(key);
continue;
};
@@ -545,11 +559,6 @@ impl RecipeSet {
}
}
/// Find all recipe `.star` files under `dir`, returning a map of recipe name
/// to its `.star` file. Intermediate directories act as categories and are
/// not themselves recipes. Within any subtree, a recipe takes either form:
/// - `.../<name>.star` — name is the file stem
/// - `.../<name>/recipe.star` — name is the parent directory
fn discover_recipes(dir: &Path) -> anyhow::Result<HashMap<String, PathBuf>> {
let mut recipes: HashMap<String, PathBuf> = HashMap::new();
+11 -2
View File
@@ -7,18 +7,27 @@ use crate::recipe::Metadata;
#[derive(Debug, Clone, Allocative, ProvidesStaticType, NoSerialize)]
pub struct Subpackage {
name: String,
file_globs: Vec<String>,
metadata: Metadata,
}
impl Subpackage {
pub fn new(name: String, metadata: Metadata) -> Self {
Self { name, metadata }
pub fn new(name: String, file_globs: Vec<String>, metadata: Metadata) -> Self {
Self {
name,
file_globs,
metadata,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn file_globs(&self) -> &[String] {
&self.file_globs
}
pub fn metadata(&self) -> &Metadata {
&self.metadata
}