mirror of
https://gitlab.redox-os.org/redox-os/redox.git
synced 2026-06-21 20:34:17 +08:00
This makes it easier to make driver changes. And in the future other recipes could potentially benefit from the same infrastructure too.
856 lines
28 KiB
Rust
856 lines
28 KiB
Rust
use cookbook::blake3::blake3_progress;
|
|
use cookbook::recipe::{Recipe, SourceRecipe, BuildKind, BuildRecipe, PackageRecipe};
|
|
use cookbook::recipe_find::recipe_find;
|
|
use std::{
|
|
env,
|
|
fs,
|
|
io::{self, Write},
|
|
path::{Path, PathBuf},
|
|
process::{self, Command, Stdio},
|
|
time::SystemTime,
|
|
};
|
|
use termion::{color, style};
|
|
use walkdir::{DirEntry, WalkDir};
|
|
|
|
fn remove_all(path: &Path) -> Result<(), String> {
|
|
if path.is_dir() {
|
|
fs::remove_dir_all(path)
|
|
} else {
|
|
fs::remove_file(path)
|
|
}.map_err(|err| format!(
|
|
"failed to remove '{}': {}\n{:?}",
|
|
path.display(),
|
|
err,
|
|
err
|
|
))
|
|
}
|
|
|
|
fn create_dir(dir: &Path) -> Result<(), String> {
|
|
fs::create_dir(dir).map_err(|err| format!(
|
|
"failed to create '{}': {}\n{:?}",
|
|
dir.display(),
|
|
err,
|
|
err
|
|
))
|
|
}
|
|
|
|
fn create_dir_clean(dir: &Path) -> Result<(), String> {
|
|
if dir.is_dir() {
|
|
remove_all(dir)?;
|
|
}
|
|
create_dir(dir)
|
|
}
|
|
|
|
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()?;
|
|
for entry_res in WalkDir::new(dir).into_iter().filter_entry(filter) {
|
|
let entry = entry_res?;
|
|
let modified = entry.metadata()?.modified()?;
|
|
if modified > newest {
|
|
newest = modified;
|
|
}
|
|
}
|
|
Ok(newest)
|
|
}
|
|
|
|
fn modified_dir(dir: &Path) -> Result<SystemTime, String> {
|
|
modified_dir_inner(dir, |_| true).map_err(|err| format!(
|
|
"failed to get modified time of '{}': {}\n{:#?}",
|
|
dir.display(),
|
|
err,
|
|
err
|
|
))
|
|
}
|
|
|
|
fn modified_dir_ignore_git(dir: &Path) -> Result<SystemTime, String> {
|
|
modified_dir_inner(dir, |entry| {
|
|
entry.file_name().to_str().map(|s| s != ".git").unwrap_or(true)
|
|
}).map_err(|err| format!(
|
|
"failed to get modified time of '{}': {}\n{:#?}",
|
|
dir.display(),
|
|
err,
|
|
err
|
|
))
|
|
}
|
|
|
|
fn rename(src: &Path, dst: &Path) -> Result<(), String> {
|
|
fs::rename(src, dst).map_err(|err| format!(
|
|
"failed to rename '{}' to '{}': {}\n{:?}",
|
|
src.display(),
|
|
dst.display(),
|
|
err,
|
|
err
|
|
))
|
|
}
|
|
|
|
fn run_command(mut command: process::Command) -> Result<(), String> {
|
|
let status = command.status().map_err(|err| format!(
|
|
"failed to run {:?}: {}\n{:#?}",
|
|
command,
|
|
err,
|
|
err
|
|
))?;
|
|
|
|
if ! status.success() {
|
|
return Err(format!(
|
|
"failed to run {:?}: exited with status {}",
|
|
command,
|
|
status
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_command_stdin(mut command: process::Command, stdin_data: &[u8]) -> Result<(), String> {
|
|
command.stdin(Stdio::piped());
|
|
|
|
let mut child = command.spawn().map_err(|err| format!(
|
|
"failed to spawn {:?}: {}\n{:#?}",
|
|
command,
|
|
err,
|
|
err
|
|
))?;
|
|
|
|
if let Some(ref mut stdin) = child.stdin {
|
|
stdin.write_all(stdin_data).map_err(|err| format!(
|
|
"failed to write stdin of {:?}: {}\n{:#?}",
|
|
command,
|
|
err,
|
|
err
|
|
))?;
|
|
} else {
|
|
return Err(format!(
|
|
"failed to find stdin of {:?}",
|
|
command
|
|
));
|
|
}
|
|
|
|
let status = child.wait().map_err(|err| format!(
|
|
"failed to run {:?}: {}\n{:#?}",
|
|
command,
|
|
err,
|
|
err
|
|
))?;
|
|
|
|
if ! status.success() {
|
|
return Err(format!(
|
|
"failed to run {:?}: exited with status {}",
|
|
command,
|
|
status
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf, String> {
|
|
let source_dir = recipe_dir.join("source");
|
|
match source {
|
|
Some(SourceRecipe::SameAs { same_as }) => {
|
|
if ! source_dir.is_symlink() {
|
|
if source_dir.is_dir() {
|
|
return Err(format!(
|
|
"'{dir}' is a directory, but recipe indicated a symlink. \n\
|
|
try removing '{dir}' if you haven't made any changes that would be lost",
|
|
dir=source_dir.display(),
|
|
));
|
|
}
|
|
let original = Path::new(same_as).join("source");
|
|
std::os::unix::fs::symlink(&original, &source_dir).map_err(|err| format!(
|
|
"failed to symlink '{}' to '{}': {}\n{:?}",
|
|
original.display(),
|
|
source_dir.display(),
|
|
err,
|
|
err
|
|
))?;
|
|
}
|
|
}
|
|
Some(SourceRecipe::Git { git, upstream, branch, rev }) => {
|
|
//TODO: use libgit?
|
|
if ! source_dir.is_dir() {
|
|
// Create source.tmp
|
|
let source_dir_tmp = recipe_dir.join("source.tmp");
|
|
create_dir_clean(&source_dir_tmp)?;
|
|
|
|
// Clone the repository to source.tmp
|
|
let mut command = Command::new("git");
|
|
command.arg("clone").arg("--recursive").arg(git);
|
|
if let Some(branch) = branch {
|
|
command.arg("--branch").arg(branch);
|
|
}
|
|
command.arg(&source_dir_tmp);
|
|
run_command(command)?;
|
|
|
|
// Move source.tmp to source atomically
|
|
rename(&source_dir_tmp, &source_dir)?;
|
|
} else {
|
|
// Don't let this code reset the origin for the cookbook repo
|
|
let source_git_dir = source_dir.join(".git");
|
|
if ! source_git_dir.is_dir() {
|
|
return Err(format!(
|
|
"'{}' is not a git repository, but recipe indicated git source",
|
|
source_dir.display(),
|
|
));
|
|
}
|
|
|
|
// Reset origin
|
|
let mut command = Command::new("git");
|
|
command.arg("-C").arg(&source_dir);
|
|
command.arg("remote").arg("set-url").arg("origin").arg(git);
|
|
run_command(command)?;
|
|
|
|
// Fetch origin
|
|
let mut command = Command::new("git");
|
|
command.arg("-C").arg(&source_dir);
|
|
command.arg("fetch").arg("origin");
|
|
run_command(command)?;
|
|
}
|
|
|
|
if let Some(_upstream) = upstream {
|
|
//TODO: set upstream URL
|
|
// git remote set-url upstream "$GIT_UPSTREAM" &> /dev/null ||
|
|
// git remote add upstream "$GIT_UPSTREAM"
|
|
// git fetch upstream
|
|
}
|
|
|
|
if let Some(rev) = rev {
|
|
// Check out specified revision
|
|
let mut command = Command::new("git");
|
|
command.arg("-C").arg(&source_dir);
|
|
command.arg("checkout").arg(rev);
|
|
run_command(command)?;
|
|
} else {
|
|
//TODO: complicated stuff to check and reset branch to origin
|
|
let mut command = Command::new("bash");
|
|
command.arg("-c").arg(r#"
|
|
ORIGIN_BRANCH="$(git branch --remotes | grep '^ origin/HEAD -> ' | cut -d ' ' -f 5-)"
|
|
if [ -n "$BRANCH" ]
|
|
then
|
|
ORIGIN_BRANCH="origin/$BRANCH"
|
|
fi
|
|
|
|
if [ "$(git rev-parse HEAD)" != "$(git rev-parse $ORIGIN_BRANCH)" ]
|
|
then
|
|
git checkout -B "$(echo "$ORIGIN_BRANCH" | cut -d / -f 2-)" "$ORIGIN_BRANCH"
|
|
fi"#);
|
|
if let Some(branch) = branch {
|
|
command.env("BRANCH", branch);
|
|
}
|
|
command.current_dir(&source_dir);
|
|
run_command(command)?;
|
|
}
|
|
|
|
// Sync submodules URL
|
|
let mut command = Command::new("git");
|
|
command.arg("-C").arg(&source_dir);
|
|
command.arg("submodule").arg("sync").arg("--recursive");
|
|
run_command(command)?;
|
|
|
|
// Update submodules
|
|
let mut command = Command::new("git");
|
|
command.arg("-C").arg(&source_dir);
|
|
command.arg("submodule").arg("update").arg("--init").arg("--recursive");
|
|
run_command(command)?;
|
|
},
|
|
Some(SourceRecipe::Tar { tar, blake3, patches, script }) => {
|
|
if ! source_dir.is_dir() {
|
|
// Download tar
|
|
//TODO: replace wget
|
|
let source_tar = recipe_dir.join("source.tar");
|
|
if ! source_tar.is_file() {
|
|
let source_tar_tmp = recipe_dir.join("source.tar.tmp");
|
|
|
|
let mut command = Command::new("wget");
|
|
command.arg(tar);
|
|
command.arg("--continue").arg("-O").arg(&source_tar_tmp);
|
|
run_command(command)?;
|
|
|
|
// Move source.tar.tmp to source.tar atomically
|
|
rename(&source_tar_tmp, &source_tar)?;
|
|
}
|
|
|
|
// Calculate blake3
|
|
let source_tar_blake3 = blake3_progress(&source_tar).map_err(|err| format!(
|
|
"failed to calculate blake3 of '{}': {}\n{:?}",
|
|
source_tar.display(),
|
|
err,
|
|
err
|
|
))?;
|
|
if let Some(blake3) = blake3 {
|
|
// Check if it matches recipe
|
|
if &source_tar_blake3 != blake3 {
|
|
return Err(format!(
|
|
"calculated blake3 '{}' does not match recipe blake3 '{}'",
|
|
source_tar_blake3,
|
|
blake3
|
|
));
|
|
}
|
|
} else {
|
|
//TODO: set blake3 hash on the recipe with something like "cook fix"
|
|
eprintln!(
|
|
"WARNING: set blake3 for '{}' to '{}'",
|
|
source_tar.display(),
|
|
source_tar_blake3
|
|
);
|
|
}
|
|
|
|
// Create source.tmp
|
|
let source_dir_tmp = recipe_dir.join("source.tmp");
|
|
create_dir_clean(&source_dir_tmp)?;
|
|
|
|
// Extract tar to source.tmp
|
|
//TODO: use tar crate (how to deal with compression?)
|
|
let mut command = Command::new("tar");
|
|
command.arg("--extract");
|
|
command.arg("--verbose");
|
|
command.arg("--file").arg(&source_tar);
|
|
command.arg("--directory").arg(&source_dir_tmp);
|
|
command.arg("--strip-components").arg("1");
|
|
run_command(command)?;
|
|
|
|
// Apply patches
|
|
for patch_name in patches {
|
|
let patch_file = recipe_dir.join(patch_name);
|
|
if ! patch_file.is_file() {
|
|
return Err(format!(
|
|
"failed to find patch file '{}'",
|
|
patch_file.display()
|
|
));
|
|
}
|
|
|
|
let patch = fs::read_to_string(&patch_file).map_err(|err| format!(
|
|
"failed to read patch file '{}': {}\n{:#?}",
|
|
patch_file.display(),
|
|
err,
|
|
err
|
|
))?;
|
|
|
|
let mut command = Command::new("patch");
|
|
command.arg("--directory").arg(&source_dir_tmp);
|
|
command.arg("--strip=1");
|
|
run_command_stdin(command, patch.as_bytes())?;
|
|
}
|
|
|
|
// Run source script
|
|
if let Some(script) = script {
|
|
let mut command = Command::new("bash");
|
|
command.arg("-ex");
|
|
command.current_dir(&source_dir_tmp);
|
|
run_command_stdin(command, script.as_bytes())?;
|
|
}
|
|
|
|
// Move source.tmp to source atomically
|
|
rename(&source_dir_tmp, &source_dir)?;
|
|
}
|
|
},
|
|
// Local Sources
|
|
None => {
|
|
if ! source_dir.is_dir() {
|
|
eprintln!(
|
|
"WARNING: Recipe without source section expected source dir at '{}'",
|
|
source_dir.display(),
|
|
);
|
|
create_dir(&source_dir)?;
|
|
}
|
|
},
|
|
}
|
|
|
|
Ok(source_dir)
|
|
}
|
|
|
|
fn build(recipe_dir: &Path, source_dir: &Path, target_dir: &Path, build: &BuildRecipe) -> Result<PathBuf, String> {
|
|
let mut dep_pkgars = vec![];
|
|
for dependency in build.dependencies.iter() {
|
|
//TODO: sanitize name
|
|
let dependency_dir = recipe_find(dependency, Path::new("recipes"))?;
|
|
if dependency_dir.is_none() {
|
|
return Err(format!(
|
|
"failed to find recipe directory '{}'",
|
|
dependency
|
|
));
|
|
}
|
|
dep_pkgars.push(dependency_dir.unwrap().join("target").join(redoxer::target()).join("stage.pkgar"));
|
|
}
|
|
|
|
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 sysroot_dir = target_dir.join("sysroot");
|
|
// Rebuild sysroot if source is newer
|
|
//TODO: rebuild on recipe changes
|
|
if sysroot_dir.is_dir() && (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)?;
|
|
}
|
|
if ! sysroot_dir.is_dir() {
|
|
// Create sysroot.tmp
|
|
let sysroot_dir_tmp = target_dir.join("sysroot.tmp");
|
|
create_dir_clean(&sysroot_dir_tmp)?;
|
|
|
|
// Make sure sysroot/include exists
|
|
create_dir(&sysroot_dir_tmp.join("include"))?;
|
|
// Make sure sysroot/lib exists
|
|
create_dir(&sysroot_dir_tmp.join("lib"))?;
|
|
|
|
for archive_path in dep_pkgars {
|
|
let public_path = "build/id_ed25519.pub.toml";
|
|
pkgar::extract(
|
|
public_path,
|
|
&archive_path,
|
|
sysroot_dir_tmp.to_str().unwrap()
|
|
).map_err(|err| format!(
|
|
"failed to install '{}' in '{}': {:?}",
|
|
archive_path.display(),
|
|
sysroot_dir_tmp.display(),
|
|
err
|
|
))?;
|
|
}
|
|
|
|
// Move sysroot.tmp to sysroot atomically
|
|
rename(&sysroot_dir_tmp, &sysroot_dir)?;
|
|
}
|
|
|
|
let stage_dir = target_dir.join("stage");
|
|
// Rebuild stage if source is newer
|
|
//TODO: rebuild on recipe changes
|
|
if stage_dir.is_dir() && (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)?;
|
|
}
|
|
|
|
if ! stage_dir.is_dir() {
|
|
// Create stage.tmp
|
|
let stage_dir_tmp = target_dir.join("stage.tmp");
|
|
create_dir_clean(&stage_dir_tmp)?;
|
|
|
|
// Create build, if it does not exist
|
|
//TODO: flag for clean builds where build is wiped out
|
|
let build_dir = target_dir.join("build");
|
|
if ! build_dir.is_dir() {
|
|
create_dir_clean(&build_dir)?;
|
|
}
|
|
|
|
let pre_script = r#"# Common pre script
|
|
# Add cookbook bins to path
|
|
export PATH="${COOKBOOK_ROOT}/bin:${PATH}"
|
|
|
|
# This puts cargo build artifacts in the build directory
|
|
export CARGO_TARGET_DIR="${COOKBOOK_BUILD}/target"
|
|
|
|
# This adds the sysroot includes for most C compilation
|
|
#TODO: check paths for spaces!
|
|
export CFLAGS="-I${COOKBOOK_SYSROOT}/include"
|
|
export CPPFLAGS="-I${COOKBOOK_SYSROOT}/include"
|
|
|
|
# This adds the sysroot libraries and compiles binaries statically for most C compilation
|
|
#TODO: check paths for spaces!
|
|
export LDFLAGS="-L${COOKBOOK_SYSROOT}/lib --static"
|
|
|
|
# These ensure that pkg-config gets the right flags from the sysroot
|
|
export PKG_CONFIG_ALLOW_CROSS=1
|
|
export PKG_CONFIG_PATH=
|
|
export PKG_CONFIG_LIBDIR="${COOKBOOK_SYSROOT}/lib/pkgconfig"
|
|
export PKG_CONFIG_SYSROOT_DIR="${COOKBOOK_SYSROOT}"
|
|
|
|
# To build the debug version of a Cargo program, add COOKBOOK_DEBUG=true, and
|
|
# to not strip symbols from the final package, add COOKBOOK_NOSTRIP=true to the recipe
|
|
# (or to your environment) before calling cookbook_cargo or cookbook_cargo_packages
|
|
build_type=release
|
|
install_flags=
|
|
build_flags=--release
|
|
if [ ! -z "${COOKBOOK_DEBUG}" ]
|
|
then
|
|
install_flags=--debug
|
|
build_flags=
|
|
build_type=debug
|
|
fi
|
|
|
|
# cargo template
|
|
COOKBOOK_CARGO="${COOKBOOK_REDOXER}"
|
|
function cookbook_cargo {
|
|
"${COOKBOOK_CARGO}" install \
|
|
--path "${COOKBOOK_SOURCE}/${PACKAGE_PATH}" \
|
|
--root "${COOKBOOK_STAGE}/usr" \
|
|
--locked \
|
|
--no-track \
|
|
${install_flags} \
|
|
"$@"
|
|
}
|
|
|
|
# helper for installing binaries that are cargo examples
|
|
function cookbook_cargo_examples {
|
|
recipe="$(basename "${COOKBOOK_RECIPE}")"
|
|
for example in "$@"
|
|
do
|
|
"${COOKBOOK_CARGO}" build \
|
|
--manifest-path "${COOKBOOK_SOURCE}/${PACKAGE_PATH}/Cargo.toml" \
|
|
--example "${example}" \
|
|
${build_flags}
|
|
mkdir -pv "${COOKBOOK_STAGE}/usr/bin"
|
|
cp -v \
|
|
"target/${TARGET}/${build_type}/examples/${example}" \
|
|
"${COOKBOOK_STAGE}/usr/bin/${recipe}_${example}"
|
|
done
|
|
}
|
|
|
|
# helper for installing binaries that are cargo packages
|
|
function cookbook_cargo_packages {
|
|
recipe="$(basename "${COOKBOOK_RECIPE}")"
|
|
for package in "$@"
|
|
do
|
|
"${COOKBOOK_CARGO}" build \
|
|
--manifest-path "${COOKBOOK_SOURCE}/${PACKAGE_PATH}/Cargo.toml" \
|
|
--package "${package}" \
|
|
${build_flags}
|
|
mkdir -pv "${COOKBOOK_STAGE}/usr/bin"
|
|
cp -v \
|
|
"target/${TARGET}/${build_type}/${package}" \
|
|
"${COOKBOOK_STAGE}/usr/bin/${recipe}_${package}"
|
|
done
|
|
}
|
|
|
|
# configure template
|
|
COOKBOOK_CONFIGURE="${COOKBOOK_SOURCE}/configure"
|
|
COOKBOOK_CONFIGURE_FLAGS=(
|
|
--host="${TARGET}"
|
|
--prefix=""
|
|
--disable-shared
|
|
--enable-static
|
|
)
|
|
COOKBOOK_MAKE="make"
|
|
COOKBOOK_MAKE_JOBS="$(nproc)"
|
|
function cookbook_configure {
|
|
"${COOKBOOK_CONFIGURE}" "${COOKBOOK_CONFIGURE_FLAGS[@]}"
|
|
"${COOKBOOK_MAKE}" -j "${COOKBOOK_MAKE_JOBS}"
|
|
"${COOKBOOK_MAKE}" install DESTDIR="${COOKBOOK_STAGE}"
|
|
}
|
|
"#;
|
|
|
|
let post_script = r#"# Common post script
|
|
# Strip binaries
|
|
if [ -d "${COOKBOOK_STAGE}/bin" ] && [ -z "${COOKBOOK_NOSTRIP}" ]
|
|
then
|
|
find "${COOKBOOK_STAGE}/bin" -type f -exec "${TARGET}-strip" -v {} ';'
|
|
fi
|
|
|
|
if [ -d "${COOKBOOK_STAGE}/usr/bin" ] && [ -z "${COOKBOOK_NOSTRIP}" ]
|
|
then
|
|
find "${COOKBOOK_STAGE}/usr/bin" -type f -exec "${TARGET}-strip" -v {} ';'
|
|
fi
|
|
|
|
# Remove libtool files
|
|
if [ -d "${COOKBOOK_STAGE}/lib" ]
|
|
then
|
|
find "${COOKBOOK_STAGE}/lib" -type f -name '*.la' -exec rm -fv {} ';'
|
|
fi
|
|
|
|
if [ -d "${COOKBOOK_STAGE}/usr/lib" ]
|
|
then
|
|
find "${COOKBOOK_STAGE}/usr/lib" -type f -name '*.la' -exec rm -fv {} ';'
|
|
fi
|
|
|
|
# Remove cargo install files
|
|
for file in .crates.toml .crates2.json
|
|
do
|
|
if [ -f "${COOKBOOK_STAGE}/${file}" ]
|
|
then
|
|
rm -v "${COOKBOOK_STAGE}/${file}"
|
|
fi
|
|
done
|
|
"#;
|
|
|
|
//TODO: better integration with redoxer (library instead of binary)
|
|
//TODO: configurable target
|
|
//TODO: Add more configurability, convert scripts to Rust?
|
|
let script = match &build.kind {
|
|
BuildKind::Cargo { package_path, cargoflags } => {
|
|
format!("PACKAGE_PATH={} cookbook_cargo {cargoflags}", package_path.as_deref().unwrap_or("."))
|
|
}
|
|
BuildKind::Configure => "cookbook_configure".to_owned(),
|
|
BuildKind::Custom { script } => script.clone(),
|
|
};
|
|
|
|
let command = {
|
|
//TODO: remove unwraps
|
|
let cookbook_build = build_dir.canonicalize().unwrap();
|
|
let cookbook_recipe = recipe_dir.canonicalize().unwrap();
|
|
let cookbook_redoxer = Path::new("target/release/cookbook_redoxer").canonicalize().unwrap();
|
|
let cookbook_root = Path::new(".").canonicalize().unwrap();
|
|
let cookbook_stage = stage_dir_tmp.canonicalize().unwrap();
|
|
let cookbook_source = source_dir.canonicalize().unwrap();
|
|
let cookbook_sysroot = sysroot_dir.canonicalize().unwrap();
|
|
|
|
let mut command = Command::new(&cookbook_redoxer);
|
|
command.arg("env");
|
|
command.arg("bash").arg("-ex");
|
|
command.current_dir(&cookbook_build);
|
|
command.env("COOKBOOK_BUILD", &cookbook_build);
|
|
command.env("COOKBOOK_RECIPE", &cookbook_recipe);
|
|
command.env("COOKBOOK_REDOXER", &cookbook_redoxer);
|
|
command.env("COOKBOOK_ROOT", &cookbook_root);
|
|
command.env("COOKBOOK_STAGE", &cookbook_stage);
|
|
command.env("COOKBOOK_SOURCE", &cookbook_source);
|
|
command.env("COOKBOOK_SYSROOT", &cookbook_sysroot);
|
|
command
|
|
};
|
|
|
|
let full_script = format!("{}\n{}\n{}", pre_script, script, post_script);
|
|
run_command_stdin(command, full_script.as_bytes())?;
|
|
|
|
// Move stage.tmp to stage atomically
|
|
rename(&stage_dir_tmp, &stage_dir)?;
|
|
}
|
|
|
|
Ok(stage_dir)
|
|
}
|
|
|
|
fn package(_recipe_dir: &Path, stage_dir: &Path, target_dir: &Path, _package: &PackageRecipe) -> Result<PathBuf, String> {
|
|
//TODO: metadata like dependencies, name, and version
|
|
|
|
let secret_path = "build/id_ed25519.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("build").is_dir() {
|
|
create_dir(Path::new("build"))?;
|
|
}
|
|
let (public_key, secret_key) = pkgar_keys::SecretKeyFile::new();
|
|
public_key.save(public_path).map_err(|err| format!(
|
|
"failed to save pkgar public key: {:?}",
|
|
err
|
|
))?;
|
|
secret_key.save(secret_path).map_err(|err| format!(
|
|
"failed to save pkgar secret key: {:?}",
|
|
err
|
|
))?;
|
|
}
|
|
|
|
let package_file = target_dir.join("stage.pkgar");
|
|
// 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 {
|
|
eprintln!("DEBUG: '{}' newer than '{}'", stage_dir.display(), package_file.display());
|
|
remove_all(&package_file)?;
|
|
}
|
|
}
|
|
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
|
|
))?;
|
|
}
|
|
|
|
Ok(package_file)
|
|
}
|
|
|
|
fn cook(recipe_dir: &Path, recipe: &Recipe, fetch_only: bool) -> Result<(), String> {
|
|
let source_dir = fetch(recipe_dir, &recipe.source).map_err(|err| format!(
|
|
"failed to fetch: {}",
|
|
err
|
|
))?;
|
|
|
|
if fetch_only { return Ok(()); }
|
|
|
|
let target_parent_dir = recipe_dir.join("target");
|
|
if ! target_parent_dir.is_dir() {
|
|
create_dir(&target_parent_dir)?;
|
|
}
|
|
let target_dir = target_parent_dir.join(redoxer::target());
|
|
if ! target_dir.is_dir() {
|
|
create_dir(&target_dir)?;
|
|
}
|
|
|
|
let stage_dir = build(recipe_dir, &source_dir, &target_dir, &recipe.build).map_err(|err| format!(
|
|
"failed to build: {}",
|
|
err
|
|
))?;
|
|
|
|
let _package_file = package(recipe_dir, &stage_dir, &target_dir, &recipe.package).map_err(|err| format!(
|
|
"failed to package: {}",
|
|
err
|
|
))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub struct CookRecipe {
|
|
name: String,
|
|
dir: PathBuf,
|
|
recipe: Recipe,
|
|
}
|
|
|
|
impl CookRecipe {
|
|
pub fn new(name: String) -> Result<Self, String> {
|
|
//TODO: sanitize recipe name?
|
|
let dir = recipe_find(&name, Path::new("recipes"))?;
|
|
if dir.is_none() {
|
|
return Err(format!(
|
|
"failed to find recipe directory '{}'",
|
|
name
|
|
));
|
|
}
|
|
let dir = dir.unwrap();
|
|
let file = dir.join("recipe.toml");
|
|
if ! file.is_file() {
|
|
return Err(format!(
|
|
"failed to find recipe file '{}'",
|
|
file.display()
|
|
));
|
|
}
|
|
|
|
let toml = fs::read_to_string(&file).map_err(|err| format!(
|
|
"failed to read recipe file '{}': {}\n{:#?}",
|
|
file.display(),
|
|
err,
|
|
err
|
|
))?;
|
|
|
|
let recipe: Recipe = toml::from_str(&toml).map_err(|err| format!(
|
|
"failed to parse recipe file '{}': {}\n{:#?}",
|
|
file.display(),
|
|
err,
|
|
err
|
|
))?;
|
|
|
|
Ok(Self {
|
|
name,
|
|
dir,
|
|
recipe
|
|
})
|
|
}
|
|
|
|
//TODO: make this more efficient, smarter, and not return duplicates
|
|
pub fn new_recursive(names: &[String], recursion: usize) -> Result<Vec<Self>, String> {
|
|
if recursion == 0 {
|
|
return Err(format!(
|
|
"recursion limit while processing build dependencies: {:#?}",
|
|
names
|
|
));
|
|
}
|
|
|
|
let mut recipes = Vec::new();
|
|
for name in names {
|
|
let recipe = Self::new(name.clone())?;
|
|
|
|
let dependencies = Self::new_recursive(
|
|
&recipe.recipe.build.dependencies,
|
|
recursion - 1
|
|
).map_err(|err| format!(
|
|
"{}: failed on loading build dependencies:\n{}",
|
|
name,
|
|
err
|
|
))?;
|
|
|
|
for dependency in dependencies {
|
|
recipes.push(dependency);
|
|
}
|
|
|
|
recipes.push(recipe);
|
|
}
|
|
|
|
Ok(recipes)
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let mut matching = true;
|
|
let mut dry_run = false;
|
|
let mut fetch_only = false;
|
|
let mut quiet = false;
|
|
let mut recipe_names = Vec::new();
|
|
for arg in env::args().skip(1) {
|
|
match arg.as_str() {
|
|
"--" if matching => matching = false,
|
|
"-d" | "--dry-run" if matching => dry_run = true,
|
|
"--fetch-only" if matching => fetch_only = true,
|
|
"-q" | "--quiet" if matching => quiet = true,
|
|
_ => recipe_names.push(arg),
|
|
}
|
|
}
|
|
|
|
let recipes = match CookRecipe::new_recursive(&recipe_names, 16) {
|
|
Ok(ok) => ok,
|
|
Err(err) => {
|
|
eprintln!(
|
|
"{}{}cook - error:{}{} {}",
|
|
style::Bold,
|
|
color::Fg(color::AnsiValue(196)),
|
|
color::Fg(color::Reset),
|
|
style::Reset,
|
|
err,
|
|
);
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
for recipe in recipes {
|
|
if ! quiet {
|
|
eprintln!(
|
|
"{}{}cook - {}{}{}",
|
|
style::Bold,
|
|
color::Fg(color::AnsiValue(215)),
|
|
recipe.name,
|
|
color::Fg(color::Reset),
|
|
style::Reset,
|
|
);
|
|
}
|
|
|
|
let res = if dry_run {
|
|
if ! quiet {
|
|
eprintln!("DRY RUN: {:#?}", recipe.recipe);
|
|
}
|
|
Ok(())
|
|
} else {
|
|
cook(&recipe.dir, &recipe.recipe, fetch_only)
|
|
};
|
|
|
|
match res {
|
|
Ok(()) => {
|
|
if ! quiet {
|
|
eprintln!(
|
|
"{}{}cook - {} - successful{}{}",
|
|
style::Bold,
|
|
color::Fg(color::AnsiValue(46)),
|
|
recipe.name,
|
|
color::Fg(color::Reset),
|
|
style::Reset,
|
|
);
|
|
}
|
|
},
|
|
Err(err) => {
|
|
eprintln!(
|
|
"{}{}cook - {} - error:{}{} {}",
|
|
style::Bold,
|
|
color::Fg(color::AnsiValue(196)),
|
|
recipe.name,
|
|
color::Fg(color::Reset),
|
|
style::Reset,
|
|
err,
|
|
);
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|