Merge branch 'change-rule' into 'master'

Implement cookbook lock and change-rule command

Closes #1801

See merge request redox-os/redox!2120
This commit is contained in:
Jeremy Soller 2026-05-06 06:20:09 -06:00
commit 953993ac05
4 changed files with 188 additions and 11 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@
/repo
/web
/cookbook.toml
/cookbook.lock
source
source.tmp
source-new

View File

@ -260,6 +260,40 @@ ucri.%: $(FSTOOLS_TAG) FORCE
$(MAKE) i.$*
endif
# Set recipe rule to "binary" then invoke clean
bc.%: $(FSTOOLS_TAG) FORCE
ifeq ($(PODMAN_BUILD),1)
$(PODMAN_RUN) make $@
else
$(REPO_BIN) change-rule --set-rule=binary $(foreach f,$(subst $(comma), ,$*),$(f))
endif
# Set recipe rule to "source" then invoke clean
sc.%: $(FSTOOLS_TAG) FORCE
ifeq ($(PODMAN_BUILD),1)
$(PODMAN_RUN) make $@
else
$(REPO_BIN) change-rule --set-rule=source $(foreach f,$(subst $(comma), ,$*),$(f))
endif
# Reset recipe rule then invoke clean
cc.%: $(FSTOOLS_TAG) FORCE
ifeq ($(PODMAN_BUILD),1)
$(PODMAN_RUN) make $@
else
$(REPO_BIN) change-rule --set-rule= $(foreach f,$(subst $(comma), ,$*),$(f))
endif
# Set recipe rule to "binary" then invoke clean and rebuild
bcr.%: $(FSTOOLS_TAG) FORCE
$(MAKE) bc.$*
$(MAKE) r.$*
# Set recipe rule to "source" then invoke clean and rebuild
scr.%: $(FSTOOLS_TAG) FORCE
$(MAKE) sc.$*
$(MAKE) r.$*
export DEBUG_BIN?=
# Debug a statically linked program with gdbgui, for example: debug.drivers-initfs DEBUG_BIN=pcid

View File

