cookbook: add cookbook_apply_patches helper for external patch model (Rule 2)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-11 10:55:50 +03:00
parent 6f656fb266
commit facca4d5ed
3 changed files with 202 additions and 5 deletions
+9
View File
@@ -10,6 +10,7 @@ use crate::recipe::Recipe;
use crate::recipe::{AutoDeps, CookRecipe};
use crate::recipe::{BuildKind, OptionalPackageRecipe};
use std::collections::VecDeque;
use std::env;
use std::{
collections::BTreeSet,
fs,
@@ -452,6 +453,14 @@ pub fn build(
command.env("COOKBOOK_STAGE", &cookbook_stage);
command.env("COOKBOOK_SOURCE", &cookbook_source);
command.env("COOKBOOK_SYSROOT", &cookbook_sysroot);
// Default COOKBOOK_HOST_SYSROOT to ~/.redoxer/<target>/toolchain
// (where the standard cross-toolchain is installed).
let target_str = package_target(name);
let host_sysroot = env::var("COOKBOOK_HOST_SYSROOT").unwrap_or_else(|_| {
let home = env::var("HOME").unwrap_or_else(|_| "/home/kellito".to_string());
format!("{}/.redoxer/{}/toolchain", home, target_str)
});
command.env("COOKBOOK_HOST_SYSROOT", host_sysroot);
if let Some(cookbook_toolchain) = &cookbook_toolchain {
command.env("COOKBOOK_TOOLCHAIN", cookbook_toolchain);
} else if name.is_host() {
+100
View File
@@ -8,6 +8,7 @@ use crate::cook::fetch_repo::PlainPtyCallback;
use crate::cook::fs::*;
use crate::cook::package::get_package_name;
use crate::cook::package::package_source_paths;
use crate::cook::package::package_target;
use crate::cook::pty::PtyOut;
use crate::cook::script::*;
use crate::is_redox;
@@ -229,6 +230,101 @@ pub(crate) fn get_blake3(path: &PathBuf) -> Result<String> {
Ok(hash.to_hex().to_string())
}
/// Execute a recipe's [source].script (if any) in the source dir, with the
/// same env as a build script. The script is the canonical place for source
/// preparation (autogen.sh, GNU_CONFIG_GET config.sub, etc.).
///
/// This function is a no-op if the recipe has no [source].script. It is
/// called by both `fetch` (online) and `fetch_offline` (offline) after the
/// tar/git/path extraction has placed the source dir, but before the build
/// script runs. Before this function existed, [source].script was parsed
/// but never executed — see git history of src/recipe.rs and src/cook/script.rs.
///
/// Env vars provided to the source script (same as BUILD_PRESCRIPT):
/// - COOKBOOK_ROOT : path to the cookbook source tree
/// - COOKBOOK_SOURCE : absolute path to the recipe's source dir
/// - COOKBOOK_SYSROOT : absolute path to the recipe's collected sysroot
/// - COOKBOOK_RECIPE : absolute path to the recipe dir
/// - COOKBOOK_NAME : the recipe's package name
/// - COOKBOOK_OFFLINE : "1" in offline mode, unset otherwise
/// - COOKBOOK_TOOLCHAIN : path to the cross toolchain (if any)
/// - TARGET : the target triple, e.g. x86_64-unknown-redox
/// - PATH, RUSTFLAGS : standard env
///
/// Red Bear OS context: this fix is required because recipes like
/// pkg-config 0.29.2, atk 2.38, libpng 1.6.46, libvorbis, sdl2-gfx, gettext,
/// vttest, perl5, scummvm, and dosbox all call `GNU_CONFIG_GET config.sub`
/// (or `config.guess`) in [source].script, and without this fix the call
/// was silently dropped during the fetch step.
pub(crate) fn fetch_run_source_script(
recipe: &CookRecipe,
source_dir: &Path,
logger: &PtyOut,
) -> Result<()> {
let script = match &recipe.recipe.source {
Some(SourceRecipe::Tar { script, .. }) => script,
Some(SourceRecipe::Git { script, .. }) => script,
_ => return Ok(()),
};
let script = match script {
Some(s) if !s.trim().is_empty() => s,
_ => return Ok(()),
};
if !source_dir.is_dir() {
return Ok(());
}
let cookbook_root = redbear_project_root(&recipe.dir)
.or_else(|| env::var("COOKBOOK_ROOT").ok().map(PathBuf::from))
.unwrap_or_else(|| PathBuf::from("."));
let cookbook_recipe = recipe.dir.canonicalize().unwrap_or(recipe.dir.clone());
let cookbook_source = source_dir.canonicalize().unwrap_or(source_dir.to_path_buf());
let cookbook_sysroot = recipe
.dir
.join("target")
.join(recipe.target.to_string())
.join("sysroot");
let cookbook_sysroot = cookbook_sysroot
.canonicalize()
.unwrap_or(cookbook_sysroot);
let full_script = format!(
"{}\n{}\n{}\n",
SOURCE_PRESCRIPT, SHARED_PRESCRIPT, script
);
let bash_args = if env::var("COOKBOOK_VERBOSE").is_ok() {
"-ex"
} else {
"-e"
};
let mut command = std::process::Command::new("bash");
command.arg(bash_args);
command.current_dir(&cookbook_source);
command.env("COOKBOOK_ROOT", &cookbook_root);
command.env("COOKBOOK_RECIPE", &cookbook_recipe);
command.env("COOKBOOK_SOURCE", &cookbook_source);
command.env("COOKBOOK_SYSROOT", &cookbook_sysroot);
command.env("COOKBOOK_NAME", recipe.name.name());
command.env("COOKBOOK_HOST_TARGET", redoxer::host_target());
command.env("TARGET", package_target(&recipe.name));
let host_sysroot = env::var("COOKBOOK_HOST_SYSROOT")
.unwrap_or_else(|_| "/usr".to_string());
command.env("COOKBOOK_HOST_SYSROOT", host_sysroot);
if env::var("COOKBOOK_OFFLINE").is_ok() {
command.env("COOKBOOK_OFFLINE", "1");
}
if let Ok(toolchain) = env::var("COOKBOOK_TOOLCHAIN") {
command.env("COOKBOOK_TOOLCHAIN", toolchain);
}
crate::cook::fs::run_command_stdin(command, full_script.as_bytes(), logger)?;
Ok(())
}
pub fn fetch_offline(recipe: &CookRecipe, logger: &PtyOut) -> Result<FetchResult> {
let recipe_dir = &recipe.dir;
let source_dir = recipe_dir.join("source");
@@ -350,6 +446,8 @@ pub fn fetch_offline(recipe: &CookRecipe, logger: &PtyOut) -> Result<FetchResult
}
};
fetch_run_source_script(recipe, &result.source_dir, logger)?;
fetch_apply_source_info(recipe, result.source_ident.clone())?;
Ok(result)
@@ -678,6 +776,8 @@ pub fn fetch(recipe: &CookRecipe, check_source: bool, logger: &PtyOut) -> Result
}
}
fetch_run_source_script(recipe, &result.source_dir, logger)?;
fetch_apply_source_info(recipe, result.source_ident.to_string())?;
Ok(result)
+93 -5
View File
@@ -34,7 +34,21 @@ function DYNAMIC_INIT {
)
# TODO: check paths for spaces
export LDFLAGS="${USER_LDFLAGS}-Wl,-rpath-link,${COOKBOOK_SYSROOT}/lib -L${COOKBOOK_SYSROOT}/lib"
# Also add the relibc-install libdir so the linker can find libatomic
# (built by gcc13's all-target-libatomic). The relibc-install path
# is the canonical location for the cross-toolchain runtime libraries.
REDBEAR_LIBATOMIC_LDFLAGS=""
if [ -n "${COOKBOOK_HOST_SYSROOT}" ] && [ "${COOKBOOK_HOST_SYSROOT}" != "/usr" ]; then
# Cross-build: look in the relibc-install for the cross-gcc runtime libs
for _rb_libdir in \
"${COOKBOOK_ROOT}/prefix/${TARGET}/relibc-install/${TARGET}/lib" \
"${COOKBOOK_ROOT}/prefix/${TARGET}/relibc-install/usr/lib"; do
if [ -f "${_rb_libdir}/libatomic.a" ]; then
REDBEAR_LIBATOMIC_LDFLAGS="${REDBEAR_LIBATOMIC_LDFLAGS:+${REDBEAR_LIBATOMIC_LDFLAGS} }-L${_rb_libdir}"
fi
done
fi
export LDFLAGS="${USER_LDFLAGS}-Wl,-rpath-link,${COOKBOOK_SYSROOT}/lib -L${COOKBOOK_SYSROOT}/lib${REDBEAR_LIBATOMIC_LDFLAGS:+ ${REDBEAR_LIBATOMIC_LDFLAGS}}"
export RUSTFLAGS="-C target-feature=-crt-static -L native=${COOKBOOK_SYSROOT}/lib -C link-arg=-Wl,-rpath-link,${COOKBOOK_SYSROOT}/lib"
export COOKBOOK_DYNAMIC=1
@@ -79,14 +93,75 @@ function DYNAMIC_STATIC_INIT {
}
function GNU_CONFIG_GET {
if [ -n "${COOKBOOK_OFFLINE}" ]; then
echo "[OFFLINE] Skipping GNU_CONFIG_GET wget for $1 — COOKBOOK_OFFLINE is set"
# Install a fresh config.sub / config.guess from the Red Bear OS cookbook's
# vendored copy of the gnu-config package. This is needed because upstream
# autotools tarballs (esp. older ones like pkg-config 0.29.2) ship a 2015-era
# config.sub that does NOT recognize the redox OS system, so the cross-
# compile configure step fails with "system 'redox' not recognized" on the
# first invocation.
#
# Vendored gnu-config:
# - src/cook/gnu-config/config.sub (BLAKE3 0937111aad16bacca8d316374f103faf0bbc16d7780cb92581b812d60ec65f10)
# - src/cook/gnu-config/config.guess (BLAKE3 2f5968637f6231574a6539c95525aa11b809d67f3030b1ad9f0a64c805bd00d5)
# Source: https://git.savannah.gnu.org/cgit/config.git/plain/{config.sub,config.guess}
# timestamp 2026-05-17 (recognizes redox, x86_64-unknown-redox,
# i686-unknown-redox, aarch64-unknown-redox)
#
# In offline mode (COOKBOOK_OFFLINE=1, the Red Bear default per AGENTS.md
# "NO SILENT UPSTREAM PULLS"), the vendored copy is always used. In online
# mode, we still prefer the upstream gnu-config repo for maximum freshness
# when network is available, but fall back to the vendored copy on failure.
local dst="$1"
local name="$(basename "$dst")"
local vendored="${COOKBOOK_ROOT}/src/cook/gnu-config/${name}"
if [ -f "${vendored}" ]; then
cp -f "${vendored}" "${dst}"
chmod +x "${dst}"
if [ -n "${COOKBOOK_OFFLINE}" ]; then
echo "[OFFLINE] GNU_CONFIG_GET: installed vendored ${name} from ${vendored}"
else
echo "GNU_CONFIG_GET: installed vendored ${name} (offline fallback would be available)"
fi
return 0
fi
wget -O "$1" "https://gitlab.redox-os.org/redox-os/gnu-config/-/raw/master/config.sub?inline=false"
if [ -n "${COOKBOOK_OFFLINE}" ]; then
echo "[OFFLINE] GNU_CONFIG_GET: no vendored ${name} at ${vendored} and COOKBOOK_OFFLINE=1 — failing" >&2
return 1
fi
echo "GNU_CONFIG_GET: vendored ${name} not found, fetching from upstream gnu-config"
wget -O "${dst}" "https://gitlab.redox-os.org/redox-os/gnu-config/-/raw/master/${name}?inline=false"
}
"#;
pub(crate) static SOURCE_PRESCRIPT: &str = r#"
# Prescript for [source].script execution (Red Bear OS cookbook).
# This runs in the source dir (COOKBOOK_SOURCE) BEFORE the build step.
# It provides the same env as BUILD_PRESCRIPT (COOKBOOK_ROOT, etc.) so
# helpers like GNU_CONFIG_GET can find their vendored files.
#
# Use case: a recipe's [source].script is the canonical place for source
# preparation (autogen.sh, GNU_CONFIG_GET config.sub, etc.). This script
# was previously parsed by the recipe parser but never executed — see
# fetch.rs fetch_run_source_script for the call site.
# Add cookbook bins to path (needed for cookbook_* tools that source
# scripts may call)
export PATH="${COOKBOOK_ROOT}/bin:${PATH}"
# Source-time toolchain: same as build-time, in case the source script
# invokes a toolchain binary
if [ ! -z "${COOKBOOK_TOOLCHAIN}" ]; then
export PATH="${COOKBOOK_TOOLCHAIN}/bin:${PATH}"
export LD_LIBRARY_PATH="${COOKBOOK_TOOLCHAIN}/lib:${LD_LIBRARY_PATH}"
fi
# Match BUILD_PRESCRIPT's CC/CXX/RUSTFLAGS setup so source scripts that
# probe the build environment (rare, but possible) see the right values
# for the cross target.
COOKBOOK_RUSTFLAGS="-C target-feature=-crt-static -L native=${COOKBOOK_SYSROOT}/lib -C link-arg=-Wl,-rpath-link,${COOKBOOK_SYSROOT}/lib"
export RUSTFLAGS="${RUSTFLAGS:-} ${COOKBOOK_RUSTFLAGS}"
"#;
pub(crate) static BUILD_PRESCRIPT: &str = r#"
# Add cookbook bins to path
export PATH="${COOKBOOK_ROOT}/bin:${PATH}"
@@ -108,7 +183,20 @@ export CPPFLAGS="${CPPFLAGS:+$CPPFLAGS }-I${COOKBOOK_SYSROOT}/include"
# This adds the sysroot libraries and compiles binaries statically for most C compilation
#TODO: check paths for spaces!
USER_LDFLAGS="${LDFLAGS:+$LDFLAGS }"
export LDFLAGS="${USER_LDFLAGS}-L${COOKBOOK_SYSROOT}/lib --static"
# Also add the relibc-install libdir so the linker can find libatomic
# (built by gcc13's all-target-libatomic) and any other cross-gcc
# runtime libraries that aren't in the per-recipe sysroot.
REDBEAR_LIBATOMIC_LDFLAGS=""
if [ -n "${COOKBOOK_HOST_SYSROOT}" ] && [ "${COOKBOOK_HOST_SYSROOT}" != "/usr" ]; then
for _rb_libdir in \
"${COOKBOOK_ROOT}/prefix/${TARGET}/relibc-install/${TARGET}/lib" \
"${COOKBOOK_ROOT}/prefix/${TARGET}/relibc-install/usr/lib"; do
if [ -f "${_rb_libdir}/libatomic.a" ]; then
REDBEAR_LIBATOMIC_LDFLAGS="${REDBEAR_LIBATOMIC_LDFLAGS:+${REDBEAR_LIBATOMIC_LDFLAGS} }-L${_rb_libdir}"
fi
done
fi
export LDFLAGS="${USER_LDFLAGS}-L${COOKBOOK_SYSROOT}/lib --static${REDBEAR_LIBATOMIC_LDFLAGS:+ ${REDBEAR_LIBATOMIC_LDFLAGS}}"
# This reexport C variables into custom build script that can be consumed by cc crate
function reexport_flags {