mirror of
https://gitlab.redox-os.org/redox-os/redox.git
synced 2026-06-17 23:44:17 +08:00
Merge branch 'optional-packages' into 'master'
Implement optional packages Closes #1749 See merge request redox-os/redox!1729
This commit is contained in:
commit
c1f13049dc
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -930,6 +930,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"blake3 1.5.3",
|
||||
"filedescriptor",
|
||||
"globset",
|
||||
"ignore",
|
||||
"libc",
|
||||
"object",
|
||||
|
||||
@ -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"] }
|
||||
|
||||
@ -10,6 +10,7 @@ coreutils = {}
|
||||
extrautils = {}
|
||||
findutils = {}
|
||||
gcc13 = {}
|
||||
"gcc13.cxx" = {}
|
||||
gnu-binutils = {}
|
||||
ion = {}
|
||||
netdb = {}
|
||||
|
||||
@ -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/**",
|
||||
]
|
||||
|
||||
@ -4,6 +4,7 @@ dependencies = [
|
||||
"automake",
|
||||
"cargo",
|
||||
"gcc13",
|
||||
"gcc13.cxx",
|
||||
"llvm18",
|
||||
"gnu-binutils",
|
||||
"gnu-make",
|
||||
|
||||
@ -17,6 +17,7 @@ dependencies = [
|
||||
"autoconf",
|
||||
"automake",
|
||||
"gcc13",
|
||||
"gcc13.cxx",
|
||||
"git",
|
||||
"gnu-make",
|
||||
"libtool",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)?;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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(())
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user