@ -1,5 +1,5 @@
use ansi_to_tui::IntoText;
use cookbook::config::{CookConfig, get_config, init_config};
use cookbook::config::{CookConfig, CookLockOpt, get_config, init_config};
use cookbook::cook::cook_build::{build, get_stage_dirs, remove_stage_dir};
use cookbook::cook::fetch::{FetchResult, fetch, fetch_offline};
use cookbook::cook::fs::{create_dir, create_target_dir, remove_all, run_command};
@ -8,7 +8,9 @@ use cookbook::cook::pty::{PtyOut, UnixSlavePty, flush_pty, setup_pty, write_to_p
use cookbook::cook::script::KILL_ALL_PID;
use cookbook::cook::tree::{self, WalkTreeEntry};
use cookbook::cook::{fetch_repo, ident};
use cookbook::recipe::{CookRecipe, recipes_flatten_package_names, recipes_mark_as_deps};
use cookbook::recipe::{
CookRecipe, SourceRecipe, recipes_flatten_package_names, recipes_mark_as_deps,
};
use cookbook::{Error, Result, staged_pkg};
use pkg::{PackageName, PackageState};
use ratatui::Terminal;
@ -50,7 +52,9 @@ const REPO_HELP_STR: &str = r#"
find find path of recipe packages
cook-tree show tree of recipe build
push-tree show tree of recipe packages
capture-rev write lock to git recipes
change-rule override rule to recipes
common flags:
--cookbook=<cookbook_dir> the "recipes" folder, default to $PWD/recipes
--repo=<repo_dir> the "repo" folder, default to $PWD/repo
@ -61,6 +65,8 @@ const REPO_HELP_STR: &str = r#"
--category=<category> apply to all recipes in <cookbook_dir>/<category>
--filesystem=<filesystem> override recipes config using installer file
--repo-binary override recipes config to use repo_binary
--set-rule=<rule> used in "change-rule", set wanted config rule
--rollback=<rev> used in "capture-rev", rollback to a commit instead
cook env and their defaults:
CI= set to any value to disable TUI
@ -85,6 +91,8 @@ struct CliConfig {
logs_dir: Option<PathBuf>,
category: Option<PathBuf>,
filesystem: Option<redox_installer::Config>,
rollback: Option<String>,
set_rule: Option<String>,
with_package_deps: bool,
all: bool,
cook: CookConfig,
@ -101,6 +109,8 @@ enum CliCommand {
Push,
PushTree,
Find,
CaptureRev,
ChangeRule,
}
impl CliCommand {
@ -108,7 +118,11 @@ impl CliCommand {
*self == CliCommand::PushTree || *self == CliCommand::CookTree || *self == CliCommand::Find
}
pub fn is_building(&self) -> bool {
*self == CliCommand::Fetch || *self == CliCommand::Cook || *self == CliCommand::CookTree
*self == CliCommand::Fetch
|| *self == CliCommand::Cook
|| *self == CliCommand::CookTree
|| *self == CliCommand::CaptureRev
|| *self == CliCommand::ChangeRule
}
pub fn is_pushing(&self) -> bool {
*self == CliCommand::Push || *self == CliCommand::PushTree
@ -134,6 +148,8 @@ impl FromStr for CliCommand {
"push-tree" => Ok(CliCommand::PushTree),
"cook-tree" => Ok(CliCommand::CookTree),
"find" => Ok(CliCommand::Find),
"capture-rev" => Ok(CliCommand::CaptureRev),
"change-rule" => Ok(CliCommand::ChangeRule),
_ => bail_options_err!("Unknown command {:?}", s),
}
}
@ -151,6 +167,8 @@ impl ToString for CliCommand {
CliCommand::PushTree => "push-tree".to_string(),
CliCommand::CookTree => "cook-tree".to_string(),
CliCommand::Find => "find".to_string(),
CliCommand::CaptureRev => "capture-rev".to_string(),
CliCommand::ChangeRule => "change-rule".to_string(),
}
}
}
@ -178,6 +196,8 @@ impl CliConfig {
cook: get_config().cook.clone(),
all: false,
filesystem: None,
rollback: None,
set_rule: None,
})
}
}
@ -242,6 +262,12 @@ fn main_inner() -> Result<()> {
if command == CliCommand::Push {
return handle_push(&recipes, &config);
}
if command == CliCommand::ChangeRule {
return handle_change_rule(&recipes, &config);
}
if command == CliCommand::CaptureRev {
return handle_capture_rev(&recipes, &config);
}
let verbose = config.cook.verbose;
for recipe in &recipes {
@ -386,13 +412,11 @@ fn repo_inner(config: &CliConfig, command: &CliCommand, recipe: &CookRecipe) ->
CliCommand::Unfetch | CliCommand::Clean | CliCommand::CleanTarget => {
handle_clean(recipe, config, command)?
}
CliCommand::Push => unreachable!(),
CliCommand::PushTree => unreachable!(),
CliCommand::CookTree => unreachable!(),
CliCommand::Find => {
println!("{}", recipe.dir.display());
false
}
_ => unreachable!(),
})
}
@ -429,6 +453,8 @@ fn parse_args(args: Vec<String>) -> Result<(CliConfig, CliCommand, Vec<CookRecip
"--repo" => config.repo_dir = PathBuf::from(value),
"--sysroot" => config.sysroot_dir = PathBuf::from(value),
"--category" => config.category = Some(PathBuf::from(value)),
"--set-rule" => config.set_rule = Some(value.into()),
"--rollback" => config.rollback = Some(value.into()),
"--filesystem" => {
config.filesystem = Some({
let r = redox_installer::Config::from_file(&PathBuf::from(value));
@ -679,6 +705,31 @@ fn parse_args(args: Vec<String>) -> Result<(CliConfig, CliCommand, Vec<CookRecip
}
};
if !get_config().recipe_lock.is_empty() {
let lock = &get_config().recipe_lock;
for recipe in recipes.iter_mut() {
if let Some(lock_recipe) = lock.get(recipe.name.as_str()) {
if let Some(rule) = &lock_recipe.fsrule {
recipe.rule = rule.into();
recipe.reload_recipe()?;
}
if let Some(gitrev) = &lock_recipe.gitrev {
if let Some(SourceRecipe::Git { rev, branch, .. }) = &mut recipe.recipe.source {
*rev = Some(gitrev.clone());
*branch = None;
} else {
println!(
"DEBUG: Recipe {:?} contains into git rev but recipe source is not git",
recipe.name.as_str()
);
}
recipe.rule = "source".into();
recipe.reload_recipe()?;
}
}
}
}
if command.is_building() && recipes.iter().any(|r| r.rule == "binary") {
let (_, repository) = fetch_repo::get_binary_repo();
for recipe in recipes.iter_mut() {
@ -917,6 +968,48 @@ fn handle_tree(recipes: &Vec<CookRecipe>, is_build_tree: bool, _config: &CliConf
Ok(())
}
fn handle_change_rule(recipes: &Vec<CookRecipe>, config: &CliConfig) -> Result<()> {
let mut lock = get_config().recipe_lock.clone();
let is_pruning = config.set_rule.as_ref().is_some_and(|s| s.is_empty());
for recipe in recipes {
let mut recipe_lock = lock.get(recipe.name.as_str()).cloned().unwrap_or_default();
let cached = if is_pruning {
recipe_lock.fsrule.take().is_none()
} else {
let new_rule = config
.set_rule
.as_ref()
.cloned()
.unwrap_or_else(|| recipe.rule.clone());
let old_rule = recipe_lock.fsrule.replace(new_rule.clone());
old_rule == Some(new_rule)
};
if recipe_lock.is_empty() {
lock.remove(recipe.name.as_str());
} else {
lock.insert(recipe.name.to_string(), recipe_lock);
}
let clean_cached = if !cached {
handle_clean(recipe, config, &CliCommand::Clean)?
} else {
true
};
if cached && clean_cached {
print_cached(&CliCommand::ChangeRule, &recipe.name);
} else {
print_success(&CliCommand::ChangeRule, &recipe.name);
}
}
CookLockOpt { recipes: lock }.save();
Ok(())
}
fn handle_capture_rev(_recipes: &Vec<CookRecipe>, _config: &CliConfig) -> Result<()> {
todo!()
}
//
// ------------- TUI SPECIFIC CODE -------------------
//

View File

@ -1,8 +1,13 @@
use std::{collections::HashMap, env, fs, str::FromStr, sync::OnceLock};
use std::{
collections::{BTreeMap, HashMap},
env, fs,
str::FromStr,
sync::OnceLock,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
#[serde(default)]
pub struct CookConfigOpt {
/// whether to run offline
@ -34,7 +39,7 @@ pub struct CookConfigOpt {
pub write_filetree: Option<bool>,
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct CookConfig {
pub offline: bool,
pub jobs: usize,
@ -67,7 +72,37 @@ impl From<CookConfigOpt> for CookConfig {
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct CookLockOpt {
pub recipes: BTreeMap<String, RecipeLock>,
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct RecipeLock {
pub fsrule: Option<String>,
pub gitrev: Option<String>,
}
impl RecipeLock {
pub fn is_empty(&self) -> bool {
self.fsrule.is_none() && self.gitrev.is_none()
}
}
const COOKBOOK_LOCK_HEADER: &str = r#"# This file is generated automatically.
# All configuration here overrides anything from recipes or config directory.
"#;
impl CookLockOpt {
pub fn save(&self) {
let str = toml::to_string(&self).unwrap();
fs::write("cookbook.lock", format!("{COOKBOOK_LOCK_HEADER}\n{str}")).unwrap();
}
}
#[derive(Debug, Default, Deserialize, PartialEq)]
#[serde(default)]
pub struct CookbookConfig {
#[serde(rename = "cook")]
@ -75,6 +110,7 @@ pub struct CookbookConfig {
#[serde(skip)]
pub cook: CookConfig,
pub mirrors: HashMap<String, String>,
pub recipe_lock: BTreeMap<String, RecipeLock>,
}
static CONFIG: OnceLock<CookbookConfig> = OnceLock::new();
@ -145,6 +181,19 @@ pub fn init_config() {
config.cook = CookConfig::from(config.cook_opt.clone());
let lock: CookLockOpt = if fs::exists("cookbook.lock").unwrap_or(false) {
let toml_content = fs::read_to_string("cookbook.lock")
.map_err(|e| format!("Unable to read lock: {:?}", e))
.unwrap();
toml::from_str(&toml_content)
.map_err(|e| format!("Unable to parse lock (plz delete manually): {:?}", e))
.unwrap()
} else {
CookLockOpt::default()
};
config.recipe_lock = lock.recipes;
CONFIG.set(config).expect("config is initialized twice");
}