Merge branch 'kek/path-source' into 'master'

Add Path variant to SourceRecipe

See merge request redox-os/cookbook!396
This commit is contained in:
Jeremy Soller 2024-09-11 21:16:44 +00:00
commit 5347e7ac1e
2 changed files with 344 additions and 256 deletions

View File

@ -1,9 +1,8 @@
use cookbook::blake3::blake3_progress; use cookbook::blake3::blake3_progress;
use cookbook::recipe::{Recipe, SourceRecipe, BuildKind, BuildRecipe, PackageRecipe}; use cookbook::recipe::{BuildKind, BuildRecipe, PackageRecipe, Recipe, SourceRecipe};
use cookbook::recipe_find::recipe_find; use cookbook::recipe_find::recipe_find;
use std::{ use std::{
env, env, fs,
fs,
io::{self, Write}, io::{self, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{self, Command, Stdio}, process::{self, Command, Stdio},
@ -17,21 +16,13 @@ fn remove_all(path: &Path) -> Result<(), String> {
fs::remove_dir_all(path) fs::remove_dir_all(path)
} else { } else {
fs::remove_file(path) fs::remove_file(path)
}.map_err(|err| format!( }
"failed to remove '{}': {}\n{:?}", .map_err(|err| format!("failed to remove '{}': {}\n{:?}", path.display(), err, err))
path.display(),
err,
err
))
} }
fn create_dir(dir: &Path) -> Result<(), String> { fn create_dir(dir: &Path) -> Result<(), String> {
fs::create_dir(dir).map_err(|err| format!( fs::create_dir(dir)
"failed to create '{}': {}\n{:?}", .map_err(|err| format!("failed to create '{}': {}\n{:?}", dir.display(), err, err))
dir.display(),
err,
err
))
} }
fn create_dir_clean(dir: &Path) -> Result<(), String> { fn create_dir_clean(dir: &Path) -> Result<(), String> {
@ -41,22 +32,43 @@ fn create_dir_clean(dir: &Path) -> Result<(), String> {
create_dir(dir) create_dir(dir)
} }
fn modified(path: &Path) -> Result<SystemTime, String> { fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
let metadata = fs::metadata(path).map_err(|err| format!( fs::create_dir_all(&dst)?;
"failed to get metadata of '{}': {}\n{:#?}", for entry in fs::read_dir(src)? {
path.display(), let entry = entry?;
err, let ty = entry.file_type()?;
err if ty.is_dir() {
))?; copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
metadata.modified().map_err(|err| format!( } else {
"failed to get modified time of '{}': {}\n{:#?}", fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
path.display(), }
err, }
err Ok(())
))
} }
fn modified_dir_inner<F: FnMut(&DirEntry) -> bool>(dir: &Path, filter: F) -> io::Result<SystemTime> { fn modified(path: &Path) -> Result<SystemTime, String> {
let metadata = fs::metadata(path).map_err(|err| {
format!(
"failed to get metadata of '{}': {}\n{:#?}",
path.display(),
err,
err
)
})?;
metadata.modified().map_err(|err| {
format!(
"failed to get modified time of '{}': {}\n{:#?}",
path.display(),
err,
err
)
})
}
fn modified_dir_inner<F: FnMut(&DirEntry) -> bool>(
dir: &Path,
filter: F,
) -> io::Result<SystemTime> {
let mut newest = fs::metadata(dir)?.modified()?; let mut newest = fs::metadata(dir)?.modified()?;
for entry_res in WalkDir::new(dir).into_iter().filter_entry(filter) { for entry_res in WalkDir::new(dir).into_iter().filter_entry(filter) {
let entry = entry_res?; let entry = entry_res?;
@ -69,48 +81,55 @@ fn modified_dir_inner<F: FnMut(&DirEntry) -> bool>(dir: &Path, filter: F) -> io:
} }
fn modified_dir(dir: &Path) -> Result<SystemTime, String> { fn modified_dir(dir: &Path) -> Result<SystemTime, String> {
modified_dir_inner(dir, |_| true).map_err(|err| format!( modified_dir_inner(dir, |_| true).map_err(|err| {
"failed to get modified time of '{}': {}\n{:#?}", format!(
dir.display(), "failed to get modified time of '{}': {}\n{:#?}",
err, dir.display(),
err err,
)) err
)
})
} }
fn modified_dir_ignore_git(dir: &Path) -> Result<SystemTime, String> { fn modified_dir_ignore_git(dir: &Path) -> Result<SystemTime, String> {
modified_dir_inner(dir, |entry| { modified_dir_inner(dir, |entry| {
entry.file_name().to_str().map(|s| s != ".git").unwrap_or(true) entry
}).map_err(|err| format!( .file_name()
"failed to get modified time of '{}': {}\n{:#?}", .to_str()
dir.display(), .map(|s| s != ".git")
err, .unwrap_or(true)
err })
)) .map_err(|err| {
format!(
"failed to get modified time of '{}': {}\n{:#?}",
dir.display(),
err,
err
)
})
} }
fn rename(src: &Path, dst: &Path) -> Result<(), String> { fn rename(src: &Path, dst: &Path) -> Result<(), String> {
fs::rename(src, dst).map_err(|err| format!( fs::rename(src, dst).map_err(|err| {
"failed to rename '{}' to '{}': {}\n{:?}", format!(
src.display(), "failed to rename '{}' to '{}': {}\n{:?}",
dst.display(), src.display(),
err, dst.display(),
err err,
)) err
)
})
} }
fn run_command(mut command: process::Command) -> Result<(), String> { fn run_command(mut command: process::Command) -> Result<(), String> {
let status = command.status().map_err(|err| format!( let status = command
"failed to run {:?}: {}\n{:#?}", .status()
command, .map_err(|err| format!("failed to run {:?}: {}\n{:#?}", command, err, err))?;
err,
err
))?;
if ! status.success() { if !status.success() {
return Err(format!( return Err(format!(
"failed to run {:?}: exited with status {}", "failed to run {:?}: exited with status {}",
command, command, status
status
)); ));
} }
@ -120,39 +139,29 @@ fn run_command(mut command: process::Command) -> Result<(), String> {
fn run_command_stdin(mut command: process::Command, stdin_data: &[u8]) -> Result<(), String> { fn run_command_stdin(mut command: process::Command, stdin_data: &[u8]) -> Result<(), String> {
command.stdin(Stdio::piped()); command.stdin(Stdio::piped());
let mut child = command.spawn().map_err(|err| format!( let mut child = command
"failed to spawn {:?}: {}\n{:#?}", .spawn()
command, .map_err(|err| format!("failed to spawn {:?}: {}\n{:#?}", command, err, err))?;
err,
err
))?;
if let Some(ref mut stdin) = child.stdin { if let Some(ref mut stdin) = child.stdin {
stdin.write_all(stdin_data).map_err(|err| format!( stdin.write_all(stdin_data).map_err(|err| {
"failed to write stdin of {:?}: {}\n{:#?}", format!(
command, "failed to write stdin of {:?}: {}\n{:#?}",
err, command, err, err
err )
))?; })?;
} else { } else {
return Err(format!( return Err(format!("failed to find stdin of {:?}", command));
"failed to find stdin of {:?}",
command
));
} }
let status = child.wait().map_err(|err| format!( let status = child
"failed to run {:?}: {}\n{:#?}", .wait()
command, .map_err(|err| format!("failed to run {:?}: {}\n{:#?}", command, err, err))?;
err,
err
))?;
if ! status.success() { if !status.success() {
return Err(format!( return Err(format!(
"failed to run {:?}: exited with status {}", "failed to run {:?}: exited with status {}",
command, command, status
status
)); ));
} }
@ -163,27 +172,44 @@ fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf, St
let source_dir = recipe_dir.join("source"); let source_dir = recipe_dir.join("source");
match source { match source {
Some(SourceRecipe::SameAs { same_as }) => { Some(SourceRecipe::SameAs { same_as }) => {
if ! source_dir.is_symlink() { if !source_dir.is_symlink() {
if source_dir.is_dir() { if source_dir.is_dir() {
return Err(format!( return Err(format!(
"'{dir}' is a directory, but recipe indicated a symlink. \n\ "'{dir}' is a directory, but recipe indicated a symlink. \n\
try removing '{dir}' if you haven't made any changes that would be lost", try removing '{dir}' if you haven't made any changes that would be lost",
dir=source_dir.display(), dir = source_dir.display(),
)); ));
} }
let original = Path::new(same_as).join("source"); let original = Path::new(same_as).join("source");
std::os::unix::fs::symlink(&original, &source_dir).map_err(|err| format!( std::os::unix::fs::symlink(&original, &source_dir).map_err(|err| {
"failed to symlink '{}' to '{}': {}\n{:?}", format!(
original.display(), "failed to symlink '{}' to '{}': {}\n{:?}",
source_dir.display(), original.display(),
err, source_dir.display(),
err err,
))?; err
)
})?;
} }
} }
Some(SourceRecipe::Git { git, upstream, branch, rev }) => { Some(SourceRecipe::Path { path }) => {
copy_dir_all(path, &source_dir).map_err(|e| {
format!(
"Couldn't copy source from {} to {}: {}",
path,
source_dir.display(),
e
)
})?;
}
Some(SourceRecipe::Git {
git,
upstream,
branch,
rev,
}) => {
//TODO: use libgit? //TODO: use libgit?
if ! source_dir.is_dir() { if !source_dir.is_dir() {
// Create source.tmp // Create source.tmp
let source_dir_tmp = recipe_dir.join("source.tmp"); let source_dir_tmp = recipe_dir.join("source.tmp");
create_dir_clean(&source_dir_tmp)?; create_dir_clean(&source_dir_tmp)?;
@ -202,7 +228,7 @@ fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf, St
} else { } else {
// Don't let this code reset the origin for the cookbook repo // Don't let this code reset the origin for the cookbook repo
let source_git_dir = source_dir.join(".git"); let source_git_dir = source_dir.join(".git");
if ! source_git_dir.is_dir() { if !source_git_dir.is_dir() {
return Err(format!( return Err(format!(
"'{}' is not a git repository, but recipe indicated git source", "'{}' is not a git repository, but recipe indicated git source",
source_dir.display(), source_dir.display(),
@ -238,7 +264,8 @@ fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf, St
} else { } else {
//TODO: complicated stuff to check and reset branch to origin //TODO: complicated stuff to check and reset branch to origin
let mut command = Command::new("bash"); let mut command = Command::new("bash");
command.arg("-c").arg(r#" command.arg("-c").arg(
r#"
ORIGIN_BRANCH="$(git branch --remotes | grep '^ origin/HEAD -> ' | cut -d ' ' -f 5-)" ORIGIN_BRANCH="$(git branch --remotes | grep '^ origin/HEAD -> ' | cut -d ' ' -f 5-)"
if [ -n "$BRANCH" ] if [ -n "$BRANCH" ]
then then
@ -248,7 +275,8 @@ fi
if [ "$(git rev-parse HEAD)" != "$(git rev-parse $ORIGIN_BRANCH)" ] if [ "$(git rev-parse HEAD)" != "$(git rev-parse $ORIGIN_BRANCH)" ]
then then
git checkout -B "$(echo "$ORIGIN_BRANCH" | cut -d / -f 2-)" "$ORIGIN_BRANCH" git checkout -B "$(echo "$ORIGIN_BRANCH" | cut -d / -f 2-)" "$ORIGIN_BRANCH"
fi"#); fi"#,
);
if let Some(branch) = branch { if let Some(branch) = branch {
command.env("BRANCH", branch); command.env("BRANCH", branch);
} }
@ -265,15 +293,24 @@ fi"#);
// Update submodules // Update submodules
let mut command = Command::new("git"); let mut command = Command::new("git");
command.arg("-C").arg(&source_dir); command.arg("-C").arg(&source_dir);
command.arg("submodule").arg("update").arg("--init").arg("--recursive"); command
.arg("submodule")
.arg("update")
.arg("--init")
.arg("--recursive");
run_command(command)?; run_command(command)?;
}, }
Some(SourceRecipe::Tar { tar, blake3, patches, script }) => { Some(SourceRecipe::Tar {
if ! source_dir.is_dir() { tar,
blake3,
patches,
script,
}) => {
if !source_dir.is_dir() {
// Download tar // Download tar
//TODO: replace wget //TODO: replace wget
let source_tar = recipe_dir.join("source.tar"); let source_tar = recipe_dir.join("source.tar");
if ! source_tar.is_file() { if !source_tar.is_file() {
let source_tar_tmp = recipe_dir.join("source.tar.tmp"); let source_tar_tmp = recipe_dir.join("source.tar.tmp");
let mut command = Command::new("wget"); let mut command = Command::new("wget");
@ -286,19 +323,20 @@ fi"#);
} }
// Calculate blake3 // Calculate blake3
let source_tar_blake3 = blake3_progress(&source_tar).map_err(|err| format!( let source_tar_blake3 = blake3_progress(&source_tar).map_err(|err| {
"failed to calculate blake3 of '{}': {}\n{:?}", format!(
source_tar.display(), "failed to calculate blake3 of '{}': {}\n{:?}",
err, source_tar.display(),
err err,
))?; err
)
})?;
if let Some(blake3) = blake3 { if let Some(blake3) = blake3 {
// Check if it matches recipe // Check if it matches recipe
if &source_tar_blake3 != blake3 { if &source_tar_blake3 != blake3 {
return Err(format!( return Err(format!(
"calculated blake3 '{}' does not match recipe blake3 '{}'", "calculated blake3 '{}' does not match recipe blake3 '{}'",
source_tar_blake3, source_tar_blake3, blake3
blake3
)); ));
} }
} else { } else {
@ -327,19 +365,21 @@ fi"#);
// Apply patches // Apply patches
for patch_name in patches { for patch_name in patches {
let patch_file = recipe_dir.join(patch_name); let patch_file = recipe_dir.join(patch_name);
if ! patch_file.is_file() { if !patch_file.is_file() {
return Err(format!( return Err(format!(
"failed to find patch file '{}'", "failed to find patch file '{}'",
patch_file.display() patch_file.display()
)); ));
} }
let patch = fs::read_to_string(&patch_file).map_err(|err| format!( let patch = fs::read_to_string(&patch_file).map_err(|err| {
"failed to read patch file '{}': {}\n{:#?}", format!(
patch_file.display(), "failed to read patch file '{}': {}\n{:#?}",
err, patch_file.display(),
err err,
))?; err
)
})?;
let mut command = Command::new("patch"); let mut command = Command::new("patch");
command.arg("--directory").arg(&source_dir_tmp); command.arg("--directory").arg(&source_dir_tmp);
@ -358,47 +398,66 @@ fi"#);
// Move source.tmp to source atomically // Move source.tmp to source atomically
rename(&source_dir_tmp, &source_dir)?; rename(&source_dir_tmp, &source_dir)?;
} }
}, }
// Local Sources // Local Sources
None => { None => {
if ! source_dir.is_dir() { if !source_dir.is_dir() {
eprintln!( eprintln!(
"WARNING: Recipe without source section expected source dir at '{}'", "WARNING: Recipe without source section expected source dir at '{}'",
source_dir.display(), source_dir.display(),
); );
create_dir(&source_dir)?; create_dir(&source_dir)?;
} }
}, }
} }
Ok(source_dir) Ok(source_dir)
} }
fn build(recipe_dir: &Path, source_dir: &Path, target_dir: &Path, build: &BuildRecipe) -> Result<PathBuf, String> { fn build(
recipe_dir: &Path,
source_dir: &Path,
target_dir: &Path,
build: &BuildRecipe,
) -> Result<PathBuf, String> {
let mut dep_pkgars = vec![]; let mut dep_pkgars = vec![];
for dependency in build.dependencies.iter() { for dependency in build.dependencies.iter() {
//TODO: sanitize name //TODO: sanitize name
let dependency_dir = recipe_find(dependency, Path::new("recipes"))?; let dependency_dir = recipe_find(dependency, Path::new("recipes"))?;
if dependency_dir.is_none() { if dependency_dir.is_none() {
return Err(format!( return Err(format!("failed to find recipe directory '{}'", dependency));
"failed to find recipe directory '{}'",
dependency
));
} }
dep_pkgars.push(dependency_dir.unwrap().join("target").join(redoxer::target()).join("stage.pkgar")); dep_pkgars.push(
dependency_dir
.unwrap()
.join("target")
.join(redoxer::target())
.join("stage.pkgar"),
);
} }
let source_modified = modified_dir_ignore_git(source_dir)?; let source_modified = modified_dir_ignore_git(source_dir)?;
let deps_modified = dep_pkgars.iter().map(|pkgar| modified(pkgar)).max().unwrap_or(Ok(SystemTime::UNIX_EPOCH))?; let deps_modified = dep_pkgars
.iter()
.map(|pkgar| modified(pkgar))
.max()
.unwrap_or(Ok(SystemTime::UNIX_EPOCH))?;
let sysroot_dir = target_dir.join("sysroot"); let sysroot_dir = target_dir.join("sysroot");
// Rebuild sysroot if source is newer // Rebuild sysroot if source is newer
//TODO: rebuild on recipe changes //TODO: rebuild on recipe changes
if sysroot_dir.is_dir() && (modified_dir(&sysroot_dir)? < source_modified || modified_dir(&sysroot_dir)? < deps_modified) { if sysroot_dir.is_dir()
eprintln!("DEBUG: '{}' newer than '{}'", source_dir.display(), sysroot_dir.display()); && (modified_dir(&sysroot_dir)? < source_modified
|| modified_dir(&sysroot_dir)? < deps_modified)
{
eprintln!(
"DEBUG: '{}' newer than '{}'",
source_dir.display(),
sysroot_dir.display()
);
remove_all(&sysroot_dir)?; remove_all(&sysroot_dir)?;
} }
if ! sysroot_dir.is_dir() { if !sysroot_dir.is_dir() {
// Create sysroot.tmp // Create sysroot.tmp
let sysroot_dir_tmp = target_dir.join("sysroot.tmp"); let sysroot_dir_tmp = target_dir.join("sysroot.tmp");
create_dir_clean(&sysroot_dir_tmp)?; create_dir_clean(&sysroot_dir_tmp)?;
@ -413,13 +472,16 @@ fn build(recipe_dir: &Path, source_dir: &Path, target_dir: &Path, build: &BuildR
pkgar::extract( pkgar::extract(
public_path, public_path,
&archive_path, &archive_path,
sysroot_dir_tmp.to_str().unwrap() sysroot_dir_tmp.to_str().unwrap(),
).map_err(|err| format!( )
"failed to install '{}' in '{}': {:?}", .map_err(|err| {
archive_path.display(), format!(
sysroot_dir_tmp.display(), "failed to install '{}' in '{}': {:?}",
err archive_path.display(),
))?; sysroot_dir_tmp.display(),
err
)
})?;
} }
// Move sysroot.tmp to sysroot atomically // Move sysroot.tmp to sysroot atomically
@ -429,12 +491,19 @@ fn build(recipe_dir: &Path, source_dir: &Path, target_dir: &Path, build: &BuildR
let stage_dir = target_dir.join("stage"); let stage_dir = target_dir.join("stage");
// Rebuild stage if source is newer // Rebuild stage if source is newer
//TODO: rebuild on recipe changes //TODO: rebuild on recipe changes
if stage_dir.is_dir() && (modified_dir(&stage_dir)? < source_modified || modified_dir(&stage_dir)? < deps_modified) { if stage_dir.is_dir()
eprintln!("DEBUG: '{}' newer than '{}'", source_dir.display(), stage_dir.display()); && (modified_dir(&stage_dir)? < source_modified
|| modified_dir(&stage_dir)? < deps_modified)
{
eprintln!(
"DEBUG: '{}' newer than '{}'",
source_dir.display(),
stage_dir.display()
);
remove_all(&stage_dir)?; remove_all(&stage_dir)?;
} }
if ! stage_dir.is_dir() { if !stage_dir.is_dir() {
// Create stage.tmp // Create stage.tmp
let stage_dir_tmp = target_dir.join("stage.tmp"); let stage_dir_tmp = target_dir.join("stage.tmp");
create_dir_clean(&stage_dir_tmp)?; create_dir_clean(&stage_dir_tmp)?;
@ -442,7 +511,7 @@ fn build(recipe_dir: &Path, source_dir: &Path, target_dir: &Path, build: &BuildR
// Create build, if it does not exist // Create build, if it does not exist
//TODO: flag for clean builds where build is wiped out //TODO: flag for clean builds where build is wiped out
let build_dir = target_dir.join("build"); let build_dir = target_dir.join("build");
if ! build_dir.is_dir() { if !build_dir.is_dir() {
create_dir_clean(&build_dir)?; create_dir_clean(&build_dir)?;
} }
@ -579,8 +648,14 @@ done
//TODO: configurable target //TODO: configurable target
//TODO: Add more configurability, convert scripts to Rust? //TODO: Add more configurability, convert scripts to Rust?
let script = match &build.kind { let script = match &build.kind {
BuildKind::Cargo { package_path, cargoflags } => { BuildKind::Cargo {
format!("PACKAGE_PATH={} cookbook_cargo {cargoflags}", package_path.as_deref().unwrap_or(".")) package_path,
cargoflags,
} => {
format!(
"PACKAGE_PATH={} cookbook_cargo {cargoflags}",
package_path.as_deref().unwrap_or(".")
)
} }
BuildKind::Configure => "cookbook_configure".to_owned(), BuildKind::Configure => "cookbook_configure".to_owned(),
BuildKind::Custom { script } => script.clone(), BuildKind::Custom { script } => script.clone(),
@ -590,7 +665,9 @@ done
//TODO: remove unwraps //TODO: remove unwraps
let cookbook_build = build_dir.canonicalize().unwrap(); let cookbook_build = build_dir.canonicalize().unwrap();
let cookbook_recipe = recipe_dir.canonicalize().unwrap(); let cookbook_recipe = recipe_dir.canonicalize().unwrap();
let cookbook_redoxer = Path::new("target/release/cookbook_redoxer").canonicalize().unwrap(); let cookbook_redoxer = Path::new("target/release/cookbook_redoxer")
.canonicalize()
.unwrap();
let cookbook_root = Path::new(".").canonicalize().unwrap(); let cookbook_root = Path::new(".").canonicalize().unwrap();
let cookbook_stage = stage_dir_tmp.canonicalize().unwrap(); let cookbook_stage = stage_dir_tmp.canonicalize().unwrap();
let cookbook_source = source_dir.canonicalize().unwrap(); let cookbook_source = source_dir.canonicalize().unwrap();
@ -620,24 +697,27 @@ done
Ok(stage_dir) Ok(stage_dir)
} }
fn package(_recipe_dir: &Path, stage_dir: &Path, target_dir: &Path, _package: &PackageRecipe) -> Result<PathBuf, String> { fn package(
_recipe_dir: &Path,
stage_dir: &Path,
target_dir: &Path,
_package: &PackageRecipe,
) -> Result<PathBuf, String> {
//TODO: metadata like dependencies, name, and version //TODO: metadata like dependencies, name, and version
let secret_path = "build/id_ed25519.toml"; let secret_path = "build/id_ed25519.toml";
let public_path = "build/id_ed25519.pub.toml"; let public_path = "build/id_ed25519.pub.toml";
if ! Path::new(secret_path).is_file() || ! Path::new(public_path).is_file() { if !Path::new(secret_path).is_file() || !Path::new(public_path).is_file() {
if ! Path::new("build").is_dir() { if !Path::new("build").is_dir() {
create_dir(Path::new("build"))?; create_dir(Path::new("build"))?;
} }
let (public_key, secret_key) = pkgar_keys::SecretKeyFile::new(); let (public_key, secret_key) = pkgar_keys::SecretKeyFile::new();
public_key.save(public_path).map_err(|err| format!( public_key
"failed to save pkgar public key: {:?}", .save(public_path)
err .map_err(|err| format!("failed to save pkgar public key: {:?}", err))?;
))?; secret_key
secret_key.save(secret_path).map_err(|err| format!( .save(secret_path)
"failed to save pkgar secret key: {:?}", .map_err(|err| format!("failed to save pkgar secret key: {:?}", err))?;
err
))?;
} }
let package_file = target_dir.join("stage.pkgar"); let package_file = target_dir.join("stage.pkgar");
@ -646,50 +726,48 @@ fn package(_recipe_dir: &Path, stage_dir: &Path, target_dir: &Path, _package: &P
if package_file.is_file() { if package_file.is_file() {
let stage_modified = modified_dir(stage_dir)?; let stage_modified = modified_dir(stage_dir)?;
if modified(&package_file)? < stage_modified { if modified(&package_file)? < stage_modified {
eprintln!("DEBUG: '{}' newer than '{}'", stage_dir.display(), package_file.display()); eprintln!(
"DEBUG: '{}' newer than '{}'",
stage_dir.display(),
package_file.display()
);
remove_all(&package_file)?; remove_all(&package_file)?;
} }
} }
if ! package_file.is_file() { if !package_file.is_file() {
pkgar::create( pkgar::create(
secret_path, secret_path,
package_file.to_str().unwrap(), package_file.to_str().unwrap(),
stage_dir.to_str().unwrap() stage_dir.to_str().unwrap(),
).map_err(|err| format!( )
"failed to create pkgar archive: {:?}", .map_err(|err| format!("failed to create pkgar archive: {:?}", err))?;
err
))?;
} }
Ok(package_file) Ok(package_file)
} }
fn cook(recipe_dir: &Path, recipe: &Recipe, fetch_only: bool) -> Result<(), String> { fn cook(recipe_dir: &Path, recipe: &Recipe, fetch_only: bool) -> Result<(), String> {
let source_dir = fetch(recipe_dir, &recipe.source).map_err(|err| format!( let source_dir =
"failed to fetch: {}", fetch(recipe_dir, &recipe.source).map_err(|err| format!("failed to fetch: {}", err))?;
err
))?;
if fetch_only { return Ok(()); } if fetch_only {
return Ok(());
}
let target_parent_dir = recipe_dir.join("target"); let target_parent_dir = recipe_dir.join("target");
if ! target_parent_dir.is_dir() { if !target_parent_dir.is_dir() {
create_dir(&target_parent_dir)?; create_dir(&target_parent_dir)?;
} }
let target_dir = target_parent_dir.join(redoxer::target()); let target_dir = target_parent_dir.join(redoxer::target());
if ! target_dir.is_dir() { if !target_dir.is_dir() {
create_dir(&target_dir)?; create_dir(&target_dir)?;
} }
let stage_dir = build(recipe_dir, &source_dir, &target_dir, &recipe.build).map_err(|err| format!( let stage_dir = build(recipe_dir, &source_dir, &target_dir, &recipe.build)
"failed to build: {}", .map_err(|err| format!("failed to build: {}", err))?;
err
))?;
let _package_file = package(recipe_dir, &stage_dir, &target_dir, &recipe.package).map_err(|err| format!( let _package_file = package(recipe_dir, &stage_dir, &target_dir, &recipe.package)
"failed to package: {}", .map_err(|err| format!("failed to package: {}", err))?;
err
))?;
Ok(()) Ok(())
} }
@ -705,39 +783,33 @@ impl CookRecipe {
//TODO: sanitize recipe name? //TODO: sanitize recipe name?
let dir = recipe_find(&name, Path::new("recipes"))?; let dir = recipe_find(&name, Path::new("recipes"))?;
if dir.is_none() { if dir.is_none() {
return Err(format!( return Err(format!("failed to find recipe directory '{}'", name));
"failed to find recipe directory '{}'",
name
));
} }
let dir = dir.unwrap(); let dir = dir.unwrap();
let file = dir.join("recipe.toml"); let file = dir.join("recipe.toml");
if ! file.is_file() { if !file.is_file() {
return Err(format!( return Err(format!("failed to find recipe file '{}'", file.display()));
"failed to find recipe file '{}'",
file.display()
));
} }
let toml = fs::read_to_string(&file).map_err(|err| format!( let toml = fs::read_to_string(&file).map_err(|err| {
"failed to read recipe file '{}': {}\n{:#?}", format!(
file.display(), "failed to read recipe file '{}': {}\n{:#?}",
err, file.display(),
err err,
))?; err
)
})?;
let recipe: Recipe = toml::from_str(&toml).map_err(|err| format!( let recipe: Recipe = toml::from_str(&toml).map_err(|err| {
"failed to parse recipe file '{}': {}\n{:#?}", format!(
file.display(), "failed to parse recipe file '{}': {}\n{:#?}",
err, file.display(),
err err,
))?; err
)
})?;
Ok(Self { Ok(Self { name, dir, recipe })
name,
dir,
recipe
})
} }
//TODO: make this more efficient, smarter, and not return duplicates //TODO: make this more efficient, smarter, and not return duplicates
@ -753,14 +825,10 @@ impl CookRecipe {
for name in names { for name in names {
let recipe = Self::new(name.clone())?; let recipe = Self::new(name.clone())?;
let dependencies = Self::new_recursive( let dependencies =
&recipe.recipe.build.dependencies, Self::new_recursive(&recipe.recipe.build.dependencies, recursion - 1).map_err(
recursion - 1 |err| format!("{}: failed on loading build dependencies:\n{}", name, err),
).map_err(|err| format!( )?;
"{}: failed on loading build dependencies:\n{}",
name,
err
))?;
for dependency in dependencies { for dependency in dependencies {
recipes.push(dependency); recipes.push(dependency);
@ -805,7 +873,7 @@ fn main() {
}; };
for recipe in recipes { for recipe in recipes {
if ! quiet { if !quiet {
eprintln!( eprintln!(
"{}{}cook - {}{}{}", "{}{}cook - {}{}{}",
style::Bold, style::Bold,
@ -817,7 +885,7 @@ fn main() {
} }
let res = if dry_run { let res = if dry_run {
if ! quiet { if !quiet {
eprintln!("DRY RUN: {:#?}", recipe.recipe); eprintln!("DRY RUN: {:#?}", recipe.recipe);
} }
Ok(()) Ok(())
@ -827,7 +895,7 @@ fn main() {
match res { match res {
Ok(()) => { Ok(()) => {
if ! quiet { if !quiet {
eprintln!( eprintln!(
"{}{}cook - {} - successful{}{}", "{}{}cook - {} - successful{}{}",
style::Bold, style::Bold,
@ -837,7 +905,7 @@ fn main() {
style::Reset, style::Reset,
); );
} }
}, }
Err(err) => { Err(err) => {
eprintln!( eprintln!(
"{}{}cook - {} - error:{}{} {}", "{}{}cook - {} - error:{}{} {}",

View File

@ -12,6 +12,11 @@ pub enum SourceRecipe {
/// Relative path to the package for which to reuse the source dir /// Relative path to the package for which to reuse the source dir
same_as: String, same_as: String,
}, },
/// Path source
Path {
/// The path to the source
path: String,
},
/// A git repository source /// A git repository source
Git { Git {
/// The URL for the git repository, such as https://gitlab.redox-os.org/redox-os/ion.git /// The URL for the git repository, such as https://gitlab.redox-os.org/redox-os/ion.git
@ -58,9 +63,7 @@ pub enum BuildKind {
Configure, Configure,
/// Will build and install using custom commands /// Will build and install using custom commands
#[serde(rename = "custom")] #[serde(rename = "custom")]
Custom { Custom { script: String },
script: String,
},
} }
#[derive(Debug, Deserialize, PartialEq, Serialize)] #[derive(Debug, Deserialize, PartialEq, Serialize)]
@ -89,14 +92,14 @@ pub struct Recipe {
pub package: PackageRecipe, pub package: PackageRecipe,
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[test] #[test]
fn git_cargo_recipe() { fn git_cargo_recipe() {
use crate::recipe::{Recipe, SourceRecipe, BuildKind, BuildRecipe, PackageRecipe}; use crate::recipe::{BuildKind, BuildRecipe, PackageRecipe, Recipe, SourceRecipe};
let recipe: Recipe = toml::from_str(r#" let recipe: Recipe = toml::from_str(
r#"
[source] [source]
git = "https://gitlab.redox-os.org/redox-os/acid.git" git = "https://gitlab.redox-os.org/redox-os/acid.git"
branch = "master" branch = "master"
@ -104,30 +107,39 @@ mod tests {
[build] [build]
template = "cargo" template = "cargo"
"#).unwrap(); "#,
)
.unwrap();
assert_eq!(recipe, Recipe { assert_eq!(
source: Some(SourceRecipe::Git { recipe,
git: "https://gitlab.redox-os.org/redox-os/acid.git".to_string(), Recipe {
upstream: None, source: Some(SourceRecipe::Git {
branch: Some("master".to_string()), git: "https://gitlab.redox-os.org/redox-os/acid.git".to_string(),
rev: Some("06344744d3d55a5ac9a62a6059cb363d40699bbc".to_string()), upstream: None,
}), branch: Some("master".to_string()),
build: BuildRecipe { rev: Some("06344744d3d55a5ac9a62a6059cb363d40699bbc".to_string()),
kind: BuildKind::Cargo, }),
dependencies: Vec::new(), build: BuildRecipe {
}, kind: BuildKind::Cargo {
package: PackageRecipe { package_path: None,
dependencies: Vec::new(), cargoflags: String::new(),
}, },
}); dependencies: Vec::new(),
},
package: PackageRecipe {
dependencies: Vec::new(),
},
}
);
} }
#[test] #[test]
fn tar_custom_recipe() { fn tar_custom_recipe() {
use crate::recipe::{Recipe, SourceRecipe, BuildKind, BuildRecipe, PackageRecipe}; use crate::recipe::{BuildKind, BuildRecipe, PackageRecipe, Recipe, SourceRecipe};
let recipe: Recipe = toml::from_str(r#" let recipe: Recipe = toml::from_str(
r#"
[source] [source]
tar = "http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz" tar = "http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz"
sha256 = "8220c0e4082fa26c07b10bfe31f641d2e33ebe1d1bb0b20221b7016bc8b78a3a" sha256 = "8220c0e4082fa26c07b10bfe31f641d2e33ebe1d1bb0b20221b7016bc8b78a3a"
@ -135,24 +147,32 @@ mod tests {
[build] [build]
template = "custom" template = "custom"
script = "make" script = "make"
"#).unwrap(); "#,
)
.unwrap();
assert_eq!(recipe, Recipe { assert_eq!(
source: Some(SourceRecipe::Tar { recipe,
tar: "http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz".to_string(), Recipe {
blake3: Some("8220c0e4082fa26c07b10bfe31f641d2e33ebe1d1bb0b20221b7016bc8b78a3a".to_string()), source: Some(SourceRecipe::Tar {
patches: Vec::new(), tar: "http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz".to_string(),
script: None, blake3: Some(
}), "8220c0e4082fa26c07b10bfe31f641d2e33ebe1d1bb0b20221b7016bc8b78a3a"
build: BuildRecipe { .to_string()
kind: BuildKind::Custom { ),
script: "make".to_string() patches: Vec::new(),
script: None,
}),
build: BuildRecipe {
kind: BuildKind::Custom {
script: "make".to_string()
},
dependencies: Vec::new(),
}, },
dependencies: Vec::new(), package: PackageRecipe {
}, dependencies: Vec::new(),
package: PackageRecipe { },
dependencies: Vec::new(), }
}, );
});
} }
} }