Merge branch 'repo-bin' into 'master'

Repo.sh in Rust with TUI

See merge request redox-os/cookbook!601
This commit is contained in:
Jeremy Soller 2025-10-29 09:34:26 -06:00
commit 3710b1dab5
19 changed files with 2375 additions and 169 deletions

338
Cargo.lock generated
View File

@ -52,6 +52,12 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-tzdata"
version = "0.1.1"
@ -67,6 +73,19 @@ dependencies = [
"libc",
]
[[package]]
name = "ansi-to-tui"
version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67555e1f1ece39d737e28c8a017721287753af3f93225e4a445b29ccb0f5912c"
dependencies = [
"nom",
"ratatui",
"simdutf8",
"smallvec",
"thiserror 1.0.69",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
@ -128,9 +147,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.98"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arg_parser"
@ -374,6 +393,21 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
dependencies = [
"rustversion",
]
[[package]]
name = "cc"
version = "1.2.29"
@ -395,6 +429,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
@ -446,7 +486,7 @@ dependencies = [
"ansi_term",
"atty",
"bitflags 1.3.2",
"strsim",
"strsim 0.8.0",
"textwrap",
"unicode-width 0.1.14",
"vec_map",
@ -458,6 +498,20 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "compact_str"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
dependencies = [
"castaway",
"cfg-if 1.0.1",
"itoa",
"rustversion",
"ryu",
"static_assertions",
]
[[package]]
name = "console"
version = "0.15.11"
@ -467,7 +521,7 @@ dependencies = [
"encode_unicode",
"libc",
"once_cell",
"unicode-width 0.2.1",
"unicode-width 0.2.0",
"windows-sys 0.59.0",
]
@ -602,6 +656,41 @@ dependencies = [
"syn",
]
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.11.1",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "deranged"
version = "0.4.0"
@ -683,6 +772,12 @@ dependencies = [
"syn",
]
[[package]]
name = "downcast-rs"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "dryoc"
version = "0.6.2"
@ -784,6 +879,17 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "filedescriptor"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"
dependencies = [
"libc",
"thiserror 1.0.69",
"winapi",
]
[[package]]
name = "flate2"
version = "1.1.2"
@ -974,9 +1080,17 @@ version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -1211,6 +1325,12 @@ dependencies = [
"zerovec",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.0.3"
@ -1267,10 +1387,19 @@ dependencies = [
"console",
"number_prefix",
"portable-atomic",
"unicode-width 0.2.1",
"unicode-width 0.2.0",
"web-time",
]
[[package]]
name = "indoc"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
dependencies = [
"rustversion",
]
[[package]]
name = "inout"
version = "0.1.4"
@ -1280,6 +1409,19 @@ dependencies = [
"generic-array",
]
[[package]]
name = "instability"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a"
dependencies = [
"darling",
"indoc",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "io-uring"
version = "0.7.8"
@ -1379,6 +1521,15 @@ version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "lru"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
dependencies = [
"hashbrown",
]
[[package]]
name = "lru-slab"
version = "0.1.2"
@ -1402,6 +1553,12 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@ -1422,6 +1579,28 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "nix"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.9.1",
"cfg-if 1.0.1",
"cfg_aliases 0.1.1",
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-conv"
version = "0.1.0"
@ -1500,6 +1679,12 @@ dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pbr"
version = "1.1.1"
@ -1627,6 +1812,27 @@ version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-pty"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4a596a2b3d2752d94f51fac2d4a96737b8705dddd311a32b9af47211f08671e"
dependencies = [
"anyhow",
"bitflags 1.3.2",
"downcast-rs",
"filedescriptor",
"lazy_static",
"libc",
"log",
"nix",
"serial2",
"shared_library",
"shell-words",
"winapi",
"winreg",
]
[[package]]
name = "potential_utf"
version = "0.1.2"
@ -1676,7 +1882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8"
dependencies = [
"bytes",
"cfg_aliases",
"cfg_aliases 0.2.1",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
@ -1716,7 +1922,7 @@ version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970"
dependencies = [
"cfg_aliases",
"cfg_aliases 0.2.1",
"libc",
"once_cell",
"socket2",
@ -1804,6 +2010,27 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384c2842d4e069d5ccacf5fe1dca4ef8d07a5444329715f0fc3c61813502d4d1"
[[package]]
name = "ratatui"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
dependencies = [
"bitflags 2.9.1",
"cassowary",
"compact_str",
"indoc",
"instability",
"itertools",
"lru",
"paste",
"strum",
"termion",
"unicode-segmentation",
"unicode-truncate",
"unicode-width 0.2.0",
]
[[package]]
name = "rayon"
version = "1.10.0"
@ -1863,13 +2090,19 @@ dependencies = [
name = "redox_cookbook"
version = "0.1.0"
dependencies = [
"ansi-to-tui",
"anyhow",
"blake3 1.5.3",
"filedescriptor",
"ignore",
"libc",
"object",
"pbr",
"pkgar 0.1.19",
"pkgar-core 0.1.19",
"pkgar-keys 0.1.19",
"portable-pty",
"ratatui",
"redox-pkg",
"redoxer",
"regex",
@ -2290,6 +2523,17 @@ dependencies = [
"serde",
]
[[package]]
name = "serial2"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cc76fa68e25e771492ca1e3c53d447ef0be3093e05cd3b47f4b712ba10c6f3c"
dependencies = [
"cfg-if 1.0.1",
"libc",
"winapi",
]
[[package]]
name = "sha2"
version = "0.10.9"
@ -2301,12 +2545,34 @@ dependencies = [
"digest 0.10.7",
]
[[package]]
name = "shared_library"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
dependencies = [
"lazy_static",
"libc",
]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "slab"
version = "0.4.10"
@ -2356,6 +2622,34 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "subtle"
version = "2.6.1"
@ -2723,6 +3017,23 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-truncate"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [
"itertools",
"unicode-segmentation",
"unicode-width 0.1.14",
]
[[package]]
name = "unicode-width"
version = "0.1.14"
@ -2731,9 +3042,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.1"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "untrusted"
@ -3256,6 +3567,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [
"winapi",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"

View File

@ -3,7 +3,7 @@ name = "redox_cookbook"
version = "0.1.0"
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
edition = "2024"
default-run = "cook"
default-run = "repo"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -21,13 +21,16 @@ path = "src/lib.rs"
doctest = false
[dependencies]
anyhow = "1"
blake3 = "=1.5.3" # 1.5.4 is incompatible with blake3 0.3 dependency from pkgar
libc = "0.2"
ignore = "0.4"
object = { version = "0.36", features = ["build_core"] }
pbr = "1.0.2"
pkgar = { path = "pkgar/pkgar" }
pkgar-core = { path = "pkgar/pkgar-core" }
pkgar-keys = { path = "pkgar/pkgar-keys" }
portable-pty = "0.9.0"
redox-pkg = "0.2.8"
redoxer = "0.2"
regex = "1.11"
@ -35,6 +38,13 @@ serde = { version = "=1.0.197", features = ["derive"] }
termion = "4"
toml = "0.8"
walkdir = "2.3.1"
filedescriptor = "0.8.3"
ansi-to-tui = "7.0.0"
[dependencies.ratatui]
version = "0.29.0"
default-features = false
features = ["termion"]
[dev-dependencies]
tempfile = "3"

View File

@ -29,7 +29,16 @@ Cookbook has special config to avoid repetitive args, place this file into `cook
```toml
# Configuration file
# This is a configuration file to avoid repetitively spelling command args.
# At the moment only mirrors here implemented but in future it will be expanded when scripts are rusted
# At the moment this configures mirror and cook configuration
# These options has defaults set below
# These options has higher priority than env
#[cook]
#jobs = <nproc>
#nonstop = false
#offline = false
#tui = true
#verbose = true
[mirrors]
# see list of GNU FTP mirrors at https://www.gnu.org/prep/ftp.en.html

View File

@ -1,19 +1,13 @@
#!/usr/bin/env bash
set -e
source config.sh
source `dirname "$0"`/config.sh
if [ $# = 0 ]
then
recipes="$(list_recipes --short)"
recipes="--all"
else
recipes="$@"
fi
for recipe_name in $recipes
do
recipe_path=`find_recipe $recipe_name`
echo -e "\033[01;38;5;215mcook - clean $recipe_name\033[0m"
rm -rf "${ROOT}/$recipe_path/target/${TARGET}"
done
repo clean $recipes

View File

@ -17,8 +17,11 @@ if [ x"${HOST}" == x"riscv64gc-unknown-redox" ] ; then
HOST="riscv64-unknown-redox"
fi
# Cookbook requires correct CWD to work
cd `dirname "$0"`
# Automatic variables
ROOT="$(cd `dirname "$0"` && pwd)"
ROOT=`pwd`
export AR="${HOST}-gcc-ar"
export AS="${HOST}-as"
@ -60,6 +63,9 @@ function pkgar {
function cook {
"$ROOT/target/release/cook" "$@"
}
function repo {
"$ROOT/target/release/repo" "$@"
}
function repo_builder {
"$ROOT/target/release/repo_builder" "$@"
}

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
set -e
source config.sh
source `dirname "$0"`/config.sh
cook --fetch-only ${@:1}
# Intentionally empty to allow fetch and cook running in parallel

12
repo.sh
View File

@ -1,8 +1,7 @@
#!/usr/bin/env bash
set -e
shopt -s nullglob
source config.sh
source `dirname "$0"`/config.sh
APPSTREAM="0"
COOK_OPT=""
@ -20,15 +19,12 @@ do
COOK_OPT+=" --nonstop"
elif [ "$arg" == "--offline" ]
then
COOK_OPT+=" --offline"
export COOKBOOK_OFFLINE=true
else
recipes+=" $arg"
fi
done
cook $COOK_OPT $recipes
repo cook $COOK_OPT $recipes
repo="$ROOT/repo/$TARGET"
mkdir -p "$repo"
repo_builder "$repo" $recipes
repo_builder "$ROOT/repo/$TARGET" $recipes

View File

@ -1,36 +1,17 @@
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 cookbook::cook::package::package;
use cookbook::recipe::{CookRecipe, Recipe};
use pkg::PackageName;
use cookbook::config::init_config;
use cookbook::cook::cook_build::build;
use termion::{color, style};
fn cook_meta(
recipe_dir: &Path,
name: &PackageName,
recipe: &Recipe,
fetch_only: bool,
) -> Result<(), String> {
if fetch_only {
return Ok(());
}
let target_dir = create_target_dir(recipe_dir)?;
let empty_deps = BTreeSet::new();
let _package_file = package_toml(&target_dir, name, recipe, &empty_deps)
.map_err(|err| format!("failed to package: {}", err))?;
Ok(())
}
fn cook(
recipe_dir: &Path,
name: &PackageName,
@ -39,12 +20,9 @@ fn cook(
fetch_only: bool,
is_offline: bool,
) -> Result<(), String> {
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),
true => fetch_offline(recipe_dir, recipe, &None),
false => fetch(recipe_dir, recipe, &None),
}
.map_err(|err| format!("failed to fetch: {}", err))?;
@ -62,10 +40,11 @@ fn cook(
recipe,
is_offline,
!is_deps,
&None,
)
.map_err(|err| format!("failed to build: {}", err))?;
let _package_file = package(&stage_dir, &target_dir, name, recipe, &auto_deps)
package(&stage_dir, &target_dir, name, recipe, &auto_deps, &None)
.map_err(|err| format!("failed to package: {}", err))?;
Ok(())

1351
src/bin/repo.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.next()
.expect("Usage: repo_builder <REPO_DIR> <recipe1> <recipe2> ...");
let repo_path = Path::new(&repo_dir);
if !repo_path.is_dir() {
fs::create_dir_all(repo_path)?;
}
// Runtime dependencies include both `[package.dependencies]` and dynamically
// linked packages discovered by auto_deps.

View File

@ -1,16 +1,59 @@
use std::{collections::HashMap, fs, sync::OnceLock};
use std::{collections::HashMap, env, fs, str::FromStr, sync::OnceLock};
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct CookConfigOpt {
/// whether to run offline
pub offline: Option<bool>,
/// whether to set jobs number instead of from nproc
pub jobs: Option<usize>,
/// whether to use TUI to allow parallel build
/// default value is yes if "CI" env unset and STDIN is open.
pub tui: Option<bool>,
/// whether to ignore build errors
pub nonstop: Option<bool>,
/// whether to print success recipes info and warnings
/// build failure still be printed anyway
pub verbose: Option<bool>,
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
pub struct CookConfig {
pub offline: bool,
pub jobs: usize,
pub tui: bool,
pub nonstop: bool,
pub verbose: bool,
}
impl From<CookConfigOpt> for CookConfig {
fn from(value: CookConfigOpt) -> Self {
CookConfig {
offline: value.offline.unwrap(),
jobs: value.jobs.unwrap(),
tui: value.tui.unwrap(),
nonstop: value.nonstop.unwrap(),
verbose: value.verbose.unwrap(),
}
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct CookbookConfig {
#[serde(rename = "cook")]
cook_opt: CookConfigOpt,
#[serde(skip)]
pub cook: CookConfig,
pub mirrors: HashMap<String, String>,
}
static CONFIG: OnceLock<CookbookConfig> = OnceLock::new();
pub fn init_config() {
let config: CookbookConfig = if fs::exists("cookbook.toml").unwrap_or(false) {
let mut config: CookbookConfig = if fs::exists("cookbook.toml").unwrap_or(false) {
let toml_content = fs::read_to_string("cookbook.toml")
.map_err(|e| format!("Unable to read config: {:?}", e))
.unwrap();
@ -21,9 +64,44 @@ pub fn init_config() {
CookbookConfig::default()
};
if config.cook_opt.tui.is_none() {
config.cook_opt.tui = Some(!env::var("CI").is_ok_and(|s| !s.is_empty()));
}
if config.cook_opt.jobs.is_none() {
config.cook_opt.jobs = Some(extract_env(
"COOKBOOK_MAKE_JOBS",
std::thread::available_parallelism()
.map(|f| usize::from(f))
.unwrap_or(1),
));
}
if config.cook_opt.offline.is_none() {
config.cook_opt.offline = Some(extract_env("COOKBOOK_OFFLINE", false));
}
if config.cook_opt.verbose.is_none() {
config.cook_opt.verbose = Some(extract_env("COOKBOOK_VERBOSE", true));
}
if config.cook_opt.nonstop.is_none() {
config.cook_opt.nonstop = Some(extract_env("COOKBOOK_NONSTOP", false));
}
config.cook = CookConfig::from(config.cook_opt.clone());
CONFIG.set(config).expect("config is initialized twice");
}
fn extract_env<T: FromStr>(key: &str, default: T) -> T {
if let Ok(e) = env::var(&key) {
str::parse(&e).unwrap_or(default)
} else {
default
}
}
pub fn get_config() -> &'static CookbookConfig {
return CONFIG.get().expect("Configuration is not initialized");
}
pub fn translate_mirror(original_url: &str) -> String {
let config = CONFIG.get().expect("Configuration is not initialized");
@ -75,6 +153,17 @@ mod tests {
let _ = CONFIG.set(app_config);
}
#[test]
fn test_parse_cook() {
let app_config: CookbookConfig = toml::from_str(
"[cook]\n\
offline = true\n",
)
.expect("Unable to parse test config");
assert_eq!(app_config.cook_opt.offline, Some(true));
assert_eq!(app_config.cook_opt.jobs, None);
}
#[test]
fn test_exact_match() {
setup_test_config();

View File

@ -3,4 +3,5 @@ pub mod cook_build;
pub mod fetch;
pub mod fs;
pub mod package;
pub mod pty;
pub mod script;

View File

@ -3,6 +3,7 @@ use pkg::{Package, PackageName};
use redoxer::target;
use crate::cook::fs::*;
use crate::cook::pty::PtyOut;
use crate::cook::script::*;
use crate::recipe::AutoDeps;
use crate::recipe::BuildKind;
@ -21,9 +22,25 @@ use crate::is_redox;
use crate::REMOTE_PKG_SOURCE;
macro_rules! log_warn {
($logger:expr, $($arg:tt)+) => {
use std::io::Write;
if $logger.is_some() {
let _ = $logger.as_ref().unwrap().1.try_clone().unwrap().write(
format!($($arg)+)
.as_bytes(),
);
} else {
eprintln!($($arg)+);
}
};
}
fn auto_deps(
stage_dir: &Path,
dep_pkgars: &BTreeSet<(PackageName, PathBuf)>,
logger: &PtyOut,
) -> BTreeSet<PackageName> {
let mut paths = BTreeSet::new();
let mut visited = BTreeSet::new();
@ -43,7 +60,10 @@ fn auto_deps(
};
if visited.contains(&dir) {
#[cfg(debug_assertions)]
eprintln!("DEBUG: auto_deps => Skipping `{dir:?}` (already visited)");
log_warn!(
logger,
"DEBUG: auto_deps => Skipping `{dir:?}` (already visited)"
);
continue;
}
assert!(
@ -90,7 +110,7 @@ fn auto_deps(
continue;
};
if let Ok(relative_path) = path.strip_prefix(stage_dir) {
eprintln!("DEBUG: {} needs {}", relative_path.display(), name);
log_warn!(logger, "DEBUG: {} needs {}", relative_path.display(), name);
}
needed.insert(name.to_string());
}
@ -124,7 +144,7 @@ fn auto_deps(
continue;
};
if needed.contains(child_name) {
eprintln!("DEBUG: {} provides {}", dep, child_name);
log_warn!(logger, "DEBUG: {} provides {}", dep, child_name);
deps.insert(dep.clone());
missing.remove(child_name);
}
@ -134,7 +154,7 @@ fn auto_deps(
}
for name in missing {
eprintln!("WARN: {} missing", name);
log_warn!(logger, "WARN: {} missing", name);
}
deps
@ -148,9 +168,14 @@ pub fn build(
recipe: &Recipe,
offline_mode: bool,
check_source: bool,
logger: &PtyOut,
) -> Result<(PathBuf, BTreeSet<PackageName>), String> {
let sysroot_dir = target_dir.join("sysroot");
let stage_dir = target_dir.join("stage");
if recipe.build.kind == BuildKind::None {
// metapackages don't need to do anything here
return Ok((stage_dir, BTreeSet::new()));
}
let mut dep_pkgars = BTreeSet::new();
for dependency in recipe.build.dependencies.iter() {
@ -169,7 +194,7 @@ pub fn build(
}
if stage_dir.exists() && !check_source {
let auto_deps = build_auto_deps(target_dir, &stage_dir, dep_pkgars)?;
let auto_deps = build_auto_deps(target_dir, &stage_dir, dep_pkgars, logger)?;
return Ok((stage_dir, auto_deps));
}
@ -185,7 +210,8 @@ pub fn build(
if sysroot_dir.is_dir() {
let sysroot_modified = modified_dir(&sysroot_dir)?;
if sysroot_modified < source_modified || sysroot_modified < deps_modified {
eprintln!(
log_warn!(
logger,
"DEBUG: '{}' newer than '{}'",
source_dir.display(),
sysroot_dir.display()
@ -234,7 +260,8 @@ pub fn build(
if stage_dir.is_dir() {
let stage_modified = modified_dir(&stage_dir)?;
if stage_modified < source_modified || stage_modified < deps_modified {
eprintln!(
log_warn!(
logger,
"DEBUG: '{}' newer than '{}'",
source_dir.display(),
stage_dir.display()
@ -292,7 +319,7 @@ pub fn build(
flags_fn("COOKBOOK_MESON_FLAGS", mesonflags),
),
BuildKind::Custom { script } => script.clone(),
BuildKind::Remote => return build_remote(target_dir, name, offline_mode),
BuildKind::Remote => return build_remote(target_dir, name, offline_mode, logger),
BuildKind::None => "".to_owned(),
};
@ -313,7 +340,7 @@ pub fn build(
} else {
let cookbook_redoxer = Path::new("target/release/cookbook_redoxer")
.canonicalize()
.unwrap();
.unwrap_or(PathBuf::from("/bin/false"));
let mut command = Command::new(&cookbook_redoxer);
command.arg("env").arg("bash").arg("-ex");
command.env("COOKBOOK_REDOXER", &cookbook_redoxer);
@ -337,13 +364,13 @@ pub fn build(
"{}\n{}\n{}\n{}",
BUILD_PRESCRIPT, SHARED_PRESCRIPT, script, BUILD_POSTSCRIPT
);
run_command_stdin(command, full_script.as_bytes())?;
run_command_stdin(command, full_script.as_bytes(), logger)?;
// Move stage.tmp to stage atomically
rename(&stage_dir_tmp, &stage_dir)?;
}
let auto_deps = build_auto_deps(target_dir, &stage_dir, dep_pkgars)?;
let auto_deps = build_auto_deps(target_dir, &stage_dir, dep_pkgars, logger)?;
Ok((stage_dir, auto_deps))
}
@ -353,6 +380,7 @@ fn build_auto_deps(
target_dir: &Path,
stage_dir: &PathBuf,
dep_pkgars: BTreeSet<(PackageName, PathBuf)>,
logger: &PtyOut,
) -> Result<BTreeSet<PackageName>, String> {
let auto_deps_path = target_dir.join("auto_deps.toml");
if auto_deps_path.is_file() && modified(&auto_deps_path)? < modified(stage_dir)? {
@ -366,7 +394,7 @@ fn build_auto_deps(
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 packages = auto_deps(stage_dir, &dep_pkgars, logger);
let wrapper = AutoDeps { packages };
serialize_and_write(&auto_deps_path, &wrapper)?;
wrapper.packages
@ -385,6 +413,7 @@ pub fn build_remote(
target_dir: &Path,
name: &PackageName,
offline_mode: bool,
logger: &PtyOut,
) -> Result<(PathBuf, BTreeSet<PackageName>), String> {
// download straight from remote source then declare pkg dependencies as autodeps dependency
let stage_dir = target_dir.join("stage");
@ -394,9 +423,9 @@ pub fn build_remote(
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)?;
download_wget(&get_remote_url(name, "pkgar"), &source_pkgar, logger)?;
download_wget(&get_remote_url(name, "toml"), &source_toml, logger)?;
download_wget(&get_pubkey_url(), &source_pubkey, logger)?;
} else {
offline_check_exists(&source_pkgar)?;
offline_check_exists(&source_toml)?;
@ -469,7 +498,7 @@ mod tests {
"Expected a loop where {dir:?} points to {root:?}"
);
let entries = auto_deps(root, &Default::default());
let entries = auto_deps(root, &Default::default(), &None);
assert!(
entries.is_empty(),
"auto_deps shouldn't have yielded any libraries"

View File

@ -1,13 +1,30 @@
use crate::config::translate_mirror;
use crate::cook::fs::*;
use crate::cook::pty::PtyOut;
use crate::cook::script::*;
use crate::is_redox;
use crate::recipe::BuildKind;
use crate::recipe::Recipe;
use crate::{blake3, recipe::SourceRecipe};
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
macro_rules! log_warn {
($logger:expr, $($arg:tt)+) => {
use std::io::Write;
if $logger.is_some() {
let _ = $logger.as_ref().unwrap().1.try_clone().unwrap().write(
format!($($arg)+)
.as_bytes(),
);
} else {
eprintln!($($arg)+);
}
};
}
pub(crate) fn get_blake3(path: &PathBuf, show_progress: bool) -> Result<String, String> {
if show_progress {
blake3::blake3_progress(&path)
@ -24,14 +41,22 @@ pub(crate) fn get_blake3(path: &PathBuf, show_progress: bool) -> Result<String,
})
}
pub fn fetch_offline(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf, String> {
pub fn fetch_offline(
recipe_dir: &Path,
recipe: &Recipe,
logger: &PtyOut,
) -> Result<PathBuf, String> {
let source_dir = recipe_dir.join("source");
match source {
if recipe.build.kind == BuildKind::None || recipe.build.kind == BuildKind::Remote {
// the build function doesn't need source dir exists
return Ok(source_dir);
}
match &recipe.source {
Some(SourceRecipe::Path { path: _ }) | None => {
return fetch(recipe_dir, source);
return fetch(recipe_dir, recipe, logger);
}
Some(SourceRecipe::SameAs { same_as: _ }) => {
return fetch(recipe_dir, source);
return fetch(recipe_dir, recipe, logger);
}
Some(SourceRecipe::Git {
git: _,
@ -52,7 +77,7 @@ pub fn fetch_offline(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result
}) => {
if !source_dir.is_dir() {
let source_tar = recipe_dir.join("source.tar");
let source_tar_blake3 = get_blake3(&source_tar, true)?;
let source_tar_blake3 = get_blake3(&source_tar, true && logger.is_none())?;
if source_tar.exists() {
if let Some(blake3) = blake3 {
if source_tar_blake3 != *blake3 {
@ -60,8 +85,8 @@ pub fn fetch_offline(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result
"The downloaded tar blake3 '{source_tar_blake3}' is not equal to blake3 in recipe.toml."
));
}
fetch_extract_tar(source_tar, &source_dir)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir)?;
fetch_extract_tar(source_tar, &source_dir, logger)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir, logger)?;
} else {
// need to trust this tar file
return Err(format!(
@ -79,18 +104,28 @@ pub fn fetch_offline(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result
Ok(source_dir)
}
pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf, String> {
pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result<PathBuf, String> {
let source_dir = recipe_dir.join("source");
match source {
if recipe.build.kind == BuildKind::None || recipe.build.kind == BuildKind::Remote {
// the build function doesn't need source dir exists
return Ok(source_dir);
}
match &recipe.source {
Some(SourceRecipe::SameAs { same_as }) => {
let (canon_dir, recipe) = fetch_resolve_canon(recipe_dir, same_as)?;
let (canon_dir, recipe) = fetch_resolve_canon(recipe_dir, &same_as)?;
// recursively fetch
fetch(&canon_dir, &recipe.source)?;
fetch_make_symlink(&source_dir, same_as)?;
fetch(&canon_dir, &recipe, logger)?;
fetch_make_symlink(&source_dir, &same_as)?;
}
Some(SourceRecipe::Path { path }) => {
if !source_dir.is_dir() || modified_dir(Path::new(path))? > modified_dir(&source_dir)? {
eprintln!("[DEBUG]: {} is newer than {}", path, source_dir.display());
if !source_dir.is_dir() || modified_dir(Path::new(&path))? > modified_dir(&source_dir)?
{
log_warn!(
logger,
"[DEBUG]: {} is newer than {}",
path,
source_dir.display()
);
copy_dir_all(path, &source_dir).map_err(|e| {
format!(
"Couldn't copy source from {} to {}: {}",
@ -122,7 +157,7 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
command
.arg("clone")
.arg("--recursive")
.arg(translate_mirror(git));
.arg(translate_mirror(&git));
if let Some(branch) = branch {
command.arg("--branch").arg(branch);
}
@ -130,7 +165,7 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
command.arg("--depth").arg("1").arg("--shallow-submodules");
}
command.arg(&source_dir_tmp);
run_command(command)?;
run_command(command, logger)?;
// Move source.tmp to source atomically
rename(&source_dir_tmp, &source_dir)?;
@ -148,13 +183,13 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
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)?;
run_command(command, logger)?;
// Fetch origin
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("fetch").arg("origin");
run_command(command)?;
run_command(command, logger)?;
}
if let Some(_upstream) = upstream {
@ -169,7 +204,7 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("checkout").arg(rev);
run_command(command)?;
run_command(command, logger)?;
} else if !shallow_clone && !is_redox() {
//TODO: complicated stuff to check and reset branch to origin
//TODO: redox can't undestand this (got exit status 1)
@ -179,7 +214,7 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
command.env("BRANCH", branch);
}
command.current_dir(&source_dir);
run_command(command)?;
run_command(command, logger)?;
}
if !patches.is_empty() || script.is_some() {
@ -187,7 +222,7 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("reset").arg("--hard");
run_command(command)?;
run_command(command, logger)?;
}
if !shallow_clone {
@ -195,7 +230,7 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("submodule").arg("sync").arg("--recursive");
run_command(command)?;
run_command(command, logger)?;
// Update submodules
let mut command = Command::new("git");
@ -205,10 +240,10 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
.arg("update")
.arg("--init")
.arg("--recursive");
run_command(command)?;
run_command(command, logger)?;
}
fetch_apply_patches(recipe_dir, patches, script, &source_dir)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir, logger)?;
}
Some(SourceRecipe::Tar {
tar,
@ -221,9 +256,9 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
while {
if !source_tar.is_file() {
tar_updated = true;
download_wget(&tar, &source_tar)?;
download_wget(&tar, &source_tar, logger)?;
}
let source_tar_blake3 = get_blake3(&source_tar, tar_updated)?;
let source_tar_blake3 = get_blake3(&source_tar, tar_updated && logger.is_none())?;
if let Some(blake3) = blake3 {
if source_tar_blake3 != *blake3 {
if tar_updated {
@ -231,7 +266,10 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
"The downloaded tar blake3 '{source_tar_blake3}' is not equal to blake3 in recipe.toml"
));
} else {
eprintln!("DEBUG: source tar blake3 is different and need redownload");
log_warn!(
logger,
"DEBUG: source tar blake3 is different and need redownload"
);
remove_all(&source_tar)?;
}
true
@ -240,7 +278,8 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
}
} else {
//TODO: set blake3 hash on the recipe with something like "cook fix"
eprintln!(
log_warn!(
logger,
"WARNING: set blake3 for '{}' to '{}'",
source_tar.display(),
source_tar_blake3
@ -250,7 +289,10 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
} {}
if source_dir.is_dir() {
if tar_updated || fetch_is_patches_newer(recipe_dir, patches, &source_dir)? {
eprintln!("DEBUG: source tar or patches is newer than the source directory");
log_warn!(
logger,
"DEBUG: source tar or patches is newer than the source directory"
);
remove_all(&source_dir)?
}
}
@ -258,8 +300,8 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
// Create source.tmp
let source_dir_tmp = recipe_dir.join("source.tmp");
create_dir_clean(&source_dir_tmp)?;
fetch_extract_tar(source_tar, &source_dir_tmp)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir_tmp)?;
fetch_extract_tar(source_tar, &source_dir_tmp, logger)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir_tmp, logger)?;
// Move source.tmp to source atomically
rename(&source_dir_tmp, &source_dir)?;
@ -268,8 +310,8 @@ pub fn fetch(recipe_dir: &Path, source: &Option<SourceRecipe>) -> Result<PathBuf
// Local Sources
None => {
if !source_dir.is_dir() {
//TODO: Don't print if build template is none or remote
eprintln!(
log_warn!(
logger,
"WARNING: Recipe without source section expected source dir at '{}'",
source_dir.display(),
);
@ -333,6 +375,7 @@ pub(crate) fn fetch_resolve_canon(
pub(crate) fn fetch_extract_tar(
source_tar: PathBuf,
source_dir_tmp: &PathBuf,
logger: &PtyOut,
) -> Result<(), String> {
let mut command = Command::new("tar");
if is_redox() {
@ -345,7 +388,7 @@ pub(crate) fn fetch_extract_tar(
command.arg(&source_tar);
command.arg("--directory").arg(source_dir_tmp);
command.arg("--strip-components").arg("1");
run_command(command)?;
run_command(command, logger)?;
Ok(())
}
@ -378,6 +421,7 @@ pub(crate) fn fetch_apply_patches(
patches: &Vec<String>,
script: &Option<String>,
source_dir_tmp: &PathBuf,
logger: &PtyOut,
) -> Result<(), String> {
for patch_name in patches {
let patch_file = recipe_dir.join(patch_name);
@ -400,12 +444,16 @@ pub(crate) fn fetch_apply_patches(
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_command_stdin(command, patch.as_bytes(), logger)?;
}
Ok(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())?;
run_command_stdin(
command,
format!("{SHARED_PRESCRIPT}\n{script}").as_bytes(),
logger,
)?;
})
}

View File

@ -8,7 +8,10 @@ use std::{
};
use walkdir::{DirEntry, WalkDir};
use crate::config::translate_mirror;
use crate::{
config::translate_mirror,
cook::pty::{PtyOut, spawn_to_pipe},
};
//TODO: pub(crate) for all of these functions
@ -146,9 +149,10 @@ pub fn rename(src: &Path, dst: &Path) -> Result<(), String> {
})
}
pub fn run_command(mut command: process::Command) -> Result<(), String> {
let status = command
.status()
pub fn run_command(mut command: process::Command, stdout_pipe: &PtyOut) -> Result<(), String> {
let status = spawn_to_pipe(&mut command, stdout_pipe)
.map_err(|err| format!("failed to run {:?}: {}\n{:#?}", command, err, err))?
.wait()
.map_err(|err| format!("failed to run {:?}: {}\n{:#?}", command, err, err))?;
if !status.success() {
@ -161,11 +165,13 @@ pub fn run_command(mut command: process::Command) -> Result<(), String> {
Ok(())
}
pub fn run_command_stdin(mut command: process::Command, stdin_data: &[u8]) -> Result<(), String> {
pub fn run_command_stdin(
mut command: process::Command,
stdin_data: &[u8],
stdout_pipe: &PtyOut,
) -> Result<(), String> {
command.stdin(Stdio::piped());
let mut child = command
.spawn()
let mut child = spawn_to_pipe(&mut command, stdout_pipe)
.map_err(|err| format!("failed to spawn {:?}: {}\n{:#?}", command, err, err))?;
if let Some(ref mut stdin) = child.stdin {
@ -217,13 +223,13 @@ pub fn offline_check_exists(path: &PathBuf) -> Result<(), String> {
Ok(())
}
pub fn download_wget(url: &str, dest: &PathBuf) -> Result<(), String> {
pub fn download_wget(url: &str, dest: &PathBuf, logger: &PtyOut) -> Result<(), String> {
if !dest.is_file() {
let dest_tmp = PathBuf::from(format!("{}.tmp", dest.display()));
let mut command = Command::new("wget");
command.arg(translate_mirror(url));
command.arg("--continue").arg("-O").arg(&dest_tmp);
run_command(command)?;
run_command(command, logger)?;
rename(&dest_tmp, &dest)?;
}
Ok(())

View File

@ -1,13 +1,10 @@
use std::{
collections::BTreeSet,
env,
path::{Path, PathBuf},
};
use std::{collections::BTreeSet, env, path::Path};
use pkg::{Package, PackageName};
use crate::{
cook::fs::*,
cook::{fs::*, pty::PtyOut},
log_to_pty,
recipe::{BuildKind, Recipe},
};
@ -17,7 +14,14 @@ pub fn package(
name: &PackageName,
recipe: &Recipe,
auto_deps: &BTreeSet<PackageName>,
) -> Result<PathBuf, String> {
logger: &PtyOut,
) -> Result<(), String> {
if recipe.build.kind == BuildKind::None {
// metapackages don't have stage dir
package_toml(target_dir, name, recipe, auto_deps)?;
return Ok(());
}
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() {
@ -34,17 +38,20 @@ pub fn package(
}
let package_file = target_dir.join("stage.pkgar");
let package_meta = target_dir.join("stage.toml");
// Rebuild package if stage is newer
//TODO: rebuild on recipe changes
if package_file.is_file() {
let stage_modified = modified_dir(stage_dir)?;
if modified(&package_file)? < stage_modified {
eprintln!(
log_to_pty!(
logger,
"DEBUG: '{}' newer than '{}'",
stage_dir.display(),
package_file.display()
);
remove_all(&package_file)?;
remove_all(&package_meta)?;
}
}
if !package_file.is_file() {
@ -54,11 +61,13 @@ pub fn package(
stage_dir.to_str().unwrap(),
)
.map_err(|err| format!("failed to create pkgar archive: {:?}", err))?;
}
if !package_meta.is_file() {
package_toml(target_dir, name, recipe, auto_deps)?;
}
Ok(package_file)
Ok(())
}
pub fn package_toml(
@ -80,7 +89,8 @@ pub fn package_toml(
depends,
};
serialize_and_write(&target_dir.join("stage.toml"), &package)?;
let toml_path = &target_dir.join("stage.toml");
serialize_and_write(&toml_path, &package)?;
return Ok(());
}

339
src/cook/pty.rs Normal file
View File

@ -0,0 +1,339 @@
use anyhow::{Error, bail};
use filedescriptor::FileDescriptor;
use libc::{self, winsize};
use std::io::Read;
use std::os::fd::FromRawFd;
use std::os::unix::io::AsRawFd;
use std::os::unix::process::CommandExt;
use std::process::Child;
use std::{io, mem, ptr};
use std::{
io::{PipeReader, PipeWriter},
process::Command,
};
pub use std::os::unix::io::RawFd;
#[macro_export]
macro_rules! log_to_pty {
($logger:expr, $($arg:tt)+) => {
use std::io::Write;
if $logger.is_some() {
let _ = $logger.as_ref().unwrap().1.try_clone().unwrap().write(
format!($($arg)+)
.as_bytes(),
);
} else {
eprintln!($($arg)+);
}
};
}
pub type PtyOut<'a> = Option<(&'a mut UnixSlavePty, &'a mut PipeWriter)>;
pub fn setup_pty() -> (
Box<dyn Read + Send>,
PipeReader,
(UnixSlavePty, std::io::PipeWriter),
) {
let pty_system = UnixPtySystem::default();
let pair = pty_system
.openpty(PtySize {
rows: 24, // Standard terminal size
cols: 80, // Standard terminal size
..Default::default()
})
.expect("Unable to open pty");
// TODO: There's no way to handle stdin
let pty_reader = pair
.master
.try_clone_reader()
.expect("Unable to clone pty reader");
let (log_reader, log_writer) = std::io::pipe().expect("Failed to create log pipe");
let pipes = (pair.slave, log_writer);
(pty_reader, log_reader, pipes)
}
pub fn spawn_to_pipe(command: &mut Command, stdout_pipe: &PtyOut) -> Result<Child, Error> {
match stdout_pipe {
Some(stdout) => stdout.0.spawn_command(command.into()),
None => Ok(command.spawn()?),
}
}
//
// based on portable-pty crate
// copied here since it isn't flexible enough
//
#[derive(Default)]
pub struct UnixPtySystem {}
/// Represents the size of the visible display area in the pty
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PtySize {
/// The number of lines of text
pub rows: u16,
/// The number of columns of text
pub cols: u16,
/// The width of a cell in pixels. Note that some systems never
/// fill this value and ignore it.
pub pixel_width: u16,
/// The height of a cell in pixels. Note that some systems never
/// fill this value and ignore it.
pub pixel_height: u16,
}
impl Default for PtySize {
fn default() -> Self {
PtySize {
rows: 24,
cols: 80,
pixel_width: 0,
pixel_height: 0,
}
}
}
fn openpty(size: PtySize) -> anyhow::Result<(UnixMasterPty, UnixSlavePty)> {
let mut master: RawFd = -1;
let mut slave: RawFd = -1;
let mut size = winsize {
ws_row: size.rows,
ws_col: size.cols,
ws_xpixel: size.pixel_width,
ws_ypixel: size.pixel_height,
};
let result = unsafe {
// BSDish systems may require mut pointers to some args
#[allow(clippy::unnecessary_mut_passed)]
libc::openpty(
&mut master,
&mut slave,
ptr::null_mut(),
ptr::null_mut(),
&mut size,
)
};
if result != 0 {
bail!("failed to openpty: {:?}", io::Error::last_os_error());
}
let master = UnixMasterPty {
fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(master) }),
};
let slave = UnixSlavePty {
fd: PtyFd(unsafe { FileDescriptor::from_raw_fd(slave) }),
};
// Ensure that these descriptors will get closed when we execute
// the child process. This is done after constructing the Pty
// instances so that we ensure that the Ptys get drop()'d if
// the cloexec() functions fail (unlikely!).
cloexec(master.fd.as_raw_fd())?;
cloexec(slave.fd.as_raw_fd())?;
Ok((master, slave))
}
pub struct PtyPair {
// slave is listed first so that it is dropped first.
// The drop order is stable and specified by rust rfc 1857
pub slave: UnixSlavePty,
pub master: UnixMasterPty,
}
impl UnixPtySystem {
fn openpty(&self, size: PtySize) -> anyhow::Result<PtyPair> {
let (master, slave) = openpty(size)?;
Ok(PtyPair {
master: master,
slave: slave,
})
}
}
struct PtyFd(pub FileDescriptor);
impl std::ops::Deref for PtyFd {
type Target = FileDescriptor;
fn deref(&self) -> &FileDescriptor {
&self.0
}
}
impl std::ops::DerefMut for PtyFd {
fn deref_mut(&mut self) -> &mut FileDescriptor {
&mut self.0
}
}
impl Read for PtyFd {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
match self.0.read(buf) {
Err(ref e) if e.raw_os_error() == Some(libc::EIO) => {
// EIO indicates that the slave pty has been closed.
// Treat this as EOF so that std::io::Read::read_to_string
// and similar functions gracefully terminate when they
// encounter this condition
Ok(0)
}
x => x,
}
}
}
impl PtyFd {
fn resize(&self, size: PtySize) -> Result<(), Error> {
let ws_size = winsize {
ws_row: size.rows,
ws_col: size.cols,
ws_xpixel: size.pixel_width,
ws_ypixel: size.pixel_height,
};
if unsafe {
libc::ioctl(
self.0.as_raw_fd(),
libc::TIOCSWINSZ as _,
&ws_size as *const _,
)
} != 0
{
bail!(
"failed to ioctl(TIOCSWINSZ): {:?}",
io::Error::last_os_error()
);
}
Ok(())
}
fn get_size(&self) -> Result<PtySize, Error> {
let mut size: winsize = unsafe { mem::zeroed() };
if unsafe {
libc::ioctl(
self.0.as_raw_fd(),
libc::TIOCGWINSZ as _,
&mut size as *mut _,
)
} != 0
{
bail!(
"failed to ioctl(TIOCGWINSZ): {:?}",
io::Error::last_os_error()
);
}
Ok(PtySize {
rows: size.ws_row,
cols: size.ws_col,
pixel_width: size.ws_xpixel,
pixel_height: size.ws_ypixel,
})
}
fn spawn_command(&self, cmd: &mut Command) -> anyhow::Result<std::process::Child> {
unsafe {
cmd
// .stdin(self.as_stdio()?)
.stdout(self.as_stdio()?)
.stderr(self.as_stdio()?)
.pre_exec(move || {
// Clean up a few things before we exec the program
// Clear out any potentially problematic signal
// dispositions that we might have inherited
for signo in &[
libc::SIGCHLD,
libc::SIGHUP,
libc::SIGINT,
libc::SIGQUIT,
libc::SIGTERM,
libc::SIGALRM,
] {
libc::signal(*signo, libc::SIG_DFL);
}
let empty_set: libc::sigset_t = std::mem::zeroed();
libc::sigprocmask(libc::SIG_SETMASK, &empty_set, std::ptr::null_mut());
// Establish ourselves as a session leader.
if libc::setsid() == -1 {
return Err(io::Error::last_os_error());
}
Ok(())
})
};
let mut child = cmd.spawn()?;
// Ensure that we close out the slave fds that Child retains;
// they are not what we need (we need the master side to reference
// them) and won't work in the usual way anyway.
// In practice these are None, but it seems best to be move them
// out in case the behavior of Command changes in the future.
// child.stdin.take();
child.stdout.take();
child.stderr.take();
Ok(child)
}
}
/// Represents the master end of a pty.
/// The file descriptor will be closed when the Pty is dropped.
pub struct UnixMasterPty {
fd: PtyFd,
}
/// Represents the slave end of a pty.
/// The file descriptor will be closed when the Pty is dropped.
pub struct UnixSlavePty {
fd: PtyFd,
}
/// Helper function to set the close-on-exec flag for a raw descriptor
fn cloexec(fd: RawFd) -> Result<(), Error> {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
if flags == -1 {
bail!(
"fcntl to read flags failed: {:?}",
io::Error::last_os_error()
);
}
let result = unsafe { libc::fcntl(fd, libc::F_SETFD, flags | libc::FD_CLOEXEC) };
if result == -1 {
bail!(
"fcntl to set CLOEXEC failed: {:?}",
io::Error::last_os_error()
);
}
Ok(())
}
impl UnixSlavePty {
fn spawn_command(&self, builder: &mut Command) -> Result<std::process::Child, Error> {
Ok(self.fd.spawn_command(builder)?)
}
}
impl UnixMasterPty {
#[allow(unused)]
fn resize(&self, size: PtySize) -> Result<(), Error> {
self.fd.resize(size)
}
#[allow(unused)]
fn get_size(&self) -> Result<PtySize, Error> {
self.fd.get_size()
}
fn try_clone_reader(&self) -> Result<Box<dyn Read + Send>, Error> {
let fd = PtyFd(self.fd.try_clone()?);
Ok(Box::new(fd))
}
}

View File

@ -1,4 +1,9 @@
use std::{collections::BTreeSet, convert::TryInto, fs, path::PathBuf};
use std::{
collections::BTreeSet,
convert::TryInto,
fs,
path::{Path, PathBuf},
};
use pkg::{PackageName, package::PackageError, recipes};
use regex::Regex;
@ -10,7 +15,7 @@ use serde::{
use crate::WALK_DEPTH;
/// Specifies how to download the source for a recipe
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum SourceRecipe {
/// Reuse the source directory of another package
@ -83,7 +88,7 @@ impl SourceRecipe {
}
/// Specifies how to build a recipe
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
#[serde(tag = "template")]
pub enum BuildKind {
/// Will not build (for meta packages)
@ -129,7 +134,7 @@ impl Default for BuildKind {
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
pub struct BuildRecipe {
#[serde(flatten, default)]
pub kind: BuildKind,
@ -137,7 +142,7 @@ pub struct BuildRecipe {
pub dependencies: Vec<PackageName>,
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
pub struct PackageRecipe {
#[serde(default)]
pub dependencies: Vec<PackageName>,
@ -146,7 +151,7 @@ pub struct PackageRecipe {
}
/// Everything required to build a Redox package
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize)]
pub struct Recipe {
/// Specifies how to download the source for this recipe
pub source: Option<SourceRecipe>,
@ -158,7 +163,19 @@ pub struct Recipe {
pub package: PackageRecipe,
}
#[derive(Debug, PartialEq)]
impl Recipe {
pub fn new(file: &PathBuf) -> Result<Recipe, PackageError> {
if !file.is_file() {
return Err(PackageError::FileMissing(file.clone()));
}
let toml = fs::read_to_string(&file)
.map_err(|err| PackageError::Parse(DeError::custom(err), Some(file.clone())))?;
let recipe: Recipe = toml::from_str(&toml)
.map_err(|err| PackageError::Parse(DeError::custom(err), Some(file.clone())))?;
Ok(recipe)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CookRecipe {
pub name: PackageName,
pub dir: PathBuf,
@ -168,24 +185,7 @@ pub struct CookRecipe {
}
impl CookRecipe {
pub fn new(
name: impl TryInto<PackageName, Error = PackageError>,
) -> Result<Self, PackageError> {
let name: PackageName = name.try_into()?;
let dir = recipes::find(name.as_str())
.ok_or_else(|| PackageError::PackageNotFound(name.clone()))?;
let file = dir.join("recipe.toml");
if !file.is_file() {
return Err(PackageError::FileMissing(file));
}
let toml = fs::read_to_string(&file)
.map_err(|err| PackageError::Parse(DeError::custom(err), Some(file.clone())))?;
let recipe: Recipe = toml::from_str(&toml)
.map_err(|err| PackageError::Parse(DeError::custom(err), Some(file)))?;
let dir = dir.to_path_buf();
pub fn new(name: PackageName, dir: PathBuf, recipe: Recipe) -> Result<Self, PackageError> {
Ok(Self {
name,
dir,
@ -194,6 +194,29 @@ impl CookRecipe {
})
}
pub fn from_name(
name: impl TryInto<PackageName, Error = PackageError>,
) -> Result<Self, PackageError> {
let name: PackageName = name.try_into()?;
let dir = recipes::find(name.as_str())
.ok_or_else(|| PackageError::PackageNotFound(name.clone()))?;
let file = dir.join("recipe.toml");
let recipe = Recipe::new(&file)?;
Self::new(name, dir.to_path_buf(), recipe)
}
pub fn from_path(dir: &Path, read_recipe: bool) -> Result<Self, PackageError> {
let file = dir.join("recipe.toml");
let name: PackageName = dir.file_name().unwrap().try_into()?;
let recipe = if read_recipe {
Recipe::new(&file)?
} else {
// clean/unfetch don't need to read recipe
Recipe::default()
};
Self::new(name, dir.to_path_buf(), recipe)
}
pub fn new_recursive(
names: &[PackageName],
recursion: usize,
@ -204,7 +227,7 @@ impl CookRecipe {
let mut recipes = Vec::new();
for name in names {
let recipe = Self::new(name.as_str())?;
let recipe = Self::from_name(name.as_str())?;
let dependencies =
Self::new_recursive(&recipe.recipe.build.dependencies, recursion - 1).map_err(
@ -253,7 +276,7 @@ impl CookRecipe {
let mut recipes: Vec<PackageName> = Vec::new();
for name in names {
let recipe = Self::new(name.as_str())?;
let recipe = Self::from_name(name.as_str())?;
let dependencies = Self::get_package_deps_recursive(
&recipe.recipe.package.dependencies,

View File

@ -1,20 +1,13 @@
#!/usr/bin/env bash
set -e
source config.sh
source `dirname "$0"`/config.sh
if [ $# = 0 ]
then
recipes="$(list_recipes --short)"
recipes="--all"
else
recipes="$@"
fi
for recipe_name in $recipes
do
recipe_path=`find_recipe $recipe_name`
echo -e "\033[01;38;5;215mcook - unfetch $recipe_name\033[0m"
rm -rfv "$recipe_path"/source "$recipe_path"/source.tar
done
repo unfetch $recipes