diff --git a/src/bin/cook.rs b/src/bin/cook.rs index 8f33c966..38d126c9 100644 --- a/src/bin/cook.rs +++ b/src/bin/cook.rs @@ -1,733 +1,18 @@ +use std::collections::BTreeSet; +use std::path::Path; +use std::{env, process}; + +use cookbook::WALK_DEPTH; +use cookbook::cook::fetch::{fetch, fetch_offline}; +use cookbook::cook::fs::create_target_dir; +use cookbook::cook::package::{package, package_toml}; +use cookbook::recipe::{BuildKind, CookRecipe, Recipe}; +use pkg::PackageName; + use cookbook::config::init_config; -use cookbook::cook::build::build_remote; -use cookbook::cook::fetch::*; -use cookbook::cook::fs::*; -use cookbook::cook::script::SHARED_PRESCRIPT; -use cookbook::recipe::{AutoDeps, BuildKind, CookRecipe, Recipe}; -use pkg::package::Package; -use pkg::{PackageName, recipes}; -use std::collections::VecDeque; -use std::convert::TryInto; -use std::{ - collections::BTreeSet, - env, fs, - path::{Path, PathBuf}, - process::{self, Command}, - str, - time::SystemTime, -}; +use cookbook::cook::cook_build::build; use termion::{color, style}; -use cookbook::{WALK_DEPTH, is_redox}; - -fn auto_deps( - stage_dir: &Path, - dep_pkgars: &BTreeSet<(PackageName, PathBuf)>, -) -> BTreeSet { - let mut paths = BTreeSet::new(); - let mut visited = BTreeSet::new(); - // 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"), - ]); - - // Recursively (DFS) walk each directory to ensure nested libs and bins are checked. - while let Some(dir) = walk.pop_front() { - let Ok(dir) = dir.canonicalize() else { - continue; - }; - if visited.contains(&dir) { - #[cfg(debug_assertions)] - eprintln!("DEBUG: auto_deps => Skipping `{dir:?}` (already visited)"); - continue; - } - assert!( - visited.insert(dir.clone()), - "Directory `{:?}` should not be in visited\nVisited: {:#?}", - dir, - visited - ); - - let Ok(read_dir) = fs::read_dir(&dir) else { - continue; - }; - for entry_res in read_dir { - let Ok(entry) = entry_res else { continue }; - let Ok(file_type) = entry.file_type() else { - continue; - }; - if file_type.is_file() { - paths.insert(entry.path()); - } else if file_type.is_dir() { - walk.push_front(entry.path()); - } - } - } - - let mut needed = BTreeSet::new(); - for path in paths { - let Ok(file) = fs::File::open(&path) else { - continue; - }; - let read_cache = object::ReadCache::new(file); - let Ok(object) = object::build::elf::Builder::read(&read_cache) else { - continue; - }; - let Some(dynamic_data) = object.dynamic_data() else { - continue; - }; - for dynamic in dynamic_data { - let object::build::elf::Dynamic::String { tag, val } = dynamic else { - continue; - }; - if *tag == object::elf::DT_NEEDED { - let Ok(name) = str::from_utf8(val) else { - continue; - }; - if let Ok(relative_path) = path.strip_prefix(stage_dir) { - eprintln!("DEBUG: {} needs {}", relative_path.display(), name); - } - needed.insert(name.to_string()); - } - } - } - - let mut missing = needed.clone(); - // relibc and friends will always be installed - for preinstalled in &["libc.so.6", "libgcc_s.so.1", "libstdc++.so.6"] { - missing.remove(*preinstalled); - } - - let mut deps = BTreeSet::new(); - if let Ok(key_file) = pkgar_keys::PublicKeyFile::open("build/id_ed25519.pub.toml") { - for (dep, archive_path) in dep_pkgars.iter() { - let Ok(mut package) = pkgar::PackageFile::new(archive_path, &key_file.pkey) else { - continue; - }; - let Ok(entries) = pkgar_core::PackageSrc::read_entries(&mut package) else { - continue; - }; - for entry in entries { - let Ok(entry_path) = pkgar::ext::EntryExt::check_path(&entry) else { - continue; - }; - for prefix in &["lib", "usr/lib"] { - let Ok(child_path) = entry_path.strip_prefix(prefix) else { - continue; - }; - let Some(child_name) = child_path.to_str() else { - continue; - }; - if needed.contains(child_name) { - eprintln!("DEBUG: {} provides {}", dep, child_name); - deps.insert(dep.clone()); - missing.remove(child_name); - } - } - } - } - } - - for name in missing { - eprintln!("WARN: {} missing", name); - } - - deps -} - -fn build( - recipe_dir: &Path, - source_dir: &Path, - target_dir: &Path, - name: &PackageName, - recipe: &Recipe, - offline_mode: bool, - check_source: bool, -) -> Result<(PathBuf, BTreeSet), String> { - let sysroot_dir = target_dir.join("sysroot"); - let stage_dir = target_dir.join("stage"); - - let mut dep_pkgars = BTreeSet::new(); - for dependency in recipe.build.dependencies.iter() { - let dependency_dir = recipes::find(dependency.as_str()); - if dependency_dir.is_none() { - return Err(format!("failed to find recipe directory '{}'", dependency)); - } - dep_pkgars.insert(( - dependency.clone(), - dependency_dir - .unwrap() - .join("target") - .join(redoxer::target()) - .join("stage.pkgar"), - )); - } - - if stage_dir.exists() && !check_source { - let auto_deps = build_auto_deps(target_dir, &stage_dir, dep_pkgars)?; - return Ok((stage_dir, auto_deps)); - } - - let source_modified = modified_dir_ignore_git(source_dir)?; - let deps_modified = dep_pkgars - .iter() - .map(|(_dep, pkgar)| modified(pkgar)) - .max() - .unwrap_or(Ok(SystemTime::UNIX_EPOCH))?; - - // Rebuild sysroot if source is newer - //TODO: rebuild on recipe changes - if sysroot_dir.is_dir() { - let sysroot_modified = modified_dir(&sysroot_dir)?; - if sysroot_modified < source_modified || sysroot_modified < 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/usr exists - create_dir(&sysroot_dir_tmp.join("usr"))?; - for folder in &["bin", "include", "lib", "share"] { - // Make sure sysroot/usr/$folder exists - create_dir(&sysroot_dir_tmp.join("usr").join(folder))?; - - // Link sysroot/$folder sysroot/usr/$folder - symlink(Path::new("usr").join(folder), &sysroot_dir_tmp.join(folder))?; - } - - for (_dep, 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)?; - } - - // 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_modified < source_modified || stage_modified < 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 -if [ -z "${IS_REDOX}" ] -then -export PATH="${COOKBOOK_ROOT}/bin:${PATH}" -fi - -# 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 -offline_flags= -if [ ! -z "${COOKBOOK_DEBUG}" ] -then - install_flags=--debug - build_flags= - build_type=debug - export CFLAGS="${CFLAGS} -g" - export CPPFLAGS="${CPPFLAGS} -g" -fi - -if [ ! -z "${COOKBOOK_OFFLINE}" ] -then -offline_flags=--offline -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} \ - ${offline_flags} \ - -j "${COOKBOOK_MAKE_JOBS}" "$@" -} - -# 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} ${offline_flags} -j "${COOKBOOK_MAKE_JOBS}" - 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} ${offline_flags} -j "${COOKBOOK_MAKE_JOBS}" - 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="/usr" - --disable-shared - --enable-static -) -COOKBOOK_MAKE="make" - -if [ -z "${COOKBOOK_MAKE_JOBS}" ] -then -if [ -z "${IS_REDOX}" ] -then -COOKBOOK_MAKE_JOBS="$(nproc)" -else -COOKBOOK_MAKE_JOBS="1" -fi -fi - -function cookbook_configure { - "${COOKBOOK_CONFIGURE}" "${COOKBOOK_CONFIGURE_FLAGS[@]}" "$@" - "${COOKBOOK_MAKE}" -j "${COOKBOOK_MAKE_JOBS}" - "${COOKBOOK_MAKE}" install DESTDIR="${COOKBOOK_STAGE}" -} - -COOKBOOK_CMAKE="cmake" -COOKBOOK_NINJA="ninja" -COOKBOOK_CMAKE_FLAGS=( - -DBUILD_SHARED_LIBS=False - -DENABLE_SHARED=False - -DENABLE_STATIC=True -) -function cookbook_cmake { - cat > cross_file.cmake <> cross_file.cmake - echo "set(CMAKE_CXX_COMPILER_LAUNCHER ${CC_WRAPPER})" >> cross_file.cmake - fi - - "${COOKBOOK_CMAKE}" "${COOKBOOK_SOURCE}" \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CROSSCOMPILING=True \ - -DCMAKE_INSTALL_INCLUDEDIR=include \ - -DCMAKE_INSTALL_LIBDIR=lib \ - -DCMAKE_INSTALL_OLDINCLUDEDIR=/include \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DCMAKE_INSTALL_SBINDIR=bin \ - -DCMAKE_TOOLCHAIN_FILE=cross_file.cmake \ - -GNinja \ - -Wno-dev \ - "${COOKBOOK_CMAKE_FLAGS[@]}" \ - "$@" - - "${COOKBOOK_NINJA}" -j"${COOKBOOK_MAKE_JOBS}" - DESTDIR="${COOKBOOK_STAGE}" "${COOKBOOK_NINJA}" install -j"${COOKBOOK_MAKE_JOBS}" -} - -COOKBOOK_MESON="meson" -COOKBOOK_MESON_FLAGS=( - --buildtype release - --wrap-mode nofallback - --strip - -Ddefault_library=static - -Dprefix=/usr -) -function cookbook_meson { - echo "[binaries]" > cross_file.txt - echo "c = [$(printf "'%s', " $CC | sed 's/, $//')]" >> cross_file.txt - echo "cpp = [$(printf "'%s', " $CXX | sed 's/, $//')]" >> cross_file.txt - echo "ar = '${AR}'" >> cross_file.txt - echo "strip = '${STRIP}'" >> cross_file.txt - echo "pkg-config = '${PKG_CONFIG}'" >> cross_file.txt - echo "llvm-config = '${TARGET}-llvm-config'" >> cross_file.txt - echo "glib-compile-resources = 'glib-compile-resources'" >> cross_file.txt - echo "glib-compile-schemas = 'glib-compile-schemas'" >> cross_file.txt - - echo "[host_machine]" >> cross_file.txt - echo "system = 'redox'" >> cross_file.txt - echo "cpu_family = '$(echo "${TARGET}" | cut -d - -f1)'" >> cross_file.txt - echo "cpu = '$(echo "${TARGET}" | cut -d - -f1)'" >> cross_file.txt - echo "endian = 'little'" >> cross_file.txt - - echo "[paths]" >> cross_file.txt - echo "prefix = '/usr'" >> cross_file.txt - echo "libdir = 'lib'" >> cross_file.txt - echo "bindir = 'bin'" >> cross_file.txt - - echo "[properties]" >> cross_file.txt - echo "needs_exe_wrapper = true" >> cross_file.txt - echo "sys_root = '${COOKBOOK_SYSROOT}'" >> cross_file.txt - echo "c_args = [$(printf "'%s', " $CFLAGS | sed 's/, $//')]" >> cross_file.txt - echo "cpp_args = [$(printf "'%s', " $CPPFLAGS | sed 's/, $//')]" >> cross_file.txt - echo "c_link_args = [$(printf "'%s', " $LDFLAGS | sed 's/, $//')]" >> cross_file.txt - - unset AR - unset AS - unset CC - unset CXX - unset LD - unset NM - unset OBJCOPY - unset OBJDUMP - unset PKG_CONFIG - unset RANLIB - unset READELF - unset STRIP - - "${COOKBOOK_MESON}" setup \ - "${COOKBOOK_SOURCE}" \ - . \ - --cross-file cross_file.txt \ - "${COOKBOOK_MESON_FLAGS[@]}" \ - "$@" - "${COOKBOOK_NINJA}" -j"${COOKBOOK_MAKE_JOBS}" - DESTDIR="${COOKBOOK_STAGE}" "${COOKBOOK_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||'"${COOKBOOK_NAME}"'|g' {} ';' - fi -done -"#; - - let flags_fn = |name, flags: &Vec| { - format!( - "{name}+=(\n{}\n)\n", - flags - .iter() - .map(|s| format!(" \"{s}\"")) - .collect::>() - .join("\n") - ) - }; - - //TODO: better integration with redoxer (library instead of binary) - //TODO: configurable target - //TODO: Add more configurability, convert scripts to Rust? - let script = match &recipe.build.kind { - BuildKind::Cargo { - package_path, - cargoflags, - } => { - format!( - "PACKAGE_PATH={} cookbook_cargo {cargoflags}", - package_path.as_deref().unwrap_or(".") - ) - } - BuildKind::Configure { configureflags } => format!( - "DYNAMIC_INIT\n{}cookbook_configure", - flags_fn("COOKBOOK_CONFIGURE_FLAGS", configureflags), - ), - BuildKind::Cmake { cmakeflags } => format!( - "DYNAMIC_INIT\n{}cookbook_cmake", - flags_fn("COOKBOOK_CMAKE_FLAGS", cmakeflags), - ), - BuildKind::Meson { mesonflags } => format!( - "DYNAMIC_INIT\n{}cookbook_meson", - flags_fn("COOKBOOK_MESON_FLAGS", mesonflags), - ), - BuildKind::Custom { script } => script.clone(), - BuildKind::Remote => return build_remote(target_dir, name, offline_mode), - BuildKind::None => "".to_owned(), - }; - - let command = { - //TODO: remove unwraps - let cookbook_build = build_dir.canonicalize().unwrap(); - let cookbook_recipe = recipe_dir.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 = if is_redox() { - let mut command = Command::new("bash"); - command.arg("-ex"); - command.env("COOKBOOK_REDOXER", "cargo"); - command - } else { - let cookbook_redoxer = Path::new("target/release/cookbook_redoxer") - .canonicalize() - .unwrap(); - let mut command = Command::new(&cookbook_redoxer); - command.arg("env").arg("bash").arg("-ex"); - command.env("COOKBOOK_REDOXER", &cookbook_redoxer); - command - }; - command.current_dir(&cookbook_build); - command.env("COOKBOOK_BUILD", &cookbook_build); - command.env("COOKBOOK_NAME", name.as_str()); - command.env("COOKBOOK_RECIPE", &cookbook_recipe); - command.env("COOKBOOK_ROOT", &cookbook_root); - command.env("COOKBOOK_STAGE", &cookbook_stage); - command.env("COOKBOOK_SOURCE", &cookbook_source); - command.env("COOKBOOK_SYSROOT", &cookbook_sysroot); - if offline_mode { - command.env("COOKBOOK_OFFLINE", "1"); - } - 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)?; - } - - let auto_deps = build_auto_deps(target_dir, &stage_dir, dep_pkgars)?; - - Ok((stage_dir, auto_deps)) -} - -/// Calculate automatic dependencies -fn build_auto_deps( - target_dir: &Path, - stage_dir: &PathBuf, - dep_pkgars: BTreeSet<(PackageName, PathBuf)>, -) -> Result, String> { - let auto_deps_path = target_dir.join("auto_deps.toml"); - if auto_deps_path.is_file() && modified(&auto_deps_path)? < modified(stage_dir)? { - remove_all(&auto_deps_path)? - } - - let auto_deps = if auto_deps_path.exists() { - let toml_content = - fs::read_to_string(&auto_deps_path).map_err(|_| "failed to read cached auto_deps")?; - let wrapper: AutoDeps = - toml::from_str(&toml_content).map_err(|_| "failed to deserialize cached auto_deps")?; - wrapper.packages - } else { - let packages = auto_deps(stage_dir, &dep_pkgars); - let wrapper = AutoDeps { packages }; - serialize_and_write(&auto_deps_path, &wrapper)?; - wrapper.packages - }; - Ok(auto_deps) -} - -fn package( - stage_dir: &Path, - target_dir: &Path, - name: &PackageName, - recipe: &Recipe, - auto_deps: &BTreeSet, -) -> Result { - 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))?; - - package_toml(target_dir, name, recipe, auto_deps)?; - } - - Ok(package_file) -} - -fn package_toml( - target_dir: &Path, - name: &PackageName, - recipe: &Recipe, - auto_deps: &BTreeSet, -) -> Result<(), String> { - let mut depends = recipe.package.dependencies.clone(); - for dep in auto_deps.iter() { - if !depends.contains(dep) { - depends.push(dep.clone()); - } - } - let package = Package { - name: name.clone(), - version: package_version(recipe), - target: env::var("TARGET").map_err(|err| format!("failed to read TARGET: {:?}", err))?, - depends, - }; - - serialize_and_write(&target_dir.join("stage.toml"), &package)?; - - return Ok(()); -} - -fn package_version(recipe: &Recipe) -> String { - if recipe.build.kind == BuildKind::None { - "".into() - } else if let Some(v) = &recipe.package.version { - v.to_string() - } else if let Some(r) = &recipe.source { - if let Some(m) = r.guess_version() { - m - } else { - "TODO".into() - } - } else { - "TODO".into() - } -} - fn cook_meta( recipe_dir: &Path, name: &PackageName, @@ -757,7 +42,6 @@ fn cook( if recipe.build.kind == BuildKind::None { return cook_meta(recipe_dir, name, recipe, fetch_only); } - let source_dir = match is_offline { true => fetch_offline(recipe_dir, &recipe.source), false => fetch(recipe_dir, &recipe.source), @@ -787,18 +71,6 @@ fn cook( Ok(()) } -fn create_target_dir(recipe_dir: &Path) -> Result { - 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)?; - } - Ok(target_dir) -} - fn main() { init_config(); let mut matching = true; @@ -912,32 +184,3 @@ fn main() { } } } - -#[cfg(test)] -mod tests { - use std::os::unix; - - use super::auto_deps; - - #[test] - fn file_system_loop_no_infinite_loop() { - // Hierarchy with an infinite loop - let temp = tempfile::tempdir().unwrap(); - let root = temp.path(); - let dir = root.join("loop"); - unix::fs::symlink(root, &dir).expect("Linking {dir:?} to {root:?}"); - - // Sanity check that we have a loop - assert_eq!( - root.canonicalize().unwrap(), - dir.canonicalize().unwrap(), - "Expected a loop where {dir:?} points to {root:?}" - ); - - let entries = auto_deps(root, &Default::default()); - assert!( - entries.is_empty(), - "auto_deps shouldn't have yielded any libraries" - ); - } -} diff --git a/src/cook.rs b/src/cook.rs index d538dbc3..4ffa8a7b 100644 --- a/src/cook.rs +++ b/src/cook.rs @@ -1,6 +1,6 @@ +// avoid confusion with build.rs +pub mod cook_build; pub mod fetch; pub mod fs; +pub mod package; pub mod script; -//TODO: Move rest of cook functions here in the next refactor -pub mod build; -//pub mod package; diff --git a/src/cook/build.rs b/src/cook/build.rs deleted file mode 100644 index cda47fd6..00000000 --- a/src/cook/build.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::{ - collections::BTreeSet, - fs, - path::{Path, PathBuf}, -}; - -use pkg::{Package, PackageName}; -use redoxer::target; - -use crate::{REMOTE_PKG_SOURCE, cook::fs::*, recipe::AutoDeps}; - -fn get_remote_url(name: &PackageName, ext: &str) -> String { - return format!("{}/{}/{}.{}", REMOTE_PKG_SOURCE, target(), name, ext); -} -fn get_pubkey_url() -> String { - return format!("{}/id_ed25519.pub.toml", REMOTE_PKG_SOURCE); -} - -pub fn build_remote( - target_dir: &Path, - name: &PackageName, - offline_mode: bool, -) -> Result<(PathBuf, BTreeSet), 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"); - let source_toml = target_dir.join("source.toml"); - let source_pubkey = target_dir.join("id_ed25519.pub.toml"); - - if !offline_mode { - download_wget(&get_remote_url(name, "pkgar"), &source_pkgar)?; - download_wget(&get_remote_url(name, "toml"), &source_toml)?; - download_wget(&get_pubkey_url(), &source_pubkey)?; - } else { - offline_check_exists(&source_pkgar)?; - offline_check_exists(&source_toml)?; - offline_check_exists(&source_pubkey)?; - } - - 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"); - - 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)? { - remove_all(&auto_deps_path)? - } - - let auto_deps = if auto_deps_path.exists() { - let toml_content = - fs::read_to_string(&auto_deps_path).map_err(|_| "failed to read cached auto_deps")?; - let wrapper: AutoDeps = - toml::from_str(&toml_content).map_err(|_| "failed to deserialize cached auto_deps")?; - wrapper.packages - } else { - let toml_content = - fs::read_to_string(&source_toml).map_err(|_| "failed to read source.toml")?; - let pkg_toml: Package = - toml::from_str(&toml_content).map_err(|_| "failed to deserialize source.toml")?; - let wrapper = AutoDeps { - packages: pkg_toml.depends.into_iter().collect(), - }; - serialize_and_write(&auto_deps_path, &wrapper)?; - wrapper.packages - }; - - Ok((stage_dir, auto_deps)) -} diff --git a/src/cook/cook_build.rs b/src/cook/cook_build.rs new file mode 100644 index 00000000..bdf7ab88 --- /dev/null +++ b/src/cook/cook_build.rs @@ -0,0 +1,478 @@ +use pkg::recipes; +use pkg::{Package, PackageName}; +use redoxer::target; + +use crate::cook::fs::*; +use crate::cook::script::*; +use crate::recipe::AutoDeps; +use crate::recipe::BuildKind; +use crate::recipe::Recipe; +use std::collections::VecDeque; +use std::{ + collections::BTreeSet, + fs, + path::{Path, PathBuf}, + process::Command, + str, + time::SystemTime, +}; + +use crate::is_redox; + +use crate::REMOTE_PKG_SOURCE; + +fn auto_deps( + stage_dir: &Path, + dep_pkgars: &BTreeSet<(PackageName, PathBuf)>, +) -> BTreeSet { + let mut paths = BTreeSet::new(); + let mut visited = BTreeSet::new(); + // 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"), + ]); + + // Recursively (DFS) walk each directory to ensure nested libs and bins are checked. + while let Some(dir) = walk.pop_front() { + let Ok(dir) = dir.canonicalize() else { + continue; + }; + if visited.contains(&dir) { + #[cfg(debug_assertions)] + eprintln!("DEBUG: auto_deps => Skipping `{dir:?}` (already visited)"); + continue; + } + assert!( + visited.insert(dir.clone()), + "Directory `{:?}` should not be in visited\nVisited: {:#?}", + dir, + visited + ); + + let Ok(read_dir) = fs::read_dir(&dir) else { + continue; + }; + for entry_res in read_dir { + let Ok(entry) = entry_res else { continue }; + let Ok(file_type) = entry.file_type() else { + continue; + }; + if file_type.is_file() { + paths.insert(entry.path()); + } else if file_type.is_dir() { + walk.push_front(entry.path()); + } + } + } + + let mut needed = BTreeSet::new(); + for path in paths { + let Ok(file) = fs::File::open(&path) else { + continue; + }; + let read_cache = object::ReadCache::new(file); + let Ok(object) = object::build::elf::Builder::read(&read_cache) else { + continue; + }; + let Some(dynamic_data) = object.dynamic_data() else { + continue; + }; + for dynamic in dynamic_data { + let object::build::elf::Dynamic::String { tag, val } = dynamic else { + continue; + }; + if *tag == object::elf::DT_NEEDED { + let Ok(name) = str::from_utf8(val) else { + continue; + }; + if let Ok(relative_path) = path.strip_prefix(stage_dir) { + eprintln!("DEBUG: {} needs {}", relative_path.display(), name); + } + needed.insert(name.to_string()); + } + } + } + + let mut missing = needed.clone(); + // relibc and friends will always be installed + for preinstalled in &["libc.so.6", "libgcc_s.so.1", "libstdc++.so.6"] { + missing.remove(*preinstalled); + } + + let mut deps = BTreeSet::new(); + if let Ok(key_file) = pkgar_keys::PublicKeyFile::open("build/id_ed25519.pub.toml") { + for (dep, archive_path) in dep_pkgars.iter() { + let Ok(mut package) = pkgar::PackageFile::new(archive_path, &key_file.pkey) else { + continue; + }; + let Ok(entries) = pkgar_core::PackageSrc::read_entries(&mut package) else { + continue; + }; + for entry in entries { + let Ok(entry_path) = pkgar::ext::EntryExt::check_path(&entry) else { + continue; + }; + for prefix in &["lib", "usr/lib"] { + let Ok(child_path) = entry_path.strip_prefix(prefix) else { + continue; + }; + let Some(child_name) = child_path.to_str() else { + continue; + }; + if needed.contains(child_name) { + eprintln!("DEBUG: {} provides {}", dep, child_name); + deps.insert(dep.clone()); + missing.remove(child_name); + } + } + } + } + } + + for name in missing { + eprintln!("WARN: {} missing", name); + } + + deps +} + +pub fn build( + recipe_dir: &Path, + source_dir: &Path, + target_dir: &Path, + name: &PackageName, + recipe: &Recipe, + offline_mode: bool, + check_source: bool, +) -> Result<(PathBuf, BTreeSet), String> { + let sysroot_dir = target_dir.join("sysroot"); + let stage_dir = target_dir.join("stage"); + + let mut dep_pkgars = BTreeSet::new(); + for dependency in recipe.build.dependencies.iter() { + let dependency_dir = recipes::find(dependency.as_str()); + if dependency_dir.is_none() { + return Err(format!("failed to find recipe directory '{}'", dependency)); + } + dep_pkgars.insert(( + dependency.clone(), + dependency_dir + .unwrap() + .join("target") + .join(redoxer::target()) + .join("stage.pkgar"), + )); + } + + if stage_dir.exists() && !check_source { + let auto_deps = build_auto_deps(target_dir, &stage_dir, dep_pkgars)?; + return Ok((stage_dir, auto_deps)); + } + + let source_modified = modified_dir_ignore_git(source_dir)?; + let deps_modified = dep_pkgars + .iter() + .map(|(_dep, pkgar)| modified(pkgar)) + .max() + .unwrap_or(Ok(SystemTime::UNIX_EPOCH))?; + + // Rebuild sysroot if source is newer + //TODO: rebuild on recipe changes + if sysroot_dir.is_dir() { + let sysroot_modified = modified_dir(&sysroot_dir)?; + if sysroot_modified < source_modified || sysroot_modified < 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/usr exists + create_dir(&sysroot_dir_tmp.join("usr"))?; + for folder in &["bin", "include", "lib", "share"] { + // Make sure sysroot/usr/$folder exists + create_dir(&sysroot_dir_tmp.join("usr").join(folder))?; + + // Link sysroot/$folder sysroot/usr/$folder + symlink(Path::new("usr").join(folder), &sysroot_dir_tmp.join(folder))?; + } + + for (_dep, 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)?; + } + + // 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_modified < source_modified || stage_modified < 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 flags_fn = |name, flags: &Vec| { + format!( + "{name}+=(\n{}\n)\n", + flags + .iter() + .map(|s| format!(" \"{s}\"")) + .collect::>() + .join("\n") + ) + }; + + //TODO: better integration with redoxer (library instead of binary) + //TODO: configurable target + //TODO: Add more configurability, convert scripts to Rust? + let script = match &recipe.build.kind { + BuildKind::Cargo { + package_path, + cargoflags, + } => { + format!( + "PACKAGE_PATH={} cookbook_cargo {cargoflags}", + package_path.as_deref().unwrap_or(".") + ) + } + BuildKind::Configure { configureflags } => format!( + "DYNAMIC_INIT\n{}cookbook_configure", + flags_fn("COOKBOOK_CONFIGURE_FLAGS", configureflags), + ), + BuildKind::Cmake { cmakeflags } => format!( + "DYNAMIC_INIT\n{}cookbook_cmake", + flags_fn("COOKBOOK_CMAKE_FLAGS", cmakeflags), + ), + BuildKind::Meson { mesonflags } => format!( + "DYNAMIC_INIT\n{}cookbook_meson", + flags_fn("COOKBOOK_MESON_FLAGS", mesonflags), + ), + BuildKind::Custom { script } => script.clone(), + BuildKind::Remote => return build_remote(target_dir, name, offline_mode), + BuildKind::None => "".to_owned(), + }; + + let command = { + //TODO: remove unwraps + let cookbook_build = build_dir.canonicalize().unwrap(); + let cookbook_recipe = recipe_dir.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 = if is_redox() { + let mut command = Command::new("bash"); + command.arg("-ex"); + command.env("COOKBOOK_REDOXER", "cargo"); + command + } else { + let cookbook_redoxer = Path::new("target/release/cookbook_redoxer") + .canonicalize() + .unwrap(); + let mut command = Command::new(&cookbook_redoxer); + command.arg("env").arg("bash").arg("-ex"); + command.env("COOKBOOK_REDOXER", &cookbook_redoxer); + command + }; + command.current_dir(&cookbook_build); + command.env("COOKBOOK_BUILD", &cookbook_build); + command.env("COOKBOOK_NAME", name.as_str()); + command.env("COOKBOOK_RECIPE", &cookbook_recipe); + command.env("COOKBOOK_ROOT", &cookbook_root); + command.env("COOKBOOK_STAGE", &cookbook_stage); + command.env("COOKBOOK_SOURCE", &cookbook_source); + command.env("COOKBOOK_SYSROOT", &cookbook_sysroot); + if offline_mode { + command.env("COOKBOOK_OFFLINE", "1"); + } + command + }; + + let full_script = format!( + "{}\n{}\n{}\n{}", + BUILD_PRESCRIPT, SHARED_PRESCRIPT, script, BUILD_POSTSCRIPT + ); + run_command_stdin(command, full_script.as_bytes())?; + + // Move stage.tmp to stage atomically + rename(&stage_dir_tmp, &stage_dir)?; + } + + let auto_deps = build_auto_deps(target_dir, &stage_dir, dep_pkgars)?; + + Ok((stage_dir, auto_deps)) +} + +/// Calculate automatic dependencies +fn build_auto_deps( + target_dir: &Path, + stage_dir: &PathBuf, + dep_pkgars: BTreeSet<(PackageName, PathBuf)>, +) -> Result, String> { + let auto_deps_path = target_dir.join("auto_deps.toml"); + if auto_deps_path.is_file() && modified(&auto_deps_path)? < modified(stage_dir)? { + remove_all(&auto_deps_path)? + } + + let auto_deps = if auto_deps_path.exists() { + let toml_content = + fs::read_to_string(&auto_deps_path).map_err(|_| "failed to read cached auto_deps")?; + let wrapper: AutoDeps = + toml::from_str(&toml_content).map_err(|_| "failed to deserialize cached auto_deps")?; + wrapper.packages + } else { + let packages = auto_deps(stage_dir, &dep_pkgars); + let wrapper = AutoDeps { packages }; + serialize_and_write(&auto_deps_path, &wrapper)?; + wrapper.packages + }; + Ok(auto_deps) +} + +fn get_remote_url(name: &PackageName, ext: &str) -> String { + return format!("{}/{}/{}.{}", REMOTE_PKG_SOURCE, target(), name, ext); +} +fn get_pubkey_url() -> String { + return format!("{}/id_ed25519.pub.toml", REMOTE_PKG_SOURCE); +} + +pub fn build_remote( + target_dir: &Path, + name: &PackageName, + offline_mode: bool, +) -> Result<(PathBuf, BTreeSet), 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"); + let source_toml = target_dir.join("source.toml"); + let source_pubkey = target_dir.join("id_ed25519.pub.toml"); + + if !offline_mode { + download_wget(&get_remote_url(name, "pkgar"), &source_pkgar)?; + download_wget(&get_remote_url(name, "toml"), &source_toml)?; + download_wget(&get_pubkey_url(), &source_pubkey)?; + } else { + offline_check_exists(&source_pkgar)?; + offline_check_exists(&source_toml)?; + offline_check_exists(&source_pubkey)?; + } + + 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"); + + 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)? { + remove_all(&auto_deps_path)? + } + + let auto_deps = if auto_deps_path.exists() { + let toml_content = + fs::read_to_string(&auto_deps_path).map_err(|_| "failed to read cached auto_deps")?; + let wrapper: AutoDeps = + toml::from_str(&toml_content).map_err(|_| "failed to deserialize cached auto_deps")?; + wrapper.packages + } else { + let toml_content = + fs::read_to_string(&source_toml).map_err(|_| "failed to read source.toml")?; + let pkg_toml: Package = + toml::from_str(&toml_content).map_err(|_| "failed to deserialize source.toml")?; + let wrapper = AutoDeps { + packages: pkg_toml.depends.into_iter().collect(), + }; + serialize_and_write(&auto_deps_path, &wrapper)?; + wrapper.packages + }; + + Ok((stage_dir, auto_deps)) +} + +#[cfg(test)] +mod tests { + use std::os::unix; + + use super::auto_deps; + + #[test] + fn file_system_loop_no_infinite_loop() { + // Hierarchy with an infinite loop + let temp = tempfile::tempdir().unwrap(); + let root = temp.path(); + let dir = root.join("loop"); + unix::fs::symlink(root, &dir).expect("Linking {dir:?} to {root:?}"); + + // Sanity check that we have a loop + assert_eq!( + root.canonicalize().unwrap(), + dir.canonicalize().unwrap(), + "Expected a loop where {dir:?} points to {root:?}" + ); + + let entries = auto_deps(root, &Default::default()); + assert!( + entries.is_empty(), + "auto_deps shouldn't have yielded any libraries" + ); + } +} diff --git a/src/cook/fs.rs b/src/cook/fs.rs index f156bb10..a3295d63 100644 --- a/src/cook/fs.rs +++ b/src/cook/fs.rs @@ -33,6 +33,18 @@ pub fn create_dir_clean(dir: &Path) -> Result<(), String> { create_dir(dir) } +pub fn create_target_dir(recipe_dir: &Path) -> Result { + 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)?; + } + Ok(target_dir) +} + pub fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { fs::create_dir_all(&dst)?; for entry in fs::read_dir(src)? { diff --git a/src/cook/package.rs b/src/cook/package.rs new file mode 100644 index 00000000..aaee9fa6 --- /dev/null +++ b/src/cook/package.rs @@ -0,0 +1,102 @@ +use std::{ + collections::BTreeSet, + env, + path::{Path, PathBuf}, +}; + +use pkg::{Package, PackageName}; + +use crate::{ + cook::fs::*, + recipe::{BuildKind, Recipe}, +}; + +pub fn package( + stage_dir: &Path, + target_dir: &Path, + name: &PackageName, + recipe: &Recipe, + auto_deps: &BTreeSet, +) -> Result { + 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))?; + + package_toml(target_dir, name, recipe, auto_deps)?; + } + + Ok(package_file) +} + +pub fn package_toml( + target_dir: &Path, + name: &PackageName, + recipe: &Recipe, + auto_deps: &BTreeSet, +) -> Result<(), String> { + let mut depends = recipe.package.dependencies.clone(); + for dep in auto_deps.iter() { + if !depends.contains(dep) { + depends.push(dep.clone()); + } + } + let package = Package { + name: name.clone(), + version: package_version(recipe), + target: env::var("TARGET").map_err(|err| format!("failed to read TARGET: {:?}", err))?, + depends, + }; + + serialize_and_write(&target_dir.join("stage.toml"), &package)?; + + return Ok(()); +} + +fn package_version(recipe: &Recipe) -> String { + if recipe.build.kind == BuildKind::None { + "".into() + } else if let Some(v) = &recipe.package.version { + v.to_string() + } else if let Some(r) = &recipe.source { + if let Some(m) = r.guess_version() { + m + } else { + "TODO".into() + } + } else { + "TODO".into() + } +} diff --git a/src/cook/script.rs b/src/cook/script.rs index aa531d69..c7311ac2 100644 --- a/src/cook/script.rs +++ b/src/cook/script.rs @@ -1,5 +1,4 @@ -//TODO: pub(crate) -pub static SHARED_PRESCRIPT: &str = r#" +pub(crate) static SHARED_PRESCRIPT: &str = r#" # Build dynamically function DYNAMIC_INIT { COOKBOOK_AUTORECONF="autoreconf" @@ -78,6 +77,270 @@ function GNU_CONFIG_GET { } "#; +pub(crate) static BUILD_PRESCRIPT: &str = r#" +# Add cookbook bins to path +if [ -z "${IS_REDOX}" ] +then +export PATH="${COOKBOOK_ROOT}/bin:${PATH}" +fi + +# 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 +offline_flags= +if [ ! -z "${COOKBOOK_DEBUG}" ] +then + install_flags=--debug + build_flags= + build_type=debug + export CFLAGS="${CFLAGS} -g" + export CPPFLAGS="${CPPFLAGS} -g" +fi + +if [ ! -z "${COOKBOOK_OFFLINE}" ] +then +offline_flags=--offline +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} \ + ${offline_flags} \ + -j "${COOKBOOK_MAKE_JOBS}" "$@" +} + +# 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} ${offline_flags} -j "${COOKBOOK_MAKE_JOBS}" + 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} ${offline_flags} -j "${COOKBOOK_MAKE_JOBS}" + 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="/usr" + --disable-shared + --enable-static +) +COOKBOOK_MAKE="make" + +if [ -z "${COOKBOOK_MAKE_JOBS}" ] +then +if [ -z "${IS_REDOX}" ] +then +COOKBOOK_MAKE_JOBS="$(nproc)" +else +COOKBOOK_MAKE_JOBS="1" +fi +fi + +function cookbook_configure { + "${COOKBOOK_CONFIGURE}" "${COOKBOOK_CONFIGURE_FLAGS[@]}" "$@" + "${COOKBOOK_MAKE}" -j "${COOKBOOK_MAKE_JOBS}" + "${COOKBOOK_MAKE}" install DESTDIR="${COOKBOOK_STAGE}" +} + +COOKBOOK_CMAKE="cmake" +COOKBOOK_NINJA="ninja" +COOKBOOK_CMAKE_FLAGS=( + -DBUILD_SHARED_LIBS=False + -DENABLE_SHARED=False + -DENABLE_STATIC=True +) +function cookbook_cmake { + cat > cross_file.cmake <> cross_file.cmake + echo "set(CMAKE_CXX_COMPILER_LAUNCHER ${CC_WRAPPER})" >> cross_file.cmake + fi + + "${COOKBOOK_CMAKE}" "${COOKBOOK_SOURCE}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CROSSCOMPILING=True \ + -DCMAKE_INSTALL_INCLUDEDIR=include \ + -DCMAKE_INSTALL_LIBDIR=lib \ + -DCMAKE_INSTALL_OLDINCLUDEDIR=/include \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_INSTALL_SBINDIR=bin \ + -DCMAKE_TOOLCHAIN_FILE=cross_file.cmake \ + -GNinja \ + -Wno-dev \ + "${COOKBOOK_CMAKE_FLAGS[@]}" \ + "$@" + + "${COOKBOOK_NINJA}" -j"${COOKBOOK_MAKE_JOBS}" + DESTDIR="${COOKBOOK_STAGE}" "${COOKBOOK_NINJA}" install -j"${COOKBOOK_MAKE_JOBS}" +} + +COOKBOOK_MESON="meson" +COOKBOOK_MESON_FLAGS=( + --buildtype release + --wrap-mode nofallback + --strip + -Ddefault_library=static + -Dprefix=/usr +) +function cookbook_meson { + echo "[binaries]" > cross_file.txt + echo "c = [$(printf "'%s', " $CC | sed 's/, $//')]" >> cross_file.txt + echo "cpp = [$(printf "'%s', " $CXX | sed 's/, $//')]" >> cross_file.txt + echo "ar = '${AR}'" >> cross_file.txt + echo "strip = '${STRIP}'" >> cross_file.txt + echo "pkg-config = '${PKG_CONFIG}'" >> cross_file.txt + echo "llvm-config = '${TARGET}-llvm-config'" >> cross_file.txt + echo "glib-compile-resources = 'glib-compile-resources'" >> cross_file.txt + echo "glib-compile-schemas = 'glib-compile-schemas'" >> cross_file.txt + + echo "[host_machine]" >> cross_file.txt + echo "system = 'redox'" >> cross_file.txt + echo "cpu_family = '$(echo "${TARGET}" | cut -d - -f1)'" >> cross_file.txt + echo "cpu = '$(echo "${TARGET}" | cut -d - -f1)'" >> cross_file.txt + echo "endian = 'little'" >> cross_file.txt + + echo "[paths]" >> cross_file.txt + echo "prefix = '/usr'" >> cross_file.txt + echo "libdir = 'lib'" >> cross_file.txt + echo "bindir = 'bin'" >> cross_file.txt + + echo "[properties]" >> cross_file.txt + echo "needs_exe_wrapper = true" >> cross_file.txt + echo "sys_root = '${COOKBOOK_SYSROOT}'" >> cross_file.txt + echo "c_args = [$(printf "'%s', " $CFLAGS | sed 's/, $//')]" >> cross_file.txt + echo "cpp_args = [$(printf "'%s', " $CPPFLAGS | sed 's/, $//')]" >> cross_file.txt + echo "c_link_args = [$(printf "'%s', " $LDFLAGS | sed 's/, $//')]" >> cross_file.txt + + unset AR + unset AS + unset CC + unset CXX + unset LD + unset NM + unset OBJCOPY + unset OBJDUMP + unset PKG_CONFIG + unset RANLIB + unset READELF + unset STRIP + + "${COOKBOOK_MESON}" setup \ + "${COOKBOOK_SOURCE}" \ + . \ + --cross-file cross_file.txt \ + "${COOKBOOK_MESON_FLAGS[@]}" \ + "$@" + "${COOKBOOK_NINJA}" -j"${COOKBOOK_MAKE_JOBS}" + DESTDIR="${COOKBOOK_STAGE}" "${COOKBOOK_NINJA}" install -j"${COOKBOOK_MAKE_JOBS}" +} +"#; + +pub(crate) static BUILD_POSTSCRIPT: &str = r#" +# 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||'"${COOKBOOK_NAME}"'|g' {} ';' + fi +done +"#; + pub(crate) static GIT_RESET_BRANCH: &str = r#" ORIGIN_BRANCH="$(git branch --remotes | grep '^ origin/HEAD -> ' | cut -d ' ' -f 5-)" if [ -n "$BRANCH" ]