From 01901959e167941653412c3668930643cdcdb8c0 Mon Sep 17 00:00:00 2001 From: llenotre Date: Sat, 30 Aug 2025 20:57:10 +0200 Subject: [PATCH 01/15] refactor: remove server --- Cargo.lock | 264 +++++++----------------------- Cargo.toml | 1 - README.md | 1 - builder/src/main.rs | 4 +- client/Cargo.toml | 1 + client/bleh/var/lib/blimp/remotes | 0 client/src/install.rs | 22 +-- client/src/main.rs | 232 +++++++++----------------- client/src/remote.rs | 44 +++-- client/src/remove.rs | 8 +- client/src/update.rs | 6 +- common/Cargo.toml | 2 +- common/src/lib.rs | 47 +++++- common/src/repository/remote.rs | 35 ++-- server/.gitignore | 1 - server/Cargo.toml | 12 -- server/default_config.json | 7 - server/src/main.rs | 50 ------ server/src/route/mod.rs | 15 -- server/src/route/package.rs | 84 ---------- 20 files changed, 238 insertions(+), 598 deletions(-) create mode 100644 client/bleh/var/lib/blimp/remotes delete mode 100644 server/.gitignore delete mode 100644 server/Cargo.toml delete mode 100644 server/default_config.json delete mode 100644 server/src/main.rs delete mode 100644 server/src/route/mod.rs delete mode 100644 server/src/route/package.rs diff --git a/Cargo.lock b/Cargo.lock index 0c779e1..984f239 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,60 +97,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "axum" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" -dependencies = [ - "axum-core", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.75" @@ -197,6 +143,7 @@ dependencies = [ name = "blimp" version = "0.1.0" dependencies = [ + "clap", "common", ] @@ -212,18 +159,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "blimp-server" -version = "0.1.0" -dependencies = [ - "axum", - "common", - "envy", - "serde", - "tracing", - "tracing-subscriber", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -298,9 +233,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.39" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" dependencies = [ "clap_builder", "clap_derive", @@ -308,9 +243,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.39" +version = "4.5.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" dependencies = [ "anstream", "anstyle", @@ -320,9 +255,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck", "proc-macro2", @@ -349,7 +284,6 @@ dependencies = [ "anyhow", "bytes", "bzip2", - "clap", "flate2", "futures", "futures-util", @@ -362,6 +296,7 @@ dependencies = [ "tar", "tokio", "tokio-util", + "toml", "utils", "xz2", ] @@ -460,15 +395,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "envy" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" -dependencies = [ - "serde", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -752,12 +678,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "hyper" version = "1.6.0" @@ -771,7 +691,6 @@ dependencies = [ "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -998,12 +917,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.172" @@ -1050,12 +963,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - [[package]] name = "memchr" version = "2.7.4" @@ -1105,16 +1012,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "number_prefix" version = "0.4.0" @@ -1186,12 +1083,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "password-hash" version = "0.5.0" @@ -1520,12 +1411,11 @@ dependencies = [ ] [[package]] -name = "serde_path_to_error" -version = "0.1.17" +name = "serde_spanned" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ - "itoa", "serde", ] @@ -1552,15 +1442,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shlex" version = "1.3.0" @@ -1686,16 +1567,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "tinystr" version = "0.8.1" @@ -1766,6 +1637,45 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + [[package]] name = "tower" version = "0.5.2" @@ -1779,7 +1689,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -1800,23 +1709,10 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" version = "0.1.33" @@ -1824,32 +1720,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "nu-ansi-term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", ] [[package]] @@ -1925,12 +1795,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - [[package]] name = "vcpkg" version = "0.2.15" @@ -2071,28 +1935,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-link" version = "0.1.1" @@ -2274,6 +2116,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index a71beba..830497e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "builder", "client", "common", - "server", ] [profile.release] diff --git a/README.md b/README.md index 572da1a..77441d3 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ Blimp is a simple package manager for Unix-like operating systems, more specific This repository contains the following components: - `blimp`: The package manager itself - `blimp-builder`: An utility to build packages -- `blimp-server`: The package manager's server The `common` crate is a library with utilities shared across components. diff --git a/builder/src/main.rs b/builder/src/main.rs index 1342746..abe39ca 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -12,12 +12,12 @@ use crate::{ }; use common::{ anyhow::{anyhow, bail, Result}, - clap::Parser, repository::Repository, serde_json, tokio::runtime::Runtime, }; use std::{env, fs, io, path::PathBuf, process::exit, str}; +use clap::Parser; /// The path to the work directory. const WORK_DIR: &str = "work/"; @@ -31,7 +31,7 @@ const WORK_DIR: &str = "work/"; \tTARGET: Target triplet for which the package builds (this is useful when cross-compiling compilers) \tBLIMP_DEBUG: If set to `true`, build files are kept for troubleshooting purpose -All environment variable are optional")] +All environment variables are optional")] #[command(version, about, long_about = None)] struct Args { /// Path to the directory containing the package to build. diff --git a/client/Cargo.toml b/client/Cargo.toml index 8a70b31..328e893 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +clap = { version = "4.5.39", features = ["derive"] } common = { path = "../common" } [features] diff --git a/client/bleh/var/lib/blimp/remotes b/client/bleh/var/lib/blimp/remotes new file mode 100644 index 0000000..e69de29 diff --git a/client/src/install.rs b/client/src/install.rs index ad2f96c..10c3093 100644 --- a/client/src/install.rs +++ b/client/src/install.rs @@ -8,7 +8,7 @@ use common::{ repository::Repository, Environment, }; -use std::{collections::HashMap, io, path::PathBuf}; +use std::{collections::HashMap, io}; // TODO Clean /// Installs the given list of packages. @@ -16,15 +16,15 @@ use std::{collections::HashMap, io, path::PathBuf}; /// Arguments: /// - `names` is the list of packages to install. /// - `env` is the blimp environment. -/// - `local_repos` is the list of paths to local package repositories. -pub async fn install( - names: &[String], - env: &mut Environment, - local_repos: Vec, -) -> Result<()> { +pub async fn install(names: &[String], env: &mut Environment) -> Result<()> { + if names.is_empty() { + bail!("must specify at least one package"); + } // The list of repositories - let repos = local_repos - .into_iter() + let repos = env + .local_repos() + .iter() + .cloned() .map(Repository::load) .collect::>>()?; // Tells whether the operation failed @@ -100,7 +100,7 @@ pub async fn install( None => { // Get package size from remote let remote = repo.get_remote().unwrap(); - let size = remote.get_size(pkg).await?; + let size = remote.get_size(env, pkg).await?; total_size += size; println!("\t- {name} ({version}) - download size: {size}"); } @@ -147,7 +147,7 @@ pub async fn install( .write(true) .truncate(true) .open(path)?; - let url = remote.download_url(pkg); + let url = remote.download_url(env, pkg); let mut task = DownloadTask::new(&url, &file).await?; while task.next().await? > 0 {} Ok::<(), common::anyhow::Error>(()) diff --git a/client/src/main.rs b/client/src/main.rs index 321079c..d32d638 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -8,185 +8,101 @@ mod remove; #[cfg(feature = "network")] mod update; +use clap::{Args, Parser, Subcommand}; use common::{ anyhow::{anyhow, Result}, tokio, Environment, }; use install::install; use remove::remove; -use std::{ - env, - path::{Path, PathBuf}, - process::exit, -}; +use std::{env, path::PathBuf, process::exit}; + +#[derive(Args, Clone, Debug)] +struct PkgList { + /// Packages + packages: Vec, +} -/// Prints command line usage. -fn print_usage() { - eprintln!( - "blimp package manager version {}", - env!("CARGO_PKG_VERSION") - ); - eprintln!(); - eprintln!("USAGE:"); - eprintln!("\tblimp [OPTIONS]"); - eprintln!(); - eprintln!("COMMAND:"); - eprintln!("\tinfo : Prints information about the given package(s)"); - eprintln!("\tinstall : Installs the given package(s)"); +#[derive(Clone, Debug, Subcommand)] +enum Action { + /// Synchronizes packages information from remotes #[cfg(feature = "network")] - { - eprintln!("\tupdate: Synchronizes packages information from remote"); - } - eprintln!( - "\tupgrade [package...]: Upgrades the given package(s). If no package is specified, \ -the package manager updates every package that is not up to date" - ); - eprintln!("\tremove : Removes the given package(s)"); - eprintln!("\tclean: Clean the cache"); + Update, + /// Prints information about the given package(s) + Info(PkgList), + /// Installs the given package(s) + Install(PkgList), + /// Upgrades the given package(s). If no package is specified, the package manager updates + /// every package that is not up-to-date + Upgrade(PkgList), + /// Removes the given package(s) + Remove(PkgList), + /// Cleans the cache + Clean, + /// Lists remote servers #[cfg(feature = "network")] - { - eprintln!("\tremote-list: Lists remote servers"); - eprintln!("\tremote-add : Adds a remote server"); - eprintln!("\tremote-remove : Removes a remote server"); - } - eprintln!(); - eprintln!("OPTIONS:"); - eprintln!("\t--verbose: Enables verbose mode"); - eprintln!( - "\t--version : When installing or upgrading, this option allows to \ -specify a version" - ); - eprintln!(); - eprintln!("ENVIRONMENT VARIABLES:"); - eprintln!("\tSYSROOT: Specifies the path to the system's root"); - eprintln!( - "\tLOCAL_REPO: Specifies paths separated by `:` at which packages are \ -stored locally (the SYSROOT variable does not apply to these paths)" - ); + RemoteList, + /// Adds a remote server + #[cfg(feature = "network")] + RemoteAdd { remote: String }, + /// Removes a remote server + #[cfg(feature = "network")] + RemoteRemove { remote: String }, } -/// Returns an environment for the given sysroot. -/// -/// If the environment's lockfile cannot be acquired, the function returns an error. -fn get_env(sysroot: &Path) -> Result { - Environment::with_root(sysroot)?.ok_or(anyhow!("failed to acquire lockfile")) +#[derive(Parser, Debug)] +#[command(version, about, long_about = None, after_long_help = "Environment variables: +\tSYSROOT: Specifies the path to the system's root +\tLOCAL_REPO: Specifies paths separated by `:` at which packages are stored locally (the SYSROOT variable does not apply to these paths) + +All environment variables are optional")] +struct Cli { + #[command(subcommand)] + action: Action, + /// The branch to use on package repositories, defaults to `stable` + #[arg(short, long)] + branch: Option, + /// The architecture to install for, defaults to the current + #[arg(short, long)] + arch: Option, } -async fn main_impl(sysroot: &Path, local_repos: Vec) -> Result { - // If no argument is specified, print usage - let args: Vec = env::args().collect(); - if args.len() <= 1 { - print_usage(); - return Ok(false); - } - // Match command - match args[1].as_str() { - "info" => { - let packages = &args[2..]; - if packages.is_empty() { - eprintln!("Please specify one or several packages"); - return Ok(false); - } - todo!() - } +async fn main_impl() -> Result<()> { + let args = Cli::parse(); + let sysroot = env::var_os("SYSROOT") + .map(PathBuf::from) + .unwrap_or(PathBuf::from("/")); + let local_repos = env::var("LOCAL_REPO") // TODO var_os + .map(|s| s.split(':').map(PathBuf::from).collect()) + .unwrap_or_default(); + let mut env = Environment::acquire(&sysroot, local_repos, args.branch, args.arch)? + .ok_or_else(|| anyhow!("failed to acquire lockfile"))?; + match args.action { #[cfg(feature = "network")] - "update" => { - let mut env = get_env(sysroot)?; - update::update(&mut env).await?; - Ok(true) - } - "install" => { - let names = &args[2..]; - if names.is_empty() { - eprintln!("Please specify one or several packages"); - return Ok(false); - } - let mut env = get_env(sysroot)?; - install(names, &mut env, local_repos).await?; - Ok(true) - } - "upgrade" => { - let names = &args[2..]; - if names.is_empty() { - eprintln!("Please specify one or several packages"); - return Ok(false); - } - let _env = get_env(sysroot)?; - todo!() - } - "remove" => { - let names = &args[2..]; - if names.is_empty() { - eprintln!("Please specify one or several packages"); - return Ok(false); - } - let mut env = get_env(sysroot)?; - remove(names, &mut env)?; - Ok(true) - } - "clean" => { - let _env = get_env(sysroot)?; - todo!() - } + Action::Update => update::update(&mut env).await?, + Action::Info(_names) => todo!(), + Action::Install(names) => install(&names.packages, &mut env).await?, + Action::Upgrade(_names) => todo!(), + Action::Remove(names) => remove(&names.packages, &mut env)?, + Action::Clean => todo!(), #[cfg(feature = "network")] - "remote-list" => { - let env = get_env(sysroot)?; - remote::list(&env).await?; - Ok(true) - } + Action::RemoteList => remote::list(&env).await?, #[cfg(feature = "network")] - "remote-add" => { - let names = &args[2..]; - if names.is_empty() { - eprintln!("Please specify one or several remotes to add"); - return Ok(false); - } - let mut env = get_env(sysroot)?; - remote::add(&mut env, names)?; - Ok(true) - } + Action::RemoteAdd { + remote, + } => remote::add(&mut env, remote)?, #[cfg(feature = "network")] - "remote-remove" => { - let names = &args[2..]; - if names.is_empty() { - eprintln!("Please specify one or several remotes to remove"); - return Ok(false); - } - let mut env = get_env(sysroot)?; - remote::remove(&mut env, names)?; - Ok(true) - } - #[cfg(not(feature = "network"))] - "update" | "remote-list" | "remote-add" | "remote-remove" => { - eprintln!( - "This feature is not enabled. To use it, recompile the package manager with the feature `network`" - ); - Ok(false) - } - cmd => { - eprintln!("Command `{cmd}` does not exist"); - eprintln!(); - print_usage(); - Ok(false) - } + Action::RemoteRemove { + remote, + } => remote::remove(&mut env, remote)?, } + Ok(()) } #[tokio::main] async fn main() { - let sysroot = env::var_os("SYSROOT") - .map(PathBuf::from) - .unwrap_or(PathBuf::from("/")); - let local_repos = env::var("LOCAL_REPO") // TODO var_os - .map(|s| s.split(':').map(PathBuf::from).collect()) - .unwrap_or_default(); - let res = main_impl(&sysroot, local_repos).await; - match res { - Ok(false) => exit(1), - Err(e) => { - eprintln!("error: {e}"); - exit(1); - } - _ => {} + if let Err(e) = main_impl().await { + eprintln!("error: {e}"); + exit(1); } } diff --git a/client/src/remote.rs b/client/src/remote.rs index 2752ca6..7540c48 100644 --- a/client/src/remote.rs +++ b/client/src/remote.rs @@ -8,47 +8,43 @@ pub async fn list(env: &Environment) -> std::io::Result<()> { println!("Remotes list:"); for remote in remotes { let host = &remote.host; - match remote.fetch_motd().await { - Ok(motd) => println!("- {host} (status: UP): {motd}"), + match remote.fetch_metadata().await { + Ok(metadata) => println!("- {host} (status: UP): {motd}", motd = metadata.motd), Err(_) => println!("- {host} (status: DOWN)"), } } Ok(()) } -/// Adds one or several remotes. +/// Adds a remote. /// /// Arguments: -/// - `env` is the environment. -/// - `remotes` is the list of remotes to add. -pub fn add(env: &mut Environment, new_remotes: &[String]) -> std::io::Result<()> { +/// - `env` is the environment +/// - `remote` is the remote to add +pub fn add(env: &mut Environment, remote: String) -> std::io::Result<()> { let mut remotes = Remote::load_list(env)?; - for remote in new_remotes { - if remotes.contains(remote.as_str()) { - eprintln!("Remote `{remote}` already exists"); - } else { - println!("Add remote `{remote}`"); - remotes.insert(Remote { - host: remote.clone(), - }); - } + if remotes.contains(remote.as_str()) { + eprintln!("Remote `{remote}` already exists"); + } else { + println!("Add remote `{remote}`"); + remotes.insert(Remote { + host: remote, + }); } Remote::save_list(env, remotes.into_iter())?; Ok(()) } -/// Removes one or several remotes. +/// Removes a remote. /// /// Arguments: -/// - `env` is the environment. -/// - `remotes` is the list of remotes to remove. -pub fn remove(env: &mut Environment, new_remotes: &[String]) -> std::io::Result<()> { +/// - `env` is the environment +/// - `remote` is the remote to remove +pub fn remove(env: &mut Environment, remote: String) -> std::io::Result<()> { let mut remotes = Remote::load_list(env)?; - for remote in new_remotes { - let existed = remotes.remove(remote.as_str()); - if !existed { - eprintln!("Remote `{remote}` not found"); - } + let existed = remotes.remove(remote.as_str()); + if !existed { + eprintln!("Remote `{remote}` not found"); } Remote::save_list(env, remotes.into_iter())?; Ok(()) diff --git a/client/src/remove.rs b/client/src/remove.rs index 51295ea..d52d825 100644 --- a/client/src/remove.rs +++ b/client/src/remove.rs @@ -1,14 +1,16 @@ //! TODO doc use common::{anyhow::Result, Environment}; - -// TODO ask for confirm before remove +use common::anyhow::bail; /// Removes the given list of packages. /// /// Arguments: /// - `names` is the list of packages to remove. /// - `env` is the blimp environment. -pub fn remove(_names: &[String], _env: &mut Environment) -> Result<()> { +pub fn remove(names: &[String], _env: &mut Environment) -> Result<()> { + if names.is_empty() { + bail!("must specify at least one package"); + } todo!() } diff --git a/client/src/update.rs b/client/src/update.rs index 2afed2a..2f9832e 100644 --- a/client/src/update.rs +++ b/client/src/update.rs @@ -9,11 +9,11 @@ use common::{ /// Updates the packages list. pub async fn update(env: &mut Environment) -> Result<()> { let remotes = Remote::load_list(env) - .map_err(|error| anyhow!("Could not update packages list: {error}"))?; - println!("Updating from remotes..."); + .map_err(|error| anyhow!("could not update packages list: {error}"))?; + println!("Update from remotes..."); let mut futures = Vec::new(); for r in &remotes { - futures.push((&r.host, r.fetch_list())); + futures.push((&r.host, r.fetch_list(env))); } let mut failed = false; for (host, f) in futures { diff --git a/common/Cargo.toml b/common/Cargo.toml index 364fedc..46087f3 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -10,7 +10,6 @@ path = "src/lib.rs" anyhow = "1.0.97" bytes = "1.10.1" bzip2 = "0.5.2" -clap = "4.5.39" flate2 = "1.1.1" futures = "0.3.31" futures-util = "0.3.31" @@ -23,6 +22,7 @@ serde_json = "1.0.140" tar = "0.4.44" tokio = { version = "1.44.2", features = ["fs", "macros", "rt", "rt-multi-thread"] } tokio-util = { version = "0.7.15", features = ["io"] } +toml = "0.9.5" utils = { git = "https://github.com/maestro-os/maestro-utils" } xz2 = "0.1.7" diff --git a/common/src/lib.rs b/common/src/lib.rs index fc991f3..a5f7e0e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -3,7 +3,6 @@ #![feature(io_error_more)] pub use anyhow; -pub use clap; pub use flate2; pub use serde_json; pub use tar; @@ -45,21 +44,39 @@ pub const USER_AGENT: &str = concat!("blimp/", env!("CARGO_PKG_VERSION")); /// /// The lockfile is destroyed when the environment is dropped. pub struct Environment { - /// The path to the sysroot of the environment. + /// The path to the sysroot of the environment sysroot: PathBuf, + /// Local repositories, if any + local_repos: Vec, + /// The branch to install from + branch: String, + /// The architecture to install for + arch: String, } impl Environment { - /// Returns an instance for the environment with the given sysroot. + /// Tries to lock the environment at `sysroot` so that no other instance can access it at the same time. /// - /// The function tries to lock the environment so that no other instance can access it at the - /// same time. If already locked, the function returns `None`. - pub fn with_root(sysroot: &Path) -> io::Result> { + /// Arguments: + /// - `sysroot` is the root directory of the system to lock + /// - `local_repos` is the list of local repositories, if any + /// - `branch` is the repository branch to use. Default: `stable` + /// - `arch` is the architecture to use. Defaults to the current + /// + /// If the environment is already locked, the function returns `None`. + pub fn acquire(sysroot: &Path, local_repos: Vec, branch: Option, arch: Option) -> io::Result> { let sysroot = sysroot.canonicalize()?; let path = sysroot.join(LOCKFILE_PATH); let acquired = lockfile::lock(&path)?; + #[cfg(target_arch = "x86")] + let default_arch = "x86"; + #[cfg(target_arch = "x86_64")] + let default_arch = "x86_64"; Ok(acquired.then_some(Self { sysroot, + local_repos, + branch: branch.unwrap_or_else(|| "stable".to_owned()), + arch: arch.unwrap_or_else(|| default_arch.to_owned()), })) } @@ -67,6 +84,24 @@ impl Environment { pub fn sysroot(&self) -> &Path { &self.sysroot } + + /// Returns the local repositories list + #[inline] + pub fn local_repos(&self) -> &[PathBuf] { + &self.local_repos + } + + /// Returns the repository branch to use + #[inline] + pub fn branch(&self) -> &str { + &self.branch + } + + /// Returns the repository architecture to use + #[inline] + pub fn architecture(&self) -> &str { + &self.arch + } /// Returns the installed version for the package with the given `name`. pub fn get_installed_version(&self, _name: &str) -> Option { diff --git a/common/src/repository/remote.rs b/common/src/repository/remote.rs index 05341bb..c9f2659 100644 --- a/common/src/repository/remote.rs +++ b/common/src/repository/remote.rs @@ -10,9 +10,10 @@ use std::{ io, io::{BufRead, BufReader, BufWriter, Write}, }; +use serde::Deserialize; /// The file which contains the list of remotes. -const REMOTES_FILE: &str = "var/lib/blimp/remotes_list"; +const REMOTES_FILE: &str = "var/lib/blimp/remotes"; /// A remote host. #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -21,6 +22,14 @@ pub struct Remote { pub host: String, } +#[derive(Deserialize)] +pub struct RemoteMetadata { + /// The server's motd + pub motd: String, + /// Available branches on the server + pub branches: Vec, +} + impl Borrow for Remote { fn borrow(&self) -> &str { &self.host @@ -61,7 +70,7 @@ impl Remote { } /// Returns the remote's motd. - pub async fn fetch_motd(&self) -> Result { + pub async fn fetch_metadata(&self) -> Result { let client = reqwest::Client::new(); let url = format!("https://{}/motd", &self.host); let response = client @@ -71,15 +80,19 @@ impl Remote { .await?; let status = response.status(); match status { - StatusCode::OK => Ok(response.text().await?), - _ => bail!("Failed to retrieve motd (status {status})"), + StatusCode::OK => { + let s = response.text().await?; + let metadata = toml::from_str(&s)?; + Ok(metadata) + }, + _ => bail!("Failed to retrieve remote metadata (status {status})"), } } /// Fetches the list of all the packages from the remote. - pub async fn fetch_list(&self) -> Result> { + pub async fn fetch_list(&self, env: &Environment) -> Result> { let client = reqwest::Client::new(); - let url = format!("https://{}/package", self.host); + let url = format!("https://{}/{}/package", self.host, env.branch); let response = client .get(url) .header("User-Agent", USER_AGENT) @@ -93,18 +106,18 @@ impl Remote { } /// Returns the download URL for the given `package`. - pub fn download_url(&self, package: &Package) -> String { + pub fn download_url(&self, env: &Environment, package: &Package) -> String { format!( - "https://{}/package/{}/version/{}/archive", - self.host, package.name, package.version + "https://{}/{}/dist/{}/{}_{}.tar.zstd", + self.host, env.branch, env.arch, package.name, package.version ) } /// Returns the download size of the package `package` in bytes. - pub async fn get_size(&self, package: &Package) -> Result { + pub async fn get_size(&self, env: &Environment, package: &Package) -> Result { let client = reqwest::Client::new(); client - .head(self.download_url(package)) + .head(self.download_url(env, package)) .header("User-Agent", USER_AGENT) .send() .await? diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index d344ba6..0000000 --- a/server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -config.json diff --git a/server/Cargo.toml b/server/Cargo.toml deleted file mode 100644 index 9168b6a..0000000 --- a/server/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "blimp-server" -version = "0.1.0" -edition = "2021" - -[dependencies] -axum = "0.8.3" -common = { path = "../common" } -envy = "0.4.2" -serde = { version = "1.0.219", features = ["derive"] } -tracing = "0.1.40" -tracing-subscriber = "0.3.18" diff --git a/server/default_config.json b/server/default_config.json deleted file mode 100644 index a9455ab..0000000 --- a/server/default_config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "port": 80, - - "motd": "This is a dummy blimp server", - - "repo_path": "path/to/public/repository" -} diff --git a/server/src/main.rs b/server/src/main.rs deleted file mode 100644 index c1e0bd7..0000000 --- a/server/src/main.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! The blimp server serves packages to be installed by the package manager. - -mod route; - -use axum::{routing::get, Router}; -use common::{repository::Repository, tokio}; -use serde::Deserialize; -use std::{io, path::PathBuf, sync::Arc}; - -/// The server's configuration. -#[derive(Deserialize)] -pub struct Config { - /// The server's port. - pub port: u16, - /// The server's motd. - pub motd: String, - /// The path to the repository containing the server's packages. - pub repo_path: String, -} - -/// The server's global context. -pub struct Context { - /// The server's motd. - motd: String, - /// The server's repository. - repo: Repository, -} - -#[tokio::main] -async fn main() -> io::Result<()> { - tracing_subscriber::fmt::init(); - let config: Config = envy::from_env().expect("configuration"); - let ctx = Arc::new(Context { - motd: config.motd, - repo: Repository::load(PathBuf::from(config.repo_path))?, - }); - let router = Router::new() - .route("/", get(route::root)) - .route("/motd", get(route::motd)) - .route("/package", get(route::package::list)) - .route("/package/:name/version/:version", get(route::package::info)) - .route( - "/package/:name/version/:version/archive", - get(route::package::archive), - ) - // TODO logging layer - .with_state(ctx); - let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", config.port)).await?; - axum::serve(listener, router).await -} diff --git a/server/src/route/mod.rs b/server/src/route/mod.rs deleted file mode 100644 index 8861ce2..0000000 --- a/server/src/route/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Endpoints implementations. - -pub mod package; - -use crate::Context; -use axum::extract::State; -use std::sync::Arc; - -pub async fn root() -> &'static str { - concat!("Blimp server version ", env!("CARGO_PKG_VERSION")) -} - -pub async fn motd(State(ctx): State>) -> String { - ctx.motd.clone() -} diff --git a/server/src/route/package.rs b/server/src/route/package.rs deleted file mode 100644 index 5f3f552..0000000 --- a/server/src/route/package.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Package endpoints. - -use crate::Context; -use axum::{ - body::Body, - extract::{Path, State}, - http::{header::CONTENT_TYPE, StatusCode}, - response::{IntoResponse, Response}, - Json, -}; -use common::{package, tokio::fs::File, tokio_util::io::ReaderStream, version::Version}; -use std::sync::Arc; -use tracing::error; - -/// Endpoint to list packages. -pub async fn list(State(ctx): State>) -> Response { - let res = ctx.repo.list_packages(); - match res { - Ok(packages) => Json(packages).into_response(), - Err(error) => { - error!(%error, "could not list packages"); - (StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response() - } - } -} - -/// Endpoint to get information about a package. -pub async fn info( - Path((name, version)): Path<(String, String)>, - State(ctx): State>, -) -> Response { - if !package::is_valid_name(&name) { - return (StatusCode::BAD_REQUEST, "invalid package name").into_response(); - } - let Ok(version) = Version::try_from(version.as_str()) else { - return (StatusCode::BAD_REQUEST, "invalid package version").into_response(); - }; - let res = ctx.repo.get_package(&name, &version); - match res { - Ok(Some(pkg)) => Json(pkg).into_response(), - Ok(None) => (StatusCode::NOT_FOUND, "package or version not found").into_response(), - Err(error) => { - error!(%error, name, %version, "could read package"); - (StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response() - } - } -} - -/// Endpoint to get the package's archive. -pub async fn archive( - Path((name, version)): Path<(String, String)>, - State(ctx): State>, -) -> Response { - if !package::is_valid_name(&name) { - return (StatusCode::BAD_REQUEST, "invalid package name").into_response(); - } - let Ok(version) = Version::try_from(version.as_str()) else { - return (StatusCode::BAD_REQUEST, "invalid package version").into_response(); - }; - // Check package exists - let res = ctx.repo.get_package(&name, &version); - match res { - Ok(Some(_)) => {} - Ok(None) => { - return (StatusCode::NOT_FOUND, "package or version not found").into_response() - } - Err(error) => { - error!(%error, name, %version, "could read package"); - return (StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response(); - } - } - // Read archive - let archive_path = ctx.repo.get_archive_path(&name, &version); - let res = File::open(&archive_path).await; - let file = match res { - Ok(f) => f, - Err(error) => { - error!(%error, name, %version, path = %archive_path.display(), "could not read package archive"); - return (StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response(); - } - }; - let body = Body::from_stream(ReaderStream::new(file)); - ([(CONTENT_TYPE, "application/x-gzip-compressed")], body).into_response() -} From 39f994da6d57d3db4200889854635144401b744e Mon Sep 17 00:00:00 2001 From: llenotre Date: Sat, 30 Aug 2025 21:00:12 +0200 Subject: [PATCH 02/15] feat: man pages --- man/man7/blimp.7 | 73 ++++++++++++++++++++++++++++++++++++++++++++++ man/man8/blimp.8 | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 man/man7/blimp.7 create mode 100644 man/man8/blimp.8 diff --git a/man/man7/blimp.7 b/man/man7/blimp.7 new file mode 100644 index 0000000..1438008 --- /dev/null +++ b/man/man7/blimp.7 @@ -0,0 +1,73 @@ +.TH BLIMP 7 +.SH NAME +blimp - package manager internals +.SH DESCRIPTION +.TP +.BR "Server-side files hierarchy" +.RS +Blimp downloads packages from a HTTP server. The server-side files hierarchy is designed in such a way to allow hosting packages without requiring a specific server program. + +Example: +.nf +.B - metadata +.B - stable/ +.B " "- index +.B " "- dist/ +.B " "- x86_64/ +.B " "- _.tar.zstd +.B " "- _.meta + ... +.B " "- x86/ + ... + ... +.B " "- i18n/ +.B " "- en/ +.B " "- _/ + ... + ... +.B " "- fr/ + ... + ... +.B " "- src/ +.B " "- _/ + ... + ... +.B - nightly/ + ... +.fi + +.B metadata +contains information about the server, in TOML format + +.B dist/ +contains all branches. +.B stable +is the canonical name for production-ready packages, while +.B nightly +is for unstable packages + +.B index +contains the list of all packages and their supported architecture, in TOML format. It is used by the client to determine the list of packages present on the server + +.B dist/ +contains packages (*.tar.zstd) and their metadata (*.meta), sorted by CPU architecture + +.B i18n/ +contains internationalization files, sorted by locale + +.B src/ +contains package sources + +When updating the packages list, the client first looks at +.B metadata +to determine the list of branches, and then it looks at the +.B index +in each, containing the list of packages. + +To download a package, the client looks up the required version in its local copy of the server's index. Then it downloads the +.B .meta +file, to look at the required dependencies. Dependencies are looked-up recursively until they are all found. Then, the client downloads all packages and installs them. +.RE +.SH "SEE ALSO" +.sp +\fBblimp\fP(1), \fBblimp-builder\fP(1) diff --git a/man/man8/blimp.8 b/man/man8/blimp.8 new file mode 100644 index 0000000..798a116 --- /dev/null +++ b/man/man8/blimp.8 @@ -0,0 +1,75 @@ +.TH BLIMP 8 +.SH NAME +blimp \- Maestro's package manager +.SH SYNOPSIS +.B blimp +update +.br +.B blimp +info +.br +.B blimp +install +.br +.B blimp +upgrade [package...] +.br +.B blimp +remove +.br +.B blimp +clean +.br +.B blimp +remote-list +.br +.B blimp +remote-add +.br +.B blimp +remote-remove +.SH DESCRIPTION +.PP +Maestro's package manager installs and upgrades packages on the system. It is able to download packages from remote servers, manages dependencies, and bootstrap new systems. +.TP +Each command has to specify an action to perform: +.TP +.B update +synchronizes packages information from remotes +.TP +.B info +prints information about the given package(s) +.TP +.B install +installs the given package(s) +.TP +.B upgrade +upgrades the given package(s). If no package is specified, the package manager updates every package that is not up-to-date +.TP +.B remove +removes the given package(s) +.TP +.B clean +cleans the cache +.TP +.B remote-list +lists remote servers +.TP +.B remote-add +adds a remote server +.TP +.B remote-remove +removes a remote server +.SH "ENVIRONMENT VARIABLES" +The following environment variables are relevant to blimp: +.TP +.B SYSROOT +path treated as the root directory of the system on which packages are installed, upgraded or removed. This is useful when bootstrapping a new system +.TP +.B LOCAL_REPO +paths to local package repositories, separated by `:`. +.B SYSROOT +does not apply to these paths +.SH "SEE ALSO" +.sp +\fBblimp\fP(7), \fBblimp-builder\fP(1) From 12c3246ab5812be2e285dbf0824a683a28bca278 Mon Sep 17 00:00:00 2001 From: llenotre Date: Sun, 31 Aug 2025 21:46:31 +0200 Subject: [PATCH 03/15] refactor: package building --- Cargo.lock | 43 ++++++++++++- builder/Cargo.toml | 1 + builder/src/build.rs | 55 ++++++++++------- builder/src/main.rs | 56 ++++++++--------- client/src/install.rs | 15 ++--- client/src/main.rs | 2 +- client/src/remote.rs | 4 +- client/src/update.rs | 2 +- common/Cargo.toml | 6 +- common/src/lib.rs | 25 +++----- common/src/package.rs | 20 +++--- common/src/repository/mod.rs | 104 +++++++++++++++++++------------- common/src/repository/remote.rs | 29 ++++----- man/man7/blimp.7 | 54 +++++++---------- 14 files changed, 233 insertions(+), 183 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 984f239..6e8e4d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "file-lock", "serde", "sha2", + "toml", ] [[package]] @@ -211,6 +212,8 @@ version = "1.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -292,13 +295,13 @@ dependencies = [ "rand", "reqwest", "serde", - "serde_json", "tar", "tokio", "tokio-util", "toml", "utils", "xz2", + "zstd", ] [[package]] @@ -907,6 +910,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -2259,3 +2272,31 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/builder/Cargo.toml b/builder/Cargo.toml index 1c4f0d4..5144817 100644 --- a/builder/Cargo.toml +++ b/builder/Cargo.toml @@ -10,6 +10,7 @@ file-lock = "2.1.11" serde = { version = "1.0.219", features = ["derive"] } sha2 = "0.10.9" clap = { version = "4.5.39", features = ["derive"] } +toml = "0.9.5" [features] default = [] diff --git a/builder/src/build.rs b/builder/src/build.rs index 1fe696b..83b6df0 100644 --- a/builder/src/build.rs +++ b/builder/src/build.rs @@ -1,11 +1,7 @@ //! Implementation of the package building procedure. use crate::{desc::BuildDescriptor, WORK_DIR}; -use common::{ - anyhow::Result, - flate2::{write::GzEncoder, Compression}, - serde_json, tar, tokio, -}; +use common::{anyhow::Result, repository::Repository, tar, tokio, zstd}; use std::{ fs, fs::File, @@ -19,7 +15,7 @@ use std::{ /// A build process is the operation of converting source code into an installable package. /// /// To build a package, the following files are required: -/// - `package.json`: The file describing the package +/// - `build.toml`: Information to prepare for building the package /// - `build-hook`: The script to build the package /// /// The package is build and then installed to a fake system root, which is then compressed. @@ -28,6 +24,7 @@ pub struct BuildProcess { input_path: PathBuf, /// The build descriptor. build_desc: BuildDescriptor, + /// The path to the build directory. build_dir: PathBuf, /// The path to the system root in which the package is installed. @@ -40,13 +37,14 @@ impl BuildProcess { /// Arguments: /// - `input_path` is the path to the directory containing information to build the package. /// - `sysroot` is the path to the system root. If `None`, a directory is created. - pub fn new(input_path: PathBuf, sysroot: Option) -> io::Result { - let build_desc_path = input_path.join("package.json"); + pub fn new(input_path: PathBuf, sysroot: Option) -> Result { + let build_desc_path = input_path.join("metadata.toml"); let build_desc = fs::read_to_string(build_desc_path)?; - let build_desc = serde_json::from_str(&build_desc)?; + let build_desc = toml::from_str(&build_desc)?; Ok(Self { input_path, build_desc, + build_dir: common::util::create_tmp_dir(WORK_DIR)?, sysroot: sysroot .map(fs::canonicalize) @@ -54,11 +52,6 @@ impl BuildProcess { }) } - /// Returns the build descriptor of the package to be built. - pub fn get_build_desc(&self) -> &BuildDescriptor { - &self.build_desc - } - /// Returns the path to the build directory. pub fn get_build_dir(&self) -> &Path { &self.build_dir @@ -114,16 +107,36 @@ impl BuildProcess { .map(|s| s.success()) } + /// Writes the package's metadata to the repository + pub fn write_metadata(&self, repo: &Repository, arch: &str) -> Result<()> { + // Make sure the arch directory exists + fs::create_dir_all(repo.get_path().join(arch))?; + // Create metadata + let metadata = toml::to_string(&self.build_desc.package)?; + fs::write( + repo.get_metadata_path( + arch, + &self.build_desc.package.name, + &self.build_desc.package.version, + ), + metadata, + )?; + Ok(()) + } + /// Creates the archive of the package after being build. - /// - /// `output_path` is the path at which the package's archive will be created. - pub fn create_archive(&self, output_path: &Path) -> io::Result<()> { - let build_desc_path = self.input_path.join("package.json"); - let tar_gz = File::create(output_path)?; - let enc = GzEncoder::new(tar_gz, Compression::default()); + pub fn create_archive(&self, repo: &Repository, arch: &str) -> io::Result<()> { + let output_path = repo.get_archive_path( + arch, + &self.build_desc.package.name, + &self.build_desc.package.version, + ); + let build_desc_path = self.input_path.join("metadata.toml"); + let archive = File::create(output_path)?; + let enc = zstd::stream::Encoder::new(archive, 0)?; let mut tar = tar::Builder::new(enc); tar.follow_symlinks(false); - tar.append_path_with_name(build_desc_path, "package.json")?; + tar.append_path_with_name(build_desc_path, "metadata.toml")?; tar.append_dir_all("data", &self.sysroot)?; // TODO add install/update/remove hooks tar.finish() diff --git a/builder/src/main.rs b/builder/src/main.rs index abe39ca..b371f94 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -10,14 +10,13 @@ use crate::{ build::BuildProcess, util::{get_build_triplet, get_jobs_count}, }; +use clap::Parser; use common::{ anyhow::{anyhow, bail, Result}, repository::Repository, - serde_json, tokio::runtime::Runtime, }; -use std::{env, fs, io, path::PathBuf, process::exit, str}; -use clap::Parser; +use std::{env, path::PathBuf, process::exit, str}; /// The path to the work directory. const WORK_DIR: &str = "work/"; @@ -34,36 +33,27 @@ const WORK_DIR: &str = "work/"; All environment variables are optional")] #[command(version, about, long_about = None)] struct Args { - /// Path to the directory containing the package to build. + /// Path to the directory containing the package to build #[arg(long)] from: PathBuf, - /// Output directory path. + /// Output directory path #[arg(long)] to: PathBuf, /// If set, the package is packed into an archive, written to this directory. /// Else, the package is directly *installed* in this directory (which acts as the system - /// root). + /// root) #[arg(long)] package: bool, } -/// Prepares the repository's directory for the package. -/// -/// On success, the function returns the output archive path. -fn prepare(build_process: &BuildProcess, to: PathBuf) -> io::Result { - // Create directory - let build_desc = build_process.get_build_desc(); - let name = &build_desc.package.name; - let version = &build_desc.package.version; - let package_path = to.join(name).join(version.to_string()); - fs::create_dir_all(&package_path)?; - // Create descriptor - let desc_path = package_path.join("desc"); - let desc = serde_json::to_string(&build_desc.package)?; - fs::write(desc_path, desc)?; - // Get archive path - let repo = Repository::load(to)?; - Ok(repo.get_archive_path(name, version)) +/// Returns the architecture directory name for the given `host` +fn get_arch(host: &str) -> &str { + let arch = host.split_once('-').map(|(a, _)| a); + match arch { + Some("i386" | "i486" | "i586" | "i686") => "x86", + Some(a) => a, + None => host, + } } fn main_impl(args: Args) -> Result<()> { @@ -72,31 +62,35 @@ fn main_impl(args: Args) -> Result<()> { let build = get_build_triplet()?; let host = env::var("HOST"); let host = host.as_deref().unwrap_or(build.as_str()); + let arch = get_arch(host); let target = env::var("TARGET"); let target = target.as_deref().unwrap_or(host); let debug = env::var("BLIMP_DEBUG") .map(|s| s == "true") .unwrap_or(false); println!("[INFO] Jobs: {jobs}; Build: {build}; Host: {host}; Target: {target}"); - let build_process = BuildProcess::new(args.from, (!args.package).then(|| args.to.clone()))?; + let sysroot = (!args.package).then(|| args.to.clone()); + let build_process = BuildProcess::new(args.from, sysroot)?; let rt = Runtime::new()?; rt.block_on(build_process.fetch_sources()) - .map_err(|e| anyhow!("Cannot fetch sources: {e}"))?; + .map_err(|e| anyhow!("cannot fetch sources: {e}"))?; println!("[INFO] Compilation..."); let success = build_process .build(jobs, &build, host, target) - .map_err(|e| anyhow!("Cannot build package: {e}"))?; + .map_err(|e| anyhow!("cannot build package: {e}"))?; if !success { - bail!("Package build failed!"); + bail!("package build failed"); } if args.package { println!("[INFO] Prepare repository at `{}`...", args.to.display()); - let archive_path = prepare(&build_process, args.to) - .map_err(|e| anyhow!("Failed to prepare directory for package: {e}"))?; + let repo = Repository::load(args.to.clone()); + build_process + .write_metadata(&repo, arch) + .map_err(|e| anyhow!("failed to prepare directory for package: {e}"))?; println!("[INFO] Create archive..."); build_process - .create_archive(&archive_path) - .map_err(|e| anyhow!("Cannot create archive: {e}"))?; + .create_archive(&repo, arch) + .map_err(|e| anyhow!("cannot create archive: {e}"))?; } if debug { eprintln!( diff --git a/client/src/install.rs b/client/src/install.rs index 10c3093..119834b 100644 --- a/client/src/install.rs +++ b/client/src/install.rs @@ -8,7 +8,7 @@ use common::{ repository::Repository, Environment, }; -use std::{collections::HashMap, io}; +use std::{collections::HashMap}; // TODO Clean /// Installs the given list of packages. @@ -26,13 +26,13 @@ pub async fn install(names: &[String], env: &mut Environment) -> Result<()> { .iter() .cloned() .map(Repository::load) - .collect::>>()?; + .collect::>(); // Tells whether the operation failed let mut failed = false; // The list of packages to install with their respective repository let mut packages = HashMap::::new(); for name in names { - let pkg = repository::get_package_with_constraint(&repos, name, None)?; + let pkg = repository::get_package_with_constraint(&repos, env.arch(), name, None)?; let Some((repo, pkg)) = pkg else { eprintln!("Package `{name}` not found!"); failed = true; @@ -58,6 +58,7 @@ pub async fn install(names: &[String], env: &mut Environment) -> Result<()> { &mut |name, version_constraint| { let res = repository::get_package_with_constraint( &repos, + env.arch(), name, Some(version_constraint), ); @@ -95,7 +96,7 @@ pub async fn install(names: &[String], env: &mut Environment) -> Result<()> { for (pkg, repo) in &total_packages { let name = &pkg.name; let version = &pkg.version; - match repo.get_package(name, version)? { + match repo.get_package(env.arch(), name, version)? { Some(_) => println!("\t- {name} ({version}) - cached"), None => { // Get package size from remote @@ -127,7 +128,7 @@ pub async fn install(names: &[String], env: &mut Environment) -> Result<()> { let mut futures = Vec::new(); // TODO download biggest packages first (sort_unstable by decreasing size) for (pkg, repo) in &total_packages { - if repo.is_in_cache(&pkg.name, &pkg.version) { + if repo.is_in_cache(env.arch(), &pkg.name, &pkg.version) { println!("`{}` is in cache.", &pkg.name); continue; } @@ -141,7 +142,7 @@ pub async fn install(names: &[String], env: &mut Environment) -> Result<()> { use common::download::DownloadTask; use std::fs::OpenOptions; - let path = repo.get_archive_path(&pkg.name, &pkg.version); + let path = repo.get_archive_path(env.arch(), &pkg.name, &pkg.version); let file = OpenOptions::new() .create(true) .write(true) @@ -172,7 +173,7 @@ pub async fn install(names: &[String], env: &mut Environment) -> Result<()> { // Install all packages for (pkg, repo) in total_packages { println!("Installing `{}`...", pkg.name); - let archive_path = repo.get_archive_path(&pkg.name, &pkg.version); + let archive_path = repo.get_archive_path(env.arch(), &pkg.name, &pkg.version); if let Err(e) = env.install(&pkg, &archive_path) { eprintln!("Failed to install `{}`: {e}", &pkg.name); failed = true; diff --git a/client/src/main.rs b/client/src/main.rs index d32d638..d4c4f8f 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -75,7 +75,7 @@ async fn main_impl() -> Result<()> { let local_repos = env::var("LOCAL_REPO") // TODO var_os .map(|s| s.split(':').map(PathBuf::from).collect()) .unwrap_or_default(); - let mut env = Environment::acquire(&sysroot, local_repos, args.branch, args.arch)? + let mut env = Environment::acquire(&sysroot, local_repos, args.arch)? .ok_or_else(|| anyhow!("failed to acquire lockfile"))?; match args.action { #[cfg(feature = "network")] diff --git a/client/src/remote.rs b/client/src/remote.rs index 7540c48..f550c68 100644 --- a/client/src/remote.rs +++ b/client/src/remote.rs @@ -8,8 +8,8 @@ pub async fn list(env: &Environment) -> std::io::Result<()> { println!("Remotes list:"); for remote in remotes { let host = &remote.host; - match remote.fetch_metadata().await { - Ok(metadata) => println!("- {host} (status: UP): {motd}", motd = metadata.motd), + match remote.fetch_motd().await { + Ok(motd) => println!("- {host} (status: UP): {motd}"), Err(_) => println!("- {host} (status: DOWN)"), } } diff --git a/client/src/update.rs b/client/src/update.rs index 2f9832e..975ede0 100644 --- a/client/src/update.rs +++ b/client/src/update.rs @@ -13,7 +13,7 @@ pub async fn update(env: &mut Environment) -> Result<()> { println!("Update from remotes..."); let mut futures = Vec::new(); for r in &remotes { - futures.push((&r.host, r.fetch_list(env))); + futures.push((&r.host, r.fetch_list())); } let mut failed = false; for (host, f) in futures { diff --git a/common/Cargo.toml b/common/Cargo.toml index 46087f3..33810fc 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -16,15 +16,15 @@ futures-util = "0.3.31" indicatif = "0.17.11" infer = "0.19.0" rand = "0.9.0" -reqwest = { version = "0.12.15", features = ["json", "stream"], optional = true } +reqwest = { version = "0.12.15", features = ["stream"], optional = true } serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" tar = "0.4.44" tokio = { version = "1.44.2", features = ["fs", "macros", "rt", "rt-multi-thread"] } tokio-util = { version = "0.7.15", features = ["io"] } -toml = "0.9.5" +toml = { version = "0.9.5" } utils = { git = "https://github.com/maestro-os/maestro-utils" } xz2 = "0.1.7" +zstd = "0.13.3" [features] default = [] diff --git a/common/src/lib.rs b/common/src/lib.rs index a5f7e0e..1d5bf19 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -4,11 +4,11 @@ pub use anyhow; pub use flate2; -pub use serde_json; pub use tar; pub use tokio; pub use tokio_util; pub use utils as maestro_utils; +pub use zstd; #[cfg(feature = "network")] pub mod download; @@ -48,23 +48,25 @@ pub struct Environment { sysroot: PathBuf, /// Local repositories, if any local_repos: Vec, - /// The branch to install from - branch: String, /// The architecture to install for arch: String, } impl Environment { - /// Tries to lock the environment at `sysroot` so that no other instance can access it at the same time. + /// Tries to lock the environment at `sysroot` so that no other instance can access it at the + /// same time. /// /// Arguments: /// - `sysroot` is the root directory of the system to lock /// - `local_repos` is the list of local repositories, if any - /// - `branch` is the repository branch to use. Default: `stable` /// - `arch` is the architecture to use. Defaults to the current /// /// If the environment is already locked, the function returns `None`. - pub fn acquire(sysroot: &Path, local_repos: Vec, branch: Option, arch: Option) -> io::Result> { + pub fn acquire( + sysroot: &Path, + local_repos: Vec, + arch: Option, + ) -> io::Result> { let sysroot = sysroot.canonicalize()?; let path = sysroot.join(LOCKFILE_PATH); let acquired = lockfile::lock(&path)?; @@ -75,7 +77,6 @@ impl Environment { Ok(acquired.then_some(Self { sysroot, local_repos, - branch: branch.unwrap_or_else(|| "stable".to_owned()), arch: arch.unwrap_or_else(|| default_arch.to_owned()), })) } @@ -84,22 +85,16 @@ impl Environment { pub fn sysroot(&self) -> &Path { &self.sysroot } - + /// Returns the local repositories list #[inline] pub fn local_repos(&self) -> &[PathBuf] { &self.local_repos } - - /// Returns the repository branch to use - #[inline] - pub fn branch(&self) -> &str { - &self.branch - } /// Returns the repository architecture to use #[inline] - pub fn architecture(&self) -> &str { + pub fn arch(&self) -> &str { &self.arch } diff --git a/common/src/package.rs b/common/src/package.rs index fc618ad..eb20836 100644 --- a/common/src/package.rs +++ b/common/src/package.rs @@ -2,12 +2,18 @@ //! //! Packages are usually downloaded from a remote host. +use anyhow::Result; use crate::{ repository::Repository, version::{Version, VersionConstraint}, }; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fmt, fs, io, io::ErrorKind, path::PathBuf}; +use std::{ + collections::HashMap, + fmt, fs, io, + io::ErrorKind, + path::{Path, PathBuf}, +}; /// Tells whether the given package name is valid. pub fn is_valid_name(name: &str) -> bool { @@ -97,14 +103,14 @@ pub struct Package { } impl Package { - /// Loads a package from the given path. + /// Loads a package from the metadata file. /// - /// If the package does not exist, the function returns None. - pub fn load(path: PathBuf) -> io::Result> { - match fs::read_to_string(path.join("desc")) { - Ok(content) => Ok(Some(serde_json::from_str(&content)?)), + /// If the package does not exist, the function returns `None`. + pub fn load(metadata_path: &Path) -> Result> { + match fs::read_to_string(metadata_path) { + Ok(content) => Ok(Some(toml::from_str(&content)?)), Err(e) if e.kind() == ErrorKind::NotFound => Ok(None), - Err(e) => Err(e), + Err(e) => Err(e.into()), } } diff --git a/common/src/repository/mod.rs b/common/src/repository/mod.rs index cbe9ebc..20ab246 100644 --- a/common/src/repository/mod.rs +++ b/common/src/repository/mod.rs @@ -5,15 +5,19 @@ #[cfg(feature = "network")] pub mod remote; +use anyhow::Result; use crate::{ package::Package, version::{Version, VersionConstraint}, }; #[cfg(feature = "network")] use remote::Remote; -use std::{fs, io, path::PathBuf}; +use std::{ + fs, io, + path::{Path, PathBuf}, +}; -/// Structure representing a local repository. +/// A local repository. pub struct Repository { /// The path to the repository. path: PathBuf, @@ -27,13 +31,19 @@ impl Repository { /// Loads the repository at the given path. /// /// If the repository is invalid, the function returns an error. - pub fn load(path: PathBuf) -> io::Result { - Ok(Self { + pub fn load(path: PathBuf) -> Self { + Self { path, #[cfg(feature = "network")] remote: None, // TODO read from file - }) + } + } + + /// Returns the repository's path + #[inline] + pub fn get_path(&self) -> &Path { + &self.path } /// Returns the remote associated with the repository. @@ -42,39 +52,42 @@ impl Repository { self.remote.as_ref() } - /// Returns the path to the descriptor of the package with the given name `name` and version - /// `version`. - pub fn get_desc_path(&self, name: &str, version: &Version) -> PathBuf { - self.path.join(name).join(version.to_string()).join("desc") + /// Returns the path to a package's metadata + pub fn get_metadata_path(&self, arch: &str, name: &str, version: &Version) -> PathBuf { + self.path + .join("dist") + .join(arch) + .join(format!("{name}_{version}.meta")) } - /// Returns the path to the archive of the package with the given name `name` and version - /// `version`. - pub fn get_archive_path(&self, name: &str, version: &Version) -> PathBuf { + /// Returns the path to a package's archive + pub fn get_archive_path(&self, arch: &str, name: &str, version: &Version) -> PathBuf { self.path - .join(name) - .join(version.to_string()) - .join("archive") + .join("dist") + .join(arch) + .join(format!("{name}_{version}.tar.zstd")) } - /// Tells whether the **archive** of the package with name `name` and version `version` is - /// present in the repository. - /// - /// Note: A package can be present in a repository with its archive. - pub fn is_in_cache(&self, name: &str, version: &Version) -> bool { - self.get_archive_path(name, version).exists() + /// Tells whether the **archive** of a package is present in the repository. + pub fn is_in_cache(&self, arch: &str, name: &str, version: &Version) -> bool { + self.get_archive_path(arch, name, version).exists() } - /// Returns the package with name `name` and version `version`. + /// Returns a package in the repository /// - /// If the package doesn't exist, the function returns None. - pub fn get_package(&self, name: &str, version: &Version) -> io::Result> { - let path = self.path.join(name).join(version.to_string()); - Package::load(path) + /// If the package does not exist, the function returns `None`. + pub fn get_package( + &self, + arch: &str, + name: &str, + version: &Version, + ) -> Result> { + let path = self.get_metadata_path(arch, name, version); + Package::load(&path) } /// Returns the list of packages with each versions in the repository. - pub fn list_packages(&self) -> io::Result> { + pub fn list_packages(&self) -> Result> { fs::read_dir(&self.path)? .filter_map(|ent| { let ent = ent.ok()?; @@ -95,7 +108,7 @@ impl Repository { let version = Version::try_from(ent_name.as_ref()).ok()?; let ent_path = ent_path.join(version.to_string()); - Package::load(ent_path).transpose() + Package::load(&ent_path).transpose() }); Some(iter) }) @@ -106,16 +119,18 @@ impl Repository { /// Returns the package with the given name. /// /// Arguments: - /// - `name` is the name of the package. + /// - `arch` is the required architecture + /// - `name` is the name of the package /// - `version_constraint` is the version constraint to match. If no constraint is specified, - /// the latest version is selected. + /// the latest version is selected /// /// If the package doesn't exist, the function returns `None`. pub fn get_package_with_constraint( &self, + arch: &str, name: &str, version_constraint: Option<&VersionConstraint>, - ) -> io::Result> { + ) -> Result> { let version = fs::read_dir(self.path.join(name))? .filter_map(|ent| { let ent = ent.ok()?; @@ -137,27 +152,28 @@ impl Repository { .max(); match version { - Some(version) => self.get_package(name, &version), + Some(version) => self.get_package(arch, name, &version), None => Ok(None), } } } // TODO Handle error reporting -/// Returns the package with name `name` and version `version` along with its associated +/// Returns the package with the given `arch`, `name` and `version` along with its associated /// repository. /// /// `repos` is the list of repositories to check on. /// -/// If the package doesn't exist, the function returns None. +/// If the package does not exist, the function returns `None`. pub fn get_package<'a>( repos: &'a [Repository], + arch: &str, name: &str, version: &Version, ) -> io::Result> { Ok(repos .iter() - .filter_map(|repo| match repo.get_package(name, version) { + .filter_map(|repo| match repo.get_package(arch, name, version) { Ok(Some(pack)) => Some((repo, pack)), _ => None, }) @@ -165,26 +181,28 @@ pub fn get_package<'a>( } // TODO Handle error reporting -/// Returns the package with the given name along with its associated repository. +/// Returns the package with the given constraints along with its associated repository. /// /// Arguments: -/// - `name` is the name of the package. +/// - `arch` is the required architecture +/// - `name` is the name of the package /// - `version_constraint` is the version constraint to match. If no constraint is specified, the -/// latest version is selected. +/// latest version is selected /// -/// If the package doesn't exist, the function returns `None`. +/// If the package does not exist, the function returns `None`. pub fn get_package_with_constraint<'a>( repos: &'a [Repository], + arch: &str, name: &str, version_constraint: Option<&VersionConstraint>, ) -> io::Result> { Ok(repos .iter() - .filter_map( - |repo| match repo.get_package_with_constraint(name, version_constraint) { + .filter_map(|repo| { + match repo.get_package_with_constraint(arch, name, version_constraint) { Ok(Some(pack)) => Some((repo, pack)), _ => None, - }, - ) + } + }) .max_by(|(_, p0), (_, p1)| p0.version.cmp(&p1.version))) } diff --git a/common/src/repository/remote.rs b/common/src/repository/remote.rs index c9f2659..8eba49b 100644 --- a/common/src/repository/remote.rs +++ b/common/src/repository/remote.rs @@ -10,7 +10,6 @@ use std::{ io, io::{BufRead, BufReader, BufWriter, Write}, }; -use serde::Deserialize; /// The file which contains the list of remotes. const REMOTES_FILE: &str = "var/lib/blimp/remotes"; @@ -22,14 +21,6 @@ pub struct Remote { pub host: String, } -#[derive(Deserialize)] -pub struct RemoteMetadata { - /// The server's motd - pub motd: String, - /// Available branches on the server - pub branches: Vec, -} - impl Borrow for Remote { fn borrow(&self) -> &str { &self.host @@ -69,8 +60,8 @@ impl Remote { Ok(()) } - /// Returns the remote's motd. - pub async fn fetch_metadata(&self) -> Result { + /// Fetches the remote's motd + pub async fn fetch_motd(&self) -> Result { let client = reqwest::Client::new(); let url = format!("https://{}/motd", &self.host); let response = client @@ -84,15 +75,15 @@ impl Remote { let s = response.text().await?; let metadata = toml::from_str(&s)?; Ok(metadata) - }, - _ => bail!("Failed to retrieve remote metadata (status {status})"), + } + _ => bail!("failed to retrieve remote metadata (status {status})"), } } /// Fetches the list of all the packages from the remote. - pub async fn fetch_list(&self, env: &Environment) -> Result> { + pub async fn fetch_list(&self) -> Result> { let client = reqwest::Client::new(); - let url = format!("https://{}/{}/package", self.host, env.branch); + let url = format!("https://{}/index", self.host); let response = client .get(url) .header("User-Agent", USER_AGENT) @@ -100,7 +91,9 @@ impl Remote { .await?; let status = response.status(); match status { - StatusCode::OK => Ok(response.json().await?), + StatusCode::OK => { + todo!() + }, _ => bail!("Failed to retrieve packages list from remote (status {status})"), } } @@ -108,8 +101,8 @@ impl Remote { /// Returns the download URL for the given `package`. pub fn download_url(&self, env: &Environment, package: &Package) -> String { format!( - "https://{}/{}/dist/{}/{}_{}.tar.zstd", - self.host, env.branch, env.arch, package.name, package.version + "https://{}/dist/{}/{}_{}.tar.zstd", + self.host, env.arch, package.name, package.version ) } diff --git a/man/man7/blimp.7 b/man/man7/blimp.7 index 1438008..24e0031 100644 --- a/man/man7/blimp.7 +++ b/man/man7/blimp.7 @@ -9,42 +9,32 @@ Blimp downloads packages from a HTTP server. The server-side files hierarchy is Example: .nf -.B - metadata -.B - stable/ -.B " "- index -.B " "- dist/ -.B " "- x86_64/ -.B " "- _.tar.zstd -.B " "- _.meta - ... -.B " "- x86/ - ... +.B - motd +.B - index +.B - dist/ +.B " "- x86_64/ +.B " "- _.tar.zstd +.B " "- _.meta ... -.B " "- i18n/ -.B " "- en/ -.B " "- _/ - ... - ... -.B " "- fr/ - ... +.B " "- x86/ ... -.B " "- src/ + ... +.B - i18n/ +.B " "- en/ .B " "- _/ ... ... -.B - nightly/ - ... +.B " "- fr/ + ... + ... +.B - src/ +.B " "- _/ + ... + ... .fi -.B metadata -contains information about the server, in TOML format - -.B dist/ -contains all branches. -.B stable -is the canonical name for production-ready packages, while -.B nightly -is for unstable packages +.B motd +the server's MOTD .B index contains the list of all packages and their supported architecture, in TOML format. It is used by the client to determine the list of packages present on the server @@ -58,11 +48,9 @@ contains internationalization files, sorted by locale .B src/ contains package sources -When updating the packages list, the client first looks at -.B metadata -to determine the list of branches, and then it looks at the +When updating the packages list, the client fetches .B index -in each, containing the list of packages. +containing the list of packages. To download a package, the client looks up the required version in its local copy of the server's index. Then it downloads the .B .meta From cd7cfe5707a8f218c9a001730bee30699a918983 Mon Sep 17 00:00:00 2001 From: llenotre Date: Sun, 31 Aug 2025 23:30:09 +0200 Subject: [PATCH 04/15] feat: package name validation --- builder/src/build.rs | 3 ++- client/src/install.rs | 2 +- client/src/remove.rs | 6 ++++-- common/src/package.rs | 20 +++++++++++++++++++- common/src/repository/mod.rs | 16 +++++++++++++--- common/src/repository/remote.rs | 2 +- 6 files changed, 40 insertions(+), 9 deletions(-) diff --git a/builder/src/build.rs b/builder/src/build.rs index 83b6df0..15817f4 100644 --- a/builder/src/build.rs +++ b/builder/src/build.rs @@ -40,7 +40,8 @@ impl BuildProcess { pub fn new(input_path: PathBuf, sysroot: Option) -> Result { let build_desc_path = input_path.join("metadata.toml"); let build_desc = fs::read_to_string(build_desc_path)?; - let build_desc = toml::from_str(&build_desc)?; + let build_desc = toml::from_str::(&build_desc)?; + build_desc.package.validate()?; Ok(Self { input_path, build_desc, diff --git a/client/src/install.rs b/client/src/install.rs index 119834b..938faba 100644 --- a/client/src/install.rs +++ b/client/src/install.rs @@ -8,7 +8,7 @@ use common::{ repository::Repository, Environment, }; -use std::{collections::HashMap}; +use std::collections::HashMap; // TODO Clean /// Installs the given list of packages. diff --git a/client/src/remove.rs b/client/src/remove.rs index d52d825..c6a930c 100644 --- a/client/src/remove.rs +++ b/client/src/remove.rs @@ -1,7 +1,9 @@ //! TODO doc -use common::{anyhow::Result, Environment}; -use common::anyhow::bail; +use common::{ + anyhow::{bail, Result}, + Environment, +}; /// Removes the given list of packages. /// diff --git a/common/src/package.rs b/common/src/package.rs index eb20836..518c438 100644 --- a/common/src/package.rs +++ b/common/src/package.rs @@ -2,11 +2,11 @@ //! //! Packages are usually downloaded from a remote host. -use anyhow::Result; use crate::{ repository::Repository, version::{Version, VersionConstraint}, }; +use anyhow::{bail, Result}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -114,6 +114,24 @@ impl Package { } } + /// Validates the package's metadata + pub fn validate(&self) -> Result<()> { + if !is_valid_name(&self.name) { + bail!("invalid package name: {}", self.name); + } + for d in &self.build_deps { + if !is_valid_name(&d.name) { + bail!("invalid dependency name: {}", d.name); + } + } + for d in &self.run_deps { + if !is_valid_name(&d.name) { + bail!("invalid dependency name: {}", d.name); + } + } + Ok(()) + } + /// Resolves the dependencies of the package and inserts them into the given `HashMap`. /// /// Arguments: diff --git a/common/src/repository/mod.rs b/common/src/repository/mod.rs index 20ab246..ace9834 100644 --- a/common/src/repository/mod.rs +++ b/common/src/repository/mod.rs @@ -5,11 +5,12 @@ #[cfg(feature = "network")] pub mod remote; -use anyhow::Result; use crate::{ + package, package::Package, version::{Version, VersionConstraint}, }; +use anyhow::{bail, Result}; #[cfg(feature = "network")] use remote::Remote; use std::{ @@ -82,6 +83,9 @@ impl Repository { name: &str, version: &Version, ) -> Result> { + if !package::is_valid_name(name) { + bail!("invalid package name: {name}"); + } let path = self.get_metadata_path(arch, name, version); Package::load(&path) } @@ -170,7 +174,10 @@ pub fn get_package<'a>( arch: &str, name: &str, version: &Version, -) -> io::Result> { +) -> Result> { + if !package::is_valid_name(name) { + bail!("invalid package name: {name}"); + } Ok(repos .iter() .filter_map(|repo| match repo.get_package(arch, name, version) { @@ -195,7 +202,10 @@ pub fn get_package_with_constraint<'a>( arch: &str, name: &str, version_constraint: Option<&VersionConstraint>, -) -> io::Result> { +) -> Result> { + if !package::is_valid_name(name) { + bail!("invalid package name: {name}"); + } Ok(repos .iter() .filter_map(|repo| { diff --git a/common/src/repository/remote.rs b/common/src/repository/remote.rs index 8eba49b..a535de0 100644 --- a/common/src/repository/remote.rs +++ b/common/src/repository/remote.rs @@ -93,7 +93,7 @@ impl Remote { match status { StatusCode::OK => { todo!() - }, + } _ => bail!("Failed to retrieve packages list from remote (status {status})"), } } From 30b8f6b975a46c4ab7e793c6c3c7105c4a8355bb Mon Sep 17 00:00:00 2001 From: llenotre Date: Mon, 1 Sep 2025 00:03:55 +0200 Subject: [PATCH 05/15] chore: cleanup --- client/bleh/var/lib/blimp/remotes | 0 common/src/repository/mod.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 client/bleh/var/lib/blimp/remotes diff --git a/client/bleh/var/lib/blimp/remotes b/client/bleh/var/lib/blimp/remotes deleted file mode 100644 index e69de29..0000000 diff --git a/common/src/repository/mod.rs b/common/src/repository/mod.rs index ace9834..d5a723a 100644 --- a/common/src/repository/mod.rs +++ b/common/src/repository/mod.rs @@ -14,7 +14,7 @@ use anyhow::{bail, Result}; #[cfg(feature = "network")] use remote::Remote; use std::{ - fs, io, + fs, path::{Path, PathBuf}, }; From 81f75024cb06ca591e730f5a46f52662fb0a52f2 Mon Sep 17 00:00:00 2001 From: llenotre Date: Mon, 1 Sep 2025 22:32:23 +0200 Subject: [PATCH 06/15] refactor: bootstrap metadata conversion to toml --- bootstrap/desc/binutils1/metadata.toml | 8 ++++++++ bootstrap/desc/binutils1/package.json | 17 --------------- bootstrap/desc/binutils2/metadata.toml | 8 ++++++++ bootstrap/desc/binutils2/package.json | 17 --------------- bootstrap/desc/gcc1/metadata.toml | 12 +++++++++++ bootstrap/desc/gcc1/package.json | 22 -------------------- bootstrap/desc/gcc2/metadata.toml | 12 +++++++++++ bootstrap/desc/gcc2/package.json | 22 -------------------- bootstrap/desc/libstdc++/metadata.toml | 12 +++++++++++ bootstrap/desc/libstdc++/package.json | 22 -------------------- bootstrap/desc/linux-headers/metadata.toml | 8 ++++++++ bootstrap/desc/linux-headers/package.json | 17 --------------- bootstrap/desc/musl/metadata.toml | 12 +++++++++++ bootstrap/desc/musl/package.json | 22 -------------------- bootstrap/desc/zlib/metadata.toml | 8 ++++++++ bootstrap/desc/zlib/package.json | 17 --------------- builder/Cargo.toml | 2 +- builder/src/build.rs | 2 +- builder/src/desc.rs | 7 ++++--- client/Cargo.toml | 2 +- common/src/package.rs | 24 ++++++++++++---------- common/src/version.rs | 5 +++-- 22 files changed, 103 insertions(+), 175 deletions(-) create mode 100644 bootstrap/desc/binutils1/metadata.toml delete mode 100644 bootstrap/desc/binutils1/package.json create mode 100644 bootstrap/desc/binutils2/metadata.toml delete mode 100644 bootstrap/desc/binutils2/package.json create mode 100644 bootstrap/desc/gcc1/metadata.toml delete mode 100644 bootstrap/desc/gcc1/package.json create mode 100644 bootstrap/desc/gcc2/metadata.toml delete mode 100644 bootstrap/desc/gcc2/package.json create mode 100644 bootstrap/desc/libstdc++/metadata.toml delete mode 100644 bootstrap/desc/libstdc++/package.json create mode 100644 bootstrap/desc/linux-headers/metadata.toml delete mode 100644 bootstrap/desc/linux-headers/package.json create mode 100644 bootstrap/desc/musl/metadata.toml delete mode 100644 bootstrap/desc/musl/package.json create mode 100644 bootstrap/desc/zlib/metadata.toml delete mode 100644 bootstrap/desc/zlib/package.json diff --git a/bootstrap/desc/binutils1/metadata.toml b/bootstrap/desc/binutils1/metadata.toml new file mode 100644 index 0000000..b8f72ec --- /dev/null +++ b/bootstrap/desc/binutils1/metadata.toml @@ -0,0 +1,8 @@ +[package] +name = "bootstrap-binutils" +version = "2.44" +description = "Stage 1 binutils for bootstrapping" + +[[source]] +location = "/" +url = "https://ftp.gnu.org/gnu/binutils/binutils-2.44.tar.gz" diff --git a/bootstrap/desc/binutils1/package.json b/bootstrap/desc/binutils1/package.json deleted file mode 100644 index 950abb6..0000000 --- a/bootstrap/desc/binutils1/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://ftp.gnu.org/gnu/binutils/binutils-2.44.tar.gz" - } - ], - "package": { - "name": "bootstrap-binutils", - "version": "2.44", - - "description": "Stage 1 binutils for bootstrapping", - - "build_deps": [], - "run_deps": [] - } -} diff --git a/bootstrap/desc/binutils2/metadata.toml b/bootstrap/desc/binutils2/metadata.toml new file mode 100644 index 0000000..aa04442 --- /dev/null +++ b/bootstrap/desc/binutils2/metadata.toml @@ -0,0 +1,8 @@ +[package] +name = "bootstrap-binutils" +version = "2.44" +description = "Stage 2 binutils for bootstrapping" + +[[source]] +location = "/" +url = "https://ftp.gnu.org/gnu/binutils/binutils-2.44.tar.gz" \ No newline at end of file diff --git a/bootstrap/desc/binutils2/package.json b/bootstrap/desc/binutils2/package.json deleted file mode 100644 index 86c5fed..0000000 --- a/bootstrap/desc/binutils2/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://ftp.gnu.org/gnu/binutils/binutils-2.44.tar.gz" - } - ], - "package": { - "name": "bootstrap-binutils", - "version": "2.44", - - "description": "Stage 2 binutils for bootstrapping", - - "build_deps": [], - "run_deps": [] - } -} diff --git a/bootstrap/desc/gcc1/metadata.toml b/bootstrap/desc/gcc1/metadata.toml new file mode 100644 index 0000000..f34374c --- /dev/null +++ b/bootstrap/desc/gcc1/metadata.toml @@ -0,0 +1,12 @@ +[package] +name = "bootstrap-gcc1" +version = "15.1.0" +description = "Stage 1 gcc for bootstrapping" + +[[source]] +location = "/" +url = "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" + +[[package.build_dep]] +name = "bootstrap-binutils" +version = "*" \ No newline at end of file diff --git a/bootstrap/desc/gcc1/package.json b/bootstrap/desc/gcc1/package.json deleted file mode 100644 index e181749..0000000 --- a/bootstrap/desc/gcc1/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" - } - ], - "package": { - "name": "bootstrap-gcc1", - "version": "15.1.0", - - "description": "Stage 1 gcc for bootstrapping", - - "build_deps": [ - { - "name": "bootstrap-binutils", - "version": "*" - } - ], - "run_deps": [] - } -} diff --git a/bootstrap/desc/gcc2/metadata.toml b/bootstrap/desc/gcc2/metadata.toml new file mode 100644 index 0000000..b979362 --- /dev/null +++ b/bootstrap/desc/gcc2/metadata.toml @@ -0,0 +1,12 @@ +[package] +name = "bootstrap-gcc2" +version = "15.1.0" +description = "Stage 2 gcc for bootstrapping" + +[[source]] +location = "/" +url = "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" + +[[package.build_dep]] +name = "bootstrap-libstdc++" +version = "*" \ No newline at end of file diff --git a/bootstrap/desc/gcc2/package.json b/bootstrap/desc/gcc2/package.json deleted file mode 100644 index c633408..0000000 --- a/bootstrap/desc/gcc2/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" - } - ], - "package": { - "name": "bootstrap-gcc2", - "version": "15.1.0", - - "description": "Stage 2 gcc for bootstrapping", - - "build_deps": [ - { - "name": "bootstrap-libstdc++", - "version": "*" - } - ], - "run_deps": [] - } -} diff --git a/bootstrap/desc/libstdc++/metadata.toml b/bootstrap/desc/libstdc++/metadata.toml new file mode 100644 index 0000000..1d1eb7d --- /dev/null +++ b/bootstrap/desc/libstdc++/metadata.toml @@ -0,0 +1,12 @@ +[package] +name = "bootstrap-libstdc++" +version = "15.1.0" +description = "libstdc++ for bootstrapping" + +[[source]] +location = "/" +url = "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" + +[[package.build_dep]] +name = "bootstrap-musl" +version = "*" \ No newline at end of file diff --git a/bootstrap/desc/libstdc++/package.json b/bootstrap/desc/libstdc++/package.json deleted file mode 100644 index ecf0593..0000000 --- a/bootstrap/desc/libstdc++/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://ftp.gnu.org/gnu/gcc/gcc-15.1.0/gcc-15.1.0.tar.gz" - } - ], - "package": { - "name": "bootstrap-libstdc++", - "version": "15.1.0", - - "description": "libstdc++ for bootstrapping", - - "build_deps": [ - { - "name": "bootstrap-musl", - "version": "*" - } - ], - "run_deps": [] - } -} diff --git a/bootstrap/desc/linux-headers/metadata.toml b/bootstrap/desc/linux-headers/metadata.toml new file mode 100644 index 0000000..169632a --- /dev/null +++ b/bootstrap/desc/linux-headers/metadata.toml @@ -0,0 +1,8 @@ +[package] +name = "bootstrap-linux-headers" +version = "6.15" +description = "Linux kernel headers for bootstrapping" + +[[source]] +location = "/" +url = "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.15.tar.xz" \ No newline at end of file diff --git a/bootstrap/desc/linux-headers/package.json b/bootstrap/desc/linux-headers/package.json deleted file mode 100644 index 7d7b022..0000000 --- a/bootstrap/desc/linux-headers/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.15.tar.xz" - } - ], - "package": { - "name": "bootstrap-linux-headers", - "version": "6.15", - - "description": "Linux kernel headers for bootstrapping", - - "build_deps": [], - "run_deps": [] - } -} diff --git a/bootstrap/desc/musl/metadata.toml b/bootstrap/desc/musl/metadata.toml new file mode 100644 index 0000000..d8008f3 --- /dev/null +++ b/bootstrap/desc/musl/metadata.toml @@ -0,0 +1,12 @@ +[package] +name = "bootstrap-musl" +version = "1.2.5" +description = "musl for bootstrapping" + +[[source]] +location = "/" +url = "https://musl.libc.org/releases/musl-1.2.5.tar.gz" + +[[package.build_dep]] +name = "bootstrap-gcc1" +version = "*" \ No newline at end of file diff --git a/bootstrap/desc/musl/package.json b/bootstrap/desc/musl/package.json deleted file mode 100644 index f944fb7..0000000 --- a/bootstrap/desc/musl/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://musl.libc.org/releases/musl-1.2.5.tar.gz" - } - ], - "package": { - "name": "bootstrap-musl", - "version": "1.2.5", - - "description": "musl for bootstrapping", - - "build_deps": [ - { - "name": "bootstrap-gcc1", - "version": "*" - } - ], - "run_deps": [] - } -} diff --git a/bootstrap/desc/zlib/metadata.toml b/bootstrap/desc/zlib/metadata.toml new file mode 100644 index 0000000..3df87ea --- /dev/null +++ b/bootstrap/desc/zlib/metadata.toml @@ -0,0 +1,8 @@ +[package] +name = "zlib" +version = "1.3.1" +description = "Bootstrapping zlib" + +[[source]] +location = "/" +url = "https://zlib.net/zlib-1.3.1.tar.gz" \ No newline at end of file diff --git a/bootstrap/desc/zlib/package.json b/bootstrap/desc/zlib/package.json deleted file mode 100644 index 779cb99..0000000 --- a/bootstrap/desc/zlib/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "sources": [ - { - "location": "/", - "url": "https://zlib.net/zlib-1.3.1.tar.gz" - } - ], - "package": { - "name": "zlib", - "version": "1.3.1", - - "description": "Bootstrapping zlib", - - "build_deps": [], - "run_deps": [] - } -} diff --git a/builder/Cargo.toml b/builder/Cargo.toml index 5144817..a62d5f1 100644 --- a/builder/Cargo.toml +++ b/builder/Cargo.toml @@ -13,5 +13,5 @@ clap = { version = "4.5.39", features = ["derive"] } toml = "0.9.5" [features] -default = [] +default = ["network"] network = ["common/network"] diff --git a/builder/src/build.rs b/builder/src/build.rs index 15817f4..86eb8ca 100644 --- a/builder/src/build.rs +++ b/builder/src/build.rs @@ -68,7 +68,7 @@ impl BuildProcess { let build_dir = Arc::new(self.build_dir.clone()); let futures = self .build_desc - .sources + .source .iter() .cloned() .map(move |s| { diff --git a/builder/src/desc.rs b/builder/src/desc.rs index 351af09..791ea0d 100644 --- a/builder/src/desc.rs +++ b/builder/src/desc.rs @@ -126,8 +126,9 @@ impl Source { /// Description of how to build a package. #[derive(Deserialize, Serialize)] pub struct BuildDescriptor { - /// The list of sources for the package. - pub sources: Vec, - /// The package's descriptor. + /// The package's descriptor pub package: Package, + /// The list of sources for the package + #[serde(default)] + pub source: Vec, } diff --git a/client/Cargo.toml b/client/Cargo.toml index 328e893..88906a7 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -8,5 +8,5 @@ clap = { version = "4.5.39", features = ["derive"] } common = { path = "../common" } [features] -default = [] +default = ["network"] network = ["common/network"] diff --git a/common/src/package.rs b/common/src/package.rs index 518c438..2b34680 100644 --- a/common/src/package.rs +++ b/common/src/package.rs @@ -89,17 +89,19 @@ impl fmt::Display for Dependency { /// A package's description. #[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct Package { - /// The package's name. + /// The package's name pub name: String, - /// The package's version. + /// The package's version pub version: Version, - /// The package's description. + /// The package's description pub description: String, - /// Dependencies required to build the package. - pub build_deps: Vec, - /// Dependencies required to run the package. - pub run_deps: Vec, + /// Dependencies required to build the package + #[serde(default)] + pub build_dep: Vec, + /// Dependencies required to run the package + #[serde(default)] + pub run_dep: Vec, } impl Package { @@ -119,12 +121,12 @@ impl Package { if !is_valid_name(&self.name) { bail!("invalid package name: {}", self.name); } - for d in &self.build_deps { + for d in &self.build_dep { if !is_valid_name(&d.name) { bail!("invalid dependency name: {}", d.name); } } - for d in &self.run_deps { + for d in &self.run_dep { if !is_valid_name(&d.name) { bail!("invalid dependency name: {}", d.name); } @@ -153,7 +155,7 @@ impl Package { let mut errors = vec![]; // TODO Add support for build dependencies - for d in &self.run_deps { + for d in &self.run_dep { // TODO check already installed packages // Get package in the installation list let pkg = packages.keys().find(|p| p.name == d.name); @@ -214,7 +216,7 @@ pub fn list_unmatched_dependencies( pkgs.iter() .flat_map(|(_, pkg)| { pkg.desc - .run_deps + .run_dep .iter() .filter(|dep| { pkgs.get(&dep.name) diff --git a/common/src/version.rs b/common/src/version.rs index 9e04cdb..5cda00a 100644 --- a/common/src/version.rs +++ b/common/src/version.rs @@ -23,8 +23,9 @@ impl<'de> Deserialize<'de> for Version { where D: Deserializer<'de>, { - let s: &str = Deserialize::deserialize(deserializer)?; - s.try_into().map_err(Error::custom) + // toml does not like &str + let s: String = Deserialize::deserialize(deserializer)?; + Self::try_from(s.as_str()).map_err(Error::custom) } } From 7af483334190a05f8653d6d12666cac306ede0ff Mon Sep 17 00:00:00 2001 From: llenotre Date: Mon, 1 Sep 2025 23:20:06 +0200 Subject: [PATCH 07/15] fix: package building --- builder/src/build.rs | 18 ++++++++---------- builder/src/desc.rs | 2 +- builder/src/main.rs | 4 ++-- common/src/package.rs | 4 ++-- common/src/version.rs | 18 +++++++++++++----- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/builder/src/build.rs b/builder/src/build.rs index 86eb8ca..f354882 100644 --- a/builder/src/build.rs +++ b/builder/src/build.rs @@ -110,18 +110,16 @@ impl BuildProcess { /// Writes the package's metadata to the repository pub fn write_metadata(&self, repo: &Repository, arch: &str) -> Result<()> { - // Make sure the arch directory exists - fs::create_dir_all(repo.get_path().join(arch))?; + let path = repo.get_metadata_path( + arch, + &self.build_desc.package.name, + &self.build_desc.package.version, + ); + // Make sure the parent directory exists + fs::create_dir_all(path.parent().unwrap())?; // Create metadata let metadata = toml::to_string(&self.build_desc.package)?; - fs::write( - repo.get_metadata_path( - arch, - &self.build_desc.package.name, - &self.build_desc.package.version, - ), - metadata, - )?; + fs::write(path, metadata)?; Ok(()) } diff --git a/builder/src/desc.rs b/builder/src/desc.rs index 791ea0d..8939597 100644 --- a/builder/src/desc.rs +++ b/builder/src/desc.rs @@ -129,6 +129,6 @@ pub struct BuildDescriptor { /// The package's descriptor pub package: Package, /// The list of sources for the package - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub source: Vec, } diff --git a/builder/src/main.rs b/builder/src/main.rs index b371f94..2e3c54b 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -86,11 +86,11 @@ fn main_impl(args: Args) -> Result<()> { let repo = Repository::load(args.to.clone()); build_process .write_metadata(&repo, arch) - .map_err(|e| anyhow!("failed to prepare directory for package: {e}"))?; + .map_err(|e| anyhow!("failed to write package metadata: {e}"))?; println!("[INFO] Create archive..."); build_process .create_archive(&repo, arch) - .map_err(|e| anyhow!("cannot create archive: {e}"))?; + .map_err(|e| anyhow!("failed to create package archive: {e}"))?; } if debug { eprintln!( diff --git a/common/src/package.rs b/common/src/package.rs index 2b34680..2ff9233 100644 --- a/common/src/package.rs +++ b/common/src/package.rs @@ -97,10 +97,10 @@ pub struct Package { pub description: String, /// Dependencies required to build the package - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub build_dep: Vec, /// Dependencies required to run the package - #[serde(default)] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub run_dep: Vec, } diff --git a/common/src/version.rs b/common/src/version.rs index 5cda00a..330e13f 100644 --- a/common/src/version.rs +++ b/common/src/version.rs @@ -1,7 +1,11 @@ //! The version structure represents the version of a package. use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; -use std::{cmp::Ordering, fmt, num::ParseIntError}; +use std::{ + cmp::{max, Ordering}, + fmt, + num::ParseIntError, +}; /// A package version. #[derive(Clone, Eq, Hash, PartialEq)] @@ -25,7 +29,7 @@ impl<'de> Deserialize<'de> for Version { { // toml does not like &str let s: String = Deserialize::deserialize(deserializer)?; - Self::try_from(s.as_str()).map_err(Error::custom) + s.as_str().try_into().map_err(Error::custom) } } @@ -46,7 +50,10 @@ impl TryFrom<&str> for Version { impl Ord for Version { fn cmp(&self, other: &Self) -> Ordering { - for (left, right) in self.components.iter().zip(other.components.iter()) { + let len = max(self.components.len(), other.components.len()); + for i in 0..len { + let left = self.components.get(i).unwrap_or(&0); + let right = self.components.get(i).unwrap_or(&0); let cmp = left.cmp(right); if cmp != Ordering::Equal { return cmp; @@ -105,8 +112,9 @@ impl<'de> Deserialize<'de> for VersionConstraint { where D: Deserializer<'de>, { - let s: &str = Deserialize::deserialize(deserializer)?; - s.try_into().map_err(Error::custom) + // toml does not like &str + let s: String = Deserialize::deserialize(deserializer)?; + s.as_str().try_into().map_err(Error::custom) } } From a0854e4a2109ffa02181a6ea1b49743a974c9a4a Mon Sep 17 00:00:00 2001 From: llenotre Date: Mon, 1 Sep 2025 23:39:55 +0200 Subject: [PATCH 08/15] fix: allow remote without motd --- client/src/remote.rs | 3 ++- client/src/update.rs | 2 +- common/src/repository/remote.rs | 9 +++++---- man/man7/blimp.7 | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/client/src/remote.rs b/client/src/remote.rs index f550c68..4326eee 100644 --- a/client/src/remote.rs +++ b/client/src/remote.rs @@ -9,7 +9,8 @@ pub async fn list(env: &Environment) -> std::io::Result<()> { for remote in remotes { let host = &remote.host; match remote.fetch_motd().await { - Ok(motd) => println!("- {host} (status: UP): {motd}"), + Ok(Some(motd)) => println!("- {host} (status: UP): {motd}"), + Ok(None) => println!("- {host} (status: UP)"), Err(_) => println!("- {host} (status: DOWN)"), } } diff --git a/client/src/update.rs b/client/src/update.rs index 975ede0..c5c3f79 100644 --- a/client/src/update.rs +++ b/client/src/update.rs @@ -13,7 +13,7 @@ pub async fn update(env: &mut Environment) -> Result<()> { println!("Update from remotes..."); let mut futures = Vec::new(); for r in &remotes { - futures.push((&r.host, r.fetch_list())); + futures.push((&r.host, r.fetch_index())); } let mut failed = false; for (host, f) in futures { diff --git a/common/src/repository/remote.rs b/common/src/repository/remote.rs index a535de0..8e303fe 100644 --- a/common/src/repository/remote.rs +++ b/common/src/repository/remote.rs @@ -61,7 +61,7 @@ impl Remote { } /// Fetches the remote's motd - pub async fn fetch_motd(&self) -> Result { + pub async fn fetch_motd(&self) -> Result> { let client = reqwest::Client::new(); let url = format!("https://{}/motd", &self.host); let response = client @@ -74,14 +74,15 @@ impl Remote { StatusCode::OK => { let s = response.text().await?; let metadata = toml::from_str(&s)?; - Ok(metadata) + Ok(Some(metadata)) } + StatusCode::NOT_FOUND => Ok(None), _ => bail!("failed to retrieve remote metadata (status {status})"), } } - /// Fetches the list of all the packages from the remote. - pub async fn fetch_list(&self) -> Result> { + /// Fetches the remote's index + pub async fn fetch_index(&self) -> Result> { let client = reqwest::Client::new(); let url = format!("https://{}/index", self.host); let response = client diff --git a/man/man7/blimp.7 b/man/man7/blimp.7 index 24e0031..91e4e15 100644 --- a/man/man7/blimp.7 +++ b/man/man7/blimp.7 @@ -13,8 +13,8 @@ Example: .B - index .B - dist/ .B " "- x86_64/ -.B " "- _.tar.zstd .B " "- _.meta +.B " "- _.tar.zstd ... .B " "- x86/ ... From 8f2c097f2c9140182cb237c705ba023134bb381b Mon Sep 17 00:00:00 2001 From: llenotre Date: Tue, 2 Sep 2025 01:46:09 +0200 Subject: [PATCH 09/15] fix: package install --- Cargo.lock | 41 ------------------------- builder/src/build.rs | 9 ++++-- client/src/install.rs | 4 +-- common/Cargo.toml | 1 - common/src/lib.rs | 53 ++++++++++++++++++++++++--------- common/src/repository/mod.rs | 31 ++++++++++--------- common/src/repository/remote.rs | 2 +- man/man7/blimp.7 | 4 +-- 8 files changed, 68 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6e8e4d8..b91b936 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,8 +212,6 @@ version = "1.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" dependencies = [ - "jobserver", - "libc", "shlex", ] @@ -301,7 +299,6 @@ dependencies = [ "toml", "utils", "xz2", - "zstd", ] [[package]] @@ -910,16 +907,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.3", - "libc", -] - [[package]] name = "js-sys" version = "0.3.77" @@ -2272,31 +2259,3 @@ dependencies = [ "quote", "syn", ] - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/builder/src/build.rs b/builder/src/build.rs index f354882..f3f3536 100644 --- a/builder/src/build.rs +++ b/builder/src/build.rs @@ -1,7 +1,12 @@ //! Implementation of the package building procedure. use crate::{desc::BuildDescriptor, WORK_DIR}; -use common::{anyhow::Result, repository::Repository, tar, tokio, zstd}; +use common::{ + anyhow::Result, + flate2::{write::GzEncoder, Compression}, + repository::Repository, + tar, tokio, +}; use std::{ fs, fs::File, @@ -132,7 +137,7 @@ impl BuildProcess { ); let build_desc_path = self.input_path.join("metadata.toml"); let archive = File::create(output_path)?; - let enc = zstd::stream::Encoder::new(archive, 0)?; + let enc = GzEncoder::new(archive, Compression::default()); let mut tar = tar::Builder::new(enc); tar.follow_symlinks(false); tar.append_path_with_name(build_desc_path, "metadata.toml")?; diff --git a/client/src/install.rs b/client/src/install.rs index 938faba..90dfaee 100644 --- a/client/src/install.rs +++ b/client/src/install.rs @@ -40,8 +40,8 @@ pub async fn install(names: &[String], env: &mut Environment) -> Result<()> { }; packages.insert(pkg, repo); // If already installed, print message - if let Some(version) = env.get_installed_version(name) { - println!("Package `{name}` version `{version}` is already installed. Reinstalling",); + if let Some(version) = env.get_installed_version(name)? { + println!("Package `{name}` version `{version}` is already installed. Reinstalling"); } } if failed { diff --git a/common/Cargo.toml b/common/Cargo.toml index 33810fc..5c3a78d 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -24,7 +24,6 @@ tokio-util = { version = "0.7.15", features = ["io"] } toml = { version = "0.9.5" } utils = { git = "https://github.com/maestro-os/maestro-utils" } xz2 = "0.1.7" -zstd = "0.13.3" [features] default = [] diff --git a/common/src/lib.rs b/common/src/lib.rs index 1d5bf19..580925a 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -8,7 +8,6 @@ pub use tar; pub use tokio; pub use tokio_util; pub use utils as maestro_utils; -pub use zstd; #[cfg(feature = "network")] pub mod download; @@ -30,9 +29,8 @@ use std::{ /// The directory containing cached packages. const LOCKFILE_PATH: &str = "var/lib/blimp/.lock"; -// TODO -/*/// The path to directory storing information about installed packages. -const INSTALLED_DB: &str = "var/lib/blimp/installed";*/ +/// The path to directory storing information about installed packages. +const INSTALLED_DB: &str = "var/lib/blimp/installed"; /// The user agent for HTTP requests. pub const USER_AGENT: &str = concat!("blimp/", env!("CARGO_PKG_VERSION")); @@ -98,20 +96,44 @@ impl Environment { &self.arch } - /// Returns the installed version for the package with the given `name`. - pub fn get_installed_version(&self, _name: &str) -> Option { - todo!() + /// If installed, returns the version of the package with the given `name` + pub fn get_installed_version(&self, name: &str) -> Result> { + // Ensure the parent directory exists + let path = self.sysroot.join(INSTALLED_DB); + fs::create_dir_all(&path)?; + // Read file and get version + let path = path.join(name); + let res = fs::read_to_string(path); + let installed = match res { + Ok(i) => i, + Err(e) if e.kind() == ErrorKind::NotFound => return Ok(None), + Err(e) => return Err(e.into()), + }; + let installed: InstalledPackage = toml::from_str(&installed)?; + Ok(Some(installed.desc.version)) + } + + /// Writes installed package information + fn write_installed_version(&self, pkg: &InstalledPackage) -> Result<()> { + // Ensure the parent directory exists + let path = self.sysroot.join(INSTALLED_DB); + fs::create_dir_all(&path)?; + // Write + let path = path.join(&pkg.desc.name); + let content = toml::to_string(pkg)?; + fs::write(path, content)?; + Ok(()) } /// Installs the given package. /// /// Arguments: - /// - `pkg` is the package to be installed. - /// - `archive_path` is the path to the archive of the package. + /// - `pkg` is the package to be installed + /// - `archive_path` is the path to the archive of the package /// /// The function does not resolve dependencies. It is the caller's responsibility to install /// them beforehand. - pub fn install(&self, _pkg: &Package, archive_path: &Path) -> Result<(), Box> { + pub fn install(&self, pkg: &Package, archive_path: &Path) -> Result<(), Box> { // Read archive let mut archive = util::read_package_archive(archive_path)?; // TODO Get hooks (pre-install-hook and post-install-hook) @@ -136,16 +158,19 @@ impl Environment { files.push(path); } // TODO Execute post-install-hook - // TODO add package to installed db + self.write_installed_version(&InstalledPackage { + desc: pkg.clone(), + files, + })?; Ok(()) } /// Installs a new version of the package, removing the previous. /// /// Arguments: - /// - `pkg` is the package to be updated. - /// - `archive_path` is the path to the archive of the new version of the package. - pub fn update(&self, _pkg: &Package, archive_path: &Path) -> Result<()> { + /// - `pkg` is the package to be updated + /// - `archive_path` is the path to the archive of the new version of the package + pub fn upgrade(&self, _pkg: &Package, archive_path: &Path) -> Result<()> { // Read archive let _archive = util::read_package_archive(archive_path)?; // TODO Get hooks (pre-update-hook and post-update-hook) diff --git a/common/src/repository/mod.rs b/common/src/repository/mod.rs index d5a723a..5790134 100644 --- a/common/src/repository/mod.rs +++ b/common/src/repository/mod.rs @@ -66,7 +66,7 @@ impl Repository { self.path .join("dist") .join(arch) - .join(format!("{name}_{version}.tar.zstd")) + .join(format!("{name}_{version}.tar.gz")) } /// Tells whether the **archive** of a package is present in the repository. @@ -135,16 +135,22 @@ impl Repository { name: &str, version_constraint: Option<&VersionConstraint>, ) -> Result> { - let version = fs::read_dir(self.path.join(name))? + let base_path = self.path.join("dist").join(arch); + fs::read_dir(base_path)? .filter_map(|ent| { let ent = ent.ok()?; - - if ent.file_type().ok()?.is_dir() { - let name = ent.file_name(); - Version::try_from(name.to_str()?).ok() - } else { - None + if !ent.file_type().ok()?.is_file() { + return None; } + let n = ent.file_name(); + let n = n.to_str()?; + // Retrieve package name and version + let name_version = n.strip_suffix(".meta")?; + let (n, version) = name_version.split_once('_')?; + if n != name { + return None; + } + Version::try_from(version).ok() }) .filter(|version| { if let Some(c) = version_constraint { @@ -153,12 +159,9 @@ impl Repository { true } }) - .max(); - - match version { - Some(version) => self.get_package(arch, name, &version), - None => Ok(None), - } + .max() + .and_then(|version| self.get_package(arch, name, &version).transpose()) + .transpose() } } diff --git a/common/src/repository/remote.rs b/common/src/repository/remote.rs index 8e303fe..b876fb8 100644 --- a/common/src/repository/remote.rs +++ b/common/src/repository/remote.rs @@ -102,7 +102,7 @@ impl Remote { /// Returns the download URL for the given `package`. pub fn download_url(&self, env: &Environment, package: &Package) -> String { format!( - "https://{}/dist/{}/{}_{}.tar.zstd", + "https://{}/dist/{}/{}_{}.tar.gz", self.host, env.arch, package.name, package.version ) } diff --git a/man/man7/blimp.7 b/man/man7/blimp.7 index 91e4e15..148780e 100644 --- a/man/man7/blimp.7 +++ b/man/man7/blimp.7 @@ -14,7 +14,7 @@ Example: .B - dist/ .B " "- x86_64/ .B " "- _.meta -.B " "- _.tar.zstd +.B " "- _.tar.gz ... .B " "- x86/ ... @@ -40,7 +40,7 @@ the server's MOTD contains the list of all packages and their supported architecture, in TOML format. It is used by the client to determine the list of packages present on the server .B dist/ -contains packages (*.tar.zstd) and their metadata (*.meta), sorted by CPU architecture +contains packages (*.tar.gz) and their metadata (*.meta), sorted by CPU architecture .B i18n/ contains internationalization files, sorted by locale From 8b5deb707d5bc06f343593e0972ecf2c33869e31 Mon Sep 17 00:00:00 2001 From: llenotre Date: Tue, 2 Sep 2025 13:51:18 +0200 Subject: [PATCH 10/15] chore: switch license to AGPLv3 --- COPYING | 661 ++++++++++++++++++++++++++++++++ LICENSE | 21 - README.md | 30 +- builder/src/build.rs | 18 + builder/src/cache.rs | 18 + builder/src/desc.rs | 18 + builder/src/main.rs | 18 + builder/src/util.rs | 18 + client/src/confirm.rs | 18 + client/src/install.rs | 18 + client/src/main.rs | 18 + client/src/remote.rs | 18 + client/src/remove.rs | 18 + client/src/update.rs | 18 + common/src/download.rs | 18 + common/src/lib.rs | 18 + common/src/lockfile.rs | 18 + common/src/package.rs | 18 + common/src/repository/mod.rs | 18 + common/src/repository/remote.rs | 18 + common/src/util.rs | 18 + common/src/version.rs | 18 + 22 files changed, 1013 insertions(+), 41 deletions(-) create mode 100644 COPYING delete mode 100644 LICENSE diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index c84b14c..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Luc Lenôtre - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 77441d3..bc3b8bc 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,18 @@

- - logo + + logo

-[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge&logo=book)](./LICENSE) -![Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fllenotre%2Fblimp%2Fmaster%2Fclient%2FCargo.toml&query=%24.package.version&style=for-the-badge&label=version) -![Continuous integration](https://img.shields.io/github/actions/workflow/status/llenotre/blimp/check.yml?style=for-the-badge&logo=github) +[![AGPL-3.0 license](https://img.shields.io/badge/license-AGPL--3.0-blue.svg?style=for-the-badge&logo=book)](./COPYING) +![Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmaestro-os%2Fblimp%2Fmaster%2Fclient%2FCargo.toml&query=%24.package.version&style=for-the-badge&label=version) +![Continuous integration](https://img.shields.io/github/actions/workflow/status/maestro-os/blimp/check.yml?style=for-the-badge&logo=github) # About -Blimp is a simple package manager for Unix-like operating systems, more specifically for [Maestro](https://github.com/llenotre/maestro). +Blimp is a simple package manager for Unix-like operating systems, more specifically for [Maestro](https://github.com/maestro-os/maestro). This repository contains the following components: - `blimp`: The package manager itself @@ -21,8 +21,6 @@ This repository contains the following components: The `common` crate is a library with utilities shared across components. - - # Build Build the package manager using: @@ -32,17 +30,13 @@ cargo build # Debug mode cargo build --release # Release mode ``` -Building with network support required the `network` feature: - -```sh -cargo build --features network # Debug mode -cargo build --features network --release # Release mode -``` - - +Features: +- `network` (default): Enable network support. Disabling this feature is necessary when the SSL library is not available. # Usage +Man pages are shipped with this repository, and are available in `man/`. + ## Blimp Synchronize packages information with remotes: @@ -75,8 +69,6 @@ Show the whole usage of the command: blimp ``` - - ## Package builder The general usage of the command is: @@ -91,8 +83,6 @@ The `--package` flag can be used to write the resulting package into an archive > **Note**: the structure of package descriptors and output packages are not yet documented as they are unstable - - ### Bootstrapping When building packages for a new system on a different target triplet than the current system, **bootstrapping** is required. diff --git a/builder/src/build.rs b/builder/src/build.rs index f3f3536..282f432 100644 --- a/builder/src/build.rs +++ b/builder/src/build.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! Implementation of the package building procedure. use crate::{desc::BuildDescriptor, WORK_DIR}; diff --git a/builder/src/cache.rs b/builder/src/cache.rs index beab31a..8b42473 100644 --- a/builder/src/cache.rs +++ b/builder/src/cache.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! Packages sources cache. use base64::{prelude::BASE64_STANDARD, Engine}; diff --git a/builder/src/desc.rs b/builder/src/desc.rs index 8939597..f54998a 100644 --- a/builder/src/desc.rs +++ b/builder/src/desc.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! A build descriptor defines how to build a package. //! //! A build descriptor contains general information about the package, but also sources for files diff --git a/builder/src/main.rs b/builder/src/main.rs index 2e3c54b..75507d6 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! Utility allowing to build packages. mod build; diff --git a/builder/src/util.rs b/builder/src/util.rs index 9ac0c94..351ea73 100644 --- a/builder/src/util.rs +++ b/builder/src/util.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! Utilities. use common::anyhow::{anyhow, Result}; diff --git a/client/src/confirm.rs b/client/src/confirm.rs index e34947c..3662185 100644 --- a/client/src/confirm.rs +++ b/client/src/confirm.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! Confirmation prompt. use common::maestro_utils; diff --git a/client/src/install.rs b/client/src/install.rs index 90dfaee..fda9a58 100644 --- a/client/src/install.rs +++ b/client/src/install.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! This module handles package installation. use crate::confirm; diff --git a/client/src/main.rs b/client/src/main.rs index d4c4f8f..b0bb1f8 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! Blimp is a simple package manager for Unix systems. mod confirm; diff --git a/client/src/remote.rs b/client/src/remote.rs index 4326eee..65a2bfd 100644 --- a/client/src/remote.rs +++ b/client/src/remote.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! Remotes management. use common::{repository::remote::Remote, Environment}; diff --git a/client/src/remove.rs b/client/src/remove.rs index c6a930c..50b7400 100644 --- a/client/src/remove.rs +++ b/client/src/remove.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! TODO doc use common::{ diff --git a/client/src/update.rs b/client/src/update.rs index c5c3f79..2d2e4c9 100644 --- a/client/src/update.rs +++ b/client/src/update.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! This module handles packages list updating. use common::{ diff --git a/common/src/download.rs b/common/src/download.rs index 12620ca..dc4e45f 100644 --- a/common/src/download.rs +++ b/common/src/download.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! This module handles files download. use crate::USER_AGENT; diff --git a/common/src/lib.rs b/common/src/lib.rs index 580925a..ec3acbc 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! The blimp library is the core of the Blimp package manager. #![feature(io_error_more)] diff --git a/common/src/lockfile.rs b/common/src/lockfile.rs index 762a771..0d1fbb5 100644 --- a/common/src/lockfile.rs +++ b/common/src/lockfile.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! The lock file allows to prevent several instances of the package manager from running at the //! same time. diff --git a/common/src/package.rs b/common/src/package.rs index 2ff9233..fd97625 100644 --- a/common/src/package.rs +++ b/common/src/package.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! A package is a software that can be installed using the package manager. //! //! Packages are usually downloaded from a remote host. diff --git a/common/src/repository/mod.rs b/common/src/repository/mod.rs index 5790134..8550c52 100644 --- a/common/src/repository/mod.rs +++ b/common/src/repository/mod.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! A repository contains packages that can be installed. //! //! A repository can be linked to a remote, from which packages can be fetched. diff --git a/common/src/repository/remote.rs b/common/src/repository/remote.rs index b876fb8..b92d66d 100644 --- a/common/src/repository/remote.rs +++ b/common/src/repository/remote.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! A remote is a remote host from which packages can be downloaded. use crate::{package::Package, Environment, USER_AGENT}; diff --git a/common/src/util.rs b/common/src/util.rs index 7f552d6..3e917e0 100644 --- a/common/src/util.rs +++ b/common/src/util.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! This module implements utility functions. use bzip2::read::BzDecoder; diff --git a/common/src/version.rs b/common/src/version.rs index 330e13f..4e732c3 100644 --- a/common/src/version.rs +++ b/common/src/version.rs @@ -1,3 +1,21 @@ +/* + * Copyright 2025 Luc Lenôtre + * + * This file is part of Maestro. + * + * Maestro is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * Maestro is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * Maestro. If not, see . + */ + //! The version structure represents the version of a package. use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; From 88fc310705299c4697277d97f4fe4f2f14a02faa Mon Sep 17 00:00:00 2001 From: llenotre Date: Tue, 2 Sep 2025 14:02:18 +0200 Subject: [PATCH 11/15] chore: bump Rust --- common/src/lib.rs | 2 -- rust-toolchain.toml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index ec3acbc..7803953 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -18,8 +18,6 @@ //! The blimp library is the core of the Blimp package manager. -#![feature(io_error_more)] - pub use anyhow; pub use flate2; pub use tar; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index da5ad19..0750be7 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2025-05-10" +channel = "nightly-2025-09-01" components = ["rustfmt", "rustc-dev", "rust-src", "clippy"] profile = "minimal" From a1d2eaad9663cedaf7da031a579cc224a79c3e83 Mon Sep 17 00:00:00 2001 From: llenotre Date: Wed, 10 Sep 2025 00:34:18 +0200 Subject: [PATCH 12/15] fix: bootstrap toolchain (wip) --- bootstrap/README.md | 2 +- bootstrap/desc/musl/build-hook | 2 +- bootstrap/init.sh | 24 +++++++++++++++++++++--- builder/src/main.rs | 4 +++- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/bootstrap/README.md b/bootstrap/README.md index 754559f..adc25f5 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -32,7 +32,7 @@ Then, each package has to be built, in the order of the table above. The command to use for building a package is: ```sh -PATH="$(pwd)/sysroot/tools:$PATH" HOST= TARGET= blimp-builder --from desc// --to sysroot/ +PATH="$(pwd)/sysroot/tools/bin:$PATH" HOST= TARGET= blimp-builder --from desc// --to sysroot/ ``` Once this is done, the second **gcc** can be used to cross compile packages (autoconf, make, etc...) on the target. diff --git a/bootstrap/desc/musl/build-hook b/bootstrap/desc/musl/build-hook index 4b3ba59..633fce3 100755 --- a/bootstrap/desc/musl/build-hook +++ b/bootstrap/desc/musl/build-hook @@ -6,7 +6,7 @@ cd * ./configure \ --build="$BUILD" \ --host="$HOST" \ - --target="$TARGET" \ + --target="$HOST" \ --prefix="/usr" \ --enable-optimize \ --enable-shared diff --git a/bootstrap/init.sh b/bootstrap/init.sh index 926ece9..547a292 100755 --- a/bootstrap/init.sh +++ b/bootstrap/init.sh @@ -2,15 +2,33 @@ set -e -mkdir -pv sysroot/tools sysroot/{etc,var} sysroot/usr/{bin,lib,lib32,sbin} +BUILD="$(cc -dumpmachine)" +if [ -z "$HOST" ]; then + echo "Missing HOST environment variable" + exit 1 +fi +# Create base directories +mkdir -pv sysroot/tools sysroot/{etc,var} sysroot/usr/{bin,lib,lib32,sbin} for i in bin lib sbin; do ln -sv "usr/$i" "sysroot/$i" done - case ${HOST%%-*} in x86_64) ln -sv usr/lib sysroot/lib64 ln -sv lib sysroot/usr/lib64 ;; -esac \ No newline at end of file +esac + +# Build packages +# TODO use release mode builder instead? +OLD_PATH="$PATH" +export PATH="$(pwd)/sysroot/tools/bin:$(pwd)/../target/debug:$PATH" +HOST="$BUILD" TARGET="$HOST" blimp-builder --from desc/binutils1/ --to sysroot/ +HOST="$BUILD" TARGET="$HOST" blimp-builder --from desc/gcc1/ --to sysroot/ +PATH="$OLD_PATH" ../target/debug/blimp-builder --from desc/linux-headers/ --to sysroot/ +HOST="$HOST" blimp-builder --from desc/musl/ --to sysroot/ +HOST="$HOST" blimp-builder --from desc/zlib/ --to sysroot/ +HOST="$HOST" blimp-builder --from desc/libstdc++/ --to sysroot/ +HOST="$HOST" TARGET="$HOST" blimp-builder --from desc/binutils2/ --to sysroot/ +HOST="$HOST" TARGET="$HOST" blimp-builder --from desc/gcc2/ --to sysroot/ diff --git a/builder/src/main.rs b/builder/src/main.rs index 75507d6..fcb3ec3 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -34,7 +34,7 @@ use common::{ repository::Repository, tokio::runtime::Runtime, }; -use std::{env, path::PathBuf, process::exit, str}; +use std::{env, fs, path::PathBuf, process::exit, str}; /// The path to the work directory. const WORK_DIR: &str = "work/"; @@ -86,6 +86,8 @@ fn main_impl(args: Args) -> Result<()> { let debug = env::var("BLIMP_DEBUG") .map(|s| s == "true") .unwrap_or(false); + fs::create_dir_all(&args.to) + .map_err(|e| anyhow!("failed to create destination directory: {e}"))?; println!("[INFO] Jobs: {jobs}; Build: {build}; Host: {host}; Target: {target}"); let sysroot = (!args.package).then(|| args.to.clone()); let build_process = BuildProcess::new(args.from, sysroot)?; From 5a9386d600fb0c289b1ffe428850eb3d7b101fd3 Mon Sep 17 00:00:00 2001 From: llenotre Date: Wed, 10 Sep 2025 01:37:25 +0200 Subject: [PATCH 13/15] fix: bootstrap toolchain --- bootstrap/init.sh | 22 ++++++++++++---------- common/src/package.rs | 11 ++++++++++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/bootstrap/init.sh b/bootstrap/init.sh index 547a292..c6cda79 100755 --- a/bootstrap/init.sh +++ b/bootstrap/init.sh @@ -2,18 +2,20 @@ set -e -BUILD="$(cc -dumpmachine)" +A="$(cc -dumpmachine)" if [ -z "$HOST" ]; then echo "Missing HOST environment variable" exit 1 fi +B="$HOST" +unset BUILD HOST TARGET # Create base directories mkdir -pv sysroot/tools sysroot/{etc,var} sysroot/usr/{bin,lib,lib32,sbin} for i in bin lib sbin; do ln -sv "usr/$i" "sysroot/$i" done -case ${HOST%%-*} in +case ${B%%-*} in x86_64) ln -sv usr/lib sysroot/lib64 ln -sv lib sysroot/usr/lib64 @@ -24,11 +26,11 @@ esac # TODO use release mode builder instead? OLD_PATH="$PATH" export PATH="$(pwd)/sysroot/tools/bin:$(pwd)/../target/debug:$PATH" -HOST="$BUILD" TARGET="$HOST" blimp-builder --from desc/binutils1/ --to sysroot/ -HOST="$BUILD" TARGET="$HOST" blimp-builder --from desc/gcc1/ --to sysroot/ -PATH="$OLD_PATH" ../target/debug/blimp-builder --from desc/linux-headers/ --to sysroot/ -HOST="$HOST" blimp-builder --from desc/musl/ --to sysroot/ -HOST="$HOST" blimp-builder --from desc/zlib/ --to sysroot/ -HOST="$HOST" blimp-builder --from desc/libstdc++/ --to sysroot/ -HOST="$HOST" TARGET="$HOST" blimp-builder --from desc/binutils2/ --to sysroot/ -HOST="$HOST" TARGET="$HOST" blimp-builder --from desc/gcc2/ --to sysroot/ +HOST="$A" TARGET="$B" blimp-builder --from desc/binutils1/ --to sysroot/ +HOST="$A" TARGET="$B" blimp-builder --from desc/gcc1/ --to sysroot/ +blimp-builder --from desc/linux-headers/ --to sysroot/ +HOST="$B" blimp-builder --from desc/musl/ --to sysroot/ +HOST="$B" blimp-builder --from desc/zlib/ --to sysroot/ +HOST="$B" blimp-builder --from desc/libstdc++/ --to sysroot/ +HOST="$B" TARGET="$B" blimp-builder --from desc/binutils2/ --to sysroot/ +HOST="$B" TARGET="$B" blimp-builder --from desc/gcc2/ --to sysroot/ diff --git a/common/src/package.rs b/common/src/package.rs index fd97625..ef65808 100644 --- a/common/src/package.rs +++ b/common/src/package.rs @@ -35,7 +35,16 @@ use std::{ /// Tells whether the given package name is valid. pub fn is_valid_name(name: &str) -> bool { - name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') + if name.len() < 2 { + return false; + } + name.chars().enumerate().all(|(i, c)| { + if i == 0 { + c.is_ascii_lowercase() + } else { + c.is_ascii_lowercase() || c.is_ascii_digit() || "+-.".contains(c) + } + }) } /// Enumeration of package dependency resolution errors. From b40aead30fad123fed5ceca96e773c8cd0cfd9fc Mon Sep 17 00:00:00 2001 From: llenotre Date: Wed, 10 Sep 2025 01:59:26 +0200 Subject: [PATCH 14/15] doc(bootstrap): update README --- bootstrap/README.md | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/bootstrap/README.md b/bootstrap/README.md index adc25f5..9e7e67b 100644 --- a/bootstrap/README.md +++ b/bootstrap/README.md @@ -2,11 +2,9 @@ Bootstrapping is the process of creating an environment which allows the cross compilation of packages. +`./init.sh` builds a cross-compilation toolchain in `sysroot/`. - -## Overview - -The following build steps are required for bootstrapping: +The following packages are built: | Package | Host triplet | Target triplet | Notes | |---------------------------------------------|--------------|----------------|-------------------------------------------------| @@ -21,18 +19,4 @@ The following build steps are required for bootstrapping: > **Note**: one last compilation of gcc (stage 3) will be necessary for a final system, but it is treated as a casual package and not discussed here. -## Building - -First, the `sysroot` directory and a basic file hierarchy must be created. Use `init.sh`: -```sh -./init.sh -``` - -Then, each package has to be built, in the order of the table above. - -The command to use for building a package is: -```sh -PATH="$(pwd)/sysroot/tools/bin:$PATH" HOST= TARGET= blimp-builder --from desc// --to sysroot/ -``` - -Once this is done, the second **gcc** can be used to cross compile packages (autoconf, make, etc...) on the target. +Once built, the second **gcc** can be used to cross compile packages on the target. From 4e07822addc190e696aac21a0c070712627a7570 Mon Sep 17 00:00:00 2001 From: llenotre Date: Fri, 12 Sep 2025 19:12:37 +0200 Subject: [PATCH 15/15] fix(cache): filenames --- builder/src/cache.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/src/cache.rs b/builder/src/cache.rs index 8b42473..8842eba 100644 --- a/builder/src/cache.rs +++ b/builder/src/cache.rs @@ -95,7 +95,6 @@ impl CacheEntry { /// Flushes the entry to the cache by computing and storing its checksum. pub fn flush(&mut self) -> io::Result<()> { let dir_path = cache_directory()?; - let path = dir_path.join(&self.encoded_key); // `.` is not part of the base64 character set let checksum_path = dir_path.join(format!("{}.checksum", self.encoded_key)); // Compute checksum @@ -114,7 +113,8 @@ impl CacheEntry { /// before. pub fn get_or_insert(key: &[u8]) -> io::Result { let dir_path = cache_directory()?; - let encoded_key = BASE64_STANDARD.encode(key); + // `/` causes issues with paths. Replace it by `-` which is not in the base64 characters set + let encoded_key = BASE64_STANDARD.encode(key).replace('/', "-"); let path = dir_path.join(&encoded_key); // Open file let opt = FileOptions::new().read(true).write(true).create(true);