diff --git a/Cargo.lock b/Cargo.lock index 80d7029f..f734d25b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -930,6 +930,7 @@ dependencies = [ "anyhow", "blake3 1.5.3", "filedescriptor", + "globset", "ignore", "libc", "object", diff --git a/Cargo.toml b/Cargo.toml index 4d91b001..b68ea8fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ tui = ["ratatui", "ansi-to-tui", "filedescriptor", "strip-ansi-escapes"] anyhow = "1" # blake3 1.5.4 is incompatible with 0.3 dependency from pkgar blake3 = "=1.5.3" +globset = "0.4" libc = "0.2" ignore = "0.4" object = { version = "0.36", features = ["build_core"] } diff --git a/config/redoxer.toml b/config/redoxer.toml index 95fd4327..5ef92a9d 100644 --- a/config/redoxer.toml +++ b/config/redoxer.toml @@ -10,6 +10,7 @@ coreutils = {} extrautils = {} findutils = {} gcc13 = {} +"gcc13.cxx" = {} gnu-binutils = {} ion = {} netdb = {} diff --git a/recipes/dev/gcc13/recipe.toml b/recipes/dev/gcc13/recipe.toml index 6b61540b..73e58177 100644 --- a/recipes/dev/gcc13/recipe.toml +++ b/recipes/dev/gcc13/recipe.toml @@ -45,3 +45,15 @@ rm -f "${COOKBOOK_STAGE}"/usr/lib/libgcc_s.so* "${COOKBOOK_STAGE}"/usr/lib/libst dependencies = [ "gnu-binutils" ] + +[[optional-packages]] +name = "cxx" +dependencies = [] +files = [ + "usr/bin/*c++", + "usr/bin/*g++", + "usr/include/c++/**", + "usr/lib/*c++*", + "usr/libexec/gcc/**/cc1plus", + "usr/share/gcc-*/python/libstdcxx/**", +] diff --git a/recipes/groups/dev-essential/recipe.toml b/recipes/groups/dev-essential/recipe.toml index 911fe766..40d3f296 100644 --- a/recipes/groups/dev-essential/recipe.toml +++ b/recipes/groups/dev-essential/recipe.toml @@ -4,6 +4,7 @@ dependencies = [ "automake", "cargo", "gcc13", + "gcc13.cxx", "llvm18", "gnu-binutils", "gnu-make", diff --git a/recipes/other/cookbook/recipe.toml b/recipes/other/cookbook/recipe.toml index 69185434..a367acbb 100644 --- a/recipes/other/cookbook/recipe.toml +++ b/recipes/other/cookbook/recipe.toml @@ -17,6 +17,7 @@ dependencies = [ "autoconf", "automake", "gcc13", + "gcc13.cxx", "git", "gnu-make", "libtool", diff --git a/src/bin/repo.rs b/src/bin/repo.rs index 40fe849c..cd09b607 100644 --- a/src/bin/repo.rs +++ b/src/bin/repo.rs @@ -1344,7 +1344,7 @@ fn run_tui_cook( let limit = 2; // arbitrary number if app.log_scroll >= total_log_lines - limit { if app.prompt.is_none() || config.cook.nonstop { - enable_auto_scroll = true; + enable_auto_scroll = true; } intended_scroll_pos = total_log_lines - limit; total_log_lines - limit @@ -1401,10 +1401,10 @@ fn run_tui_cook( ); let mut log_paragraph = Paragraph::new(log_lines).block( - Block::default() - .title(log_title) - .title_bottom(instruct) - .borders(Borders::ALL), + Block::default() + .title(log_title) + .title_bottom(instruct) + .borders(Borders::ALL), ); if !app.auto_scroll { diff --git a/src/bin/repo_builder.rs b/src/bin/repo_builder.rs index 58a7cbb3..ad990261 100644 --- a/src/bin/repo_builder.rs +++ b/src/bin/repo_builder.rs @@ -1,7 +1,8 @@ use anyhow::anyhow; use cookbook::WALK_DEPTH; use cookbook::config::{get_config, init_config}; -use cookbook::cook::package::package_target; +use cookbook::cook::package::{get_package_name, package_stage_paths, package_target}; +use cookbook::recipe::CookRecipe; use pkg::{Package, PackageName, recipes}; use std::collections::{BTreeMap, HashMap}; use std::env; @@ -88,30 +89,38 @@ fn publish_packages(config: &CliConfig) -> anyhow::Result<()> { eprintln!("recipe {} not found", recipe); continue; }; - let cookbook_recipe = Path::new(&recipe_path); - let target = package_target(recipe); - let stage_dir = cookbook_recipe.join("target").join(&target).join("stage"); - - let pkgar_src = stage_dir.with_extension("pkgar"); - let pkgar_dst = repo_path.join(format!("{}.pkgar", recipe)); - let toml_src = stage_dir.with_extension("toml"); - let toml_dst = repo_path.join(format!("{}.toml", recipe)); - - if !fs::exists(&toml_src)? { - eprintln!("recipe {} is missing stage.toml", recipe); + let Ok(cookbook_recipe) = CookRecipe::from_path(recipe_path, true) else { + eprintln!("recipe {} unable to read", recipe); continue; - } + }; - if is_newer(&toml_src, &toml_dst) { - eprintln!("\x1b[01;38;5;155mrepo - publishing {}\x1b[0m", recipe); - if fs::exists(&pkgar_src)? { - fs::copy(&pkgar_src, &pkgar_dst)?; + let target = package_target(recipe); + let target_dir = cookbook_recipe.dir.join("target").join(&target); + + let packages = cookbook_recipe.recipe.get_packages_list(); + + for package in packages { + let (stage_dir, pkgar_src, toml_src) = package_stage_paths(package, &target_dir); + let recipe_name = get_package_name(recipe.name(), package); + let pkgar_dst = repo_path.join(format!("{}.pkgar", recipe_name)); + let toml_dst = repo_path.join(format!("{}.toml", recipe_name)); + + if !fs::exists(&toml_src)? { + eprintln!("recipe {} is missing stage.toml", recipe_name); + continue; } - fs::copy(&toml_src, &toml_dst)?; - } - if stage_dir.join("usr/share/metainfo").exists() { - appstream_sources.insert(recipe.name().to_string(), stage_dir.clone()); + if is_newer(&toml_src, &toml_dst) { + eprintln!("\x1b[01;38;5;155mrepo - publishing {}\x1b[0m", recipe_name); + if fs::exists(&pkgar_src)? { + fs::copy(&pkgar_src, &pkgar_dst)?; + } + fs::copy(&toml_src, &toml_dst)?; + } + + if stage_dir.join("usr/share/metainfo").exists() { + appstream_sources.insert(recipe.name().to_string(), stage_dir.clone()); + } } } @@ -205,7 +214,11 @@ fn publish_packages(config: &CliConfig) -> anyhow::Result<()> { // FIXME: Use proper TOML serializer let mut output = String::from("[packages]\n"); for (name, version) in &packages { - output.push_str(&format!("{name} = {version}\n")); + output.push_str(&if name.contains('.') { + format!("\"{name}\" = {version}\n") + } else { + format!("{name} = {version}\n") + }); } let mut output_file = File::create(&repo_toml_path)?; diff --git a/src/cook/cook_build.rs b/src/cook/cook_build.rs index 6d95bb7d..7651befe 100644 --- a/src/cook/cook_build.rs +++ b/src/cook/cook_build.rs @@ -2,12 +2,12 @@ use pkg::package::PackageError; use pkg::{Package, PackageName}; use crate::cook::fs::*; -use crate::cook::package::package_target; +use crate::cook::package::{package_source_paths, package_target}; use crate::cook::pty::PtyOut; use crate::cook::script::*; -use crate::recipe::BuildKind; use crate::recipe::Recipe; use crate::recipe::{AutoDeps, CookRecipe}; +use crate::recipe::{BuildKind, OptionalPackageRecipe}; use std::collections::VecDeque; use std::{ collections::BTreeSet, @@ -21,7 +21,8 @@ use std::{ use crate::{is_redox, log_to_pty}; fn auto_deps_from_dynamic_linking( - stage_dir: &Path, + stage_dirs: &Vec, + target_dir: &Path, dep_pkgars: &BTreeSet<(PackageName, PathBuf)>, logger: &PtyOut, ) -> BTreeSet { @@ -29,13 +30,14 @@ fn auto_deps_from_dynamic_linking( let mut visited = BTreeSet::new(); let verbose = crate::config::get_config().cook.verbose; // Base directories may need to be updated for packages that place binaries in odd locations. - let mut walk = VecDeque::from([ - stage_dir.join("libexec"), - stage_dir.join("usr/bin"), - stage_dir.join("usr/games"), - stage_dir.join("usr/lib"), - stage_dir.join("usr/libexec"), - ]); + let mut walk = VecDeque::new(); + + for stage_dir in stage_dirs { + walk.push_back(stage_dir.join("usr/bin")); + walk.push_back(stage_dir.join("usr/games")); + walk.push_back(stage_dir.join("usr/lib")); + walk.push_back(stage_dir.join("usr/libexec")); + } // Recursively (DFS) walk each directory to ensure nested libs and bins are checked. while let Some(dir) = walk.pop_front() { @@ -93,7 +95,7 @@ fn auto_deps_from_dynamic_linking( let Ok(name) = str::from_utf8(val) else { continue; }; - if let Ok(relative_path) = path.strip_prefix(stage_dir) { + if let Ok(relative_path) = path.strip_prefix(target_dir) { if verbose { log_to_pty!(logger, "DEBUG: {} needs {}", relative_path.display(), name); } @@ -173,15 +175,15 @@ pub fn build( offline_mode: bool, check_source: bool, logger: &PtyOut, -) -> Result<(PathBuf, BTreeSet), String> { +) -> Result<(Vec, BTreeSet), String> { let sysroot_dir = target_dir.join("sysroot"); let toolchain_dir = target_dir.join("toolchain"); - let stage_dir = target_dir.join("stage"); + let stage_dirs = get_stage_dirs(&recipe.optional_packages, target_dir); let cli_verbose = crate::config::get_config().cook.verbose; let cli_jobs = crate::config::get_config().cook.jobs; if recipe.build.kind == BuildKind::None { // metapackages don't need to do anything here - return Ok((stage_dir, BTreeSet::new())); + return Ok((stage_dirs, BTreeSet::new())); } let mut dep_pkgars = BTreeSet::new(); @@ -205,9 +207,9 @@ pub fn build( } } - if stage_dir.exists() && !check_source { - let auto_deps = build_auto_deps(recipe, target_dir, &stage_dir, dep_pkgars, logger)?; - return Ok((stage_dir, auto_deps)); + if !check_source && stage_dirs.iter().all(|dir| dir.exists()) { + let auto_deps = build_auto_deps(recipe, target_dir, &stage_dirs, dep_pkgars, logger)?; + return Ok((stage_dirs, auto_deps)); } let source_modified = modified_dir_ignore_git(source_dir).unwrap_or(SystemTime::UNIX_EPOCH); @@ -251,18 +253,26 @@ pub fn build( // Rebuild stage if source is newer //TODO: rebuild on recipe changes - if stage_dir.is_dir() { - let stage_modified = modified_dir(&stage_dir)?; + if stage_dirs.iter().any(|dir| dir.is_dir()) { + let stage_modified = + modified_all(&stage_dirs, modified_dir).unwrap_or(SystemTime::UNIX_EPOCH); if stage_modified < source_modified || stage_modified < deps_modified || stage_modified < deps_host_modified { - log_to_pty!(logger, "DEBUG: updating '{}'", stage_dir.display()); - remove_all(&stage_dir)?; + for stage_dir in &stage_dirs { + log_to_pty!(logger, "DEBUG: updating '{}'", stage_dir.display()); + if stage_dir.is_dir() { + remove_all(&stage_dir)?; + } + } } } - if !stage_dir.is_dir() { + if !stage_dirs.last().is_some_and(|dir| dir.is_dir()) { + let stage_dir = stage_dirs + .last() + .expect("Should have atleast one stage dir"); // Create stage.tmp let stage_dir_tmp = target_dir.join("stage.tmp"); create_dir_clean(&stage_dir_tmp)?; @@ -285,6 +295,9 @@ pub fn build( ) }; + if recipe.build.kind == BuildKind::Remote { + return build_remote(stage_dirs, recipe, target_dir); + } //TODO: better integration with redoxer (library instead of binary) //TODO: configurable target //TODO: Add more configurability, convert scripts to Rust? @@ -311,7 +324,7 @@ pub fn build( flags_fn("COOKBOOK_MESON_FLAGS", mesonflags), ), BuildKind::Custom { script } => script.clone(), - BuildKind::Remote => return build_remote(target_dir), + BuildKind::Remote => unreachable!(), BuildKind::None => "".to_owned(), }; @@ -370,13 +383,46 @@ pub fn build( ); run_command_stdin(command, full_script.as_bytes(), logger)?; + // Move to each features dir + let mut globs = Vec::new(); + for (i, feat) in recipe.optional_packages.iter().enumerate() { + let stage_dir = &stage_dirs[i]; + create_dir_clean(&stage_dir)?; + for path in &feat.files { + let glob = globset::Glob::new(&path).map_err(|e| format!("{}", e))?; + globs.push((glob.compile_matcher(), stage_dir.clone())); + } + } + move_dir_all_fn( + &stage_dir_tmp, + &Box::new(|path: PathBuf| { + for (glob, dst) in &globs { + if glob.is_match(&path) { + return Some(dst.as_path()); + } + } + None + }), + ) + .map_err(|e| format!("Unable to move {e:?}"))?; + // Move stage.tmp to stage atomically rename(&stage_dir_tmp, &stage_dir)?; } - let auto_deps = build_auto_deps(recipe, target_dir, &stage_dir, dep_pkgars, logger)?; + let auto_deps = build_auto_deps(recipe, target_dir, &stage_dirs, dep_pkgars, logger)?; - Ok((stage_dir, auto_deps)) + Ok((stage_dirs, auto_deps)) +} + +fn get_stage_dirs(features: &Vec, target_dir: &Path) -> Vec { + let mut v = Vec::new(); + for f in features { + v.push(target_dir.join(format!("stage.{}", f.name))); + } + // intentionally added last as it contains leftover files from package features + v.push(target_dir.join(format!("stage"))); + v } fn build_deps_dir( @@ -433,12 +479,13 @@ fn build_deps_dir( fn build_auto_deps( recipe: &Recipe, target_dir: &Path, - stage_dir: &PathBuf, + stage_dirs: &Vec, mut dep_pkgars: BTreeSet<(PackageName, PathBuf)>, logger: &PtyOut, ) -> Result, String> { let auto_deps_path = target_dir.join("auto_deps.toml"); - if auto_deps_path.is_file() && modified(&auto_deps_path)? < modified(stage_dir)? { + if auto_deps_path.is_file() && modified(&auto_deps_path)? < modified_all(stage_dirs, modified)? + { remove_all(&auto_deps_path)? } @@ -449,7 +496,8 @@ fn build_auto_deps( toml::from_str(&toml_content).map_err(|_| "failed to deserialize cached auto_deps")?; wrapper.packages } else { - let mut dynamic_deps = auto_deps_from_dynamic_linking(stage_dir, &dep_pkgars, logger); + let mut dynamic_deps = + auto_deps_from_dynamic_linking(stage_dirs, target_dir, &dep_pkgars, logger); dep_pkgars.retain(|x| recipe.build.dependencies.contains(&x.0)); let package_deps = auto_deps_from_static_package_deps(&dep_pkgars, &dynamic_deps).unwrap_or_default(); @@ -464,35 +512,38 @@ fn build_auto_deps( Ok(auto_deps) } -pub fn build_remote(target_dir: &Path) -> Result<(PathBuf, BTreeSet), String> { - // download straight from remote source then declare pkg dependencies as autodeps dependency - let stage_dir = target_dir.join("stage"); - - let source_pkgar = target_dir.join("source.pkgar"); +pub fn build_remote( + stage_dirs: Vec, + recipe: &Recipe, + target_dir: &Path, +) -> Result<(Vec, BTreeSet), String> { let source_toml = target_dir.join("source.toml"); let source_pubkey = target_dir.join("id_ed25519.pub.toml"); - if stage_dir.is_dir() && modified(&source_pkgar)? > modified(&stage_dir)? { - remove_all(&stage_dir)? - } - if !stage_dir.is_dir() { - let stage_dir_tmp = target_dir.join("stage.tmp"); + let packages = recipe.get_packages_list(); + for (i, package) in packages.into_iter().enumerate() { + // declare pkg dependencies as autodeps dependency + let stage_dir = &stage_dirs[i]; - pkgar::extract(&source_pubkey, &source_pkgar, &stage_dir_tmp).map_err(|err| { - format!( - "failed to install '{}' in '{}': {:?}", - source_pkgar.display(), - stage_dir_tmp.display(), - err - ) - })?; - - // Move stage.tmp to stage atomically - rename(&stage_dir_tmp, &stage_dir)?; + if !stage_dir.is_dir() { + let (_, source_pkgar, _) = package_source_paths(package, &target_dir); + let stage_dir_tmp = target_dir.join("stage.tmp"); + pkgar::extract(&source_pubkey, &source_pkgar, &stage_dir_tmp).map_err(|err| { + format!( + "failed to install '{}' in '{}': {:?}", + source_pkgar.display(), + stage_dir_tmp.display(), + err + ) + })?; + // Move stage.tmp to stage atomically + rename(&stage_dir_tmp, &stage_dir)?; + } } let auto_deps_path = target_dir.join("auto_deps.toml"); - if auto_deps_path.is_file() && modified(&auto_deps_path)? < modified(&stage_dir)? { + if auto_deps_path.is_file() && modified(&auto_deps_path)? < modified_all(&stage_dirs, modified)? + { remove_all(&auto_deps_path)? } @@ -513,16 +564,13 @@ pub fn build_remote(target_dir: &Path) -> Result<(PathBuf, BTreeSet serialize_and_write(&auto_deps_path, &wrapper)?; wrapper.packages }; - - Ok((stage_dir, auto_deps)) + Ok((stage_dirs, auto_deps)) } #[cfg(test)] mod tests { use std::os::unix; - use super::auto_deps_from_dynamic_linking; - #[test] fn file_system_loop_no_infinite_loop() { let mut root = std::env::temp_dir(); @@ -541,7 +589,12 @@ mod tests { "Expected a loop where {dir:?} points to {root:?}" ); - let entries = auto_deps_from_dynamic_linking(&root, &Default::default(), &None); + let entries = super::auto_deps_from_dynamic_linking( + &root, + &root.join(".."), + &Default::default(), + &None, + ); assert!( entries.is_empty(), "auto_deps shouldn't have yielded any libraries" diff --git a/src/cook/fetch.rs b/src/cook/fetch.rs index 8b8a136d..b04deec5 100644 --- a/src/cook/fetch.rs +++ b/src/cook/fetch.rs @@ -1,6 +1,8 @@ use crate::REMOTE_PKG_SOURCE; use crate::config::translate_mirror; use crate::cook::fs::*; +use crate::cook::package::get_package_name; +use crate::cook::package::package_source_paths; use crate::cook::pty::PtyOut; use crate::cook::script::*; use crate::is_redox; @@ -39,7 +41,7 @@ pub fn fetch_offline( return Ok(source_dir); } if recipe.build.kind == BuildKind::Remote { - fetch_remote(recipe_dir, true, logger)?; + fetch_remote(recipe_dir, recipe, true, logger)?; return Ok(source_dir); } @@ -104,7 +106,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result String { return format!("{}/id_ed25519.pub.toml", REMOTE_PKG_SOURCE); } -pub fn fetch_remote(recipe_dir: &Path, offline_mode: bool, logger: &PtyOut) -> Result<(), String> { - // TODO: allow download to host target +pub fn fetch_remote( + recipe_dir: &Path, + recipe: &Recipe, + offline_mode: bool, + logger: &PtyOut, +) -> Result<(), String> { + // TODO: allow download to host target (waiting for build server to have them) let target = redoxer::target(); let target_dir = create_target_dir(recipe_dir, target)?; + let source_pubkey = target_dir.join("id_ed25519.pub.toml"); + if !offline_mode { + download_wget(&get_pubkey_url(), &source_pubkey, logger)?; + } else { + offline_check_exists(&source_pubkey)?; + } + + let packages = recipe.get_packages_list(); + let name = recipe_dir .file_name() .ok_or("Unable to get recipe name")? .to_str() .unwrap(); - let source_pkgar = target_dir.join("source.pkgar"); - let source_toml = target_dir.join("source.toml"); - let source_pubkey = target_dir.join("id_ed25519.pub.toml"); - if !offline_mode { - //TODO: Check freshness - download_wget(&get_remote_url(name, "pkgar"), &source_pkgar, logger)?; - download_wget(&get_remote_url(name, "toml"), &source_toml, logger)?; - download_wget(&get_pubkey_url(), &source_pubkey, logger)?; - } else { - offline_check_exists(&source_pkgar)?; - offline_check_exists(&source_toml)?; - offline_check_exists(&source_pubkey)?; + for package in packages { + let (_, source_pkgar, source_toml) = package_source_paths(package, &target_dir); + let source_name = get_package_name(name, package); + + if !offline_mode { + //TODO: Check freshness + download_wget( + &get_remote_url(&source_name, "pkgar"), + &source_pkgar, + logger, + )?; + download_wget(&get_remote_url(&source_name, "toml"), &source_toml, logger)?; + } else { + offline_check_exists(&source_pkgar)?; + offline_check_exists(&source_toml)?; + } } Ok(()) diff --git a/src/cook/fs.rs b/src/cook/fs.rs index b89df61e..31e61c3a 100644 --- a/src/cook/fs.rs +++ b/src/cook/fs.rs @@ -62,6 +62,44 @@ pub fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> io::Result< Ok(()) } +pub fn move_dir_all_fn<'a>( + src: impl AsRef, + mv: &'a Box Option<&'a Path>>, +) -> io::Result<()> { + move_dir_all_inner_fn(&src, &src, mv) +} + +fn move_dir_all_inner_fn<'a>( + src: impl AsRef, + srcrel: impl AsRef, + mv: &'a Box Option<&'a Path>>, +) -> io::Result<()> { + let mut files = Vec::new(); + for entry in fs::read_dir(&src)? { + let entry = entry?; + let ty = entry.file_type()?; + if ty.is_dir() { + move_dir_all_inner_fn(entry.path(), srcrel.as_ref(), mv)?; + } else { + let path: PathBuf = entry.path(); + let Ok(relpath) = path.strip_prefix(&srcrel) else { + continue; + }; + + if let Some(dst) = mv(relpath.to_path_buf()) { + files.push((entry.path(), relpath.to_path_buf(), dst.to_owned())); + } + } + } + for (src, srcrel, dst) in files { + let path = dst.join(&srcrel); + fs::create_dir_all(&path.parent().unwrap())?; + println!("{:?} -> {:?}", src.display(), path.display()); + std::fs::rename(&src, &path)?; + } + Ok(()) +} + pub fn symlink(original: impl AsRef, link: impl AsRef) -> Result<(), String> { std::os::unix::fs::symlink(&original, &link).map_err(|err| { format!( @@ -93,6 +131,20 @@ pub fn modified(path: &Path) -> Result { }) } +pub fn modified_all( + path: &Vec, + func: fn(path: &Path) -> Result, +) -> Result { + let mut newest = SystemTime::UNIX_EPOCH; + for entry_res in path { + let modified = func(entry_res)?; + if modified > newest { + newest = modified; + } + } + Ok(newest) +} + pub fn modified_dir_inner bool>( dir: &Path, filter: F, diff --git a/src/cook/package.rs b/src/cook/package.rs index cec7f22b..9998d2f5 100644 --- a/src/cook/package.rs +++ b/src/cook/package.rs @@ -9,11 +9,11 @@ use crate::{ blake3::hash_to_hex, cook::{fs::*, pty::PtyOut}, log_to_pty, - recipe::{BuildKind, Recipe}, + recipe::{BuildKind, OptionalPackageRecipe, Recipe}, }; pub fn package( - stage_dir: &Path, + stage_dirs: &Vec, target_dir: &Path, name: &PackageName, recipe: &Recipe, @@ -21,8 +21,15 @@ pub fn package( logger: &PtyOut, ) -> Result<(), String> { if recipe.build.kind == BuildKind::None { - // metapackages don't have stage dir - package_toml(target_dir, name, recipe, None, auto_deps)?; + // metapackages don't have stage dir and optional packages + package_toml( + target_dir.join("stage.toml"), + name, + recipe, + None, + recipe.package.dependencies.clone(), + &auto_deps, + )?; return Ok(()); } @@ -41,51 +48,82 @@ pub fn package( .map_err(|err| format!("failed to save pkgar secret key: {:?}", err))?; } - let package_file = target_dir.join("stage.pkgar"); - let package_meta = target_dir.join("stage.toml"); - // Rebuild package if stage is newer - //TODO: rebuild on recipe changes - if package_file.is_file() { - let stage_modified = modified_dir(stage_dir)?; - if modified(&package_file)? < stage_modified { + let stage_modified = modified_all(stage_dirs, modified_dir)?; + + let packages = recipe.get_packages_list(); + + for package in packages { + let (stage_dir, package_file, package_meta) = package_stage_paths(package, target_dir); + // Rebuild package if stage is newer + if package_file.is_file() && modified(&package_file)? < stage_modified { log_to_pty!(logger, "DEBUG: updating '{}'", package_file.display()); remove_all(&package_file)?; - remove_all(&package_meta)?; + if package_meta.is_file() { + remove_all(&package_meta)?; + } } - } - if !package_file.is_file() { - pkgar::create( - secret_path, - package_file.to_str().unwrap(), - stage_dir.to_str().unwrap(), - ) - .map_err(|err| format!("failed to create pkgar archive: {:?}", err))?; - } - if !package_meta.is_file() { - package_toml( - target_dir, - name, - recipe, - Some((Path::new(public_path), &package_file)), - auto_deps, - )?; + if !package_file.is_file() { + pkgar::create( + secret_path, + package_file.to_str().unwrap(), + stage_dir.to_str().unwrap(), + ) + .map_err(|err| format!("failed to create pkgar archive: {:?}", err))?; + } + + let deps = if let Some(package) = package { + let mut b = BTreeSet::new(); + for dep in &package.dependencies { + let dep_name = if dep.name() == "" { + PackageName::new(format!("{}.{}", name.name(), package.name)) + .map_err(|e| format!("{}", e))? + } else { + dep.clone() + }; + b.insert(dep_name); + } + b.insert(name.clone()); + b + } else { + auto_deps.clone() + }; + + if !package_meta.is_file() { + let name = match package { + Some(p) => PackageName::new(format!("{}.{}", name.name(), p.name)) + .map_err(|e| format!("{}", e))?, + None => name.clone(), + }; + let package_deps = match package { + Some(p) => p.dependencies.clone(), + None => recipe.package.dependencies.clone(), + }; + package_toml( + package_meta, + &name, + recipe, + Some((Path::new(public_path), &package_file)), + package_deps, + &deps, + )?; + } } Ok(()) } pub fn package_toml( - target_dir: &Path, + toml_path: PathBuf, name: &PackageName, recipe: &Recipe, package_file: Option<(&Path, &PathBuf)>, + mut package_deps: Vec, auto_deps: &BTreeSet, ) -> Result<(), String> { - let mut depends = recipe.package.dependencies.clone(); for dep in auto_deps.iter() { - if !depends.contains(dep) { - depends.push(dep.clone()); + if !package_deps.contains(dep) { + package_deps.push(dep.clone()); } } @@ -112,19 +150,22 @@ pub fn package_toml( }; let package = Package { - name: PackageName::new(name.name()).unwrap(), + name: PackageName::new(if name.is_host() { + &name.as_str()["host:".len()..] + } else { + name.as_str() + }) + .unwrap(), version: package_version(recipe), target: package_target(name).to_string(), blake3: hash, // this size will be different once pkgar supports compression network_size: size, storage_size: size, - depends, + depends: package_deps, }; - let toml_path = &target_dir.join("stage.toml"); serialize_and_write(&toml_path, &package)?; - return Ok(()); } @@ -151,3 +192,42 @@ pub fn package_target(name: &PackageName) -> &'static str { redoxer::target() } } + +pub fn package_stage_paths( + package: Option<&OptionalPackageRecipe>, + target_dir: &Path, +) -> (PathBuf, PathBuf, PathBuf) { + package_name_paths(package, target_dir, "stage") +} + +pub fn package_source_paths( + package: Option<&OptionalPackageRecipe>, + target_dir: &Path, +) -> (PathBuf, PathBuf, PathBuf) { + package_name_paths(package, target_dir, "source") +} + +fn package_name_paths( + package: Option<&OptionalPackageRecipe>, + target_dir: &Path, + name: &str, +) -> (PathBuf, PathBuf, PathBuf) { + let prefix_name = get_package_name(name, package); + let package_stage = target_dir.join(&prefix_name); + let package_file = package_stage.with_added_extension("pkgar"); + let package_meta = package_stage.with_added_extension("toml"); + (package_stage, package_file, package_meta) +} + +pub fn get_package_name(name: &str, package: Option<&OptionalPackageRecipe>) -> String { + get_package_name_inner(name, package.map(|p| p.name.as_str())) +} + +fn get_package_name_inner(name: &str, package: Option<&str>) -> String { + let mut prefix_name = name.to_string(); + if let Some(package) = package { + prefix_name.push('.'); + prefix_name.push_str(package); + } + prefix_name +} diff --git a/src/recipe.rs b/src/recipe.rs index f3204a6d..68e0b5d6 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -135,34 +135,43 @@ impl Default for BuildKind { } #[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)] +#[serde(default)] pub struct BuildRecipe { - #[serde(flatten, default)] + #[serde(flatten)] pub kind: BuildKind, - #[serde(default)] pub dependencies: Vec, - #[serde(default, rename = "dev-dependencies")] + #[serde(rename = "dev-dependencies")] pub dev_dependencies: Vec, } #[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)] +#[serde(default)] pub struct PackageRecipe { - #[serde(default)] pub dependencies: Vec, - #[serde(default)] pub version: Option, } +#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct OptionalPackageRecipe { + pub name: String, + pub dependencies: Vec, + pub files: Vec, +} + /// Everything required to build a Redox package #[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)] +#[serde(default)] pub struct Recipe { /// Specifies how to download the source for this recipe pub source: Option, /// Specifies how to build this recipe - #[serde(default)] pub build: BuildRecipe, /// Specifies how to package this recipe - #[serde(default)] pub package: PackageRecipe, + /// Specifies optional packages based from this recipe + #[serde(rename = "optional-packages")] + pub optional_packages: Vec, } impl BuildRecipe { @@ -205,6 +214,14 @@ impl Recipe { .map_err(|err| PackageError::Parse(DeError::custom(err), Some(file.clone())))?; Ok(recipe) } + + pub fn get_packages_list(&self) -> Vec> { + let mut packages: Vec> = + self.optional_packages.iter().map(|p| Some(p)).collect(); + // the mandatory package, put last because of cook_build + packages.push(None); + packages + } } impl CookRecipe { @@ -443,7 +460,7 @@ mod tests { package_path: None, cargoflags: String::new(), }), - package: PackageRecipe::default(), + ..Default::default() } ); } @@ -480,7 +497,7 @@ mod tests { build: BuildRecipe::new(BuildKind::Custom { script: "make".to_string() }), - package: PackageRecipe::default(), + ..Default::default() } ); @@ -509,8 +526,9 @@ mod tests { build: BuildRecipe::new(BuildKind::None), package: PackageRecipe { dependencies: vec![PackageName::new("gcc13").unwrap()], - version: None, + ....Default::default() }, + ..Default::default() } ); }