Implement optional packages

This commit is contained in:
Wildan M 2025-12-03 15:15:55 +07:00
parent 695a72f6df
commit b5c9bc2e18
No known key found for this signature in database
GPG Key ID: 01AC53185C679C79
13 changed files with 398 additions and 145 deletions

1
Cargo.lock generated
View File

@ -930,6 +930,7 @@ dependencies = [
"anyhow",
"blake3 1.5.3",
"filedescriptor",
"globset",
"ignore",
"libc",
"object",

View File

@ -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"] }

View File

@ -10,6 +10,7 @@ coreutils = {}
extrautils = {}
findutils = {}
gcc13 = {}
"gcc13.cxx" = {}
gnu-binutils = {}
ion = {}
netdb = {}

View File

@ -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/**",
]

View File

@ -4,6 +4,7 @@ dependencies = [
"automake",
"cargo",
"gcc13",
"gcc13.cxx",
"llvm18",
"gnu-binutils",
"gnu-make",

View File

@ -17,6 +17,7 @@ dependencies = [
"autoconf",
"automake",
"gcc13",
"gcc13.cxx",
"git",
"gnu-make",
"libtool",

View File

@ -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 {

View File

@ -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)?;

View File

@ -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<PathBuf>,
target_dir: &Path,
dep_pkgars: &BTreeSet<(PackageName, PathBuf)>,
logger: &PtyOut,
) -> BTreeSet<PackageName> {
@ -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<PackageName>), String> {
) -> Result<(Vec<PathBuf>, BTreeSet<PackageName>), 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<OptionalPackageRecipe>, target_dir: &Path) -> Vec<PathBuf> {
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<PathBuf>,
mut dep_pkgars: BTreeSet<(PackageName, PathBuf)>,
logger: &PtyOut,
) -> Result<BTreeSet<PackageName>, 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<PackageName>), 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<PathBuf>,
recipe: &Recipe,
target_dir: &Path,
) -> Result<(Vec<PathBuf>, BTreeSet<PackageName>), 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<PackageName>
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"

View File

@ -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<Path
return Ok(source_dir);
}
if recipe.build.kind == BuildKind::Remote {
fetch_remote(recipe_dir, false, logger)?;
fetch_remote(recipe_dir, recipe, false, logger)?;
return Ok(source_dir);
}
@ -441,28 +443,46 @@ fn get_pubkey_url() -> 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(())

View File

@ -62,6 +62,44 @@ pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<
Ok(())
}
pub fn move_dir_all_fn<'a>(
src: impl AsRef<Path>,
mv: &'a Box<impl Fn(PathBuf) -> Option<&'a Path>>,
) -> io::Result<()> {
move_dir_all_inner_fn(&src, &src, mv)
}
fn move_dir_all_inner_fn<'a>(
src: impl AsRef<Path>,
srcrel: impl AsRef<Path>,
mv: &'a Box<impl Fn(PathBuf) -> 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<Path>, link: impl AsRef<Path>) -> Result<(), String> {
std::os::unix::fs::symlink(&original, &link).map_err(|err| {
format!(
@ -93,6 +131,20 @@ pub fn modified(path: &Path) -> Result<SystemTime, String> {
})
}
pub fn modified_all(
path: &Vec<PathBuf>,
func: fn(path: &Path) -> Result<SystemTime, String>,
) -> Result<SystemTime, String> {
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<F: FnMut(&DirEntry) -> bool>(
dir: &Path,
filter: F,

View File

@ -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<PathBuf>,
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<PackageName>,
auto_deps: &BTreeSet<PackageName>,
) -> 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
}

View File

@ -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<PackageName>,
#[serde(default, rename = "dev-dependencies")]
#[serde(rename = "dev-dependencies")]
pub dev_dependencies: Vec<PackageName>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct PackageRecipe {
#[serde(default)]
pub dependencies: Vec<PackageName>,
#[serde(default)]
pub version: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct OptionalPackageRecipe {
pub name: String,
pub dependencies: Vec<PackageName>,
pub files: Vec<String>,
}
/// 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<SourceRecipe>,
/// 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<OptionalPackageRecipe>,
}
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<Option<&OptionalPackageRecipe>> {
let mut packages: Vec<Option<&OptionalPackageRecipe>> =
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()
}
);
}