mirror of
https://gitlab.redox-os.org/redox-os/redox.git
synced 2026-06-20 20:04:19 +08:00
These shall only be added iff `PREFER_STATIC` is not set. Signed-off-by: Anhad Singh <andypython@protonmail.com>
1023 lines
32 KiB
Rust
1023 lines
32 KiB
Rust
use cookbook::blake3::blake3_progress;
|
|
use cookbook::recipe::{BuildKind, BuildRecipe, PackageRecipe, Recipe, SourceRecipe};
|
|
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 should_build_shared() -> bool {
|
|
use std::sync::OnceLock;
|
|
static YES: OnceLock<bool> = OnceLock::new();
|
|
*YES.get_or_init(|| env::var("COOKBOOK_PREFER_STATIC").expect("COOKBOOK_PREFER_STATIC").is_empty())
|
|
}
|
|
|
|
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 copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
|
fs::create_dir_all(&dst)?;
|
|
for entry in fs::read_dir(src)? {
|
|
let entry = entry?;
|
|
let ty = entry.file_type()?;
|
|
if ty.is_dir() {
|
|
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
|
} else {
|
|
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
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(())
|
|
}
|
|
|
|
static SHARED_PRESCRIPT: &str = r#"
|
|
function DYNAMIC_INIT {
|
|
COOKBOOK_AUTORECONF="autoreconf"
|
|
autotools_recursive_regenerate() {
|
|
for f in $(find . -name configure.ac -o -name configure.in -type f | sort); do
|
|
echo "* autotools regen in '$(dirname $f)'..."
|
|
( cd "$(dirname "$f")" && "${COOKBOOK_AUTORECONF}" -fvi "$@" -I${COOKBOOK_HOST_SYSROOT}/share/aclocal )
|
|
done
|
|
}
|
|
|
|
if [[ -n "$COOKBOOK_PREFER_STATIC" ]]; then
|
|
return
|
|
fi
|
|
|
|
echo "WARN: Program is being compiled dynamically."
|
|
|
|
COOKBOOK_CONFIGURE_FLAGS=(
|
|
--host="${GNU_TARGET}"
|
|
--prefix=""
|
|
--enable-shared
|
|
--disable-static
|
|
)
|
|
|
|
# TODO: check paths for spaces
|
|
export LDFLAGS="-L${COOKBOOK_SYSROOT}/lib"
|
|
export RUSTFLAGS="-C target-feature=-crt-static"
|
|
}
|
|
"#;
|
|
|
|
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::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?
|
|
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, format!("{SHARED_PRESCRIPT}\n{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,
|
|
name: &str,
|
|
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="${GNU_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}"
|
|
}
|
|
|
|
function cookbook_cmake {
|
|
cat > CMakeToolchain-x86_64.cmake <<EOF
|
|
set(CMAKE_SYSTEM_NAME UnixPaths)
|
|
set(CMAKE_FIND_ROOT_PATH ${COOKBOOK_SYSROOT})
|
|
set(CMAKE_C_COMPILER ${TARGET}-gcc)
|
|
set(CMAKE_CXX_COMPILER ${TARGET}-g++)
|
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
|
set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-Wl,-soname,")
|
|
set(CMAKE_PLATFORM_USES_PATH_WHEN_NO_SONAME 1)
|
|
EOF
|
|
|
|
cmake "${COOKBOOK_SOURCE}" \
|
|
-DCMAKE_TOOLCHAIN_FILE=./CMakeToolchain-x86_64.cmake
|
|
-DCMAKE_INSTALL_PREFIX="." \
|
|
-DCMAKE_INSTALL_LIBDIR=lib \
|
|
-DCMAKE_INSTALL_SBINDIR=bin \
|
|
-DCMAKE_INSTALL_INCLUDEDIR="include" \
|
|
-DCMAKE_INSTALL_OLDINCLUDEDIR="/include" \
|
|
-DCMAKE_BUILD_TYPE=Release \
|
|
-DBUILD_SHARED_LIBS=True \
|
|
-DENABLE_STATIC=False \
|
|
-GNinja \
|
|
-Wno-dev \
|
|
"${COOKBOOK_CMAKE_FLAGS[@]}"
|
|
|
|
ninja -j"${COOKBOOK_MAKE_JOBS}"
|
|
DESTDIR="${COOKBOOK_STAGE}" ninja install -j"${COOKBOOK_MAKE_JOBS}"
|
|
}
|
|
|
|
"#;
|
|
|
|
let post_script = r#"# Common post script
|
|
# Strip binaries
|
|
for dir in "${COOKBOOK_STAGE}/bin" "${COOKBOOK_STAGE}/usr/bin"
|
|
do
|
|
if [ -d "${dir}" ] && [ -z "${COOKBOOK_NOSTRIP}" ]
|
|
then
|
|
find "${dir}" -type f -exec "${GNU_TARGET}-strip" -v {} ';'
|
|
fi
|
|
done
|
|
|
|
# Remove libtool files
|
|
for dir in "${COOKBOOK_STAGE}/lib" "${COOKBOOK_STAGE}/usr/lib"
|
|
do
|
|
if [ -d "${dir}" ]
|
|
then
|
|
find "${dir}" -type f -name '*.la' -exec rm -fv {} ';'
|
|
fi
|
|
done
|
|
|
|
# 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
|
|
|
|
# Add pkgname to appstream metadata
|
|
for dir in "${COOKBOOK_STAGE}/share/metainfo" "${COOKBOOK_STAGE}/usr/share/metainfo"
|
|
do
|
|
if [ -d "${dir}" ]
|
|
then
|
|
find "${dir}" -type f -name '*.xml' -exec sed -i 's|</component>|<pkgname>'"${COOKBOOK_NAME}"'</pkgname></component>|g' {} ';'
|
|
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_NAME", name);
|
|
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{}\n{}", pre_script, SHARED_PRESCRIPT, 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,
|
|
name: &str,
|
|
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))?;
|
|
|
|
//TODO: share struct with pkgutils?
|
|
#[derive(serde::Serialize)]
|
|
struct StageToml {
|
|
name: String,
|
|
version: String,
|
|
target: String,
|
|
depends: Vec<String>,
|
|
}
|
|
let depends = if should_build_shared() {
|
|
package.dependencies.iter().chain(package.shared_deps.iter()).cloned().collect()
|
|
} else {
|
|
package.dependencies.clone()
|
|
};
|
|
let stage_toml = toml::to_string(&StageToml {
|
|
name: name.into(),
|
|
version: "TODO".into(),
|
|
target: env::var("TARGET")
|
|
.map_err(|err| format!("failed to read TARGET: {:?}", err))?,
|
|
depends
|
|
})
|
|
.map_err(|err| format!("failed to serialize stage.toml: {:?}", err))?;
|
|
fs::write(target_dir.join("stage.toml"), stage_toml)
|
|
.map_err(|err| format!("failed to write stage.toml: {:?}", err))?;
|
|
}
|
|
|
|
Ok(package_file)
|
|
}
|
|
|
|
fn cook(recipe_dir: &Path, name: &str, 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, name, &recipe.build)
|
|
.map_err(|err| format!("failed to build: {}", err))?;
|
|
|
|
let _package_file = package(recipe_dir, &stage_dir, &target_dir, name, &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.name, &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);
|
|
}
|
|
}
|
|
}
|
|
}
|