f31522130f
Build system (5 gaps hardened): - COOKBOOK_OFFLINE defaults to true (fork-mode) - normalize_patch handles diff -ruN format - New 'repo validate-patches' command (25/25 relibc patches) - 14 patched Qt/Wayland/display recipes added to protected list - relibc archive regenerated with current patch chain Boot fixes (fixable): - Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset) - D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped) - redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped) - daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch) - udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async) - relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs - greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait) - greeter-ui: built and linked (header guard unification, sem_compat stubs removed) - mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps - greeter config: removed stale keymapd dependency from display/greeter services - prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified Unfixable (diagnosed, upstream): - i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort - kded6/greeter-ui: page fault 0x8 — Qt library null deref - Thread panics fd != -1 — Rust std library on Redox - DHCP timeout / eth0 MAC — QEMU user-mode networking - hwrngd/thermald — no hardware RNG/thermal in VM - live preload allocation — BIOS memory fragmentation, continues on demand
263 lines
8.5 KiB
Rust
263 lines
8.5 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
env, fs,
|
|
io::{IsTerminal, stdin},
|
|
str::FromStr,
|
|
sync::OnceLock,
|
|
};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
|
|
#[serde(default)]
|
|
pub struct CookConfigOpt {
|
|
/// whether to run offline
|
|
pub offline: Option<bool>,
|
|
/// whether to set jobs number instead of from nproc
|
|
pub jobs: Option<usize>,
|
|
/// whether to use TUI to allow parallel build
|
|
/// default value is yes if "CI" env unset and STDIN is open.
|
|
pub tui: Option<bool>,
|
|
/// whether to write logs to build/logs dir, default true on TUI
|
|
pub logs: Option<bool>,
|
|
/// whether to ignore build errors
|
|
pub nonstop: Option<bool>,
|
|
/// whether to archive packages with compressed format
|
|
pub compressed: Option<bool>,
|
|
/// whether to print verbose logs to certain commands
|
|
/// build failure still be printed anyway
|
|
pub verbose: Option<bool>,
|
|
/// whether to always clean the build directory before building
|
|
pub clean_build: Option<bool>,
|
|
/// whether to always clean the target directory after building
|
|
/// (deletes everything except pkgar files)
|
|
pub clean_target: Option<bool>,
|
|
/// whether to always write stage.files metadata
|
|
pub write_filetree: Option<bool>,
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
|
|
pub struct CookConfig {
|
|
pub offline: bool,
|
|
pub jobs: usize,
|
|
pub tui: bool,
|
|
pub logs: bool,
|
|
pub nonstop: bool,
|
|
pub compressed: bool,
|
|
pub verbose: bool,
|
|
pub clean_build: bool,
|
|
pub clean_target: bool,
|
|
pub write_filetree: bool,
|
|
}
|
|
|
|
impl From<CookConfigOpt> for CookConfig {
|
|
fn from(value: CookConfigOpt) -> Self {
|
|
CookConfig {
|
|
offline: value.offline.unwrap(),
|
|
jobs: value.jobs.unwrap(),
|
|
tui: value.tui.unwrap(),
|
|
logs: value.logs.unwrap(),
|
|
nonstop: value.nonstop.unwrap(),
|
|
compressed: value.compressed.unwrap(),
|
|
verbose: value.verbose.unwrap(),
|
|
clean_build: value.clean_build.unwrap(),
|
|
clean_target: value.clean_target.unwrap(),
|
|
write_filetree: value.write_filetree.unwrap(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
|
#[serde(default)]
|
|
pub struct CookbookConfig {
|
|
#[serde(rename = "cook")]
|
|
cook_opt: CookConfigOpt,
|
|
#[serde(skip)]
|
|
pub cook: CookConfig,
|
|
pub mirrors: HashMap<String, String>,
|
|
}
|
|
|
|
static CONFIG: OnceLock<CookbookConfig> = OnceLock::new();
|
|
|
|
pub fn init_config() {
|
|
let mut config: CookbookConfig = if fs::exists("cookbook.toml").unwrap_or(false) {
|
|
let toml_content = fs::read_to_string("cookbook.toml")
|
|
.map_err(|e| format!("Unable to read config: {:?}", e))
|
|
.unwrap();
|
|
toml::from_str(&toml_content)
|
|
.map_err(|e| format!("Unable to parse config: {:?}", e))
|
|
.unwrap()
|
|
} else {
|
|
CookbookConfig::default()
|
|
};
|
|
|
|
if config.cook_opt.tui.is_none() {
|
|
let ci_set = env::var("CI").is_ok_and(|s| !s.is_empty());
|
|
let tty = stdin().is_terminal();
|
|
config.cook_opt.tui = Some(!ci_set && tty);
|
|
}
|
|
if config.cook_opt.jobs.is_none() {
|
|
config.cook_opt.jobs = Some(extract_env(
|
|
"COOKBOOK_MAKE_JOBS",
|
|
std::thread::available_parallelism()
|
|
.map(|f| usize::from(f))
|
|
.unwrap_or(1),
|
|
));
|
|
}
|
|
if config.cook_opt.logs.is_none() {
|
|
config.cook_opt.logs = Some(extract_env("COOKBOOK_LOGS", config.cook_opt.tui.unwrap()));
|
|
}
|
|
if config.cook_opt.offline.is_none() {
|
|
config.cook_opt.offline = Some(extract_env("COOKBOOK_OFFLINE", true));
|
|
}
|
|
if config.cook_opt.compressed.is_none() {
|
|
config.cook_opt.compressed = Some(extract_env("COOKBOOK_COMPRESSED", false));
|
|
}
|
|
if config.cook_opt.verbose.is_none() {
|
|
config.cook_opt.verbose = Some(extract_env("COOKBOOK_VERBOSE", true));
|
|
}
|
|
if config.cook_opt.nonstop.is_none() {
|
|
config.cook_opt.nonstop = Some(extract_env("COOKBOOK_NONSTOP", false));
|
|
}
|
|
if config.cook_opt.clean_build.is_none() {
|
|
config.cook_opt.clean_build = Some(extract_env("COOKBOOK_CLEAN_BUILD", false));
|
|
}
|
|
if config.cook_opt.clean_target.is_none() {
|
|
config.cook_opt.clean_target = Some(extract_env("COOKBOOK_CLEAN_TARGET", false));
|
|
}
|
|
if config.cook_opt.write_filetree.is_none() {
|
|
config.cook_opt.write_filetree = Some(extract_env(
|
|
"COOKBOOK_WRITE_FILETREE",
|
|
config.cook_opt.clean_target.unwrap_or(false) || extract_env("COOKBOOK_WEB", false),
|
|
));
|
|
}
|
|
if config.mirrors.len() == 0 {
|
|
// The GNU FTP mirror below is automatically inserted for convenience
|
|
// You can choose other mirrors by setting it on cookbook.toml
|
|
config.mirrors.insert(
|
|
"ftp.gnu.org/gnu".to_string(),
|
|
"mirrors.ocf.berkeley.edu/gnu".to_string(),
|
|
);
|
|
}
|
|
|
|
config.cook = CookConfig::from(config.cook_opt.clone());
|
|
|
|
CONFIG.set(config).expect("config is initialized twice");
|
|
}
|
|
|
|
fn extract_env<T: FromStr>(key: &str, default: T) -> T {
|
|
if let Ok(e) = env::var(&key) {
|
|
str::parse(&e).unwrap_or(default)
|
|
} else {
|
|
default
|
|
}
|
|
}
|
|
|
|
pub fn get_config() -> &'static CookbookConfig {
|
|
return CONFIG.get().expect("Configuration is not initialized");
|
|
}
|
|
|
|
pub fn translate_mirror(original_url: &str) -> String {
|
|
let config = CONFIG.get().expect("Configuration is not initialized");
|
|
|
|
let stripped_url = original_url
|
|
.strip_prefix("https://")
|
|
.or_else(|| original_url.strip_prefix("http://"))
|
|
.unwrap_or(original_url);
|
|
|
|
let mut best_match_prefix: Option<&String> = None;
|
|
|
|
for prefix in config.mirrors.keys() {
|
|
if stripped_url.starts_with(prefix) {
|
|
match best_match_prefix {
|
|
Some(current_best) if prefix.len() > current_best.len() => {
|
|
best_match_prefix = Some(prefix);
|
|
}
|
|
None => {
|
|
best_match_prefix = Some(prefix);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(prefix) = best_match_prefix {
|
|
let mirror_base = config.mirrors.get(prefix).unwrap();
|
|
let suffix = &stripped_url[prefix.len()..];
|
|
let ptotocol = &original_url[..(original_url.len() - stripped_url.len())];
|
|
return format!("{}{}{}", ptotocol, mirror_base, suffix);
|
|
}
|
|
|
|
original_url.to_string()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn setup_test_config() {
|
|
let app_config = toml::from_str(
|
|
"[mirrors]\n\
|
|
\"ftp.gnu.org/gnu\" = \"example.com/gnu\"\n\
|
|
\"github.com/foo/bar\" = \"github.com/baz/bar\"\n\
|
|
\"github.com/a\" = \"github.com/b\"\n",
|
|
)
|
|
.expect("Unable to parse test config");
|
|
// This will be called for each test. If the config is already set,
|
|
// it will do nothing, which is fine as all tests use the same config.
|
|
let _ = CONFIG.set(app_config);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_cook() {
|
|
let app_config: CookbookConfig = toml::from_str(
|
|
"[cook]\n\
|
|
offline = true\n",
|
|
)
|
|
.expect("Unable to parse test config");
|
|
assert_eq!(app_config.cook_opt.offline, Some(true));
|
|
assert_eq!(app_config.cook_opt.jobs, None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_exact_match() {
|
|
setup_test_config();
|
|
assert_eq!(translate_mirror("ftp.gnu.org/gnu"), "example.com/gnu");
|
|
assert_eq!(translate_mirror("github.com/foo/bar"), "github.com/baz/bar");
|
|
}
|
|
|
|
#[test]
|
|
fn test_prefix_match() {
|
|
setup_test_config();
|
|
assert_eq!(
|
|
translate_mirror("https://github.com/a/c"),
|
|
"https://github.com/b/c"
|
|
);
|
|
assert_eq!(
|
|
translate_mirror("https://ftp.gnu.org/gnu/bash/bash-5.2.15.tar.gz"),
|
|
"https://example.com/gnu/bash/bash-5.2.15.tar.gz"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_longest_prefix_match() {
|
|
setup_test_config();
|
|
// "github.com/foo/bar" is a longer and more specific prefix than "github.com/a",
|
|
// so it should be chosen for the translation.
|
|
assert_eq!(
|
|
translate_mirror("https://github.com/foo/bar/baz"),
|
|
"https://github.com/baz/bar/baz"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_no_match() {
|
|
setup_test_config();
|
|
assert_eq!(translate_mirror("www.rust-lang.org"), "www.rust-lang.org");
|
|
assert_eq!(
|
|
translate_mirror("http://github.com/unrelated/repo"),
|
|
"http://github.com/unrelated/repo"
|
|
);
|
|
}
|
|
}
|