diff --git a/Cargo.lock b/Cargo.lock index 4cd7fda..75996e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,7 +160,6 @@ name = "blimp" version = "0.1.0" dependencies = [ "common", - "tokio", ] [[package]] @@ -1179,7 +1178,6 @@ dependencies = [ "base64", "bytes", "encoding_rs", - "futures-channel", "futures-core", "futures-util", "h2", diff --git a/builder/src/build.rs b/builder/src/build.rs index fe5a0b3..a56b4c2 100644 --- a/builder/src/build.rs +++ b/builder/src/build.rs @@ -2,6 +2,7 @@ use crate::{desc::BuildDescriptor, WORK_DIR}; use anyhow::Result; +use common::serde_json; use flate2::{write::GzEncoder, Compression}; use std::{ fs, @@ -37,7 +38,8 @@ impl BuildProcess { /// `input_path` is the path to the directory containing information to build the package. pub fn new(input_path: PathBuf) -> io::Result { let build_desc_path = input_path.join("package.json"); - let build_desc = common::util::read_json::(&build_desc_path)?; + let build_desc = fs::read_to_string(build_desc_path)?; + let build_desc = serde_json::from_str(&build_desc)?; Ok(Self { input_path, build_desc, diff --git a/builder/src/main.rs b/builder/src/main.rs index a9525db..8c50fc2 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -9,8 +9,8 @@ use crate::{ util::{get_build_triplet, get_jobs_count}, }; use anyhow::{anyhow, bail, Result}; -use common::repository::Repository; -use std::{env, fs, path::PathBuf, process::exit, str}; +use common::{repository::Repository, serde_json}; +use std::{env, fs, io, path::PathBuf, process::exit, str}; use tokio::runtime::Runtime; /// The path to the work directory. @@ -41,6 +41,25 @@ fn print_usage(bin: &str) { eprintln!("All environment variable are optional"); } +/// 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)) +} + /// Builds the package. /// /// `from` and `to` correspond to the command line arguments. @@ -70,18 +89,8 @@ fn build(from: PathBuf, to: PathBuf) -> Result<()> { bail!("Package build failed!"); } println!("[INFO] Prepare repository at `{}`...", to.display()); - // TODO Move to separate function - let archive_path = { - 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)?; - let desc_path = package_path.join("desc"); - common::util::write_json(&desc_path, &build_desc.package)?; - let repo = Repository::load(to)?; - repo.get_archive_path(name, version) - }; + let archive_path = prepare(&build_process, to) + .map_err(|e| anyhow!("Failed to prepare directory for package: {e}"))?; println!("[INFO] Create archive..."); build_process .create_archive(&archive_path) diff --git a/client/Cargo.toml b/client/Cargo.toml index 2fe99a1..8a70b31 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] common = { path = "../common" } -tokio = { version = "1.41.1", features = ["macros", "rt", "rt-multi-thread"] } [features] default = [] diff --git a/client/src/install.rs b/client/src/install.rs index 86e04f2..f6ff6fd 100644 --- a/client/src/install.rs +++ b/client/src/install.rs @@ -10,7 +10,7 @@ use common::{ repository::Repository, Environment, }; -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, io, path::PathBuf}; // TODO Clean /// Installs the given list of packages. @@ -22,20 +22,17 @@ use std::{collections::HashMap, path::PathBuf}; pub async fn install( names: &[String], env: &mut Environment, - local_repos: &[PathBuf], + local_repos: Vec, ) -> Result<()> { - let installed = env.load_installed_list()?; - - let mut failed = false; - // The list of repositories let repos = local_repos - .iter() - .map(|path| Repository::load(path.clone())) - .collect::, _>>()?; + .into_iter() + .map(Repository::load) + .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 Some((repo, pkg)) = pkg else { @@ -44,23 +41,17 @@ pub async fn install( continue; }; packages.insert(pkg, repo); - - if let Some(installed) = installed.get(name) { - println!( - "Package `{name}` version `{}` is already installed. Reinstalling", - installed.desc.version - ); + // If already installed, print message + if let Some(version) = env.get_installed_version(name) { + println!("Package `{name}` version `{version}` is already installed. Reinstalling",); } } if failed { bail!("installation failed"); } - println!("Resolving dependencies..."); - // The list of all packages, dependencies included let mut total_packages = packages.clone(); - // TODO check dependencies for all packages at once to avoid duplicate errors // Resolving dependencies for (package, _) in packages { @@ -79,14 +70,11 @@ pub async fn install( return None; } }; - match pkg { Some((repo, pkg)) => Some((pkg, repo)), - // If not present, check on remote None => { - // TODO - todo!(); + todo!() } } }, @@ -95,16 +83,13 @@ pub async fn install( for e in errs { eprintln!("{e}"); } - failed = true; } } if failed { bail!("installation failed"); } - println!("Packages to be installed:"); - // List packages to be installed #[cfg(feature = "network")] { @@ -112,22 +97,17 @@ pub async fn install( for (pkg, repo) in &total_packages { let name = &pkg.name; let version = &pkg.version; - match repo.get_package(name, version)? { Some(_) => println!("\t- {name} ({version}) - cached"), - None => { - let remote = repo.get_remote().unwrap(); - // Get package size from remote + let remote = repo.get_remote().unwrap(); let size = remote.get_size(pkg).await?; total_size += size; - println!("\t- {name} ({version}) - download size: {size}"); } } } - println!( "Total download size: {}", common::maestro_utils::util::ByteSize(total_size) @@ -139,24 +119,20 @@ pub async fn install( println!("\t- {} ({}) - cached", pkg.name, pkg.version); } } - if !confirm::prompt() { println!("Aborting."); return Ok(()); } - #[cfg(feature = "network")] { println!("Downloading packages..."); 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) { println!("`{}` is in cache.", &pkg.name); continue; } - if let Some(remote) = repo.get_remote() { // TODO limit the number of packages downloaded concurrently futures.push(( @@ -173,7 +149,6 @@ pub async fn install( )); } } - // TODO Add progress bar for (name, version, f) in futures { match f.await { @@ -187,14 +162,11 @@ pub async fn install( bail!("installation failed"); } } - println!(); println!("Installing packages..."); - - // Installing all packages + // Install all packages for (pkg, repo) in total_packages { println!("Installing `{}`...", pkg.name); - let archive_path = repo.get_archive_path(&pkg.name, &pkg.version); if let Err(e) = env.install(&pkg, &archive_path) { eprintln!("Failed to install `{}`: {e}", &pkg.name); @@ -204,6 +176,5 @@ pub async fn install( if failed { bail!("installation failed"); } - Ok(()) } diff --git a/client/src/main.rs b/client/src/main.rs index 837cb77..c2a7985 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -10,7 +10,7 @@ mod update; use common::{ anyhow::{anyhow, Result}, - Environment, + tokio, Environment, }; use install::install; use remove::remove; @@ -19,7 +19,6 @@ use std::{ path::{Path, PathBuf}, process::exit, }; -use tokio::runtime::Runtime; /// Prints command line usage. fn print_usage() { @@ -73,7 +72,7 @@ fn get_env(sysroot: &Path) -> Result { Environment::with_root(sysroot)?.ok_or(anyhow!("failed to acquire lockfile")) } -fn main_impl(sysroot: &Path, local_repos: &[PathBuf]) -> Result { +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 { @@ -93,8 +92,7 @@ fn main_impl(sysroot: &Path, local_repos: &[PathBuf]) -> Result { #[cfg(feature = "network")] "update" => { let mut env = get_env(sysroot)?; - let rt = Runtime::new()?; - rt.block_on(update::update(&mut env))?; + update::update(&mut env).await?; Ok(true) } "install" => { @@ -104,8 +102,7 @@ fn main_impl(sysroot: &Path, local_repos: &[PathBuf]) -> Result { return Ok(false); } let mut env = get_env(sysroot)?; - let rt = Runtime::new()?; - rt.block_on(install(names, &mut env, local_repos))?; + install(names, &mut env, local_repos).await?; Ok(true) } "upgrade" => { @@ -134,7 +131,7 @@ fn main_impl(sysroot: &Path, local_repos: &[PathBuf]) -> Result { #[cfg(feature = "network")] "remote-list" => { let env = get_env(sysroot)?; - remote::list(&env)?; + remote::list(&env).await?; Ok(true) } #[cfg(feature = "network")] @@ -180,10 +177,11 @@ async fn main() { let sysroot = env::var_os("SYSROOT") .map(PathBuf::from) .unwrap_or(PathBuf::from("/")); - let local_repos: Vec = env::var("LOCAL_REPO") // TODO var_os + let local_repos = env::var("LOCAL_REPO") // TODO var_os .map(|s| s.split(':').map(PathBuf::from).collect()) .unwrap_or_default(); - match main_impl(&sysroot, &local_repos) { + let res = main_impl(&sysroot, local_repos).await; + match res { Ok(false) => exit(1), Err(e) => { eprintln!("error: {e}"); diff --git a/client/src/remote.rs b/client/src/remote.rs index 1f9c5bd..2752ca6 100644 --- a/client/src/remote.rs +++ b/client/src/remote.rs @@ -3,12 +3,12 @@ use common::{repository::remote::Remote, Environment}; /// Lists remotes. -pub fn list(env: &Environment) -> std::io::Result<()> { +pub async fn list(env: &Environment) -> std::io::Result<()> { let remotes = Remote::load_list(env)?; println!("Remotes list:"); for remote in remotes { let host = &remote.host; - match remote.fetch_motd() { + match remote.fetch_motd().await { Ok(motd) => println!("- {host} (status: UP): {motd}"), Err(_) => println!("- {host} (status: DOWN)"), } diff --git a/client/src/remove.rs b/client/src/remove.rs index 1d658aa..51295ea 100644 --- a/client/src/remove.rs +++ b/client/src/remove.rs @@ -1,9 +1,6 @@ //! TODO doc -use common::{ - anyhow::{anyhow, bail, Result}, - package, Environment, -}; +use common::{anyhow::Result, Environment}; // TODO ask for confirm before remove @@ -12,46 +9,6 @@ use common::{ /// Arguments: /// - `names` is the list of packages to remove. /// - `env` is the blimp environment. -pub fn remove(names: &[String], env: &mut Environment) -> Result<()> { - let installed = env.load_installed_list()?; - - // The list of remaining packages after the remove operation - let remaining = { - let mut installed = installed.clone(); - installed.retain(|name, _| !names.contains(name)); - - installed - }; - - // Check for dependency breakages - let unmatched_deps = package::list_unmatched_dependencies(&remaining); - if !unmatched_deps.is_empty() { - eprintln!("The following dependencies would be broken:"); - - for (pkg, dep) in unmatched_deps { - eprintln!( - "- for package `{}` (version `{}`): {}", - pkg.desc.name, pkg.desc.version, dep - ); - } - - bail!("dependencies would break"); - } - - let mut failed = false; - // Remove packages - for name in names { - if let Some(installed) = installed.get(name) { - env.remove(installed) - .map_err(|e| anyhow!("failed to remove package `{}`: {e}", installed.desc.name))?; - } else { - eprintln!("Package `{}` not found!", name); - failed = true; - } - } - if failed { - bail!("cannot remove packages"); - } - - Ok(()) +pub fn remove(_names: &[String], _env: &mut Environment) -> Result<()> { + todo!() } diff --git a/client/src/update.rs b/client/src/update.rs index 0132bcb..2afed2a 100644 --- a/client/src/update.rs +++ b/client/src/update.rs @@ -1,45 +1,35 @@ //! This module handles packages list updating. use common::{ - anyhow::{anyhow, Result}, + anyhow::{anyhow, bail, Result}, repository::remote::Remote, Environment, }; /// Updates the packages list. pub async fn update(env: &mut Environment) -> Result<()> { - let remotes = - Remote::load_list(env).map_err(|e| anyhow!("Could not update packages list: {}", e))?; - + let remotes = Remote::load_list(env) + .map_err(|error| anyhow!("Could not update packages list: {error}"))?; println!("Updating from remotes..."); - let mut futures = Vec::new(); - for r in remotes { - let host = r.host.to_owned(); - // TODO limit the number of concurrent tasks running - futures.push((host, tokio::spawn(async move { r.fetch_list().await }))); + for r in &remotes { + futures.push((&r.host, r.fetch_list())); } - - let mut err = false; + let mut failed = false; for (host, f) in futures { - match f.await? { + match f.await { Ok(packages) => { println!("Remote `{host}`: Found {} package(s).", packages.len()); - - // TODO - todo!(); + todo!() } - Err(e) => { eprintln!("Remote `{host}`: {e}"); - err = true; + failed = true; } } } - - if err { - Err(anyhow!("update failed")) - } else { - Ok(()) + if failed { + bail!("update failed"); } + Ok(()) } diff --git a/common/Cargo.toml b/common/Cargo.toml index 9ab03df..64a7dac 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -15,11 +15,11 @@ futures = "0.3.31" futures-util = "0.3.31" infer = "0.16.0" rand = "0.8.5" -reqwest = { version = "0.12.9", features = ["blocking", "json", "stream"], optional = true } +reqwest = { version = "0.12.9", features = ["json", "stream"], optional = true } serde = { version = "1.0.214", features = ["derive"] } serde_json = "1.0.132" tar = "0.4.43" -tokio = { version = "1.41.1", features = ["fs", "rt", "rt-multi-thread"] } +tokio = { version = "1.41.1", features = ["fs", "macros", "rt", "rt-multi-thread"] } tokio-util = { version = "0.7.12", features = ["io"] } 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 c81be32..68309d6 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -16,10 +16,10 @@ pub mod repository; pub mod util; pub mod version; +use crate::version::Version; use anyhow::Result; use package::{InstalledPackage, Package}; use std::{ - collections::HashMap, error::Error, fs, io, io::ErrorKind, @@ -27,9 +27,9 @@ use std::{ }; /// The directory containing cached packages. -const LOCKFILE_PATH: &str = "/usr/lib/blimp/.lock"; -/// The path to the file storing the list of installed packages. -const INSTALLED_FILE: &str = "/usr/lib/blimp/installed"; +const LOCKFILE_PATH: &str = "var/lib/blimp/.lock"; +/// The path to directory storing information about installed packages. +const INSTALLED_DB: &str = "var/lib/blimp/installed"; /// An environment is a system managed by the package manager. /// @@ -49,7 +49,7 @@ impl Environment { /// same time. If already locked, the function returns `None`. pub fn with_root(sysroot: &Path) -> io::Result> { let sysroot = sysroot.canonicalize()?; - let path = util::concat_paths(&sysroot, LOCKFILE_PATH); + let path = sysroot.join(LOCKFILE_PATH); let acquired = lockfile::lock(&path)?; Ok(acquired.then_some(Self { sysroot, @@ -57,29 +57,13 @@ impl Environment { } /// Returns the sysroot of the current environment. - pub fn get_sysroot(&self) -> &Path { + pub fn sysroot(&self) -> &Path { &self.sysroot } - /// Loads the list of installed packages. - /// - /// The key is the name of the package and the value is the installed package. - pub fn load_installed_list(&self) -> io::Result> { - let path = util::concat_paths(&self.sysroot, INSTALLED_FILE); - match util::read_json::>(&path) { - Ok(pkgs) => Ok(pkgs), - Err(e) if e.kind() == ErrorKind::NotFound => Ok(HashMap::new()), - Err(e) => Err(e), - } - } - - /// Updates the list of installed packages to the disk. - pub fn update_installed_list( - &self, - list: &HashMap, - ) -> io::Result<()> { - let path = util::concat_paths(&self.sysroot, INSTALLED_FILE); - util::write_json(&path, list) + /// Returns the installed version for the package with the given `name`. + pub fn get_installed_version(&self, _name: &str) -> Option { + todo!() } /// Installs the given package. @@ -90,7 +74,7 @@ impl Environment { /// /// 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) @@ -115,16 +99,7 @@ impl Environment { files.push(path); } // TODO Execute post-install-hook - // Update installed list - let mut installed = self.load_installed_list()?; - installed.insert( - pkg.name.to_owned(), - InstalledPackage { - desc: pkg.clone(), - files, - }, - ); - self.update_installed_list(&installed)?; + // TODO add package to installed db Ok(()) } @@ -133,25 +108,14 @@ impl Environment { /// 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<()> { + pub fn update(&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) // TODO Execute pre-update-hook - // The list of installed files - let files = vec![]; // TODO Patch files corresponding to the ones in inner data archive // TODO Execute post-update-hook - // Update installed list - let mut installed = self.load_installed_list()?; - installed.insert( - pkg.name.to_owned(), - InstalledPackage { - desc: pkg.clone(), - files, - }, - ); - self.update_installed_list(&installed)?; + // TODO update package in installed db Ok(()) } @@ -184,17 +148,14 @@ impl Environment { } } // TODO Execute post-remove-hook - // Update installed list - let mut installed = self.load_installed_list()?; - installed.remove(&pkg.desc.name); - self.update_installed_list(&installed)?; + // TODO remove package from installed db Ok(()) } } impl Drop for Environment { fn drop(&mut self) { - let path = util::concat_paths(&self.sysroot, LOCKFILE_PATH); + let path = self.sysroot.join(LOCKFILE_PATH); lockfile::unlock(&path).unwrap_or_else(|e| eprintln!("blimp: could remove lockfile: {e}")); } } diff --git a/common/src/package.rs b/common/src/package.rs index dd8c08a..97a2270 100644 --- a/common/src/package.rs +++ b/common/src/package.rs @@ -99,7 +99,7 @@ pub struct Package { impl Package { /// Loads a package from the given path. /// - /// If the package doesn't exist, the function returns None. + /// 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)?)), diff --git a/common/src/repository/remote.rs b/common/src/repository/remote.rs index 9839648..005f334 100644 --- a/common/src/repository/remote.rs +++ b/common/src/repository/remote.rs @@ -12,7 +12,7 @@ use std::{ }; /// The file which contains the list of remotes. -const REMOTES_FILE: &str = "usr/lib/blimp/remotes_list"; +const REMOTES_FILE: &str = "var/lib/blimp/remotes_list"; /// A remote host. #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -30,7 +30,7 @@ impl Borrow for Remote { impl Remote { /// Loads and returns the list of remote hosts. pub fn load_list(env: &Environment) -> io::Result> { - let path = env.get_sysroot().join(REMOTES_FILE); + let path = env.sysroot().join(REMOTES_FILE); let file = File::open(path)?; let reader = BufReader::new(file); reader @@ -45,7 +45,7 @@ impl Remote { /// Saves the list of remote hosts. pub fn save_list(env: &Environment, remotes: impl Iterator) -> io::Result<()> { - let path = env.get_sysroot().join(REMOTES_FILE); + let path = env.sysroot().join(REMOTES_FILE); let file = OpenOptions::new() .read(true) .write(true) @@ -61,13 +61,12 @@ impl Remote { } /// Returns the remote's motd. - pub fn fetch_motd(&self) -> Result { + pub async fn fetch_motd(&self) -> Result { let url = format!("https://{}/motd", &self.host); - let response = reqwest::blocking::get(url)?; + let response = reqwest::get(url).await?; let status = response.status(); - let content = response.text()?; match status { - StatusCode::OK => Ok(content), + StatusCode::OK => Ok(response.text().await?), _ => bail!("Failed to retrieve motd (status {status})"), } } diff --git a/common/src/util.rs b/common/src/util.rs index 5badcce..3eaadea 100644 --- a/common/src/util.rs +++ b/common/src/util.rs @@ -3,12 +3,11 @@ use bzip2::read::BzDecoder; use flate2::read::GzDecoder; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -use serde::{Deserialize, Serialize}; use std::{ fs, fs::{File, OpenOptions}, io, - io::{BufReader, BufWriter, Read}, + io::Read, os::unix, path::{Path, PathBuf}, }; @@ -107,21 +106,6 @@ pub fn recursive_copy(src: &Path, dst: &Path) -> io::Result<()> { fs::set_permissions(dst, src_metadata.permissions()) } -// TODO: rework to allow deserialize from structs with lifetimes (currently unefficient) -/// Reads a JSON file. -pub fn read_json Deserialize<'a>>(file: &Path) -> io::Result { - let file = File::open(file)?; - let reader = BufReader::new(file); - Ok(serde_json::from_reader(reader)?) -} - -/// Writes a JSON file. -pub fn write_json(file: &Path, data: &T) -> io::Result<()> { - let file = File::create(file)?; - let writer = BufWriter::new(file); - Ok(serde_json::to_writer_pretty(writer, &data)?) -} - /// Concatenates the given paths. /// /// This function is different from the [`Path::join`] in that it does not suppress the