From 21e4d669687ea52dac2644fc56657743b395555e Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 7 Jul 2022 20:02:08 +0200 Subject: [PATCH 01/43] Make fs and command operations asynchronous Signed-off-by: trivernis --- Cargo.lock | 97 ++++++++++++++++++++++++++++++++++- Cargo.toml | 7 ++- src/internal/commands.rs | 15 +++--- src/internal/detect.rs | 3 +- src/internal/rpc.rs | 2 +- src/internal/sort.rs | 30 +++++------ src/internal/sudoloop.rs | 20 ++++---- src/main.rs | 46 +++++++++-------- src/operations/aur_install.rs | 41 ++++++++++----- src/operations/clean.rs | 9 +++- src/operations/install.rs | 3 +- src/operations/search.rs | 5 +- src/operations/uninstall.rs | 7 ++- src/operations/upgrade.rs | 26 +++++----- 14 files changed, 223 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4fc82ac..30d79f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,8 +4,9 @@ version = 3 [[package]] name = "Amethyst" -version = "3.3.0" +version = "4.0.0" dependencies = [ + "async-recursion", "clap", "colored", "libc", @@ -14,6 +15,7 @@ dependencies = [ "regex", "rusqlite", "serde", + "tokio", "ureq", ] @@ -28,6 +30,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "async-recursion" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atty" version = "0.2.14" @@ -57,6 +70,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + [[package]] name = "cc" version = "1.0.73" @@ -335,6 +354,18 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + [[package]] name = "native-tls" version = "0.2.10" @@ -416,6 +447,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + [[package]] name = "pkg-config" version = "0.3.25" @@ -582,6 +619,15 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "smallvec" version = "1.9.0" @@ -662,6 +708,55 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "tokio" +version = "1.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "once_cell", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "tracing", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +dependencies = [ + "once_cell", +] + [[package]] name = "unicode-bidi" version = "0.3.8" diff --git a/Cargo.toml b/Cargo.toml index 45548c7..6f2a3ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "Amethyst" -version = "3.3.0" +version = "4.0.0" authors = ["michal ", "axtlos "] edition = "2021" description = "A fast and efficient AUR helper" @@ -60,3 +60,8 @@ ureq = { version = "2.4.0", default-features = false, features = [ "native-tls", serde = { version = "1.0.138", default-features = false, features = [ "derive", "serde_derive" ] } native-tls = { version = "0.2.10", default-features = false } libc = { version = "0.2.126", default-features = false } +async-recursion = "1.0.0" + +[dependencies.tokio] +version = "1.19.2" +features = ["rt", "io-std", "io-util", "process", "time", "macros", "tracing", "fs"] diff --git a/src/internal/commands.rs b/src/internal/commands.rs index f2df391..b8e86ad 100644 --- a/src/internal/commands.rs +++ b/src/internal/commands.rs @@ -1,5 +1,6 @@ use std::ffi::{OsStr, OsString}; -use std::process::{Child, Command, ExitStatus, Stdio}; +use std::process::{ExitStatus, Stdio}; +use tokio::process::{Child, Command}; use crate::internal::error::{AppError, AppResult}; use crate::internal::is_tty; @@ -84,8 +85,8 @@ impl ShellCommand { } /// Waits for the child to exit but returns an error when it exists with a non-zero status code - pub fn wait_success(self) -> AppResult<()> { - let status = self.wait()?; + pub async fn wait_success(self) -> AppResult<()> { + let status = self.wait().await?; if status.success() { Ok(()) } else { @@ -94,17 +95,17 @@ impl ShellCommand { } /// Waits for the child to exit and returns the output status - pub fn wait(self) -> AppResult { + pub async fn wait(self) -> AppResult { let mut child = self.spawn(false)?; - child.wait().map_err(AppError::from) + child.wait().await.map_err(AppError::from) } /// Waits with output until the program completed and /// returns the string output object - pub fn wait_with_output(self) -> AppResult { + pub async fn wait_with_output(self) -> AppResult { let child = self.spawn(true)?; - let output = child.wait_with_output()?; + let output = child.wait_with_output().await?; let stdout = String::from_utf8(output.stdout).map_err(|e| AppError::from(e.to_string()))?; let stderr = String::from_utf8(output.stderr).map_err(|e| AppError::from(e.to_string()))?; diff --git a/src/internal/detect.rs b/src/internal/detect.rs index 2f64a28..f084922 100644 --- a/src/internal/detect.rs +++ b/src/internal/detect.rs @@ -3,7 +3,7 @@ use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::{prompt, warn}; -pub fn detect() { +pub async fn detect() { let mut pacnew = vec![]; for entry in std::fs::read_dir("/etc").unwrap() { @@ -24,6 +24,7 @@ pub fn detect() { ShellCommand::pacdiff() .elevated() .wait() + .await .silent_unwrap(AppExitCode::PacmanError); } } diff --git a/src/internal/rpc.rs b/src/internal/rpc.rs index 341d32a..0c371b0 100644 --- a/src/internal/rpc.rs +++ b/src/internal/rpc.rs @@ -30,7 +30,7 @@ pub struct InfoResults { pub const URL: &str = "https://aur.archlinux.org/"; -pub fn rpcinfo(pkg: String) -> InfoResults { +pub fn rpcinfo(pkg: &String) -> InfoResults { let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap()); let agent = ureq::AgentBuilder::new() .tls_connector(tls_connector) diff --git a/src/internal/sort.rs b/src/internal/sort.rs index d64747c..37cf1e7 100644 --- a/src/internal/sort.rs +++ b/src/internal/sort.rs @@ -4,42 +4,42 @@ use crate::internal::{clean, rpc, structs}; use crate::{log, Options}; pub fn sort(input: &[String], options: Options) -> structs::Sorted { - let mut repo: Vec = vec![]; - let mut aur: Vec = vec![]; - let mut nf: Vec = vec![]; + let mut repo_packages: Vec = vec![]; + let mut aur_packages: Vec = vec![]; + let mut missing_packages: Vec = vec![]; let verbosity = options.verbosity; - let a = clean(input, options); + let packages = clean(input, options); if verbosity >= 1 { - log!("Sorting: {:?}", a.join(" ")); + log!("Sorting: {:?}", packages.join(" ")); } - for b in a { + for package in packages { let rs = Command::new("pacman") .arg("-Ss") - .arg(format!("^{}$", &b)) + .arg(format!("^{}$", &package)) .stdout(Stdio::null()) .status() .expect("Something has gone wrong"); if let Some(0) = rs.code() { if verbosity >= 1 { - log!("{} found in repos", b); + log!("{} found in repos", package); } - repo.push(b.to_string()); - } else if rpc::rpcinfo(b.to_string()).found { + repo_packages.push(package.to_string()); + } else if rpc::rpcinfo(&package).found { if verbosity >= 1 { - log!("{} found in AUR", b); + log!("{} found in AUR", package); } - aur.push(b.to_string()); + aur_packages.push(package.to_string()); } else { if verbosity >= 1 { - log!("{} not found", b); + log!("{} not found", package); } - nf.push(b.to_string()); + missing_packages.push(package.to_string()); } } - structs::Sorted::new(repo, aur, nf) + structs::Sorted::new(repo_packages, aur_packages, missing_packages) } diff --git a/src/internal/sudoloop.rs b/src/internal/sudoloop.rs index a4f6b3a..9bf7d85 100644 --- a/src/internal/sudoloop.rs +++ b/src/internal/sudoloop.rs @@ -1,16 +1,18 @@ -use crate::ShellCommand; -use std::thread; use std::time::Duration; +use crate::ShellCommand; + /// Loop sudo so it doesn't time out -pub fn start_sudoloop() { - prompt_sudo(); - std::thread::spawn(|| loop { - prompt_sudo(); - thread::sleep(Duration::from_secs(3 * 60)) +pub async fn start_sudoloop() { + prompt_sudo().await; + tokio::task::spawn(async move { + loop { + prompt_sudo().await; + tokio::time::sleep(Duration::from_secs(3 * 60)).await; + } }); } -fn prompt_sudo() { - while ShellCommand::sudo().arg("-v").wait_success().is_err() {} +async fn prompt_sudo() { + while ShellCommand::sudo().arg("-v").wait_success().await.is_err() {} } diff --git a/src/main.rs b/src/main.rs index ee61b35..8413aa2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,8 @@ mod database; mod internal; mod operations; -fn main() { +#[tokio::main(flavor = "current_thread")] +async fn main() { if unsafe { libc::geteuid() } == 0 { crash!( AppExitCode::RunAsRoot, "Running amethyst as root is disallowed as it can lead to system breakage. Instead, amethyst will prompt you when it needs superuser permissions"); } @@ -35,38 +36,38 @@ fn main() { init(options); if args.sudoloop { - start_sudoloop(); + start_sudoloop().await; } match args.subcommand.unwrap_or_default() { - Operation::Install(install_args) => cmd_install(install_args, options), - Operation::Remove(remove_args) => cmd_remove(remove_args, options), - Operation::Search(search_args) => cmd_search(search_args, options), - Operation::Query(query_args) => cmd_query(query_args), + Operation::Install(install_args) => cmd_install(install_args, options).await, + Operation::Remove(remove_args) => cmd_remove(remove_args, options).await, + Operation::Search(search_args) => cmd_search(search_args, options).await, + Operation::Query(query_args) => cmd_query(query_args).await, Operation::Upgrade => { info!("Performing system upgrade"); - operations::upgrade(options); + operations::upgrade(options).await; } Operation::Clean => { info!("Removing orphaned packages"); - operations::clean(options); + operations::clean(options).await; } } - detect(); + detect().await; } -fn cmd_install(args: InstallArgs, options: Options) { +async fn cmd_install(args: InstallArgs, options: Options) { let packages = args.packages; let sorted = sort(&packages, options); info!("Attempting to install packages: {}", packages.join(", ")); if !sorted.repo.is_empty() { - operations::install(sorted.repo, options); + operations::install(sorted.repo, options).await; } if !sorted.aur.is_empty() { - operations::aur_install(sorted.aur, options); + operations::aur_install(sorted.aur, options).await; } if !sorted.nf.is_empty() { crash!( @@ -80,6 +81,7 @@ fn cmd_install(args: InstallArgs, options: Options) { .arg("-c") .arg("sudo find /etc -name *.pacnew") .wait_with_output() + .await .silent_unwrap(AppExitCode::Other) .stdout; @@ -92,51 +94,55 @@ fn cmd_install(args: InstallArgs, options: Options) { } } -fn cmd_remove(args: RemoveArgs, options: Options) { +async fn cmd_remove(args: RemoveArgs, options: Options) { let packages = args.packages; info!("Uninstalling packages: {}", &packages.join(", ")); - operations::uninstall(packages, options); + operations::uninstall(packages, options).await; } -fn cmd_search(args: SearchArgs, options: Options) { +async fn cmd_search(args: SearchArgs, options: Options) { let query_string = args.search.join(" "); if args.aur { info!("Searching AUR for {}", &query_string); - operations::aur_search(&query_string, options); + operations::aur_search(&query_string, options).await; } if args.repo { info!("Searching repos for {}", &query_string); - operations::search(&query_string, options); + operations::search(&query_string, options).await; } if !args.aur && !args.repo { info!("Searching AUR and repos for {}", &query_string); - operations::search(&query_string, options); - operations::aur_search(&query_string, options); + operations::search(&query_string, options).await; + operations::aur_search(&query_string, options).await; } } -fn cmd_query(args: QueryArgs) { +async fn cmd_query(args: QueryArgs) { if args.aur { ShellCommand::pacman() .arg("-Qm") .wait_success() + .await .silent_unwrap(AppExitCode::PacmanError); } if args.repo { ShellCommand::pacman() .arg("-Qn") .wait_success() + .await .silent_unwrap(AppExitCode::PacmanError); } if !args.repo && !args.aur { ShellCommand::pacman() .arg("-Qn") .wait_success() + .await .silent_unwrap(AppExitCode::PacmanError); ShellCommand::pacman() .arg("-Qm") .wait_success() + .await .silent_unwrap(AppExitCode::PacmanError); } } diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 065f443..1af7c3e 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -1,8 +1,9 @@ +use async_recursion::async_recursion; +use std::env; use std::env::set_current_dir; -use std::fs::remove_dir_all; use std::path::Path; use std::process::Command; -use std::{env, fs}; +use tokio::fs; use crate::internal::commands::ShellCommand; use crate::internal::error::SilentUnwrap; @@ -10,20 +11,22 @@ use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcinfo; use crate::{crash, info, log, prompt, Options}; -pub fn aur_install(a: Vec, options: Options) { +/// Installs a given list of packages from the aur +#[async_recursion] +pub async fn aur_install(packages: Vec, options: Options) { let url = crate::internal::rpc::URL; let cachedir = format!("{}/.cache/ame/", env::var("HOME").unwrap()); let verbosity = options.verbosity; let noconfirm = options.noconfirm; if verbosity >= 1 { - log!("Installing from AUR: {:?}", &a); + log!("Installing from AUR: {:?}", &packages); } - info!("Installing packages {} from the AUR", a.join(", ")); + info!("Installing packages {} from the AUR", packages.join(", ")); - for package in a { - let rpcres = rpcinfo(package); + for package in packages { + let rpcres = rpcinfo(&package); if !rpcres.found { break; @@ -42,6 +45,7 @@ pub fn aur_install(a: Vec, options: Options) { .arg("clone") .arg(format!("{}/{}", url, pkg)) .wait() + .await .silent_unwrap(AppExitCode::GitError); if verbosity >= 1 { @@ -107,6 +111,7 @@ pub fn aur_install(a: Vec, options: Options) { .arg("-c") .arg(format!("ls {}/*.install &> /dev/null", pkg)) .wait() + .await .silent_unwrap(AppExitCode::Other); if status.success() { @@ -114,12 +119,15 @@ pub fn aur_install(a: Vec, options: Options) { .arg("-c") .arg(format!("{} {}/*.install", editor, pkg)) .wait() + .await .silent_unwrap(AppExitCode::Other); } let p2 = prompt!(default true, "Would you still like to install {}?", pkg); if !p2 { - fs::remove_dir_all(format!("{}/{}", cachedir, pkg)).unwrap(); + fs::remove_dir_all(format!("{}/{}", cachedir, pkg)) + .await + .unwrap(); crash!(AppExitCode::UserCancellation, "Not proceeding"); } } @@ -129,12 +137,12 @@ pub fn aur_install(a: Vec, options: Options) { info!("Moving on to install dependencies"); if !sorted.repo.is_empty() { - crate::operations::install(sorted.repo, newopts); - crate::operations::install(md_sorted.repo, newopts); + crate::operations::install(sorted.repo, newopts).await; + crate::operations::install(md_sorted.repo, newopts).await; } if !sorted.aur.is_empty() { - crate::operations::aur_install(sorted.aur, newopts); - crate::operations::aur_install(md_sorted.aur, newopts); + crate::operations::aur_install(sorted.aur, newopts).await; + crate::operations::aur_install(md_sorted.aur, newopts).await; } let mut makepkg_args = vec!["-rsci", "--skippgp"]; @@ -151,10 +159,13 @@ pub fn aur_install(a: Vec, options: Options) { let status = ShellCommand::makepkg() .args(makepkg_args) .wait() + .await .silent_unwrap(AppExitCode::MakePkgError); if !status.success() { - fs::remove_dir_all(format!("{}/{}", cachedir, pkg)).unwrap(); + fs::remove_dir_all(format!("{}/{}", cachedir, pkg)) + .await + .unwrap(); crash!( AppExitCode::PacmanError, "Error encountered while installing {}, aborting", @@ -163,7 +174,9 @@ pub fn aur_install(a: Vec, options: Options) { } set_current_dir(&cachedir).unwrap(); - remove_dir_all(format!("{}/{}", cachedir, &pkg)).unwrap(); + fs::remove_dir_all(format!("{}/{}", cachedir, &pkg)) + .await + .unwrap(); // pushes package to database crate::database::add(rpcres.package.unwrap(), options); diff --git a/src/operations/clean.rs b/src/operations/clean.rs index a7dceeb..5feae6e 100644 --- a/src/operations/clean.rs +++ b/src/operations/clean.rs @@ -1,4 +1,4 @@ -use std::process::Command; +use tokio::process::Command; use crate::crash; use crate::info; @@ -9,13 +9,15 @@ use crate::log; use crate::prompt; use crate::Options; -pub fn clean(options: Options) { +/// Removes orphaned packages and cache +pub async fn clean(options: Options) { let verbosity = options.verbosity; let noconfirm = options.noconfirm; let orphaned_packages = ShellCommand::pacman() .arg("-Qdtq") .wait_with_output() + .await .silent_unwrap(AppExitCode::PacmanError); if orphaned_packages.stdout.as_str() == "" { @@ -51,6 +53,7 @@ pub fn clean(options: Options) { .elevated() .args(pacman_args) .wait() + .await .silent_unwrap(AppExitCode::PacmanError); if pacman_result.success() { @@ -92,6 +95,7 @@ pub fn clean(options: Options) { ) }) .wait() + .await .unwrap(); if verbosity >= 1 { @@ -102,6 +106,7 @@ pub fn clean(options: Options) { .elevated() .args(pacman_args) .wait() + .await .silent_unwrap(AppExitCode::PacmanError); if pacman_result.success() { diff --git a/src/operations/install.rs b/src/operations/install.rs index 2de6272..989a22d 100644 --- a/src/operations/install.rs +++ b/src/operations/install.rs @@ -3,7 +3,7 @@ use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::{crash, info, log, Options}; -pub fn install(packages: Vec, options: Options) { +pub async fn install(packages: Vec, options: Options) { info!("Installing packages {} from repos", &packages.join(", ")); let mut opers = vec!["-S", "--needed"]; if options.noconfirm { @@ -24,6 +24,7 @@ pub fn install(packages: Vec, options: Options) { .args(opers) .args(&packages) .wait() + .await .silent_unwrap(AppExitCode::PacmanError); if !status.success() { crash!( diff --git a/src/operations/search.rs b/src/operations/search.rs index 0303602..5e0d958 100644 --- a/src/operations/search.rs +++ b/src/operations/search.rs @@ -4,7 +4,7 @@ use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcsearch; use crate::{log, Options}; -pub fn aur_search(query: &str, options: Options) { +pub async fn aur_search(query: &str, options: Options) { let verbosity = options.verbosity; let res = rpcsearch(query.to_string()); @@ -25,12 +25,13 @@ pub fn aur_search(query: &str, options: Options) { } } -pub fn repo_search(query: &str, options: Options) { +pub async fn repo_search(query: &str, options: Options) { let verbosity = options.verbosity; let output = ShellCommand::pacman() .arg("-Ss") .arg(query) .wait_with_output() + .await .silent_unwrap(AppExitCode::PacmanError) .stdout; diff --git a/src/operations/uninstall.rs b/src/operations/uninstall.rs index 646e65a..af0efac 100644 --- a/src/operations/uninstall.rs +++ b/src/operations/uninstall.rs @@ -1,12 +1,13 @@ +use std::env; use std::path::Path; -use std::{env, fs}; +use tokio::fs; use crate::internal::commands::ShellCommand; use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::{log, Options}; -pub fn uninstall(packages: Vec, options: Options) { +pub async fn uninstall(packages: Vec, options: Options) { let mut pacman_args = vec!["-Rs"]; pacman_args.append(&mut packages.iter().map(|s| s.as_str()).collect()); @@ -22,6 +23,7 @@ pub fn uninstall(packages: Vec, options: Options) { .elevated() .args(pacman_args) .wait_success() + .await .silent_unwrap(AppExitCode::PacmanError); if verbosity >= 1 { @@ -45,6 +47,7 @@ pub fn uninstall(packages: Vec, options: Options) { env::var("HOME").unwrap(), package ))) + .await .unwrap(); } } diff --git a/src/operations/upgrade.rs b/src/operations/upgrade.rs index 3ba5b47..cd41919 100644 --- a/src/operations/upgrade.rs +++ b/src/operations/upgrade.rs @@ -5,7 +5,8 @@ use crate::internal::rpc::rpcinfo; use crate::operations::aur_install::aur_install; use crate::{info, log, prompt, Options}; -pub fn upgrade(options: Options) { +/// Upgrades all installed packages +pub async fn upgrade(options: Options) { let verbosity = options.verbosity; let noconfirm = options.noconfirm; @@ -22,15 +23,16 @@ pub fn upgrade(options: Options) { .elevated() .args(pacman_args) .wait() + .await .silent_unwrap(AppExitCode::PacmanError); if pacman_result.success() { info!("Successfully upgraded repo packages"); } else { - let cont = prompt!(default false, + let continue_upgrading = prompt!(default false, "Failed to upgrade repo packages, continue to upgrading AUR packages?", ); - if !cont { + if !continue_upgrading { info!("Exiting"); std::process::exit(AppExitCode::PacmanError as i32); } @@ -40,23 +42,23 @@ pub fn upgrade(options: Options) { log!("Upgrading AUR packages"); } - let res = crate::database::query(options); + let packages = crate::database::query(options); if verbosity >= 1 { - log!("{:?}", &res); + log!("{:?}", &packages); } - let mut aur_upgrades = vec![]; - for r in res { - let re = r.clone(); - let ver = rpcinfo(r.name); - if ver.package.unwrap().version != r.version { - aur_upgrades.push(re.name); + + for package in packages { + let remote_package = rpcinfo(&package.name); + + if remote_package.package.unwrap().version != package.version { + aur_upgrades.push(package.name); } } if !aur_upgrades.is_empty() { - aur_install(aur_upgrades, options); + aur_install(aur_upgrades, options).await; } else { info!("No upgrades available for installed AUR packages"); } From e3d3135e43e211387088432da6b62427d8709239 Mon Sep 17 00:00:00 2001 From: Michal Date: Sat, 27 Aug 2022 12:23:17 +0000 Subject: [PATCH 02/43] Added search-by directive for AUR --- src/args.rs | 5 +++ src/internal/rpc.rs | 7 ++-- src/main.rs | 2 +- src/operations/mod.rs | 2 +- src/operations/search.rs | 69 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/args.rs b/src/args.rs index a21df05..be063c4 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,5 +1,6 @@ #![allow(clippy::module_name_repetitions)] +use crate::operations::SearchBy; use clap::{Parser, Subcommand, ValueHint}; #[derive(Debug, Clone, Parser)] @@ -105,6 +106,10 @@ pub struct SearchArgs { /// The string the package must match in the search #[clap(required = true)] pub search: Vec, + + /// Sets the search-by directive for searching the AUR only + #[clap(long, short, possible_values = SearchBy::variants())] + pub by: Option, } #[derive(Default, Debug, Clone, Parser)] diff --git a/src/internal/rpc.rs b/src/internal/rpc.rs index 81ca890..ebc5764 100644 --- a/src/internal/rpc.rs +++ b/src/internal/rpc.rs @@ -1,3 +1,4 @@ +use crate::operations::SearchBy; use std::sync::Arc; #[derive(serde::Deserialize, Debug, Clone)] @@ -75,7 +76,7 @@ pub fn rpcinfo(pkg: &str) -> InfoResults { } /// Return a struct of type [`SearchResults`] from the AUR. -pub fn rpcsearch(pkg: &str) -> SearchResults { +pub fn rpcsearch(pkg: &str, by: SearchBy) -> SearchResults { // Initialise TLS connector let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap()); @@ -87,8 +88,8 @@ pub fn rpcsearch(pkg: &str) -> SearchResults { // Send request and parse results into json agent .get(&format!( - "https://aur.archlinux.org/rpc/?v=5&type=search&arg={}", - pkg + "https://aur.archlinux.org/rpc/?v=5&type=search&by={}&arg={}", + by, pkg )) .call() .unwrap() diff --git a/src/main.rs b/src/main.rs index 5cc42d7..4386ec2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -214,7 +214,7 @@ fn cmd_search(args: &SearchArgs, options: Options) { let asp = spinner!("Searching AUR for {}", query_string); // Search AUR - let ret = operations::aur_search(&query_string, options); + let ret = operations::aur_search(&query_string, options, args.by.unwrap_or_default()); asp.stop_bold("AUR search complete"); ret diff --git a/src/operations/mod.rs b/src/operations/mod.rs index ef348bb..0843999 100644 --- a/src/operations/mod.rs +++ b/src/operations/mod.rs @@ -1,7 +1,7 @@ pub use aur_install::*; pub use clean::*; pub use install::*; -pub use search::{aur_search, repo_search as search}; +pub use search::{aur_search, repo_search as search, SearchBy}; pub use uninstall::*; pub use upgrade::*; diff --git a/src/operations/search.rs b/src/operations/search.rs index d1f9dee..35865be 100644 --- a/src/operations/search.rs +++ b/src/operations/search.rs @@ -2,17 +2,82 @@ use chrono::{Local, TimeZone}; use colored::Colorize; use textwrap::wrap; +use std::fmt::Display; +use std::str::FromStr; + use crate::internal::commands::ShellCommand; use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcsearch; use crate::{log, Options}; +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Clone, Copy)] +pub enum SearchBy { + Name, + NameDesc, + Maintainer, + Depends, + MakeDepends, + OptDepends, + CheckDepends, +} + +impl FromStr for SearchBy { + type Err = String; + fn from_str(s: &str) -> Result { + match s { + "name" => Ok(Self::Name), + "name-desc" => Ok(Self::NameDesc), + "maintainer" => Ok(Self::Maintainer), + "depends" => Ok(Self::Depends), + "makedepends" => Ok(Self::MakeDepends), + "optdepends" => Ok(Self::OptDepends), + "checkdepends" => Ok(Self::CheckDepends), + _ => Err(format!("Invalid search-by directive \"{}\"", s)), + } + } +} + +impl Display for SearchBy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Name => write!(f, "name"), + Self::NameDesc => write!(f, "name-desc"), + Self::Maintainer => write!(f, "maintainer"), + Self::Depends => write!(f, "depends"), + Self::MakeDepends => write!(f, "makedepends"), + Self::OptDepends => write!(f, "optdepends"), + Self::CheckDepends => write!(f, "checkdepends"), + } + } +} + +impl Default for SearchBy { + fn default() -> Self { + Self::NameDesc + } +} + +impl SearchBy { + pub fn variants() -> Vec<&'static str> { + vec![ + "name", + "name-desc", + "maintainer", + "depends", + "makedepends", + "optdepends", + "checkdepends", + ] + } +} + #[allow(clippy::module_name_repetitions)] /// Searches for packages from the AUR and returns wrapped results -pub fn aur_search(query: &str, options: Options) -> String { +pub fn aur_search(query: &str, options: Options, by: SearchBy) -> String { // Query AUR for package info - let res = rpcsearch(query); + let res = rpcsearch(query, by); // Get verbosity let verbosity = options.verbosity; From b484e2b26873f7b1d979915e031a4dd4ce50a653 Mon Sep 17 00:00:00 2001 From: Michal Date: Sat, 27 Aug 2022 13:36:03 +0000 Subject: [PATCH 03/43] Initial implementation of sorting aur and repo results together --- Cargo.lock | 1 + Cargo.toml | 3 +- src/args.rs | 7 +- src/internal/rpc.rs | 7 +- src/main.rs | 40 ++++++-- src/operations/mod.rs | 2 +- src/operations/search.rs | 204 ++++++++++++++------------------------- 7 files changed, 111 insertions(+), 153 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c2440b..4cc7dd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ dependencies = [ "rm_rf", "serde", "spinoff", + "strsim", "textwrap", "toml", "ureq", diff --git a/Cargo.toml b/Cargo.toml index d59267f..62e0ff5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,4 +35,5 @@ spinoff = { version = "0.5.2", default-features = false } textwrap = { version = "0.15.0", features = [ "terminal_size", "smawk" ] } chrono = { version = "0.4.22", default-features = false, features = [ "clock", "std", "wasmbind" ] } toml = { version = "0.5.9", default-features = false } -crossterm = { version = "0.25.0", default-features = false } \ No newline at end of file +crossterm = { version = "0.25.0", default-features = false } +strsim = { version = "0.10.0", default-features = false } \ No newline at end of file diff --git a/src/args.rs b/src/args.rs index be063c4..241d96c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,6 +1,5 @@ #![allow(clippy::module_name_repetitions)] -use crate::operations::SearchBy; use clap::{Parser, Subcommand, ValueHint}; #[derive(Debug, Clone, Parser)] @@ -105,11 +104,7 @@ pub struct SearchArgs { /// The string the package must match in the search #[clap(required = true)] - pub search: Vec, - - /// Sets the search-by directive for searching the AUR only - #[clap(long, short, possible_values = SearchBy::variants())] - pub by: Option, + pub search: String, } #[derive(Default, Debug, Clone, Parser)] diff --git a/src/internal/rpc.rs b/src/internal/rpc.rs index ebc5764..81ca890 100644 --- a/src/internal/rpc.rs +++ b/src/internal/rpc.rs @@ -1,4 +1,3 @@ -use crate::operations::SearchBy; use std::sync::Arc; #[derive(serde::Deserialize, Debug, Clone)] @@ -76,7 +75,7 @@ pub fn rpcinfo(pkg: &str) -> InfoResults { } /// Return a struct of type [`SearchResults`] from the AUR. -pub fn rpcsearch(pkg: &str, by: SearchBy) -> SearchResults { +pub fn rpcsearch(pkg: &str) -> SearchResults { // Initialise TLS connector let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap()); @@ -88,8 +87,8 @@ pub fn rpcsearch(pkg: &str, by: SearchBy) -> SearchResults { // Send request and parse results into json agent .get(&format!( - "https://aur.archlinux.org/rpc/?v=5&type=search&by={}&arg={}", - by, pkg + "https://aur.archlinux.org/rpc/?v=5&type=search&arg={}", + pkg )) .call() .unwrap() diff --git a/src/main.rs b/src/main.rs index 4386ec2..60af43d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,11 @@ use args::Args; use clap::{CommandFactory, Parser}; use clap_complete::{Generator, Shell}; +use strsim::sorensen_dice; + use internal::commands::ShellCommand; use internal::error::SilentUnwrap; + use std::env; use std::fs; use std::path::Path; @@ -17,6 +20,7 @@ use crate::args::{ use crate::internal::exit_code::AppExitCode; use crate::internal::utils::pager; use crate::internal::{detect, init, sort, start_sudoloop, structs::Options}; +use crate::operations::ResultsVec; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; @@ -186,7 +190,7 @@ fn cmd_remove(args: RemoveArgs, options: Options) { fn cmd_search(args: &SearchArgs, options: Options) { // Initialise variables - let query_string = args.search.join(" "); + let query_string = &args.search; // Logic for searching let repo = args.repo || env::args().collect::>()[1] == "-Ssr"; @@ -198,12 +202,12 @@ fn cmd_search(args: &SearchArgs, options: Options) { let rsp = spinner!("Searching repos for {}", query_string); // Search repos - let ret = operations::search(&query_string, options); + let ret = operations::search(query_string, options); rsp.stop_bold("Repo search complete"); ret } else { - "".to_string() + Vec::new() }; // Start AUR spinner @@ -214,30 +218,46 @@ fn cmd_search(args: &SearchArgs, options: Options) { let asp = spinner!("Searching AUR for {}", query_string); // Search AUR - let ret = operations::aur_search(&query_string, options, args.by.unwrap_or_default()); + let ret = operations::aur_search(&query_string, options); asp.stop_bold("AUR search complete"); ret } else { - "".to_string() + Vec::new() }; - let results = repo_results + "\n" + &aur_results; + let mut results = repo_results + .into_iter() + .chain(aur_results) + .collect::>(); + + // Sort results by how closely they match the query + results.sort_by(|a, b| { + let a_score = sorensen_dice(&a.name, query_string); + let b_score = sorensen_dice(&b.name, query_string); + b_score + .partial_cmp(&a_score) + .unwrap_or(std::cmp::Ordering::Equal) + }); + + let results = ResultsVec::from(results); // Print results either way, so that the user can see the results after they exit `less` let text = if internal::uwu_enabled() { - uwu!(results.trim()) + uwu!(results.to_string()) } else { - results.trim().to_string() + results.to_string() }; + let text = text.trim().to_string(); + println!("{}", text); // Check if results are longer than terminal height - if results.lines().count() > crossterm::terminal::size().unwrap().1 as usize { + if results.0.len() > (crossterm::terminal::size().unwrap().1 / 2).into() { // If so, paginate results #[allow(clippy::let_underscore_drop)] - let _ = pager(&results.trim().to_string()); + let _ = pager(&results.to_string()); } } diff --git a/src/operations/mod.rs b/src/operations/mod.rs index 0843999..936f7c9 100644 --- a/src/operations/mod.rs +++ b/src/operations/mod.rs @@ -1,7 +1,7 @@ pub use aur_install::*; pub use clean::*; pub use install::*; -pub use search::{aur_search, repo_search as search, SearchBy}; +pub use search::{aur_search, repo_search as search, ResultsVec}; pub use uninstall::*; pub use upgrade::*; diff --git a/src/operations/search.rs b/src/operations/search.rs index 35865be..b20bf2f 100644 --- a/src/operations/search.rs +++ b/src/operations/search.rs @@ -3,7 +3,6 @@ use colored::Colorize; use textwrap::wrap; use std::fmt::Display; -use std::str::FromStr; use crate::internal::commands::ShellCommand; use crate::internal::error::SilentUnwrap; @@ -12,72 +11,69 @@ use crate::internal::rpc::rpcsearch; use crate::{log, Options}; #[allow(clippy::module_name_repetitions)] -#[derive(Debug, Clone, Copy)] -pub enum SearchBy { - Name, - NameDesc, - Maintainer, - Depends, - MakeDepends, - OptDepends, - CheckDepends, +#[derive(Debug, Clone)] +pub struct SearchResult { + pub repo: String, + pub name: String, + pub version: String, + pub ood: Option, + pub description: String, } -impl FromStr for SearchBy { - type Err = String; - fn from_str(s: &str) -> Result { - match s { - "name" => Ok(Self::Name), - "name-desc" => Ok(Self::NameDesc), - "maintainer" => Ok(Self::Maintainer), - "depends" => Ok(Self::Depends), - "makedepends" => Ok(Self::MakeDepends), - "optdepends" => Ok(Self::OptDepends), - "checkdepends" => Ok(Self::CheckDepends), - _ => Err(format!("Invalid search-by directive \"{}\"", s)), - } - } -} - -impl Display for SearchBy { +impl Display for SearchResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Name => write!(f, "name"), - Self::NameDesc => write!(f, "name-desc"), - Self::Maintainer => write!(f, "maintainer"), - Self::Depends => write!(f, "depends"), - Self::MakeDepends => write!(f, "makedepends"), - Self::OptDepends => write!(f, "optdepends"), - Self::CheckDepends => write!(f, "checkdepends"), - } + let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 4) + .subsequent_indent(" "); + let description = wrap(&self.description, opts).join("\n"); + write!( + f, + "{}{} {} {}\n {}", + if self.repo == "aur" { + (self.repo.clone() + "/").bold().cyan() + } else { + (self.repo.clone() + "/").bold().purple() + }, + self.name.bold(), + self.version.bold().green(), + if self.ood.is_some() { + format!( + "[out of date: since {}]", + Local + .timestamp(self.ood.unwrap().try_into().unwrap(), 0) + .date_naive() + ) + .bold() + .red() + } else { + "".bold() + }, + description + ) } } -impl Default for SearchBy { - fn default() -> Self { - Self::NameDesc +pub struct ResultsVec(pub Vec); + +impl From> for ResultsVec { + fn from(v: Vec) -> Self { + Self(v) } } -impl SearchBy { - pub fn variants() -> Vec<&'static str> { - vec![ - "name", - "name-desc", - "maintainer", - "depends", - "makedepends", - "optdepends", - "checkdepends", - ] +impl Display for ResultsVec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for result in &self.0 { + writeln!(f, "{}", result)?; + } + Ok(()) } } #[allow(clippy::module_name_repetitions)] /// Searches for packages from the AUR and returns wrapped results -pub fn aur_search(query: &str, options: Options, by: SearchBy) -> String { +pub fn aur_search(query: &str, options: Options) -> Vec { // Query AUR for package info - let res = rpcsearch(query, by); + let res = rpcsearch(query); // Get verbosity let verbosity = options.verbosity; @@ -85,36 +81,17 @@ pub fn aur_search(query: &str, options: Options, by: SearchBy) -> String { // Format output let mut results_vec = vec![]; for package in &res.results { - // Define wrapping options - let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 4) - .subsequent_indent(" "); - - let result = format!( - "{}{} {} {}\n {}", - "aur/".cyan().bold(), - package.name.bold(), - package.version.green().bold(), - if package.out_of_date.is_some() { - format!( - "[out of date: since {}]", - Local - .timestamp(package.out_of_date.unwrap().try_into().unwrap(), 0) - .date_naive() - ) - .red() - .bold() - } else { - "".bold() - }, - wrap( - package - .description - .as_ref() - .unwrap_or(&"No description".to_string()), - opts, - ) - .join("\n"), - ); + let result = SearchResult { + repo: "aur".to_string(), + name: package.name.to_string(), + version: package.version.to_string(), + ood: package.out_of_date, + description: package + .description + .as_ref() + .unwrap_or(&"No description".to_string()) + .to_string(), + }; results_vec.push(result); } @@ -126,19 +103,12 @@ pub fn aur_search(query: &str, options: Options, by: SearchBy) -> String { ); } - results_vec.join("\n") -} - -struct SearchResult { - repo: String, - name: String, - version: String, - description: String, + results_vec } #[allow(clippy::module_name_repetitions)] /// Searches for packages from the repos and returns wrapped results -pub fn repo_search(query: &str, options: Options) -> String { +pub fn repo_search(query: &str, options: Options) -> Vec { // Initialise variables let verbosity = options.verbosity; @@ -156,22 +126,19 @@ pub fn repo_search(query: &str, options: Options) -> String { // Initialise results vector let mut results_vec: Vec = vec![]; - let clone = lines.clone().collect::>(); - if clone.len() == 1 && clone[0].is_empty() { - // If no results, return empty string - return "".to_string(); - } - // Iterate over lines for line in lines { - let parts: Vec<&str> = line.split('\\').collect(); - let res = SearchResult { - repo: parts[0].to_string(), - name: parts[1].to_string(), - version: parts[2].to_string(), - description: parts[3].to_string(), - }; - results_vec.push(res); + if line.contains('\\') { + let parts: Vec<&str> = line.split('\\').collect(); + let res = SearchResult { + repo: parts[0].to_string(), + name: parts[1].to_string(), + version: parts[2].to_string(), + ood: None, + description: parts[3].to_string(), + }; + results_vec.push(res); + } } if verbosity >= 1 { @@ -182,30 +149,5 @@ pub fn repo_search(query: &str, options: Options) -> String { ); } - // Format output - let results_vec = results_vec - .into_iter() - .map(|res| { - let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 4) - .subsequent_indent(" "); - format!( - "{}{}{} {}\n {}", - res.repo.purple().bold(), - "/".purple().bold(), - res.name.bold(), - res.version.green().bold(), - if res.description.is_empty() { - "No description".to_string() - } else { - wrap(&res.description, opts).join("\n") - }, - ) - }) - .collect::>(); - - if output.trim().is_empty() { - "".to_string() - } else { - results_vec.join("\n") - } + results_vec } From 43e55480b8a1041ec86cad967fe937d170bef8d9 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 27 Aug 2022 19:08:52 +0200 Subject: [PATCH 04/43] Fix async installation Signed-off-by: trivernis --- Cargo.lock | 621 ++++++++++++++++++++++++++++++++-- Cargo.toml | 18 +- Containerfile | 32 ++ run-isolated.sh | 4 + src/database/add.rs | 21 -- src/database/initialise.rs | 41 --- src/database/mod.rs | 17 - src/database/query.rs | 63 ---- src/database/remove.rs | 23 -- src/internal/error.rs | 8 + src/internal/exit_code.rs | 3 +- src/internal/fs_utils.rs | 49 +++ src/internal/initialise.rs | 4 - src/internal/mod.rs | 1 + src/internal/rpc.rs | 77 +---- src/internal/sort.rs | 11 +- src/main.rs | 23 +- src/operations/aur_install.rs | 66 ++-- src/operations/search.rs | 16 +- src/operations/uninstall.rs | 1 - src/operations/upgrade.rs | 36 +- 21 files changed, 797 insertions(+), 338 deletions(-) create mode 100644 Containerfile create mode 100755 run-isolated.sh delete mode 100644 src/database/add.rs delete mode 100644 src/database/initialise.rs delete mode 100644 src/database/mod.rs delete mode 100644 src/database/query.rs delete mode 100644 src/database/remove.rs create mode 100644 src/internal/fs_utils.rs diff --git a/Cargo.lock b/Cargo.lock index 30d79f9..7ac18e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,8 +7,10 @@ name = "Amethyst" version = "4.0.0" dependencies = [ "async-recursion", + "aur-rpc", "clap", "colored", + "futures", "libc", "mimalloc", "native-tls", @@ -16,6 +18,8 @@ dependencies = [ "rusqlite", "serde", "tokio", + "tracing", + "tracing-subscriber", "ureq", ] @@ -30,6 +34,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "async-recursion" version = "1.0.0" @@ -52,6 +65,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "aur-rpc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34866076a1b9a1af170e30ff8bb5f3d82ab8b3c204d8f11f3b93f41d55e8f15" +dependencies = [ + "reqwest", + "serde", + "thiserror", + "tracing", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -70,6 +95,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + [[package]] name = "bytes" version = "1.1.0" @@ -96,9 +127,9 @@ checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" [[package]] name = "clap" -version = "3.2.8" +version = "3.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" +checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" dependencies = [ "atty", "bitflags", @@ -114,9 +145,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.7" +version = "3.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" +checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" dependencies = [ "heck", "proc-macro-error", @@ -161,6 +192,15 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -182,6 +222,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -207,6 +253,95 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab30e97ab6aacfe635fad58f22c2bb06c8b685f7421eb1e064a729e2a5f481fa" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" + +[[package]] +name = "futures-executor" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5" + +[[package]] +name = "futures-macro" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" + +[[package]] +name = "futures-task" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306" + +[[package]] +name = "futures-util" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getrandom" version = "0.2.7" @@ -219,12 +354,22 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.11.2" +name = "h2" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ - "ahash", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] @@ -232,14 +377,17 @@ name = "hashbrown" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +dependencies = [ + "ahash", +] [[package]] name = "hashlink" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086" dependencies = [ - "hashbrown 0.11.2", + "hashbrown", ] [[package]] @@ -257,6 +405,77 @@ dependencies = [ "libc", ] +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "idna" version = "0.2.3" @@ -275,7 +494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown 0.12.1", + "hashbrown", ] [[package]] @@ -287,12 +506,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + [[package]] name = "itoa" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -301,9 +535,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "libmimalloc-sys" @@ -316,9 +550,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.23.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cafc7c74096c336d9d27145f7ebd4f4b6f95ba16aa5a282387267e6925cb58" +checksum = "9f0455f2c1bc9a7caa792907026e469c1d91761fb0ea37cbb16427c77280cf35" dependencies = [ "pkg-config", "vcpkg", @@ -333,6 +567,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.9" @@ -354,6 +597,12 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "mio" version = "0.8.4" @@ -386,9 +635,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.12.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8b1a9b2518dc799a2271eff1688707eb315f0d4697aa6b0871369ca4c4da55" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" [[package]] name = "openssl" @@ -453,6 +702,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.25" @@ -512,18 +767,27 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.6" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "remove_dir_all" @@ -534,18 +798,54 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rusqlite" -version = "0.26.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba4d3462c8b2e4d7f4fcfcf2b296dc6b65404fbbc7b63daa37fd485c149daf7" +checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" dependencies = [ "bitflags", "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", - "memchr", "smallvec", ] @@ -590,18 +890,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.138" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.138" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", @@ -619,6 +919,27 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -628,12 +949,28 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + [[package]] name = "smallvec" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "strsim" version = "0.10.0" @@ -693,6 +1030,35 @@ dependencies = [ "terminal_size", ] +[[package]] +name = "thiserror" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -710,10 +1076,11 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.19.2" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ + "autocfg", "bytes", "libc", "memchr", @@ -721,6 +1088,7 @@ dependencies = [ "once_cell", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", "tracing", "winapi", @@ -737,26 +1105,104 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" dependencies = [ + "ansi_term", + "matchers", "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "unicode-bidi" version = "0.3.8" @@ -780,9 +1226,9 @@ dependencies = [ [[package]] name = "ureq" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9399fa2f927a3d327187cbd201480cee55bee6ac5d3c77dd27f0c6814cff16d5" +checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f" dependencies = [ "base64", "chunked_transfer", @@ -806,6 +1252,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -818,12 +1270,98 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "web-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -897,3 +1435,12 @@ name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml index 6f2a3ce..7bf8d41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,16 +52,20 @@ codegen-units = 1 [dependencies] mimalloc = { version = "0.1.29", default-features = false } -clap = { version = "3.2.8", features = [ "derive", "wrap_help" ] } -regex = { version = "1.5.6", default-features = false, features = [ "std", "unicode-perl" ] } -rusqlite = { version = "0.26.3", default-features = false } +clap = { version = "3.2.17", features = [ "derive", "wrap_help" ] } +regex = { version = "1.6.0", default-features = false, features = [ "std", "unicode-perl" ] } +rusqlite = { version = "0.28.0", default-features = false } colored = "2.0.0" -ureq = { version = "2.4.0", default-features = false, features = [ "native-tls", "json" ] } -serde = { version = "1.0.138", default-features = false, features = [ "derive", "serde_derive" ] } +ureq = { version = "2.5.0", default-features = false, features = [ "native-tls", "json" ] } +serde = { version = "1.0.144", default-features = false, features = [ "derive", "serde_derive" ] } native-tls = { version = "0.2.10", default-features = false } -libc = { version = "0.2.126", default-features = false } +libc = { version = "0.2.132", default-features = false } async-recursion = "1.0.0" +aur-rpc = "0.1.2" +futures = "0.3.23" +tracing = "0.1.36" +tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } [dependencies.tokio] -version = "1.19.2" +version = "1.20.1" features = ["rt", "io-std", "io-util", "process", "time", "macros", "tracing", "fs"] diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..d42e31f --- /dev/null +++ b/Containerfile @@ -0,0 +1,32 @@ +ARG BASE_IMAGE=docker.io/archlinux:latest +FROM ${BASE_IMAGE} as build_base +RUN pacman -Syu --noconfirm +RUN pacman -S --noconfirm base-devel curl bash +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" + +FROM build_base as builder +WORKDIR /usr/src +RUN cargo new amethyst +WORKDIR /usr/src/amethyst +COPY Cargo.toml Cargo.lock ./ +RUN cargo fetch +COPY src ./src +RUN cargo build --frozen +RUN mkdir /tmp/ame +RUN cp target/debug/ame /tmp/ame/ + +FROM ${BASE_IMAGE} as runtime +RUN pacman -Syu --noconfirm +RUN pacman -S --noconfirm base-devel zsh wget vim git binutils fakeroot pacman-contrib sudo +RUN useradd -r -d /home/ame -p $(echo "ame" | openssl passwd -1 -stdin) ame -G wheel +RUN echo '%wheel ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers +RUN mkdir /home/ame +RUN chown ame:ame /home/ame +COPY --from=builder /tmp/ame/ame /usr/bin/ +RUN rm -f $(pacdiff -o -f) +USER ame +RUN mkdir -p /home/ame/.local/share +RUN touch /home/ame/.zshrc +ENV AME_LOG=debug +ENTRYPOINT ["zsh"] \ No newline at end of file diff --git a/run-isolated.sh b/run-isolated.sh new file mode 100755 index 0000000..2b79c16 --- /dev/null +++ b/run-isolated.sh @@ -0,0 +1,4 @@ +#!/bin/bash +podman build . -t ame-debug \ +&& podman container rm ame-debug \ +&& podman run -i -t --name ame-debug ame-debug diff --git a/src/database/add.rs b/src/database/add.rs deleted file mode 100644 index ebcbf70..0000000 --- a/src/database/add.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::internal::exit_code::AppExitCode; -use crate::internal::rpc::Package; -use crate::{crash, log, Options}; - -use super::get_database_connection; - -pub fn add(pkg: Package, options: Options) { - let conn = get_database_connection(); - - if options.verbosity >= 1 { - log!("Adding package {} to database", pkg.name); - } - let pkg_description = pkg - .description - .unwrap_or_else(|| "No description found.".parse().unwrap()); - conn.execute("INSERT OR REPLACE INTO packages (name, version, description, depends, make_depends) VALUES (?1, ?2, ?3, ?4, ?5)", - [&pkg.name, &pkg.version, &pkg_description, &pkg.depends.join(" "), &pkg.make_depends.join(" ")], - ).unwrap_or_else(|e| - crash!(AppExitCode::FailedAddingPkg, "Failed adding package {} to the database: {}", pkg.name, e) - ); -} diff --git a/src/database/initialise.rs b/src/database/initialise.rs deleted file mode 100644 index e7e1214..0000000 --- a/src/database/initialise.rs +++ /dev/null @@ -1,41 +0,0 @@ -use rusqlite::Connection; -use std::env; -use std::path::Path; - -use crate::internal::exit_code::AppExitCode; -use crate::{crash, log, Options}; - -pub fn init(options: Options) { - let path = format!("{}/.local/share/ame/db.sqlite", env::var("HOME").unwrap()); - let dbpath = Path::new(&path); - let verbosity = options.verbosity; - - if verbosity >= 1 { - log!("Creating database at {}", &path); - } - - let conn = - Connection::open(dbpath).expect("Couldn't create database at ~/.local/share/ame/db.sqlite"); - - if verbosity >= 1 { - log!("Populating database with table"); - } - - conn.execute( - "CREATE TABLE packages ( - name TEXT PRIMARY KEY NOT NULL, - version TEXT NOT NULL, - description TEXT, - depends BLOB, - make_depends BLOB - )", - [], - ) - .unwrap_or_else(|e| { - crash!( - AppExitCode::FailedInitDb, - "Couldn't initialise database: {}", - e, - ) - }); -} diff --git a/src/database/mod.rs b/src/database/mod.rs deleted file mode 100644 index 4eb9d9a..0000000 --- a/src/database/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::{env, path::PathBuf}; - -pub mod add; -pub mod initialise; -pub mod query; -pub mod remove; - -pub use add::*; -pub use initialise::*; -pub use query::*; -pub use remove::*; -use rusqlite::Connection; - -fn get_database_connection() -> Connection { - let db_path = format!("{}/.local/share/ame/db.sqlite", env::var("HOME").unwrap()); - Connection::open(PathBuf::from(db_path)).expect("Couldn't connect to database") -} diff --git a/src/database/query.rs b/src/database/query.rs deleted file mode 100644 index 91e644e..0000000 --- a/src/database/query.rs +++ /dev/null @@ -1,63 +0,0 @@ -use rusqlite::Connection; -use std::env; -use std::path::Path; - -use crate::internal::rpc::Package; -use crate::{log, Options}; - -pub fn query(options: Options) -> Vec { - let verbosity = options.verbosity; - - if verbosity >= 1 { - log!("Connecting to database"); - } - - let conn = Connection::open(Path::new(&format!( - "{}/.local/share/ame/db.sqlite", - env::var("HOME").unwrap() - ))) - .expect("Couldn't connect to database"); - - if verbosity >= 1 { - log!("Querying database for input"); - } - - let mut rs = conn.prepare("SELECT * FROM packages;").unwrap(); - let packages_iter = rs - .query_map([], |row| { - Ok(Package { - name: row.get(0).unwrap(), - version: row.get(1).unwrap(), - description: row.get(2).unwrap(), - depends: row - .get::(3) - .unwrap() - .split(' ') - .map(|s| s.to_string()) - .collect::>(), - make_depends: row - .get::(4) - .unwrap() - .split(' ') - .map(|s| s.to_string()) - .collect::>(), - }) - }) - .expect("Couldn't query database for packages"); - - if verbosity >= 1 { - log!("Retrieved results"); - } - - let mut results: Vec = vec![]; - - for package in packages_iter { - results.push(package.unwrap()); - } - - if verbosity >= 1 { - log!("Collected results"); - } - - results -} diff --git a/src/database/remove.rs b/src/database/remove.rs deleted file mode 100644 index ef1e5da..0000000 --- a/src/database/remove.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::{log, Options}; - -use super::get_database_connection; - -pub fn remove(pkg: &str, options: Options) { - let conn = get_database_connection(); - - let verbosity = options.verbosity; - - if verbosity >= 1 { - log!("Removing package {} from database", pkg); - } - - conn.execute( - "DELETE FROM packages - WHERE EXISTS - (SELECT * - FROM packages - WHERE name = ?);", - [pkg], - ) - .expect("Couldn't delete package from database"); -} diff --git a/src/internal/error.rs b/src/internal/error.rs index 99977ad..538e2a6 100644 --- a/src/internal/error.rs +++ b/src/internal/error.rs @@ -11,6 +11,7 @@ pub type AppResult = Result; pub enum AppError { Io(std::io::Error), Other(String), + Rpc(aur_rpc::error::RPCError), NonZeroExit, } @@ -20,6 +21,7 @@ impl Display for AppError { AppError::Io(io) => Display::fmt(io, f), AppError::Other(s) => Display::fmt(s, f), AppError::NonZeroExit => Display::fmt("exited with non zero code", f), + AppError::Rpc(e) => Display::fmt(e, f), } } } @@ -32,6 +34,12 @@ impl From for AppError { } } +impl From for AppError { + fn from(e: aur_rpc::error::RPCError) -> Self { + Self::Rpc(e) + } +} + impl From for AppError { fn from(string: String) -> Self { Self::Other(string) diff --git a/src/internal/exit_code.rs b/src/internal/exit_code.rs index aa34dff..fde2234 100644 --- a/src/internal/exit_code.rs +++ b/src/internal/exit_code.rs @@ -1,12 +1,11 @@ pub enum AppExitCode { RunAsRoot = 1, - FailedAddingPkg = 2, - FailedInitDb = 3, FailedCreatingPaths = 4, MissingDeps = 5, UserCancellation = 6, PacmanError = 7, GitError = 8, MakePkgError = 9, + RpcError = 10, Other = 102, } diff --git a/src/internal/fs_utils.rs b/src/internal/fs_utils.rs new file mode 100644 index 0000000..2990df4 --- /dev/null +++ b/src/internal/fs_utils.rs @@ -0,0 +1,49 @@ +use std::{ + collections::VecDeque, + path::{Path, PathBuf}, +}; + +use futures::future; +use tokio::fs; + +#[tracing::instrument(level = "debug")] +pub async fn rmdir_recursive(path: &Path) -> std::io::Result<()> { + let mut files: Vec = Vec::new(); + let mut folders: Vec = Vec::new(); + + if path.is_dir() { + folders.push(path.into()); + } else { + files.push(path.into()); + } + + let mut folders_to_scan: VecDeque<_> = folders.clone().into(); + + while let Some(path) = folders_to_scan.pop_front() { + let mut dir_content = fs::read_dir(&path).await?; + + while let Some(entry) = dir_content.next_entry().await? { + let entry = entry.path(); + + if entry.is_dir() { + folders_to_scan.push_back(entry.clone()); + folders.push(entry); + } else { + files.push(entry); + } + } + } + + tracing::debug!("Deleting {} files", files.len()); + future::try_join_all(files.into_iter().map(fs::remove_file)).await?; + + tracing::debug!("Deleting {} folders", folders.len()); + + folders.reverse(); + for folder in folders { + tracing::trace!("Deleting {folder:?}"); + fs::remove_dir(folder).await?; + } + + Ok(()) +} diff --git a/src/internal/initialise.rs b/src/internal/initialise.rs index ae62244..9d09a36 100644 --- a/src/internal/initialise.rs +++ b/src/internal/initialise.rs @@ -27,10 +27,6 @@ pub fn init(options: Options) { } } - if !Path::new(&format!("{}/.local/share/ame/db.sqlite", homedir)).exists() { - crate::database::init(options); - } - if !Path::new(&format!("{}/.cache/ame/", homedir)).exists() { let r = std::fs::create_dir_all(format!("{}/.cache/ame", homedir)); match r { diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 87befd6..f387d26 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -9,6 +9,7 @@ pub mod commands; mod detect; pub mod error; pub mod exit_code; +pub mod fs_utils; mod initialise; pub mod rpc; mod sort; diff --git a/src/internal/rpc.rs b/src/internal/rpc.rs index 0c371b0..9e43f76 100644 --- a/src/internal/rpc.rs +++ b/src/internal/rpc.rs @@ -1,75 +1,16 @@ -use std::sync::Arc; - -#[derive(serde::Deserialize, Debug, Clone)] -pub struct Package { - #[serde(rename = "Name")] - pub name: String, - #[serde(rename = "Version")] - pub version: String, - #[serde(rename = "Description")] - pub description: Option, - #[serde(rename = "Depends")] - #[serde(default)] - pub depends: Vec, - #[serde(rename = "MakeDepends")] - #[serde(default)] - pub make_depends: Vec, -} - -#[derive(serde::Deserialize)] -pub struct SearchResults { - pub resultcount: u32, - pub results: Vec, -} - -#[derive(Clone)] -pub struct InfoResults { - pub found: bool, - pub package: Option, -} +use aur_rpc::{PackageInfo, PackageMetadata}; +use super::error::AppResult; pub const URL: &str = "https://aur.archlinux.org/"; -pub fn rpcinfo(pkg: &String) -> InfoResults { - let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap()); - let agent = ureq::AgentBuilder::new() - .tls_connector(tls_connector) - .build(); - let res: SearchResults = agent - .get(&format!( - "https://aur.archlinux.org/rpc/?v=5&type=info&arg={}", - pkg - )) - .call() - .unwrap() - .into_json() - .unwrap(); +pub async fn rpcinfo(pkg: &str) -> AppResult> { + let packages = aur_rpc::info(vec![pkg]).await?; - if res.results.is_empty() { - InfoResults { - found: false, - package: None, - } - } else { - InfoResults { - found: true, - package: Some(res.results[0].clone()), - } - } + Ok(packages.into_iter().next()) } -pub fn rpcsearch(pkg: String) -> SearchResults { - let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap()); - let agent = ureq::AgentBuilder::new() - .tls_connector(tls_connector) - .build(); - agent - .get(&format!( - "https://aur.archlinux.org/rpc/?v=5&type=search&arg={}", - pkg - )) - .call() - .unwrap() - .into_json::() - .unwrap() +pub async fn rpcsearch(pkg: String) -> AppResult> { + let search_results = aur_rpc::search(pkg).await?; + + Ok(search_results) } diff --git a/src/internal/sort.rs b/src/internal/sort.rs index 37cf1e7..92d185a 100644 --- a/src/internal/sort.rs +++ b/src/internal/sort.rs @@ -3,7 +3,10 @@ use std::process::{Command, Stdio}; use crate::internal::{clean, rpc, structs}; use crate::{log, Options}; -pub fn sort(input: &[String], options: Options) -> structs::Sorted { +use super::error::SilentUnwrap; +use super::exit_code::AppExitCode; + +pub async fn sort(input: &[String], options: Options) -> structs::Sorted { let mut repo_packages: Vec = vec![]; let mut aur_packages: Vec = vec![]; let mut missing_packages: Vec = vec![]; @@ -28,7 +31,11 @@ pub fn sort(input: &[String], options: Options) -> structs::Sorted { log!("{} found in repos", package); } repo_packages.push(package.to_string()); - } else if rpc::rpcinfo(&package).found { + } else if rpc::rpcinfo(&package) + .await + .silent_unwrap(AppExitCode::RpcError) + .is_some() + { if verbosity >= 1 { log!("{} found in AUR", package); } diff --git a/src/main.rs b/src/main.rs index 8413aa2..e492a80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,12 +7,14 @@ use crate::args::{InstallArgs, Operation, QueryArgs, RemoveArgs, SearchArgs}; use crate::internal::detect; use crate::internal::exit_code::AppExitCode; use crate::internal::{init, sort, start_sudoloop, structs::Options}; +use tracing_subscriber::fmt::format::FmtSpan; +use tracing_subscriber::EnvFilter; +use std::str::FromStr; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; mod args; -mod database; mod internal; mod operations; @@ -21,6 +23,7 @@ async fn main() { if unsafe { libc::geteuid() } == 0 { crash!( AppExitCode::RunAsRoot, "Running amethyst as root is disallowed as it can lead to system breakage. Instead, amethyst will prompt you when it needs superuser permissions"); } + init_logger(); let args: Args = Args::parse(); @@ -57,9 +60,25 @@ async fn main() { detect().await; } +/// Initializes the tracing logger +/// Can be used for debug purposes _or_ verbose output +fn init_logger() { + const DEFAULT_ENV_FILTER: &str = "warn"; + let filter_string = + std::env::var("AME_LOG").unwrap_or_else(|_| DEFAULT_ENV_FILTER.to_string()); + let env_filter = + EnvFilter::from_str(&*filter_string).expect("failed to parse env filter string"); + tracing_subscriber::fmt::SubscriberBuilder::default() + .with_env_filter(env_filter) + .with_writer(std::io::stdout) + .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) + .compact() + .init(); +} + async fn cmd_install(args: InstallArgs, options: Options) { let packages = args.packages; - let sorted = sort(&packages, options); + let sorted = sort(&packages, options).await; info!("Attempting to install packages: {}", packages.join(", ")); diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 1af7c3e..9719487 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -1,7 +1,7 @@ use async_recursion::async_recursion; use std::env; use std::env::set_current_dir; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Command; use tokio::fs; @@ -9,7 +9,7 @@ use crate::internal::commands::ShellCommand; use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcinfo; -use crate::{crash, info, log, prompt, Options}; +use crate::{crash, info, internal::fs_utils::rmdir_recursive, log, prompt, Options}; /// Installs a given list of packages from the aur #[async_recursion] @@ -25,17 +25,20 @@ pub async fn aur_install(packages: Vec, options: Options) { info!("Installing packages {} from the AUR", packages.join(", ")); - for package in packages { - let rpcres = rpcinfo(&package); + for package_name in packages { + let rpcres = rpcinfo(&package_name) + .await + .silent_unwrap(AppExitCode::RpcError); - if !rpcres.found { + if rpcres.is_none() { break; } - let pkg = &rpcres.package.as_ref().unwrap().name; + let package = rpcres.unwrap(); + let pkg_name = package.metadata.name; if verbosity >= 1 { - log!("Cloning {} into cachedir", pkg); + log!("Cloning {} into cachedir", pkg_name); } info!("Cloning package source"); @@ -43,7 +46,7 @@ pub async fn aur_install(packages: Vec, options: Options) { set_current_dir(Path::new(&cachedir)).unwrap(); ShellCommand::git() .arg("clone") - .arg(format!("{}/{}", url, pkg)) + .arg(format!("{}/{}", url, pkg_name)) .wait() .await .silent_unwrap(AppExitCode::GitError); @@ -51,30 +54,29 @@ pub async fn aur_install(packages: Vec, options: Options) { if verbosity >= 1 { log!( "Cloned {} into cachedir, moving on to resolving dependencies", - pkg + pkg_name ); log!( "Raw dependencies for package {} are:\n{:?}", - pkg, - rpcres.package.as_ref().unwrap().depends.join(", ") + pkg_name, + package.depends, ); log!( "Raw makedepends for package {} are:\n{:?}", - pkg, - rpcres.package.as_ref().unwrap().make_depends.join(", ") + pkg_name, + package.make_depends.join(", "), ); } // dep sorting log!("Sorting dependencies"); - let sorted = crate::internal::sort(&rpcres.package.as_ref().unwrap().depends, options); + let sorted = crate::internal::sort(&package.depends, options).await; log!("Sorting make dependencies"); - let md_sorted = - crate::internal::sort(&rpcres.package.as_ref().unwrap().make_depends, options); + let md_sorted = crate::internal::sort(&package.make_depends, options).await; if verbosity >= 1 { - log!("Sorted dependencies for {} are:\n{:?}", pkg, &sorted); - log!("Sorted makedepends for {} are:\n{:?}", pkg, &md_sorted); + log!("Sorted dependencies for {} are:\n{:?}", pkg_name, &sorted); + log!("Sorted makedepends for {} are:\n{:?}", pkg_name, &md_sorted); } let newopts = Options { @@ -88,20 +90,20 @@ pub async fn aur_install(packages: Vec, options: Options) { AppExitCode::MissingDeps, "Could not find dependencies {} for package {}, aborting", sorted.nf.join(", "), - pkg, + pkg_name, ); } if !noconfirm { let p1 = prompt!(default false, "Would you like to review {}'s PKGBUILD (and any .install files if present)?", - pkg + pkg_name, ); let editor: &str = &env::var("PAGER").unwrap_or_else(|_| "less".parse().unwrap()); if p1 { Command::new(editor) - .arg(format!("{}/PKGBUILD", pkg)) + .arg(format!("{}/PKGBUILD", pkg_name)) .spawn() .unwrap() .wait() @@ -109,7 +111,7 @@ pub async fn aur_install(packages: Vec, options: Options) { let status = ShellCommand::bash() .arg("-c") - .arg(format!("ls {}/*.install &> /dev/null", pkg)) + .arg(format!("ls {}/*.install &> /dev/null", pkg_name)) .wait() .await .silent_unwrap(AppExitCode::Other); @@ -117,15 +119,15 @@ pub async fn aur_install(packages: Vec, options: Options) { if status.success() { ShellCommand::bash() .arg("-c") - .arg(format!("{} {}/*.install", editor, pkg)) + .arg(format!("{} {}/*.install", editor, pkg_name)) .wait() .await .silent_unwrap(AppExitCode::Other); } - let p2 = prompt!(default true, "Would you still like to install {}?", pkg); + let p2 = prompt!(default true, "Would you still like to install {}?", pkg_name); if !p2 { - fs::remove_dir_all(format!("{}/{}", cachedir, pkg)) + fs::remove_dir_all(format!("{}/{}", cachedir, pkg_name)) .await .unwrap(); crash!(AppExitCode::UserCancellation, "Not proceeding"); @@ -155,7 +157,7 @@ pub async fn aur_install(packages: Vec, options: Options) { // package building and installing info!("Building time!"); - set_current_dir(format!("{}/{}", cachedir, pkg)).unwrap(); + set_current_dir(format!("{}/{}", cachedir, pkg_name)).unwrap(); let status = ShellCommand::makepkg() .args(makepkg_args) .wait() @@ -163,22 +165,18 @@ pub async fn aur_install(packages: Vec, options: Options) { .silent_unwrap(AppExitCode::MakePkgError); if !status.success() { - fs::remove_dir_all(format!("{}/{}", cachedir, pkg)) + fs::remove_dir_all(format!("{}/{}", cachedir, pkg_name)) .await .unwrap(); crash!( AppExitCode::PacmanError, "Error encountered while installing {}, aborting", - pkg, + pkg_name, ); } set_current_dir(&cachedir).unwrap(); - fs::remove_dir_all(format!("{}/{}", cachedir, &pkg)) - .await - .unwrap(); - - // pushes package to database - crate::database::add(rpcres.package.unwrap(), options); + let package_cache = PathBuf::from(format!("{cachedir}/{pkg_name}")); + rmdir_recursive(&package_cache).await.unwrap() } } diff --git a/src/operations/search.rs b/src/operations/search.rs index 5e0d958..89e44ec 100644 --- a/src/operations/search.rs +++ b/src/operations/search.rs @@ -6,22 +6,20 @@ use crate::{log, Options}; pub async fn aur_search(query: &str, options: Options) { let verbosity = options.verbosity; - let res = rpcsearch(query.to_string()); + let packages = rpcsearch(query.to_string()) + .await + .silent_unwrap(AppExitCode::RpcError); + let total_results = packages.len(); - for package in &res.results { + for package in &packages { println!( "aur/{} {}\n {}", - package.name, - package.version, - package - .description - .as_ref() - .unwrap_or(&"No description".to_string()) + package.name, package.version, package.description ) } if verbosity >= 1 { - log!("Found {} resuls for \"{}\" in AUR", res.resultcount, query); + log!("Found {total_results} resuls for \"{query}\" in AUR",); } } diff --git a/src/operations/uninstall.rs b/src/operations/uninstall.rs index af0efac..a5cd55a 100644 --- a/src/operations/uninstall.rs +++ b/src/operations/uninstall.rs @@ -31,7 +31,6 @@ pub async fn uninstall(packages: Vec, options: Options) { } for package in packages { - crate::database::remove(&package, options); if Path::new(&format!( "{}/.cache/ame/{}", env::var("HOME").unwrap(), diff --git a/src/operations/upgrade.rs b/src/operations/upgrade.rs index cd41919..176d46a 100644 --- a/src/operations/upgrade.rs +++ b/src/operations/upgrade.rs @@ -3,7 +3,7 @@ use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcinfo; use crate::operations::aur_install::aur_install; -use crate::{info, log, prompt, Options}; +use crate::{info, log, prompt, warn, Options}; /// Upgrades all installed packages pub async fn upgrade(options: Options) { @@ -42,18 +42,40 @@ pub async fn upgrade(options: Options) { log!("Upgrading AUR packages"); } - let packages = crate::database::query(options); + let pacman_output = ShellCommand::pacman() + .arg("-Qm") + .args(&["--color", "never"]) + .wait_with_output() + .await + .silent_unwrap(AppExitCode::PacmanError); + let non_native_pkgs = pacman_output + .stdout + .split('\n') + .filter(|p| !p.is_empty()) + .filter_map(|p| p.split_once(' ')) + .collect::>(); if verbosity >= 1 { - log!("{:?}", &packages); + log!("{:?}", non_native_pkgs); } let mut aur_upgrades = vec![]; - for package in packages { - let remote_package = rpcinfo(&package.name); + for (pkg_name, pkg_version) in non_native_pkgs { + if verbosity >= 1 { + log!( + "remote package: name = {}, version = {}", + pkg_name, + pkg_version + ); + } + let remote_package = rpcinfo(pkg_name).await.silent_unwrap(AppExitCode::RpcError); - if remote_package.package.unwrap().version != package.version { - aur_upgrades.push(package.name); + if let Some(remote_package) = remote_package { + if remote_package.metadata.version != pkg_version { + aur_upgrades.push(pkg_name.to_string()); + } + } else { + warn!("Could not find the remote package for {}", pkg_name); } } From adeeb75ba6564882a322fc5073aa523f83e31d2a Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 27 Aug 2022 19:48:35 +0200 Subject: [PATCH 05/43] Add wrapper for pacman install Signed-off-by: trivernis --- src/internal/mod.rs | 1 + src/internal/pacman_db.rs | 14 +++++++++++ src/internal/resolve.rs | 1 + src/main.rs | 6 ++--- src/operations/install.rs | 23 +++++------------ src/wrapper/mod.rs | 1 + src/wrapper/pacman.rs | 53 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 79 insertions(+), 20 deletions(-) create mode 100644 src/internal/pacman_db.rs create mode 100644 src/internal/resolve.rs create mode 100644 src/wrapper/mod.rs create mode 100644 src/wrapper/pacman.rs diff --git a/src/internal/mod.rs b/src/internal/mod.rs index f387d26..a7f801a 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -11,6 +11,7 @@ pub mod error; pub mod exit_code; pub mod fs_utils; mod initialise; +pub mod resolve; pub mod rpc; mod sort; pub mod structs; diff --git a/src/internal/pacman_db.rs b/src/internal/pacman_db.rs new file mode 100644 index 0000000..a3f318f --- /dev/null +++ b/src/internal/pacman_db.rs @@ -0,0 +1,14 @@ +use alpm::Alpm; +use lazy_static::lazy_static; +use pacmanconf::Config; + +fn get_alpm() -> Alpm { + alpm_utils::alpm_with_conf(get_pacman_config()).unwrap() +} + +fn get_pacman_config() -> &'static Config { + lazy_static! { + static ref PACMAN_CONF: Config = Config::new().unwrap(); + } + &PACMAN_CONF +} diff --git a/src/internal/resolve.rs b/src/internal/resolve.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/internal/resolve.rs @@ -0,0 +1 @@ + diff --git a/src/main.rs b/src/main.rs index e492a80..37dcac3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,9 @@ use crate::args::{InstallArgs, Operation, QueryArgs, RemoveArgs, SearchArgs}; use crate::internal::detect; use crate::internal::exit_code::AppExitCode; use crate::internal::{init, sort, start_sudoloop, structs::Options}; +use std::str::FromStr; use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::EnvFilter; -use std::str::FromStr; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; @@ -17,6 +17,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; mod args; mod internal; mod operations; +mod wrapper; #[tokio::main(flavor = "current_thread")] async fn main() { @@ -64,8 +65,7 @@ async fn main() { /// Can be used for debug purposes _or_ verbose output fn init_logger() { const DEFAULT_ENV_FILTER: &str = "warn"; - let filter_string = - std::env::var("AME_LOG").unwrap_or_else(|_| DEFAULT_ENV_FILTER.to_string()); + let filter_string = std::env::var("AME_LOG").unwrap_or_else(|_| DEFAULT_ENV_FILTER.to_string()); let env_filter = EnvFilter::from_str(&*filter_string).expect("failed to parse env filter string"); tracing_subscriber::fmt::SubscriberBuilder::default() diff --git a/src/operations/install.rs b/src/operations/install.rs index 989a22d..be7408c 100644 --- a/src/operations/install.rs +++ b/src/operations/install.rs @@ -1,17 +1,9 @@ -use crate::internal::commands::ShellCommand; -use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; +use crate::wrapper::pacman::{PacmanInstallArgs, PacmanWrapper}; use crate::{crash, info, log, Options}; pub async fn install(packages: Vec, options: Options) { info!("Installing packages {} from repos", &packages.join(", ")); - let mut opers = vec!["-S", "--needed"]; - if options.noconfirm { - opers.push("--noconfirm"); - } - if options.asdeps { - opers.push("--asdeps"); - } let verbosity = options.verbosity; if !packages.is_empty() { @@ -19,14 +11,11 @@ pub async fn install(packages: Vec, options: Options) { log!("Installing from repos: {:?}", &packages); } - let status = ShellCommand::pacman() - .elevated() - .args(opers) - .args(&packages) - .wait() - .await - .silent_unwrap(AppExitCode::PacmanError); - if !status.success() { + let result = PacmanWrapper::install( + PacmanInstallArgs::from_options(options).packages(packages.clone()), + ) + .await; + if result.is_err() { crash!( AppExitCode::PacmanError, "An error occured while installing packages: {}, aborting", diff --git a/src/wrapper/mod.rs b/src/wrapper/mod.rs new file mode 100644 index 0000000..f4da7e8 --- /dev/null +++ b/src/wrapper/mod.rs @@ -0,0 +1 @@ +pub mod pacman; diff --git a/src/wrapper/pacman.rs b/src/wrapper/pacman.rs new file mode 100644 index 0000000..2339b67 --- /dev/null +++ b/src/wrapper/pacman.rs @@ -0,0 +1,53 @@ +use crate::internal::{commands::ShellCommand, error::AppResult, structs::Options}; + +pub struct PacmanWrapper; + +impl PacmanWrapper { + pub async fn install(args: PacmanInstallArgs) -> AppResult<()> { + let mut command = ShellCommand::pacman().elevated().arg("-S").arg("--needed"); + + if args.no_confirm { + command = command.arg("--noconfirm") + } + + if args.as_deps { + command = command.arg("--asdeps") + } + + command.args(args.packages).wait_success().await + } +} + +#[derive(Debug, Default)] +pub struct PacmanInstallArgs { + packages: Vec, + as_deps: bool, + no_confirm: bool, +} + +impl PacmanInstallArgs { + pub fn from_options(options: Options) -> Self { + Self::default() + .as_deps(options.asdeps) + .no_confirm(options.noconfirm) + } + + pub fn packages>(mut self, packages: I) -> Self { + let mut packages = packages.into_iter().collect(); + self.packages.append(&mut packages); + + self + } + + pub fn no_confirm(mut self, no_confirm: bool) -> Self { + self.no_confirm = no_confirm; + + self + } + + pub fn as_deps(mut self, as_deps: bool) -> Self { + self.as_deps = as_deps; + + self + } +} From bb6b5cc1623d411d65c06f270c970584abd447df Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 27 Aug 2022 19:58:23 +0200 Subject: [PATCH 06/43] Change wrapper to builder pattern (fancy stuff) Signed-off-by: trivernis --- src/{wrapper => builder}/mod.rs | 0 src/{wrapper => builder}/pacman.rs | 37 ++++++++++++++---------------- src/main.rs | 2 +- src/operations/install.rs | 11 +++++---- 4 files changed, 24 insertions(+), 26 deletions(-) rename src/{wrapper => builder}/mod.rs (100%) rename src/{wrapper => builder}/pacman.rs (77%) diff --git a/src/wrapper/mod.rs b/src/builder/mod.rs similarity index 100% rename from src/wrapper/mod.rs rename to src/builder/mod.rs diff --git a/src/wrapper/pacman.rs b/src/builder/pacman.rs similarity index 77% rename from src/wrapper/pacman.rs rename to src/builder/pacman.rs index 2339b67..4d75cba 100644 --- a/src/wrapper/pacman.rs +++ b/src/builder/pacman.rs @@ -1,31 +1,13 @@ use crate::internal::{commands::ShellCommand, error::AppResult, structs::Options}; -pub struct PacmanWrapper; - -impl PacmanWrapper { - pub async fn install(args: PacmanInstallArgs) -> AppResult<()> { - let mut command = ShellCommand::pacman().elevated().arg("-S").arg("--needed"); - - if args.no_confirm { - command = command.arg("--noconfirm") - } - - if args.as_deps { - command = command.arg("--asdeps") - } - - command.args(args.packages).wait_success().await - } -} - #[derive(Debug, Default)] -pub struct PacmanInstallArgs { +pub struct PacmanInstallBuilder { packages: Vec, as_deps: bool, no_confirm: bool, } -impl PacmanInstallArgs { +impl PacmanInstallBuilder { pub fn from_options(options: Options) -> Self { Self::default() .as_deps(options.asdeps) @@ -50,4 +32,19 @@ impl PacmanInstallArgs { self } + + #[tracing::instrument(level = "debug")] + pub async fn install(self) -> AppResult<()> { + let mut command = ShellCommand::pacman().elevated().arg("-S").arg("--needed"); + + if self.no_confirm { + command = command.arg("--noconfirm") + } + + if self.as_deps { + command = command.arg("--asdeps") + } + + command.args(self.packages).wait_success().await + } } diff --git a/src/main.rs b/src/main.rs index 37dcac3..cf8b7f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,9 +15,9 @@ use tracing_subscriber::EnvFilter; static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; mod args; +mod builder; mod internal; mod operations; -mod wrapper; #[tokio::main(flavor = "current_thread")] async fn main() { diff --git a/src/operations/install.rs b/src/operations/install.rs index be7408c..b014650 100644 --- a/src/operations/install.rs +++ b/src/operations/install.rs @@ -1,5 +1,5 @@ +use crate::builder::pacman::PacmanInstallBuilder; use crate::internal::exit_code::AppExitCode; -use crate::wrapper::pacman::{PacmanInstallArgs, PacmanWrapper}; use crate::{crash, info, log, Options}; pub async fn install(packages: Vec, options: Options) { @@ -11,10 +11,11 @@ pub async fn install(packages: Vec, options: Options) { log!("Installing from repos: {:?}", &packages); } - let result = PacmanWrapper::install( - PacmanInstallArgs::from_options(options).packages(packages.clone()), - ) - .await; + let result = PacmanInstallBuilder::from_options(options) + .packages(packages.clone()) + .install() + .await; + if result.is_err() { crash!( AppExitCode::PacmanError, From 1f5a2127e1f39199f404739d06a940f979e57f31 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 27 Aug 2022 21:15:20 +0200 Subject: [PATCH 07/43] Add builder for pacman query Signed-off-by: trivernis --- Containerfile | 4 +++- src/builder/pacman.rs | 44 +++++++++++++++++++++++++++++++++++++++ src/operations/upgrade.rs | 42 +++++++++++++++---------------------- 3 files changed, 64 insertions(+), 26 deletions(-) diff --git a/Containerfile b/Containerfile index d42e31f..77d3667 100644 --- a/Containerfile +++ b/Containerfile @@ -12,6 +12,8 @@ WORKDIR /usr/src/amethyst COPY Cargo.toml Cargo.lock ./ RUN cargo fetch COPY src ./src +RUN mkdir target +COPY target/debug/build ./target/debug/build RUN cargo build --frozen RUN mkdir /tmp/ame RUN cp target/debug/ame /tmp/ame/ @@ -28,5 +30,5 @@ RUN rm -f $(pacdiff -o -f) USER ame RUN mkdir -p /home/ame/.local/share RUN touch /home/ame/.zshrc -ENV AME_LOG=debug +ENV AME_LOG=debug,hyper=info ENTRYPOINT ["zsh"] \ No newline at end of file diff --git a/src/builder/pacman.rs b/src/builder/pacman.rs index 4d75cba..9dd2d7e 100644 --- a/src/builder/pacman.rs +++ b/src/builder/pacman.rs @@ -27,6 +27,7 @@ impl PacmanInstallBuilder { self } + #[allow(clippy::wrong_self_convention)] pub fn as_deps(mut self, as_deps: bool) -> Self { self.as_deps = as_deps; @@ -48,3 +49,46 @@ impl PacmanInstallBuilder { command.args(self.packages).wait_success().await } } + +#[derive(Debug, Default)] +pub struct PacmanQueryBuilder { + foreign: bool, +} + +impl PacmanQueryBuilder { + /// Query for foreign packages + pub fn foreign(mut self, foreign: bool) -> Self { + self.foreign = foreign; + + self + } + + #[tracing::instrument(level = "debug")] + pub async fn query(self) -> AppResult> { + let mut command = ShellCommand::pacman().arg("-Q").arg("--color").arg("never"); + + if self.foreign { + command = command.arg("-m"); + } + + let output = command.wait_with_output().await?; + let packages = output + .stdout + .split('\n') + .filter(|p| !p.is_empty()) + .filter_map(|p| p.split_once(' ')) + .map(|(name, version)| BasicPackageInfo { + name: name.to_string(), + version: version.to_string(), + }) + .collect(); + + Ok(packages) + } +} + +#[derive(Clone, Debug)] +pub struct BasicPackageInfo { + pub name: String, + pub version: String, +} diff --git a/src/operations/upgrade.rs b/src/operations/upgrade.rs index 176d46a..55e31a5 100644 --- a/src/operations/upgrade.rs +++ b/src/operations/upgrade.rs @@ -1,3 +1,4 @@ +use crate::builder::pacman::PacmanQueryBuilder; use crate::internal::commands::ShellCommand; use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; @@ -42,40 +43,31 @@ pub async fn upgrade(options: Options) { log!("Upgrading AUR packages"); } - let pacman_output = ShellCommand::pacman() - .arg("-Qm") - .args(&["--color", "never"]) - .wait_with_output() + let non_native_pkgs = PacmanQueryBuilder::default() + .foreign(true) + .query() .await .silent_unwrap(AppExitCode::PacmanError); - let non_native_pkgs = pacman_output - .stdout - .split('\n') - .filter(|p| !p.is_empty()) - .filter_map(|p| p.split_once(' ')) - .collect::>(); - if verbosity >= 1 { - log!("{:?}", non_native_pkgs); - } + tracing::debug!("aur packages: {non_native_pkgs:?}"); let mut aur_upgrades = vec![]; - for (pkg_name, pkg_version) in non_native_pkgs { - if verbosity >= 1 { - log!( - "remote package: name = {}, version = {}", - pkg_name, - pkg_version - ); - } - let remote_package = rpcinfo(pkg_name).await.silent_unwrap(AppExitCode::RpcError); + for pkg in non_native_pkgs { + let remote_package = rpcinfo(&pkg.name) + .await + .silent_unwrap(AppExitCode::RpcError); if let Some(remote_package) = remote_package { - if remote_package.metadata.version != pkg_version { - aur_upgrades.push(pkg_name.to_string()); + if remote_package.metadata.version != pkg.version { + tracing::debug!( + "local version: {}, remote version: {}", + pkg.version, + remote_package.metadata.version + ); + aur_upgrades.push(pkg.name); } } else { - warn!("Could not find the remote package for {}", pkg_name); + warn!("Could not find the remote package for {}", pkg.name); } } From 76a301bade8a88370dd26295c4bbeb88fd1011b1 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 27 Aug 2022 22:57:55 +0200 Subject: [PATCH 08/43] Fix clippy warnings Signed-off-by: trivernis --- src/internal/config.rs | 2 -- src/internal/exit_code.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/src/internal/config.rs b/src/internal/config.rs index a356157..e4865a6 100644 --- a/src/internal/config.rs +++ b/src/internal/config.rs @@ -3,8 +3,6 @@ use serde::Deserialize; use std::{env, fs, path::PathBuf}; -use crate::{crash, AppExitCode}; - #[derive(Debug, Deserialize)] pub struct Config { pub base: ConfigBase, diff --git a/src/internal/exit_code.rs b/src/internal/exit_code.rs index f35e960..a6e2ec4 100644 --- a/src/internal/exit_code.rs +++ b/src/internal/exit_code.rs @@ -8,7 +8,6 @@ pub enum AppExitCode { PacmanError = 5, GitError = 6, MakePkgError = 7, - ConfigParseError = 8, RpcError = 9, Other = 63, } From f6659727e8711fb3de3b0dd1e0ff8c75f219ef66 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 27 Aug 2022 23:00:31 +0200 Subject: [PATCH 09/43] Remove placeholder binaries Signed-off-by: trivernis --- Cargo.toml | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0e62359..2112777 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,35 +10,6 @@ default-run = "ame" [features] pkg-warner = [] -[[bin]] -name = "apt" -path = "src/bin/apt.rs" -required-features = ["pkg-warner"] - -[[bin]] -name = "apt-get" -path = "src/bin/apt-get.rs" -required-features = ["pkg-warner"] - -[[bin]] -name = "dnf" -path = "src/bin/dnf.rs" -required-features = ["pkg-warner"] - -[[bin]] -name = "eopkg" -path = "src/bin/eopkg.rs" -required-features = ["pkg-warner"] - -[[bin]] -name = "yum" -path = "src/bin/yum.rs" -required-features = ["pkg-warner"] - -[[bin]] -name = "zypper" -path = "src/bin/zypper.rs" -required-features = ["pkg-warner"] [[bin]] name = "ame" From 052d176f4ac5139a0048cf89a903149b99b3e2bd Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 28 Aug 2022 18:08:29 +0200 Subject: [PATCH 10/43] Reimplement logging by utilizing the tracing crate Signed-off-by: trivernis --- Cargo.lock | 147 ++++++++++++++++-------- Cargo.toml | 7 +- src/internal/clean.rs | 9 +- src/internal/detect.rs | 24 ++-- src/internal/initialise.rs | 35 ++---- src/internal/mod.rs | 5 - src/internal/sort.rs | 21 +--- src/internal/structs.rs | 1 - src/internal/sudoloop.rs | 9 +- src/internal/utils.rs | 204 ++-------------------------------- src/logging/fmt_layer.rs | 146 ++++++++++++++++++++++++ src/logging/handler.rs | 177 +++++++++++++++++++++++++++++ src/logging/mod.rs | 69 ++++++++++++ src/main.rs | 41 ++----- src/operations/aur_install.rs | 64 +++++------ src/operations/clean.rs | 31 ++---- src/operations/install.rs | 13 +-- src/operations/search.rs | 20 ++-- src/operations/uninstall.rs | 15 +-- src/operations/upgrade.rs | 27 ++--- 20 files changed, 628 insertions(+), 437 deletions(-) create mode 100644 src/logging/fmt_layer.rs create mode 100644 src/logging/handler.rs create mode 100644 src/logging/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 8b7b4b1..8805615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,13 +13,16 @@ dependencies = [ "color-eyre", "colored", "crossterm", + "dialoguer", "futures", + "indicatif", + "lazy_static", "libc", "native-tls", + "parking_lot", "regex", "rusqlite", "serde", - "spinoff", "textwrap", "tokio", "toml", @@ -257,6 +260,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "console" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "terminal_size", + "unicode-width", + "winapi", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -298,6 +315,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "dialoguer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92e7e37ecef6857fdc0c0c5d42fd5b0938e46590c2183cc92dd310a6d078eb1" +dependencies = [ + "console", + "tempfile", + "zeroize", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -338,6 +372,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" @@ -625,6 +665,18 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc42b206e70d86ec03285b123e65a5458c92027d1fb2ae3555878b8113b3ddf" +dependencies = [ + "console", + "number_prefix", + "tokio", + "unicode-width", +] + [[package]] name = "instant" version = "0.1.12" @@ -696,12 +748,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "matchers" version = "0.1.0" @@ -768,6 +814,22 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.29.0" @@ -856,10 +918,13 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ + "backtrace", "cfg-if", "libc", + "petgraph", "redox_syscall", "smallvec", + "thread-id", "windows-sys", ] @@ -869,6 +934,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1030,12 +1105,6 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" -[[package]] -name = "rustversion" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" - [[package]] name = "ryu" version = "1.0.11" @@ -1194,46 +1263,12 @@ dependencies = [ "winapi", ] -[[package]] -name = "spinoff" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c139aa6a2b4ed01ef761dfd593eb5b02218dbf35a3a0f10940b72f5bfe70426" -dependencies = [ - "colored", - "maplit", - "once_cell", - "strum", -] - [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "syn" version = "1.0.99" @@ -1310,6 +1345,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thread-id" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fdfe0627923f7411a43ec9ec9c39c3a9b4151be313e0922042581fb6c9b717f" +dependencies = [ + "libc", + "redox_syscall", + "winapi", +] + [[package]] name = "thread_local" version = "1.1.4" @@ -1345,6 +1391,7 @@ dependencies = [ "libc", "memchr", "mio", + "num_cpus", "once_cell", "pin-project-lite", "signal-hook-registry", @@ -1738,3 +1785,9 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/Cargo.toml b/Cargo.toml index 2112777..6f29b97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,12 +39,15 @@ futures = "0.3.23" tracing = "0.1.36" tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } textwrap = "0.15.0" -spinoff = "0.5.3" crossterm = "0.25.0" toml = "0.5.9" clap_complete = "3.2.4" color-eyre = { version = "0.6.2", features = ["issue-url", "url"] } +indicatif = { version = "0.17.0", features = ["tokio"] } +lazy_static = "1.4.0" +parking_lot = { version = "0.12.1", features = ["deadlock_detection"] } +dialoguer = "0.10.2" [dependencies.tokio] version = "1.20.1" -features = ["rt", "io-std", "io-util", "process", "time", "macros", "tracing", "fs"] +features = ["rt", "rt-multi-thread", "io-std", "io-util", "process", "time", "macros", "tracing", "fs"] diff --git a/src/internal/clean.rs b/src/internal/clean.rs index 5a45d51..85bcd8e 100644 --- a/src/internal/clean.rs +++ b/src/internal/clean.rs @@ -1,13 +1,10 @@ use regex::Regex; -use crate::{log, Options}; - /// Strips packages from versioning and other extraneous information. -pub fn clean(a: &[String], options: Options) -> Vec { +pub fn clean(a: &[String]) -> Vec { // Strip versioning from package names let r = Regex::new(r"(\S+)((?:>=|<=|>|<|=\W)\S+$)").unwrap(); let mut cleaned: Vec = vec![]; - let verbosity = options.verbosity; // Push cleaned package names to vector for b in a { @@ -19,9 +16,7 @@ pub fn clean(a: &[String], options: Options) -> Vec { } } - if verbosity >= 1 { - log!("Cleaned: {:?}\nInto: {:?}", a, cleaned); - } + tracing::debug!("Cleaned: {:?}\nInto: {:?}", a, cleaned); cleaned } diff --git a/src/internal/detect.rs b/src/internal/detect.rs index 64c7a25..3b667f9 100644 --- a/src/internal/detect.rs +++ b/src/internal/detect.rs @@ -2,12 +2,16 @@ use crate::internal::commands::ShellCommand; use crate::internal::config; use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; -use crate::{prompt, spinner, warn}; +use crate::logging::get_logger; +use crate::prompt; + +use super::prompt_sudo_single; /// Searches the filesystem for .pacnew files and helps the user deal with them. pub async fn detect() { - // Start spinner - let sp = spinner!("Scanning for pacnew files"); + prompt_sudo_single().await.expect("Sudo prompt failed"); + let pb = get_logger().new_progress_spinner(); + pb.set_message("Scanning for pacnew files"); let mut pacnew = vec![]; @@ -27,11 +31,13 @@ pub async fn detect() { // If pacnew files are found, warn the user and prompt to pacdiff if pacnew.is_empty() { - sp.stop_bold("No pacnew files found"); + pb.finish_with_message("No pacnew files found"); + get_logger().reset_output_type(); } else { - sp.stop_bold("It appears that at least one program you have installed / upgraded has installed a .pacnew config file. These are created when you have modified a program's configuration, and a package upgrade could not automatically merge the new file."); + pb.finish_with_message("It appears that at least one program you have installed / upgraded has installed a .pacnew config file. These are created when you have modified a program's configuration, and a package upgrade could not automatically merge the new file."); + get_logger().reset_output_type(); - let choice = prompt!(default false, "Would you like Amethyst to run pacdiff to deal with this? You can always deal with this later by running `sudo pacdiff`"); + let choice = prompt!(default no, "Would you like Amethyst to run pacdiff to deal with this? You can always deal with this later by running `sudo pacdiff`"); if choice { let config = config::read(); if config.base.pacdiff_warn { @@ -41,9 +47,9 @@ pub async fn detect() { .await .silent_unwrap(AppExitCode::PacmanError); } else { - warn!("Pacdiff uses vimdiff by default to edit files for merging. You can focus panes by mousing over them and pressing left click, and scroll up and down using your mouse's scroll wheel (or the arrow keys). To exit vimdiff, press the following key combination: ESC, :qa!, ENTER"); - warn!("You can surpress this warning in the future by setting `pacdiff_warn` to \"false\" in ~/.config/ame/config.toml"); - let cont = prompt!(default false, "Continue?"); + tracing::warn!("Pacdiff uses vimdiff by default to edit files for merging. You can focus panes by mousing over them and pressing left click, and scroll up and down using your mouse's scroll wheel (or the arrow keys). To exit vimdiff, press the following key combination: ESC, :qa!, ENTER"); + tracing::warn!("You can surpress this warning in the future by setting `pacdiff_warn` to \"false\" in ~/.config/ame/config.toml"); + let cont = prompt!(default no, "Continue?"); if cont { ShellCommand::pacdiff() .elevated() diff --git a/src/internal/initialise.rs b/src/internal/initialise.rs index 9d09a36..db4305b 100644 --- a/src/internal/initialise.rs +++ b/src/internal/initialise.rs @@ -2,19 +2,16 @@ use std::env; use std::path::Path; use std::process::Command; -use crate::{crash, internal::exit_code::AppExitCode, log, Options}; +use crate::{crash, internal::exit_code::AppExitCode}; -pub fn init(options: Options) { - let verbosity = options.verbosity; +pub fn init() { let homedir = env::var("HOME").unwrap(); if !Path::new(&format!("{}/.local/share/ame", homedir)).exists() { let r = std::fs::create_dir_all(format!("{}/.local/share/ame", homedir)); match r { Ok(_) => { - if verbosity >= 1 { - log!("Created path: {}/.local/share/ame", homedir); - } + tracing::debug!("Created path: {}/.local/share/ame", homedir); } Err(e) => { crash!( @@ -31,9 +28,7 @@ pub fn init(options: Options) { let r = std::fs::create_dir_all(format!("{}/.cache/ame", homedir)); match r { Ok(_) => { - if verbosity >= 1 { - log!("Created path: {}/.cache/ame", homedir); - } + tracing::debug!("Created path: {}/.cache/ame", homedir); } Err(e) => { crash!( @@ -48,9 +43,7 @@ pub fn init(options: Options) { let r = std::fs::remove_dir_all(format!("{}/.cache/ame", homedir)); match r { Ok(_) => { - if verbosity >= 1 { - log!("Removing cache: {}/.cache/ame", homedir); - } + tracing::debug!("Removing cache: {}/.cache/ame", homedir); } Err(e) => { crash!( @@ -64,9 +57,7 @@ pub fn init(options: Options) { let r2 = std::fs::create_dir_all(format!("{}/.cache/ame", homedir)); match r2 { Ok(_) => { - if verbosity >= 1 { - log!("Created path: {}/.cache/ame", homedir); - } + tracing::debug!("Created path: {}/.cache/ame", homedir); } Err(e2) => { crash!( @@ -86,9 +77,7 @@ pub fn init(options: Options) { .status(); match r { Ok(_) => { - if verbosity >= 1 { - log!("Set correct permissions for path: {}/.cache/ame", homedir); - } + tracing::debug!("Set correct permissions for path: {}/.cache/ame", homedir); } Err(e) => { crash!( @@ -106,12 +95,10 @@ pub fn init(options: Options) { .status(); match r { Ok(_) => { - if verbosity >= 1 { - log!( - "Set correct permissions for path: {}/.local/share/ame", - homedir - ); - } + tracing::debug!( + "Set correct permissions for path: {}/.local/share/ame", + homedir + ); } Err(e) => { crash!( diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 9ba022c..18d9952 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -45,11 +45,6 @@ pub fn uwu_enabled() -> bool { config.extra.uwu.unwrap_or(false) } -pub fn uwu_debug_enabled() -> bool { - let config = config::read(); - config.extra.uwu_debug.unwrap_or(false) -} - /// Checks if we're running in a tty. If we do we can assume that /// the output can safely be colorized. pub fn is_tty() -> bool { diff --git a/src/internal/sort.rs b/src/internal/sort.rs index 4608010..1f55bb0 100644 --- a/src/internal/sort.rs +++ b/src/internal/sort.rs @@ -1,7 +1,7 @@ use std::process::{Command, Stdio}; use crate::internal::{clean, rpc, structs}; -use crate::{log, Options}; +use crate::Options; use super::error::SilentUnwrap; use super::exit_code::AppExitCode; @@ -11,13 +11,10 @@ pub async fn sort(input: &[String], options: Options) -> structs::Sorted { let mut repo_packages: Vec = vec![]; let mut aur_packages: Vec = vec![]; let mut missing_packages: Vec = vec![]; - let verbosity = options.verbosity; - let packages = clean(input, options); + let packages = clean(input); - if verbosity >= 1 { - log!("Sorting: {:?}", packages.join(" ")); - } + tracing::debug!("Sorting: {:?}", packages.join(" ")); for package in packages { let rs = Command::new("pacman") @@ -28,23 +25,17 @@ pub async fn sort(input: &[String], options: Options) -> structs::Sorted { .expect("Something has gone wrong"); if let Some(0) = rs.code() { - if verbosity >= 1 { - log!("{} found in repos", package); - } + tracing::debug!("{} found in repos", package); repo_packages.push(package.to_string()); } else if rpc::rpcinfo(&package) .await .silent_unwrap(AppExitCode::RpcError) .is_some() { - if verbosity >= 1 { - log!("{} found in AUR", package); - } + tracing::debug!("{} found in AUR", package); aur_packages.push(package.to_string()); } else { - if verbosity >= 1 { - log!("{} not found", package); - } + tracing::debug!("{} not found", package); missing_packages.push(package.to_string()); } } diff --git a/src/internal/structs.rs b/src/internal/structs.rs index ed632c3..9c3f075 100644 --- a/src/internal/structs.rs +++ b/src/internal/structs.rs @@ -18,7 +18,6 @@ impl Sorted { #[derive(Clone, Debug, Copy)] /// Options to be passed down to internal functions pub struct Options { - pub verbosity: usize, pub noconfirm: bool, pub asdeps: bool, } diff --git a/src/internal/sudoloop.rs b/src/internal/sudoloop.rs index 2e2c7d1..d2a7a4b 100644 --- a/src/internal/sudoloop.rs +++ b/src/internal/sudoloop.rs @@ -2,6 +2,8 @@ use std::time::Duration; use crate::ShellCommand; +use super::error::AppResult; + /// Loop sudo so it doesn't time out #[tracing::instrument(level = "trace")] pub async fn start_sudoloop() { @@ -16,5 +18,10 @@ pub async fn start_sudoloop() { #[tracing::instrument(level = "trace")] async fn prompt_sudo() { - while ShellCommand::sudo().arg("-v").wait_success().await.is_err() {} + while prompt_sudo_single().await.is_err() {} +} + +#[tracing::instrument(level = "trace")] +pub async fn prompt_sudo_single() -> AppResult<()> { + ShellCommand::sudo().arg("-v").wait_success().await } diff --git a/src/internal/utils.rs b/src/internal/utils.rs index 90359f2..d32e170 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -1,36 +1,8 @@ -use colored::Colorize; -use std::io; -use std::io::Write; use std::process::exit; -use std::time::UNIX_EPOCH; -use textwrap::wrap; use crate::internal::exit_code::AppExitCode; -use crate::{internal, uwu}; - -const OK_SYMBOL: &str = "❖"; -const ERR_SYMBOL: &str = "X"; -const WARN_SYMBOL: &str = "!"; -const PROMPT_SYMBOL: &str = "?"; - -const PROMPT_YN_DEFAULT_TRUE: &str = "[Y/n]"; -const PROMPT_YN_DEFAULT_FALSE: &str = "[y/N]"; - -#[macro_export] -/// Macro for printing a message to stdout. -macro_rules! info { - ($($arg:tt)+) => { - $crate::internal::utils::log_info(format!($($arg)+)) - } -} - -#[macro_export] -/// Macro for printing a warning message non-destructively. -macro_rules! warn { - ($($arg:tt)+) => { - $crate::internal::utils::log_warn(format!($($arg)+)) - } -} +use crate::logging::get_logger; +use crate::logging::handler::PromptDefault; #[macro_export] /// Macro for printing a message and destructively exiting @@ -40,179 +12,27 @@ macro_rules! crash { } } -#[macro_export] -/// Macro for logging to stderr -macro_rules! log { - ($($arg:tt)+) => { - $crate::internal::utils::log_debug(format!($($arg)+)) - } -} - #[macro_export] /// Macro for prompting the user with a yes/no question. macro_rules! prompt { - (default $default:expr, $($arg:tt)+) => { - $crate::internal::utils::prompt_yn(format!($($arg)+), $default) - } -} - -#[macro_export] -/// Macro for creating a spinner. -macro_rules! spinner { - ($($arg:tt)+) => { - $crate::internal::utils::spinner_fn(format!($($arg)+)) - } -} - -/// Print a formatted message to stdout. -pub fn log_info(msg: String) { - let msg = if internal::uwu_enabled() { - uwu!(&msg) - } else { - msg + (default yes, $($arg:tt)+) => { + $crate::internal::utils::prompt_yn(format!($($arg)+), $crate::logging::handler::PromptDefault::Yes) }; - - let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2) - .subsequent_indent(" "); - - println!( - "{} {}", - OK_SYMBOL.purple(), - wrap(&msg, opts).join("\n").bold() - ); -} - -/// Print a non-destructive warning message -pub fn log_warn(msg: String) { - let msg = if internal::uwu_enabled() { - uwu!(&msg) - } else { - msg + (default no, $($arg:tt)+) => { + $crate::internal::utils::prompt_yn(format!($($arg)+), $crate::logging::handler::PromptDefault::No) }; - - let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2) - .subsequent_indent(" "); - - println!( - "{} {}", - WARN_SYMBOL.yellow(), - wrap(&msg, opts).join("\n").yellow().bold() - ); + (no default, $($arg:tt)+) => { + $crate::internal::utils::prompt_yn(format!($($arg)+), $crate::logging::handler::PromptDefault::None) + } } /// Logs a message and exits the program with the given exit code. pub fn log_and_crash(msg: String, exit_code: AppExitCode) -> ! { - let msg = if internal::uwu_enabled() { - uwu!(&msg) - } else { - msg - }; - - let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2) - .subsequent_indent(" "); - - println!( - "{} {}", - ERR_SYMBOL.red().bold(), - wrap(&msg, opts).join("\n").red().bold() - ); + tracing::error!(msg); exit(exit_code as i32); } -/// Logs a message to stderr with timestamp -pub fn log_debug(msg: String) { - let msg = if internal::uwu_enabled() && internal::uwu_debug_enabled() { - uwu!(&msg) - } else { - msg - }; - - eprintln!( - "{} {}", - std::time::SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(), - msg - ); -} - /// Prompts the user for a yes/no answer. -pub fn prompt_yn(question: String, default_true: bool) -> bool { - let yn_prompt = if default_true { - PROMPT_YN_DEFAULT_TRUE - } else { - PROMPT_YN_DEFAULT_FALSE - }; - - let question = if internal::uwu_enabled() { - uwu!(&question) - } else { - question - }; - - let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2) - .subsequent_indent(" "); - - print!( - "{} {} {}: ", - PROMPT_SYMBOL.purple(), - wrap(&question, opts).join("\n").bold(), - yn_prompt - ); - - let mut yn: String = String::new(); - - io::stdout().flush().ok(); - io::stdin().read_line(&mut yn).unwrap(); - - if yn.trim().to_lowercase() == "n" || yn.trim().to_lowercase() == "no" { - false - } else if yn.trim().to_lowercase() == "y" || yn.trim().to_lowercase() == "yes" { - true - } else { - default_true - } -} - -pub struct Spinner { - spinner: spinoff::Spinner, -} - -impl Spinner { - pub fn stop_bold(self, text: &str) { - let text = if internal::uwu_enabled() { - uwu!(text) - } else { - text.to_string() - }; - - let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2) - .subsequent_indent(" "); - - let symbol = format!("{}", OK_SYMBOL.purple()); - let text = format!("{}", wrap(&text, opts).join("\n").bold()); - - self.spinner.stop_and_persist(&symbol, &text); - } -} - -/// Returns a spinner that can be used to display progress. -pub fn spinner_fn(text: String) -> Spinner { - let text = if internal::uwu_enabled() { - uwu!(&text) - } else { - text - }; - - let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2) - .subsequent_indent(" "); - - Spinner { - spinner: spinoff::Spinner::new( - spinoff::Spinners::Line, - format!("{}", wrap(&text, opts).join("\n").bold()), - spinoff::Color::Magenta, - ), - } +pub fn prompt_yn(question: String, prompt_default: PromptDefault) -> bool { + get_logger().prompt(question, prompt_default) } diff --git a/src/logging/fmt_layer.rs b/src/logging/fmt_layer.rs new file mode 100644 index 0000000..2c187c7 --- /dev/null +++ b/src/logging/fmt_layer.rs @@ -0,0 +1,146 @@ +use colored::Colorize; +use std::collections::HashMap; +use std::sync::Arc; +use tracing_subscriber::registry::LookupSpan; + +use tracing::field::Visit; +use tracing::{span, Level, Metadata, Subscriber}; +use tracing_subscriber::Layer; + +use super::handler::LogHandler; +use super::Verbosity; + +const ENABLED_MODULES: &[&str] = &["ame"]; + +pub struct AmeFormatLayer { + logger: Arc, +} + +impl AmeFormatLayer { + pub fn new(logger: Arc) -> Self { + Self { logger } + } + + fn is_level_loggable(&self, level: &Level) -> bool { + self.logger.is_loggable(Verbosity::from_level(level)) + } + + fn is_enabled(&self, metadata: &Metadata) -> bool { + let level = metadata.level(); + if !self.is_level_loggable(level) { + false + } else if let Some(module_path) = metadata.module_path() { + ENABLED_MODULES.iter().any(|m| module_path.starts_with(m)) + } else { + false + } + } + + fn log(&self, msg: String, level: &Level) { + match Verbosity::from_level(level) { + Verbosity::Error => self.logger.log_error(msg), + Verbosity::Warning => self.logger.log_warning(msg), + Verbosity::Info => self.logger.log_info(msg), + Verbosity::Debug => self.logger.log_debug(msg), + Verbosity::Trace => self.logger.log_trace(msg), + } + } +} + +impl LookupSpan<'a>> Layer for AmeFormatLayer { + /// When entering a span + fn on_new_span( + &self, + attrs: &span::Attributes<'_>, + _id: &span::Id, + _ctx: tracing_subscriber::layer::Context<'_, S>, + ) { + let metadata = attrs.metadata(); + if self.is_enabled(metadata) { + let mut visitor = ValueDebugStorage::default(); + attrs.record(&mut visitor); + let fields: Vec = visitor + .values + .into_iter() + .map(|(k, v)| format!("{k} = {v}")) + .collect(); + let mut fields_str = fields.join("\n "); + + if !fields_str.is_empty() { + fields_str = format!("\n {fields_str}"); + } + + if let Some(module) = metadata.module_path() { + self.log( + format!( + "{} {}::{} {}", + "ENTER".italic(), + module, + metadata.name(), + fields_str.dimmed() + ), + metadata.level(), + ) + } else { + self.log( + format!( + "{} {} {}", + "ENTER".italic(), + metadata.name(), + fields_str.dimmed() + ), + metadata.level(), + ) + } + } + } + + fn on_exit(&self, id: &span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { + let span = ctx.span(id).unwrap(); + let metadata = span.metadata(); + + if self.is_enabled(metadata) { + if let Some(module) = metadata.module_path() { + self.log( + format!("{} {}::{}", "EXIT".italic(), module, metadata.name(),), + metadata.level(), + ); + } else { + self.log( + format!("{} {}", "EXIT".italic(), metadata.name()), + metadata.level(), + ); + } + } + } + + fn on_event( + &self, + event: &tracing::Event<'_>, + _ctx: tracing_subscriber::layer::Context<'_, S>, + ) { + let metadata = event.metadata(); + + if self.is_enabled(metadata) { + let mut visitor = ValueDebugStorage::default(); + event.record(&mut visitor); + let mut values = visitor.values; + + if let Some(msg) = values.remove("message") { + self.log(msg, metadata.level()) + } + } + } +} + +#[derive(Default)] +pub struct ValueDebugStorage { + pub values: HashMap, +} + +impl Visit for ValueDebugStorage { + fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { + self.values + .insert(field.name().to_string(), format!("{:?}", value)); + } +} diff --git a/src/logging/handler.rs b/src/logging/handler.rs new file mode 100644 index 0000000..968ac06 --- /dev/null +++ b/src/logging/handler.rs @@ -0,0 +1,177 @@ +use colored::Colorize; +use indicatif::{MultiProgress, ProgressBar}; +use std::{ + sync::{atomic::AtomicBool, Arc}, + time::Duration, +}; + +use crate::uwu; +use dialoguer::Confirm; + +use super::Verbosity; +use parking_lot::RwLock; + +const OK_SYMBOL: &str = "❖"; +const ERR_SYMBOL: &str = "X"; +const WARN_SYMBOL: &str = "!"; +const DEBUG_SYMBOL: &str = "⌘"; +const TRACE_SYMBOL: &str = "🗲"; +const PROMPT_SYMBOL: &str = "?"; + +pub struct LogHandler { + level: Arc>, + output_type: Arc>, + uwu_enabled: Arc, +} + +impl Default for LogHandler { + fn default() -> Self { + Self { + level: Arc::new(RwLock::new(Verbosity::Info)), + output_type: Arc::new(RwLock::new(OutputType::Stderr)), + uwu_enabled: Arc::new(AtomicBool::new(false)), + } + } +} + +#[allow(unused)] +pub enum OutputType { + Stdout, + Stderr, + MultiProgress(Arc), + Progress(Arc), +} + +#[allow(unused)] +pub enum PromptDefault { + Yes, + No, + None, +} + +impl LogHandler { + pub fn log_error(&self, msg: String) { + if self.is_loggable(Verbosity::Error) { + let msg = self.preformat_msg(msg); + let msg = format!("{} {}", ERR_SYMBOL.red().bold(), msg.bold().red()); + self.log(msg); + } + } + + pub fn log_warning(&self, msg: String) { + if self.is_loggable(Verbosity::Warning) { + let msg = self.preformat_msg(msg); + let msg = format!("{} {}", WARN_SYMBOL.yellow(), msg.yellow().bold()); + self.log(msg); + } + } + + pub fn log_info(&self, msg: String) { + if self.is_loggable(Verbosity::Info) { + let msg = self.preformat_msg(msg); + let msg = format!("{} {}", OK_SYMBOL.purple(), msg.bold()); + self.log(msg); + } + } + + pub fn log_debug(&self, msg: String) { + if self.is_loggable(Verbosity::Debug) { + let msg = self.preformat_msg(msg); + let msg = format!("{} {}", DEBUG_SYMBOL.blue(), msg); + + self.log(msg); + } + } + + pub fn log_trace(&self, msg: String) { + if self.is_loggable(Verbosity::Trace) { + let msg = self.preformat_msg(msg); + let msg = format!("{} {}", TRACE_SYMBOL.cyan(), msg.dimmed()); + self.log(msg); + } + } + + /// Prompts the user with a question and a default selection + pub fn prompt(&self, question: String, p_default: PromptDefault) -> bool { + let question = self.preformat_msg(question); + let question = format!("{} {}", PROMPT_SYMBOL.purple(), question.bold()); + let mut confirm = Confirm::new(); + confirm.with_prompt(question); + + match p_default { + PromptDefault::Yes => { + confirm.default(true); + } + PromptDefault::No => { + confirm.default(false); + } + PromptDefault::None => {} + } + confirm.interact().unwrap() + } + + pub fn set_verbosity(&self, level: Verbosity) { + (*self.level.write()) = level; + } + + pub fn reset_output_type(&self) { + self.set_output_type(OutputType::Stdout); + } + + /// Creates a new progress spinner and registers it on the log handler + pub fn new_progress_spinner(&self) -> Arc { + let progress_bar = ProgressBar::new_spinner().with_message("Scanning for pacnew files"); + progress_bar.enable_steady_tick(Duration::from_millis(250)); + let pb = Arc::new(progress_bar); + self.set_progress_bar(pb.clone()); + + pb + } + + /// Registeres a progress bar on the log handler + pub fn set_progress_bar(&self, pb: Arc) { + self.set_output_type(OutputType::Progress(pb)) + } + + /// Sets the output type of the log handler to either stdout/stderr or a progress bar + pub fn set_output_type(&self, output: OutputType) { + (*self.output_type.write()) = output; + } + + pub fn set_uwu_enabled(&self, enabled: bool) { + self.uwu_enabled + .store(enabled, std::sync::atomic::Ordering::Relaxed); + } + + pub(crate) fn is_loggable(&self, level: Verbosity) -> bool { + (*self.level.read()) >= level + } + + fn preformat_msg(&self, msg: String) -> String { + let msg = self.apply_uwu(msg); + let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2) + .subsequent_indent(" "); + + textwrap::wrap(&msg, opts).join("\n") + } + + fn apply_uwu(&self, msg: String) -> String { + if self.uwu_enabled.load(std::sync::atomic::Ordering::Relaxed) { + uwu!(msg) + } else { + msg + } + } + + fn log(&self, msg: String) { + let output_type = self.output_type.read(); + match &*output_type { + OutputType::Stdout => println!("{}", msg), + OutputType::Stderr => eprintln!("{}", msg), + OutputType::MultiProgress(m) => { + let _ = m.println(msg); + } + OutputType::Progress(p) => p.println(msg), + }; + } +} diff --git a/src/logging/mod.rs b/src/logging/mod.rs new file mode 100644 index 0000000..b93210e --- /dev/null +++ b/src/logging/mod.rs @@ -0,0 +1,69 @@ +use std::sync::Arc; + +use lazy_static::lazy_static; +use tracing::Level; +use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; +use tracing_subscriber::Registry; + +mod fmt_layer; +use fmt_layer::AmeFormatLayer; + +use crate::internal::uwu_enabled; + +use self::handler::LogHandler; +pub mod handler; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Verbosity { + #[allow(dead_code)] + Error = 0, + #[allow(dead_code)] + Warning = 1, + Info = 2, + Debug = 3, + Trace = 4, +} + +impl From for Verbosity { + fn from(num_verbosity: usize) -> Self { + match num_verbosity { + 0 => Self::Info, + 1 => Self::Debug, + 2 => Self::Trace, + _ => Self::Info, + } + } +} + +impl Verbosity { + fn from_level(l: &Level) -> Self { + match *l { + Level::ERROR => Self::Error, + Level::WARN => Self::Warning, + Level::INFO => Self::Info, + Level::DEBUG => Self::Debug, + Level::TRACE => Self::Trace, + } + } +} + +/// Initializes the tracing logger +/// Can be used for debug purposes _or_ verbose output +pub fn init_logger(verbosity: Verbosity) { + let logger = get_logger(); + logger.set_verbosity(verbosity); + logger.set_uwu_enabled(uwu_enabled()); + let ame_layer = AmeFormatLayer::new(logger); + + let subscriber = Registry::default().with(ame_layer); + tracing::subscriber::set_global_default(subscriber).unwrap(); +} + +/// Returns the global logger instance +pub fn get_logger() -> Arc { + lazy_static! { + static ref LOGGER: Arc = Arc::new(LogHandler::default()); + } + + Arc::clone(&LOGGER) +} diff --git a/src/main.rs b/src/main.rs index 33c097c..80366a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,34 +10,32 @@ use crate::internal::exit_code::AppExitCode; use crate::internal::{init, sort, start_sudoloop, structs::Options}; use clap_complete::{Generator, Shell}; use std::str::FromStr; -use tracing_subscriber::fmt::format::FmtSpan; -use tracing_subscriber::EnvFilter; mod args; mod builder; mod internal; +mod logging; mod operations; +use logging::init_logger; -#[tokio::main(flavor = "current_thread")] +#[tokio::main] async fn main() { color_eyre::install().unwrap(); if unsafe { libc::geteuid() } == 0 { crash!( AppExitCode::RunAsRoot, "Running amethyst as root is disallowed as it can lead to system breakage. Instead, amethyst will prompt you when it needs superuser permissions"); } - init_logger(); let args: Args = Args::parse(); + init_logger(args.verbose.into()); - let verbosity = args.verbose; let noconfirm = args.no_confirm; let options = Options { - verbosity, noconfirm, asdeps: false, }; - init(options); + init(); if args.sudoloop { start_sudoloop().await; @@ -49,11 +47,11 @@ async fn main() { Operation::Search(search_args) => cmd_search(search_args, options).await, Operation::Query(query_args) => cmd_query(query_args).await, Operation::Upgrade(upgrade_args) => { - info!("Performing system upgrade"); + tracing::info!("Performing system upgrade"); operations::upgrade(upgrade_args, options).await; } Operation::Clean => { - info!("Removing orphaned packages"); + tracing::info!("Removing orphaned packages"); operations::clean(options).await; } Operation::Info(info_args) => cmd_info(info_args).await, @@ -64,21 +62,6 @@ async fn main() { detect().await; } -/// Initializes the tracing logger -/// Can be used for debug purposes _or_ verbose output -fn init_logger() { - const DEFAULT_ENV_FILTER: &str = "warn"; - let filter_string = std::env::var("AME_LOG").unwrap_or_else(|_| DEFAULT_ENV_FILTER.to_string()); - let env_filter = - EnvFilter::from_str(&*filter_string).expect("failed to parse env filter string"); - tracing_subscriber::fmt::SubscriberBuilder::default() - .with_env_filter(env_filter) - .with_writer(std::io::stdout) - .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) - .compact() - .init(); -} - #[tracing::instrument(level = "trace")] async fn cmd_install(args: InstallArgs, options: Options) { let packages = args.packages; @@ -113,14 +96,14 @@ async fn cmd_install(args: InstallArgs, options: Options) { .split_whitespace() .collect::>() .join(", "); - info!("You have .pacnew files in /etc ({pacnew_files}) that you haven't removed or acted upon, it is recommended you do that now" ); + tracing::info!("You have .pacnew files in /etc ({pacnew_files}) that you haven't removed or acted upon, it is recommended you do that now" ); } } #[tracing::instrument(level = "trace")] async fn cmd_remove(args: RemoveArgs, options: Options) { let packages = args.packages; - info!("Uninstalling packages: {}", &packages.join(", ")); + tracing::info!("Uninstalling packages: {}", &packages.join(", ")); operations::uninstall(packages, options).await; } @@ -129,16 +112,16 @@ async fn cmd_search(args: SearchArgs, options: Options) { let query_string = args.search; if args.aur { - info!("Searching AUR for {}", &query_string); + tracing::info!("Searching AUR for {}", &query_string); operations::aur_search(&query_string, args.by, options).await; } if args.repo { - info!("Searching repos for {}", &query_string); + tracing::info!("Searching repos for {}", &query_string); operations::search(&query_string, options).await; } if !args.aur && !args.repo { - info!("Searching AUR and repos for {}", &query_string); + tracing::info!("Searching AUR and repos for {}", &query_string); operations::search(&query_string, options).await; operations::aur_search(&query_string, args.by, options).await; } diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index d582479..5fa719b 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -9,7 +9,7 @@ use crate::internal::commands::ShellCommand; use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcinfo; -use crate::{crash, info, internal::fs_utils::rmdir_recursive, log, prompt, Options}; +use crate::{crash, internal::fs_utils::rmdir_recursive, prompt, Options}; /// Installs a given list of packages from the aur #[tracing::instrument(level = "trace")] @@ -17,14 +17,11 @@ use crate::{crash, info, internal::fs_utils::rmdir_recursive, log, prompt, Optio pub async fn aur_install(packages: Vec, options: Options) { let url = crate::internal::rpc::URL; let cachedir = format!("{}/.cache/ame/", env::var("HOME").unwrap()); - let verbosity = options.verbosity; let noconfirm = options.noconfirm; - if verbosity >= 1 { - log!("Installing from AUR: {:?}", &packages); - } + tracing::debug!("Installing from AUR: {:?}", &packages); - info!("Installing packages {} from the AUR", packages.join(", ")); + tracing::info!("Installing packages {} from the AUR", packages.join(", ")); for package_name in packages { let rpcres = rpcinfo(&package_name) @@ -38,11 +35,9 @@ pub async fn aur_install(packages: Vec, options: Options) { let package = rpcres.unwrap(); let pkg_name = package.metadata.name; - if verbosity >= 1 { - log!("Cloning {} into cachedir", pkg_name); - } + tracing::debug!("Cloning {} into cachedir", pkg_name); - info!("Cloning package source"); + tracing::info!("Cloning package source"); set_current_dir(Path::new(&cachedir)).unwrap(); ShellCommand::git() @@ -52,36 +47,31 @@ pub async fn aur_install(packages: Vec, options: Options) { .await .silent_unwrap(AppExitCode::GitError); - if verbosity >= 1 { - log!( - "Cloned {} into cachedir, moving on to resolving dependencies", - pkg_name - ); - log!( - "Raw dependencies for package {} are:\n{:?}", - pkg_name, - package.depends, - ); - log!( - "Raw makedepends for package {} are:\n{:?}", - pkg_name, - package.make_depends.join(", "), - ); - } + tracing::debug!( + "Cloned {} into cachedir, moving on to resolving dependencies", + pkg_name + ); + tracing::debug!( + "Raw dependencies for package {} are:\n{:?}", + pkg_name, + package.depends, + ); + tracing::debug!( + "Raw makedepends for package {} are:\n{:?}", + pkg_name, + package.make_depends.join(", "), + ); // dep sorting - log!("Sorting dependencies"); + tracing::debug!("Sorting dependencies"); let sorted = crate::internal::sort(&package.depends, options).await; - log!("Sorting make dependencies"); + tracing::debug!("Sorting make dependencies"); let md_sorted = crate::internal::sort(&package.make_depends, options).await; - if verbosity >= 1 { - log!("Sorted dependencies for {} are:\n{:?}", pkg_name, &sorted); - log!("Sorted makedepends for {} are:\n{:?}", pkg_name, &md_sorted); - } + tracing::debug!("Sorted dependencies for {} are:\n{:?}", pkg_name, &sorted); + tracing::debug!("Sorted makedepends for {} are:\n{:?}", pkg_name, &md_sorted); let newopts = Options { - verbosity, noconfirm, asdeps: true, }; @@ -96,7 +86,7 @@ pub async fn aur_install(packages: Vec, options: Options) { } if !noconfirm { - let p1 = prompt!(default false, + let p1 = prompt!(default no, "Would you like to review {}'s PKGBUILD (and any .install files if present)?", pkg_name, ); @@ -126,7 +116,7 @@ pub async fn aur_install(packages: Vec, options: Options) { .silent_unwrap(AppExitCode::Other); } - let p2 = prompt!(default true, "Would you still like to install {}?", pkg_name); + let p2 = prompt!(default yes, "Would you still like to install {}?", pkg_name); if !p2 { fs::remove_dir_all(format!("{}/{}", cachedir, pkg_name)) .await @@ -137,7 +127,7 @@ pub async fn aur_install(packages: Vec, options: Options) { } // dep installing - info!("Moving on to install dependencies"); + tracing::info!("Moving on to install dependencies"); if !sorted.repo.is_empty() { crate::operations::install(sorted.repo, newopts).await; @@ -157,7 +147,7 @@ pub async fn aur_install(packages: Vec, options: Options) { } // package building and installing - info!("Building time!"); + tracing::info!("Building time!"); set_current_dir(format!("{}/{}", cachedir, pkg_name)).unwrap(); let status = ShellCommand::makepkg() .args(makepkg_args) diff --git a/src/operations/clean.rs b/src/operations/clean.rs index 82af6c1..4444ead 100644 --- a/src/operations/clean.rs +++ b/src/operations/clean.rs @@ -1,18 +1,15 @@ use tokio::process::Command; use crate::crash; -use crate::info; use crate::internal::commands::ShellCommand; use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; -use crate::log; use crate::prompt; use crate::Options; /// Removes orphaned packages and cache #[tracing::instrument(level = "trace")] pub async fn clean(options: Options) { - let verbosity = options.verbosity; let noconfirm = options.noconfirm; // Check for orphaned packages @@ -24,17 +21,17 @@ pub async fn clean(options: Options) { if orphaned_packages.stdout.as_str().is_empty() { // If no orphaned packages found, do nothing - info!("No orphaned packages found"); + tracing::info!("No orphaned packages found"); } else { // Prompt users whether to remove orphaned packages - info!( + tracing::info!( "Removing orphans would uninstall the following packages: \n{}", &orphaned_packages.stdout ); - let cont = prompt!(default false, "Continue?"); + let cont = prompt!(default no, "Continue?"); if !cont { // If user doesn't want to continue, break - info!("Exiting"); + tracing::info!("Exiting"); std::process::exit(AppExitCode::PacmanError as i32); } @@ -52,9 +49,7 @@ pub async fn clean(options: Options) { } } - if verbosity >= 1 { - log!("Removing orphans: {:?}", orphaned_packages_vec); - } + tracing::debug!("Removing orphans: {:?}", orphaned_packages_vec); // Remove orphaned packages let pacman_result = ShellCommand::pacman() @@ -66,7 +61,7 @@ pub async fn clean(options: Options) { if pacman_result.success() { // If pacman succeeded, notify user - info!("Successfully removed orphans"); + tracing::info!("Successfully removed orphans"); } else { // If pacman failed, crash crash!(AppExitCode::PacmanError, "Failed to remove orphans",); @@ -74,7 +69,7 @@ pub async fn clean(options: Options) { } // Prompt the user whether to clear the Amethyst cache - let clear_ame_cache = prompt!(default false, "Clear Amethyst's internal PKGBUILD cache?"); + let clear_ame_cache = prompt!(default no, "Clear Amethyst's internal PKGBUILD cache?"); if clear_ame_cache { // Remove ~/.cache/ame Command::new("rm") @@ -91,7 +86,7 @@ pub async fn clean(options: Options) { let clear_pacman_cache = if noconfirm { true } else { - prompt!(default false, "Also clear pacman's package cache?") + prompt!(default no, "Also clear pacman's package cache?") }; if clear_pacman_cache { @@ -107,9 +102,7 @@ pub async fn clean(options: Options) { paccache_args.push("--noconfirm"); } - if verbosity >= 1 { - log!("Clearing using `paccache -r`"); - } + tracing::debug!("Clearing using `paccache -r`"); // Clear pacman's cache (keeping latest 3 versions of installed packages) Command::new("sudo") @@ -127,9 +120,7 @@ pub async fn clean(options: Options) { .await .unwrap(); - if verbosity >= 1 { - log!("Clearing using `pacman -Sc`"); - } + tracing::debug!("Clearing using `pacman -Sc`"); // Clear pacman's cache (keeping only installed packages) let pacman_result = ShellCommand::pacman() @@ -141,7 +132,7 @@ pub async fn clean(options: Options) { if pacman_result.success() { // If pacman succeeded, notify user - info!("Successfully cleared package cache"); + tracing::info!("Successfully cleared package cache"); } else { // If pacman failed, crash crash!(AppExitCode::PacmanError, "Failed to clear package cache",); diff --git a/src/operations/install.rs b/src/operations/install.rs index 1123cbc..2e21039 100644 --- a/src/operations/install.rs +++ b/src/operations/install.rs @@ -1,16 +1,13 @@ use crate::builder::pacman::PacmanInstallBuilder; use crate::internal::exit_code::AppExitCode; -use crate::{crash, info, log, Options}; +use crate::{crash, Options}; #[tracing::instrument(level = "trace")] pub async fn install(packages: Vec, options: Options) { - info!("Installing packages {} from repos", &packages.join(", ")); - let verbosity = options.verbosity; + tracing::info!("Installing packages {} from repos", &packages.join(", ")); if !packages.is_empty() { - if verbosity >= 1 { - log!("Installing from repos: {:?}", &packages); - } + tracing::debug!("Installing from repos: {:?}", &packages); let result = PacmanInstallBuilder::from_options(options) .packages(packages.clone()) @@ -25,8 +22,6 @@ pub async fn install(packages: Vec, options: Options) { ); } - if verbosity >= 1 { - log!("Installing packages: {:?} was successful", &packages); - } + tracing::debug!("Installing packages: {:?} was successful", &packages); } } diff --git a/src/operations/search.rs b/src/operations/search.rs index e51ad45..4165533 100644 --- a/src/operations/search.rs +++ b/src/operations/search.rs @@ -4,12 +4,11 @@ use crate::internal::commands::ShellCommand; use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcsearch; -use crate::{log, Options}; +use crate::Options; use aur_rpc::SearchField; #[tracing::instrument(level = "trace")] pub async fn aur_search(query: &str, by_field: Option, options: Options) { - let verbosity = options.verbosity; let packages = rpcsearch(query.to_string(), by_field.map(SearchBy::into)) .await .silent_unwrap(AppExitCode::RpcError); @@ -22,14 +21,11 @@ pub async fn aur_search(query: &str, by_field: Option, options: Option ) } - if verbosity >= 1 { - log!("Found {total_results} resuls for \"{query}\" in AUR",); - } + tracing::debug!("Found {total_results} resuls for \"{query}\" in AUR",); } #[tracing::instrument(level = "trace")] pub async fn repo_search(query: &str, options: Options) { - let verbosity = options.verbosity; let output = ShellCommand::pacman() .arg("-Ss") .arg(query) @@ -38,13 +34,11 @@ pub async fn repo_search(query: &str, options: Options) { .silent_unwrap(AppExitCode::PacmanError) .stdout; - if verbosity >= 1 { - log!( - "Found {} results for \"{}\" in repos", - &output.split('\n').count() / 2, - &query - ); - } + tracing::debug!( + "Found {} results for \"{}\" in repos", + &output.split('\n').count() / 2, + &query + ); println!("{}", output) } diff --git a/src/operations/uninstall.rs b/src/operations/uninstall.rs index 24029ee..fe89e3d 100644 --- a/src/operations/uninstall.rs +++ b/src/operations/uninstall.rs @@ -5,7 +5,7 @@ use tokio::fs; use crate::internal::commands::ShellCommand; use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; -use crate::{log, Options}; +use crate::Options; #[tracing::instrument(level = "trace")] pub async fn uninstall(packages: Vec, options: Options) { @@ -15,10 +15,7 @@ pub async fn uninstall(packages: Vec, options: Options) { if options.noconfirm { pacman_args.push("--noconfirm"); } - let verbosity = options.verbosity; - if verbosity >= 1 { - log!("Uninstalling: {:?}", &packages); - } + tracing::debug!("Uninstalling: {:?}", &packages); ShellCommand::pacman() .elevated() @@ -27,9 +24,7 @@ pub async fn uninstall(packages: Vec, options: Options) { .await .silent_unwrap(AppExitCode::PacmanError); - if verbosity >= 1 { - log!("Uninstalling packages: {:?} exited with code 0", &packages); - } + tracing::debug!("Uninstalling packages: {:?} exited with code 0", &packages); for package in packages { if Path::new(&format!( @@ -39,9 +34,7 @@ pub async fn uninstall(packages: Vec, options: Options) { )) .exists() { - if verbosity >= 1 { - log!("Old cache directory found, deleting"); - } + tracing::debug!("Old cache directory found, deleting"); fs::remove_dir_all(Path::new(&format!( "{}/.cache/ame/{}", env::var("HOME").unwrap(), diff --git a/src/operations/upgrade.rs b/src/operations/upgrade.rs index dbc63b5..1dae44c 100644 --- a/src/operations/upgrade.rs +++ b/src/operations/upgrade.rs @@ -5,7 +5,7 @@ use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; use crate::internal::rpc::rpcinfo; use crate::operations::aur_install::aur_install; -use crate::{info, log, prompt, warn, Options}; +use crate::{prompt, Options}; /// Upgrades all installed packages #[tracing::instrument(level = "trace")] @@ -16,11 +16,14 @@ pub async fn upgrade(args: UpgradeArgs, options: Options) { if args.aur { upgrade_aur(options).await; } + if !args.aur && !args.repo { + upgrade_repo(options).await; + upgrade_aur(options).await; + } } #[tracing::instrument(level = "trace")] async fn upgrade_repo(options: Options) { - let verbosity = options.verbosity; let noconfirm = options.noconfirm; let mut pacman_args = vec!["-Syu"]; @@ -28,9 +31,7 @@ async fn upgrade_repo(options: Options) { pacman_args.push("--noconfirm"); } - if verbosity >= 1 { - log!("Upgrading repo packages"); - } + tracing::debug!("Upgrading repo packages"); let pacman_result = ShellCommand::pacman() .elevated() @@ -40,13 +41,13 @@ async fn upgrade_repo(options: Options) { .silent_unwrap(AppExitCode::PacmanError); if pacman_result.success() { - info!("Successfully upgraded repo packages"); + tracing::info!("Successfully upgraded repo packages"); } else { - let continue_upgrading = prompt!(default false, + let continue_upgrading = prompt!(default no, "Failed to upgrade repo packages, continue to upgrading AUR packages?", ); if !continue_upgrading { - info!("Exiting"); + tracing::info!("Exiting"); std::process::exit(AppExitCode::PacmanError as i32); } } @@ -54,11 +55,7 @@ async fn upgrade_repo(options: Options) { #[tracing::instrument(level = "trace")] async fn upgrade_aur(options: Options) { - let verbosity = options.verbosity; - - if verbosity >= 1 { - log!("Upgrading AUR packages"); - } + tracing::debug!("Upgrading AUR packages"); let non_native_pkgs = PacmanQueryBuilder::foreign() .color(PacmanColor::Never) @@ -84,13 +81,13 @@ async fn upgrade_aur(options: Options) { aur_upgrades.push(pkg.name); } } else { - warn!("Could not find the remote package for {}", pkg.name); + tracing::warn!("Could not find the remote package for {}", pkg.name); } } if !aur_upgrades.is_empty() { aur_install(aur_upgrades, options).await; } else { - info!("No upgrades available for installed AUR packages"); + tracing::info!("No upgrades available for installed AUR packages"); } } From 972a6b026d1168d1d85c3ac913ed35dbd7702f20 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 28 Aug 2022 18:31:11 +0200 Subject: [PATCH 11/43] Remove unused dependencies Signed-off-by: trivernis --- Cargo.lock | 94 ------------------------------------------------------ Cargo.toml | 2 -- 2 files changed, 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8805615..8b0567a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,14 +21,12 @@ dependencies = [ "native-tls", "parking_lot", "regex", - "rusqlite", "serde", "textwrap", "tokio", "toml", "tracing", "tracing-subscriber", - "ureq", ] [[package]] @@ -46,17 +44,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" version = "0.7.18" @@ -166,12 +153,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chunked_transfer" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" - [[package]] name = "clap" version = "3.2.17" @@ -351,18 +332,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - [[package]] name = "fastrand" version = "1.8.0" @@ -498,17 +467,6 @@ dependencies = [ "slab", ] -[[package]] -name = "getrandom" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "gimli" version = "0.26.2" @@ -539,18 +497,6 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashlink" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086" -dependencies = [ - "hashbrown", -] [[package]] name = "heck" @@ -719,16 +665,6 @@ version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" -[[package]] -name = "libsqlite3-sys" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f0455f2c1bc9a7caa792907026e469c1d91761fb0ea37cbb16427c77280cf35" -dependencies = [ - "pkg-config", - "vcpkg", -] - [[package]] name = "lock_api" version = "0.4.7" @@ -1085,20 +1021,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "rusqlite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" -dependencies = [ - "bitflags", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", -] - [[package]] name = "rustc-demangle" version = "0.1.21" @@ -1565,22 +1487,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" -[[package]] -name = "ureq" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f" -dependencies = [ - "base64", - "chunked_transfer", - "log", - "native-tls", - "once_cell", - "serde", - "serde_json", - "url", -] - [[package]] name = "url" version = "2.2.2" diff --git a/Cargo.toml b/Cargo.toml index 6f29b97..e872c59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,9 +27,7 @@ opt-level = 0 [dependencies] clap = { version = "3.2.17", features = [ "derive", "wrap_help" ] } regex = { version = "1.6.0", default-features = false, features = [ "std", "unicode-perl" ] } -rusqlite = { version = "0.28.0", default-features = false } colored = "2.0.0" -ureq = { version = "2.5.0", default-features = false, features = [ "native-tls", "json" ] } serde = { version = "1.0.144", default-features = false, features = [ "derive", "serde_derive" ] } native-tls = { version = "0.2.10", default-features = false } libc = { version = "0.2.132", default-features = false } From 813c06d31dd6ffeeb8e4eebb78608d33d1cf87d0 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 29 Aug 2022 10:27:25 +0200 Subject: [PATCH 12/43] Fix debug container script Signed-off-by: Trivernis --- Containerfile | 1 - run-isolated.sh | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Containerfile b/Containerfile index e617e1f..20cbd38 100644 --- a/Containerfile +++ b/Containerfile @@ -11,7 +11,6 @@ RUN cargo new amethyst WORKDIR /usr/src/amethyst COPY Cargo.toml Cargo.lock ./ RUN mkdir target -COPY target/debug/build ./target/debug/build RUN cargo fetch COPY src ./src RUN cargo build --frozen diff --git a/run-isolated.sh b/run-isolated.sh index 2b79c16..910618b 100755 --- a/run-isolated.sh +++ b/run-isolated.sh @@ -1,4 +1,7 @@ #!/bin/bash -podman build . -t ame-debug \ -&& podman container rm ame-debug \ -&& podman run -i -t --name ame-debug ame-debug +podman build . -t ame-debug + +if [ $? -eq 0 ]; then + podman container exists ame-debug && podman container rm ame-debug + podman run -i -t --name ame-debug ame-debug +fi \ No newline at end of file From 362c2cf6ea81234226523932aa2652b6164eb69d Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 29 Aug 2022 12:10:23 +0200 Subject: [PATCH 13/43] Fix clean function not stripping the package version Signed-off-by: Trivernis --- src/internal/clean.rs | 19 +++++++------------ src/internal/fs_utils.rs | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/internal/clean.rs b/src/internal/clean.rs index 85bcd8e..b37039e 100644 --- a/src/internal/clean.rs +++ b/src/internal/clean.rs @@ -3,18 +3,13 @@ use regex::Regex; /// Strips packages from versioning and other extraneous information. pub fn clean(a: &[String]) -> Vec { // Strip versioning from package names - let r = Regex::new(r"(\S+)((?:>=|<=|>|<|=\W)\S+$)").unwrap(); - let mut cleaned: Vec = vec![]; - - // Push cleaned package names to vector - for b in a { - if r.captures_iter(b).count() > 0 { - let c = r.captures(b).unwrap().get(1).map_or("", |m| m.as_str()); - cleaned.push(c.to_string()); - } else { - cleaned.push(b.to_string()); - } - } + let cleaned = a.iter() + .map(|name| + name + .split_once("=") + .map(|n| n.0.to_string()) + .unwrap_or(name.to_string())) + .collect(); tracing::debug!("Cleaned: {:?}\nInto: {:?}", a, cleaned); diff --git a/src/internal/fs_utils.rs b/src/internal/fs_utils.rs index 2990df4..c011c69 100644 --- a/src/internal/fs_utils.rs +++ b/src/internal/fs_utils.rs @@ -6,7 +6,7 @@ use std::{ use futures::future; use tokio::fs; -#[tracing::instrument(level = "debug")] +#[tracing::instrument(level = "trace")] pub async fn rmdir_recursive(path: &Path) -> std::io::Result<()> { let mut files: Vec = Vec::new(); let mut folders: Vec = Vec::new(); From bef4fbcb02ab2aec8eacea2c9084b14b5d4a3f8c Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 29 Aug 2022 21:01:26 +0200 Subject: [PATCH 14/43] [WIP] Implement parallel aur install Signed-off-by: trivernis --- Cargo.lock | 67 +++++++++ Cargo.toml | 2 + run-isolated.sh | 2 +- src/builder/git.rs | 69 ++++++++++ src/builder/mod.rs | 1 + src/builder/pacman.rs | 27 ++++ src/internal/dependencies.rs | 207 ++++++++++++++++++++++++++++ src/internal/mod.rs | 2 +- src/internal/resolve.rs | 1 - src/internal/utils.rs | 23 ++++ src/logging/handler.rs | 32 ++++- src/operations/aur_install.rs | 253 ++++++++++++++-------------------- 12 files changed, 529 insertions(+), 157 deletions(-) create mode 100644 src/builder/git.rs create mode 100644 src/internal/dependencies.rs delete mode 100644 src/internal/resolve.rs diff --git a/Cargo.lock b/Cargo.lock index 8b0567a..fd16aec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,8 +14,10 @@ dependencies = [ "colored", "crossterm", "dialoguer", + "directories", "futures", "indicatif", + "lazy-regex", "lazy_static", "libc", "native-tls", @@ -307,6 +309,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -467,6 +489,17 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.26.2" @@ -653,6 +686,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy-regex" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b12f2eb6ed7d39405c5eb25a034b4c106a9ad87a6d9be3298de6c5f32fd57d" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2496e5264069bc726ccf37eb76b9cd89406ae110d836c3f76729f99c8a23293" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -949,6 +1005,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regex" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index e872c59..8f8160c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,8 @@ indicatif = { version = "0.17.0", features = ["tokio"] } lazy_static = "1.4.0" parking_lot = { version = "0.12.1", features = ["deadlock_detection"] } dialoguer = "0.10.2" +lazy-regex = "2.3.0" +directories = "4.0.1" [dependencies.tokio] version = "1.20.1" diff --git a/run-isolated.sh b/run-isolated.sh index 910618b..a62c0cb 100755 --- a/run-isolated.sh +++ b/run-isolated.sh @@ -2,6 +2,6 @@ podman build . -t ame-debug if [ $? -eq 0 ]; then - podman container exists ame-debug && podman container rm ame-debug + podman container exists ame-debug && podman container rm -f ame-debug podman run -i -t --name ame-debug ame-debug fi \ No newline at end of file diff --git a/src/builder/git.rs b/src/builder/git.rs new file mode 100644 index 0000000..69b0267 --- /dev/null +++ b/src/builder/git.rs @@ -0,0 +1,69 @@ +use std::path::{Path, PathBuf}; + +use crate::internal::{ + commands::ShellCommand, + error::{AppError, AppResult}, +}; + +#[derive(Debug, Default)] +pub struct GitCloneBuilder { + url: String, + directory: PathBuf, +} + +impl GitCloneBuilder { + pub fn url(mut self, url: S) -> Self { + self.url = url.to_string(); + + self + } + + pub fn directory>(mut self, path: P) -> Self { + self.directory = path.as_ref().into(); + + self + } + + pub async fn clone(self) -> AppResult<()> { + let result = ShellCommand::git() + .arg("clone") + .arg(self.url) + .arg(self.directory) + .wait_with_output() + .await?; + + if result.status.success() { + Ok(()) + } else { + Err(AppError::Other(result.stderr)) + } + } +} + +#[derive(Debug, Default)] +pub struct GitPullBuilder { + directory: PathBuf, +} + +impl GitPullBuilder { + pub fn directory>(mut self, path: P) -> Self { + self.directory = path.as_ref().into(); + + self + } + + pub async fn pull(self) -> AppResult<()> { + let result = ShellCommand::git() + .arg("-C") + .arg(self.directory) + .arg("pull") + .wait_with_output() + .await?; + + if result.status.success() { + Ok(()) + } else { + Err(AppError::Other(result.stderr)) + } + } +} diff --git a/src/builder/mod.rs b/src/builder/mod.rs index f4da7e8..cb71352 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -1 +1,2 @@ +pub mod git; pub mod pacman; diff --git a/src/builder/pacman.rs b/src/builder/pacman.rs index 4a88f3f..f7a48b3 100644 --- a/src/builder/pacman.rs +++ b/src/builder/pacman.rs @@ -160,3 +160,30 @@ pub struct BasicPackageInfo { pub name: String, pub version: String, } + +#[derive(Default)] +pub struct PacmanSearchBuilder { + query: String, +} + +impl PacmanSearchBuilder { + pub fn query>(mut self, query: S) -> Self { + if !self.query.is_empty() { + self.query.push(' '); + } + self.query.push_str(query.as_ref()); + + self + } + + /// Searches and returns if the execution result was ok + pub async fn search(self) -> AppResult { + let result = self.build_command().wait_with_output().await?; + + Ok(result.status.success()) + } + + fn build_command(self) -> ShellCommand { + ShellCommand::pacman().arg("-Ss").arg(self.query) + } +} diff --git a/src/internal/dependencies.rs b/src/internal/dependencies.rs new file mode 100644 index 0000000..e767f82 --- /dev/null +++ b/src/internal/dependencies.rs @@ -0,0 +1,207 @@ +use std::collections::HashSet; + +use aur_rpc::PackageInfo; +use futures::future; + +use crate::builder::pacman::PacmanSearchBuilder; + +use super::error::AppResult; +use lazy_regex::regex; + +#[derive(Clone, Debug)] +pub struct DependencyInformation { + pub depends: DependencyCollection, + pub make_depends: DependencyCollection, +} + +#[derive(Clone, Debug, Default)] +pub struct DependencyCollection { + pub aur: Vec, + pub repo: Vec, + pub not_found: Vec, +} + +#[derive(Clone, Debug)] +pub struct Dependency { + pub name: String, + #[allow(unused)] + pub condition: Option, + #[allow(unused)] + pub version: Option, +} + +#[derive(Clone, Debug)] +pub enum Condition { + Gt, + Ge, + Eq, + Le, + Lt, +} + +impl Condition { + pub fn try_from_str(s: &str) -> Option { + match s { + "=" => Some(Self::Eq), + "<=" => Some(Self::Le), + ">=" => Some(Self::Ge), + ">" => Some(Self::Gt), + "<" => Some(Self::Lt), + _ => None, + } + } +} + +impl DependencyInformation { + /// Resolves all dependency information for a given package + #[tracing::instrument(level = "trace")] + pub async fn for_package(package: &PackageInfo) -> AppResult { + let make_depends = Self::resolve_make_depends(package).await?; + let depends = Self::resolve_depends(package).await?; + + Ok(Self { + depends, + make_depends, + }) + } + + /// Resolves all make dependencies for a package + #[tracing::instrument(level = "trace")] + async fn resolve_make_depends(package: &PackageInfo) -> AppResult { + let mut packages_to_resolve: HashSet = package + .make_depends + .iter() + .filter_map(Self::map_dep_to_name) + .collect(); + let mut already_searched = HashSet::new(); + let mut dependencies = DependencyCollection::default(); + + while !packages_to_resolve.is_empty() { + already_searched.extend(packages_to_resolve.iter().cloned()); + Self::extend_by_repo_packages(&mut packages_to_resolve, &mut dependencies).await?; + + let mut aur_packages = aur_rpc::info(&packages_to_resolve).await?; + aur_packages.iter().for_each(|p| { + packages_to_resolve.remove(&p.metadata.name); + }); + let not_found = std::mem::take(&mut packages_to_resolve); + + dependencies + .not_found + .append(&mut not_found.into_iter().collect()); + + packages_to_resolve = Self::get_filtered_make_depends(&aur_packages, &already_searched); + dependencies.aur.append(&mut aur_packages); + } + + Ok(dependencies) + } + + /// Resolves all dependencies for a package + #[tracing::instrument(level = "trace")] + async fn resolve_depends(package: &PackageInfo) -> AppResult { + let mut packages_to_resolve: HashSet = package + .depends + .iter() + .filter_map(Self::map_dep_to_name) + .collect(); + let mut already_searched = HashSet::new(); + let mut dependencies = DependencyCollection::default(); + + while !packages_to_resolve.is_empty() { + already_searched.extend(packages_to_resolve.iter().cloned()); + Self::extend_by_repo_packages(&mut packages_to_resolve, &mut dependencies).await?; + + let mut aur_packages = aur_rpc::info(&packages_to_resolve).await?; + aur_packages.iter().for_each(|p| { + packages_to_resolve.remove(&p.metadata.name); + }); + let not_found = std::mem::take(&mut packages_to_resolve); + + dependencies + .not_found + .append(&mut not_found.into_iter().collect()); + + packages_to_resolve = Self::get_filtered_depends(&aur_packages, &already_searched); + dependencies.aur.append(&mut aur_packages); + } + + Ok(dependencies) + } + + async fn extend_by_repo_packages( + to_resolve: &mut HashSet, + dependencies: &mut DependencyCollection, + ) -> AppResult<()> { + let repo_deps = Self::find_repo_packages(to_resolve.clone()).await?; + to_resolve.retain(|p| !repo_deps.contains(p)); + dependencies + .repo + .append(&mut repo_deps.into_iter().collect()); + + Ok(()) + } + + fn get_filtered_make_depends( + aur_packages: &Vec, + searched: &HashSet, + ) -> HashSet { + aur_packages + .iter() + .flat_map(|p| p.make_depends.iter().filter_map(Self::map_dep_to_name)) + .filter(|d| !searched.contains(d)) + .collect() + } + + fn get_filtered_depends( + aur_packages: &Vec, + searched: &HashSet, + ) -> HashSet { + aur_packages + .iter() + .flat_map(|p| p.depends.iter().filter_map(Self::map_dep_to_name)) + .filter(|d| !searched.contains(d)) + .collect() + } + + fn map_dep_to_name(dep: &String) -> Option { + Dependency::try_from_str(dep).map(|d| d.name) + } + + #[tracing::instrument(level = "trace")] + async fn find_repo_packages(pkg_names: HashSet) -> AppResult> { + let repo_searches = pkg_names.iter().cloned().map(|p| async { + let search_result = PacmanSearchBuilder::default().query(&p).search().await?; + AppResult::Ok((p, search_result)) + }); + let repo_deps = future::try_join_all(repo_searches).await?; + let repo_deps: HashSet = repo_deps + .into_iter() + .filter_map(|(p, found)| if found { Some(p) } else { None }) + .collect(); + + Ok(repo_deps) + } +} + +impl Dependency { + #[tracing::instrument(level = "trace")] + pub fn try_from_str(s: &str) -> Option { + let r = + regex!(r#"^(?P[\w\-]+)((?P<=|=|>=|>|<)(?P\d+(\.\d+)*))?$"#); + let caps = r.captures(s)?; + let name = caps["name"].to_string(); + let condition = caps + .name("condition") + .map(|c| c.as_str()) + .and_then(Condition::try_from_str); + let version = caps.name("version").map(|v| v.as_str().into()); + tracing::debug!("Parsed dependency to {name} {condition:?} {version:?}"); + + Some(Dependency { + name, + condition, + version, + }) + } +} diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 18d9952..2e63968 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -10,12 +10,12 @@ pub use sudoloop::*; mod clean; pub mod commands; pub mod config; +pub mod dependencies; mod detect; pub mod error; pub mod exit_code; pub mod fs_utils; mod initialise; -pub mod resolve; pub mod rpc; mod sort; pub mod structs; diff --git a/src/internal/resolve.rs b/src/internal/resolve.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/internal/resolve.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/internal/utils.rs b/src/internal/utils.rs index d32e170..3f19d94 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -1,8 +1,13 @@ +use std::fs; +use std::path::Path; use std::process::exit; +use directories::ProjectDirs; + use crate::internal::exit_code::AppExitCode; use crate::logging::get_logger; use crate::logging::handler::PromptDefault; +use lazy_static::lazy_static; #[macro_export] /// Macro for printing a message and destructively exiting @@ -36,3 +41,21 @@ pub fn log_and_crash(msg: String, exit_code: AppExitCode) -> ! { pub fn prompt_yn(question: String, prompt_default: PromptDefault) -> bool { get_logger().prompt(question, prompt_default) } + +pub fn get_cache_dir() -> &'static Path { + let cache_dir = get_directories().cache_dir(); + + if !cache_dir.exists() { + fs::create_dir_all(cache_dir).unwrap(); + } + + cache_dir +} + +fn get_directories() -> &'static ProjectDirs { + lazy_static! { + static ref DIRECTORIES: ProjectDirs = ProjectDirs::from("com", "crystal", "ame").unwrap(); + } + + &*DIRECTORIES +} diff --git a/src/logging/handler.rs b/src/logging/handler.rs index 968ac06..dfdbb66 100644 --- a/src/logging/handler.rs +++ b/src/logging/handler.rs @@ -114,30 +114,50 @@ impl LogHandler { (*self.level.write()) = level; } + #[tracing::instrument(level = "trace", skip_all)] pub fn reset_output_type(&self) { self.set_output_type(OutputType::Stdout); } /// Creates a new progress spinner and registers it on the log handler + #[tracing::instrument(level = "trace", skip_all)] pub fn new_progress_spinner(&self) -> Arc { - let progress_bar = ProgressBar::new_spinner().with_message("Scanning for pacnew files"); - progress_bar.enable_steady_tick(Duration::from_millis(250)); - let pb = Arc::new(progress_bar); - self.set_progress_bar(pb.clone()); + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(Duration::from_millis(250)); - pb + let mut output_type = self.output_type.write(); + + if let OutputType::MultiProgress(mp) = &*output_type { + Arc::new(mp.add(pb.clone())) + } else { + let pb = Arc::new(pb); + *output_type = OutputType::Progress(pb.clone()); + + pb + } + } + + #[tracing::instrument(level = "trace", skip_all)] + pub fn new_multi_progress(&self) -> Arc { + let mp = Arc::new(MultiProgress::new()); + self.set_output_type(OutputType::MultiProgress(mp.clone())); + + mp } /// Registeres a progress bar on the log handler - pub fn set_progress_bar(&self, pb: Arc) { + #[tracing::instrument(level = "trace", skip_all)] + fn set_progress_bar(&self, pb: Arc) { self.set_output_type(OutputType::Progress(pb)) } /// Sets the output type of the log handler to either stdout/stderr or a progress bar + #[tracing::instrument(level = "trace", skip_all)] pub fn set_output_type(&self, output: OutputType) { (*self.output_type.write()) = output; } + #[tracing::instrument(level = "trace", skip_all)] pub fn set_uwu_enabled(&self, enabled: bool) { self.uwu_enabled .store(enabled, std::sync::atomic::Ordering::Relaxed); diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 5fa719b..f273b89 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -1,173 +1,130 @@ use async_recursion::async_recursion; +use aur_rpc::PackageInfo; +use crossterm::style::Stylize; +use futures::future; +use indicatif::ProgressBar; use std::env; use std::env::set_current_dir; use std::path::{Path, PathBuf}; use std::process::Command; +use std::sync::Arc; +use std::time::Duration; use tokio::fs; +use crate::builder::git::{GitCloneBuilder, GitPullBuilder}; use crate::internal::commands::ShellCommand; -use crate::internal::error::SilentUnwrap; +use crate::internal::dependencies::DependencyInformation; +use crate::internal::error::{AppError, AppResult, SilentUnwrap}; use crate::internal::exit_code::AppExitCode; -use crate::internal::rpc::rpcinfo; +use crate::internal::rpc::{self, rpcinfo}; +use crate::internal::utils::get_cache_dir; +use crate::logging::get_logger; use crate::{crash, internal::fs_utils::rmdir_recursive, prompt, Options}; /// Installs a given list of packages from the aur #[tracing::instrument(level = "trace")] #[async_recursion] pub async fn aur_install(packages: Vec, options: Options) { - let url = crate::internal::rpc::URL; - let cachedir = format!("{}/.cache/ame/", env::var("HOME").unwrap()); let noconfirm = options.noconfirm; tracing::debug!("Installing from AUR: {:?}", &packages); tracing::info!("Installing packages {} from the AUR", packages.join(", ")); - for package_name in packages { - let rpcres = rpcinfo(&package_name) - .await - .silent_unwrap(AppExitCode::RpcError); - - if rpcres.is_none() { - break; - } - - let package = rpcres.unwrap(); - let pkg_name = package.metadata.name; - - tracing::debug!("Cloning {} into cachedir", pkg_name); - - tracing::info!("Cloning package source"); - - set_current_dir(Path::new(&cachedir)).unwrap(); - ShellCommand::git() - .arg("clone") - .arg(format!("{}/{}", url, pkg_name)) - .wait() - .await - .silent_unwrap(AppExitCode::GitError); - - tracing::debug!( - "Cloned {} into cachedir, moving on to resolving dependencies", - pkg_name - ); - tracing::debug!( - "Raw dependencies for package {} are:\n{:?}", - pkg_name, - package.depends, - ); - tracing::debug!( - "Raw makedepends for package {} are:\n{:?}", - pkg_name, - package.make_depends.join(", "), + let pb = get_logger().new_progress_spinner(); + pb.set_message("Fetching package information"); + + let package_info = aur_rpc::info(&packages) + .await + .map_err(AppError::from) + .silent_unwrap(AppExitCode::RpcError); + + tracing::debug!("package info = {package_info:?}"); + tokio::time::sleep(Duration::from_secs(1)).await; + + if package_info.len() != packages.len() { + let mut not_found = packages.clone(); + package_info + .iter() + .for_each(|pkg| not_found.retain(|p| pkg.metadata.name != *p)); + crash!( + AppExitCode::MissingDeps, + "Could not find the package: {}", + not_found.join(",").italic(), ); + } - // dep sorting - tracing::debug!("Sorting dependencies"); - let sorted = crate::internal::sort(&package.depends, options).await; - tracing::debug!("Sorting make dependencies"); - let md_sorted = crate::internal::sort(&package.make_depends, options).await; - - tracing::debug!("Sorted dependencies for {} are:\n{:?}", pkg_name, &sorted); - tracing::debug!("Sorted makedepends for {} are:\n{:?}", pkg_name, &md_sorted); - - let newopts = Options { - noconfirm, - asdeps: true, - }; - - if !sorted.nf.is_empty() || !md_sorted.nf.is_empty() { - crash!( - AppExitCode::MissingDeps, - "Could not find dependencies {} for package {}, aborting", - sorted.nf.join(", "), - pkg_name, - ); - } - - if !noconfirm { - let p1 = prompt!(default no, - "Would you like to review {}'s PKGBUILD (and any .install files if present)?", - pkg_name, - ); - let editor: &str = &env::var("PAGER").unwrap_or_else(|_| "less".parse().unwrap()); - - if p1 { - Command::new(editor) - .arg(format!("{}/PKGBUILD", pkg_name)) - .spawn() - .unwrap() - .wait() - .unwrap(); - - let status = ShellCommand::bash() - .arg("-c") - .arg(format!("ls {}/*.install &> /dev/null", pkg_name)) - .wait() - .await - .silent_unwrap(AppExitCode::Other); - - if status.success() { - ShellCommand::bash() - .arg("-c") - .arg(format!("{} {}/*.install", editor, pkg_name)) - .wait() - .await - .silent_unwrap(AppExitCode::Other); - } - - let p2 = prompt!(default yes, "Would you still like to install {}?", pkg_name); - if !p2 { - fs::remove_dir_all(format!("{}/{}", cachedir, pkg_name)) - .await - .unwrap(); - crash!(AppExitCode::UserCancellation, "Not proceeding"); - } - } - } - - // dep installing - tracing::info!("Moving on to install dependencies"); - - if !sorted.repo.is_empty() { - crate::operations::install(sorted.repo, newopts).await; - crate::operations::install(md_sorted.repo, newopts).await; - } - if !sorted.aur.is_empty() { - crate::operations::aur_install(sorted.aur, newopts).await; - crate::operations::aur_install(md_sorted.aur, newopts).await; - } - - let mut makepkg_args = vec!["-rsci", "--skippgp"]; - if options.asdeps { - makepkg_args.push("--asdeps") - } - if options.noconfirm { - makepkg_args.push("--noconfirm") - } - - // package building and installing - tracing::info!("Building time!"); - set_current_dir(format!("{}/{}", cachedir, pkg_name)).unwrap(); - let status = ShellCommand::makepkg() - .args(makepkg_args) - .wait() - .await - .silent_unwrap(AppExitCode::MakePkgError); - - if !status.success() { - fs::remove_dir_all(format!("{}/{}", cachedir, pkg_name)) - .await - .unwrap(); - crash!( - AppExitCode::PacmanError, - "Error encountered while installing {}, aborting", - pkg_name, - ); - } - - set_current_dir(&cachedir).unwrap(); - let package_cache = PathBuf::from(format!("{cachedir}/{pkg_name}")); - rmdir_recursive(&package_cache).await.unwrap() + pb.finish_with_message("Found all packages in the aur"); + + get_logger().new_multi_progress(); + + future::try_join_all(package_info.iter().map(download_aur_source)) + .await + .unwrap(); + tokio::time::sleep(Duration::from_secs(1)).await; + + let dependencies = future::try_join_all(package_info.iter().map(|pkg| async { + get_logger() + .new_progress_spinner() + .set_message(format!("{}: Fetching dependencies", pkg.metadata.name)); + DependencyInformation::for_package(pkg).await + })) + .await + .silent_unwrap(AppExitCode::RpcError); + tokio::time::sleep(Duration::from_secs(1)).await; + + let aur_build_dependencies: Vec = dependencies + .iter() + .flat_map(|d| d.make_depends.aur.clone()) + .collect(); + + let aur_dependencies: Vec = dependencies + .iter() + .flat_map(|d| d.depends.aur.clone()) + .collect(); + + get_logger().reset_output_type(); + tracing::info!( + "Installing {} build dependencies", + aur_build_dependencies.len() + ); + get_logger().new_multi_progress(); + + future::try_join_all(aur_build_dependencies.iter().map(download_aur_source)) + .await + .unwrap(); +} + +#[tracing::instrument(level = "trace", skip_all)] +async fn download_aur_source(info: &PackageInfo) -> AppResult { + let pb = get_logger().new_progress_spinner(); + let pkg_name = &info.metadata.name; + pb.set_message(format!("{pkg_name}: Downloading sources")); + + let cache_dir = get_cache_dir(); + let pkg_dir = cache_dir.join(&pkg_name); + tokio::time::sleep(Duration::from_secs(1)).await; + + if pkg_dir.exists() { + pb.set_message(format!("{pkg_name}: Pulling latest changes {pkg_name}")); + GitPullBuilder::default().directory(&pkg_dir).pull().await?; + tokio::time::sleep(Duration::from_secs(1)).await; + } else { + let aur_url = rpc::URL; + let repository_url = format!("{aur_url}/{pkg_name}"); + pb.set_message(format!("{pkg_name}: Cloning aur repository")); + + GitCloneBuilder::default() + .url(repository_url) + .directory(&pkg_dir) + .clone() + .await?; + tokio::time::sleep(Duration::from_secs(1)).await; + + pb.set_message(format!("{pkg_name}: Downloading and extracting files")); } + tokio::time::sleep(Duration::from_secs(1)).await; + pb.finish_with_message(format!("{pkg_name} is ready to build")); + + Ok(pkg_dir) } From 8f294fb6a4697adc6f48dc8e27c80430119966e8 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 29 Aug 2022 21:14:03 +0200 Subject: [PATCH 15/43] [WIP] Add repo dependency install step Signed-off-by: trivernis --- src/operations/aur_install.rs | 42 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index f273b89..286c952 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -2,24 +2,16 @@ use async_recursion::async_recursion; use aur_rpc::PackageInfo; use crossterm::style::Stylize; use futures::future; -use indicatif::ProgressBar; -use std::env; -use std::env::set_current_dir; -use std::path::{Path, PathBuf}; -use std::process::Command; -use std::sync::Arc; -use std::time::Duration; -use tokio::fs; +use std::path::PathBuf; use crate::builder::git::{GitCloneBuilder, GitPullBuilder}; -use crate::internal::commands::ShellCommand; +use crate::builder::pacman::PacmanInstallBuilder; use crate::internal::dependencies::DependencyInformation; use crate::internal::error::{AppError, AppResult, SilentUnwrap}; use crate::internal::exit_code::AppExitCode; -use crate::internal::rpc::{self, rpcinfo}; use crate::internal::utils::get_cache_dir; use crate::logging::get_logger; -use crate::{crash, internal::fs_utils::rmdir_recursive, prompt, Options}; +use crate::{crash, Options}; /// Installs a given list of packages from the aur #[tracing::instrument(level = "trace")] @@ -40,7 +32,6 @@ pub async fn aur_install(packages: Vec, options: Options) { .silent_unwrap(AppExitCode::RpcError); tracing::debug!("package info = {package_info:?}"); - tokio::time::sleep(Duration::from_secs(1)).await; if package_info.len() != packages.len() { let mut not_found = packages.clone(); @@ -61,7 +52,10 @@ pub async fn aur_install(packages: Vec, options: Options) { future::try_join_all(package_info.iter().map(download_aur_source)) .await .unwrap(); - tokio::time::sleep(Duration::from_secs(1)).await; + + get_logger().reset_output_type(); + tracing::info!("All sources are ready."); + get_logger().new_multi_progress(); let dependencies = future::try_join_all(package_info.iter().map(|pkg| async { get_logger() @@ -71,21 +65,29 @@ pub async fn aur_install(packages: Vec, options: Options) { })) .await .silent_unwrap(AppExitCode::RpcError); - tokio::time::sleep(Duration::from_secs(1)).await; let aur_build_dependencies: Vec = dependencies .iter() .flat_map(|d| d.make_depends.aur.clone()) .collect(); - let aur_dependencies: Vec = dependencies + let repo_build_dependencies: Vec = dependencies .iter() - .flat_map(|d| d.depends.aur.clone()) + .flat_map(|d| d.make_depends.repo.clone()) .collect(); get_logger().reset_output_type(); + + tracing::info!("Installing repo build dependencies"); + PacmanInstallBuilder::default() + .as_deps(true) + .packages(repo_build_dependencies) + .install() + .await + .silent_unwrap(AppExitCode::PacmanError); + tracing::info!( - "Installing {} build dependencies", + "Installing {} build dependencies from the aur", aur_build_dependencies.len() ); get_logger().new_multi_progress(); @@ -103,14 +105,12 @@ async fn download_aur_source(info: &PackageInfo) -> AppResult { let cache_dir = get_cache_dir(); let pkg_dir = cache_dir.join(&pkg_name); - tokio::time::sleep(Duration::from_secs(1)).await; if pkg_dir.exists() { pb.set_message(format!("{pkg_name}: Pulling latest changes {pkg_name}")); GitPullBuilder::default().directory(&pkg_dir).pull().await?; - tokio::time::sleep(Duration::from_secs(1)).await; } else { - let aur_url = rpc::URL; + let aur_url = crate::internal::rpc::URL; let repository_url = format!("{aur_url}/{pkg_name}"); pb.set_message(format!("{pkg_name}: Cloning aur repository")); @@ -119,11 +119,9 @@ async fn download_aur_source(info: &PackageInfo) -> AppResult { .directory(&pkg_dir) .clone() .await?; - tokio::time::sleep(Duration::from_secs(1)).await; pb.set_message(format!("{pkg_name}: Downloading and extracting files")); } - tokio::time::sleep(Duration::from_secs(1)).await; pb.finish_with_message(format!("{pkg_name} is ready to build")); Ok(pkg_dir) From e0f58e1aba37b964b5e46c9c9717f1e4a1b3339d Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 30 Aug 2022 21:49:03 +0200 Subject: [PATCH 16/43] [WIP] Parallel builds and installs Signed-off-by: trivernis --- src/builder/makepkg.rs | 134 ++++++++++++++++++++ src/builder/mod.rs | 1 + src/builder/pacman.rs | 25 +++- src/internal/commands.rs | 36 ++++-- src/internal/error.rs | 2 + src/operations/aur_install.rs | 229 +++++++++++++++++++++++++++++----- 6 files changed, 385 insertions(+), 42 deletions(-) create mode 100644 src/builder/makepkg.rs diff --git a/src/builder/makepkg.rs b/src/builder/makepkg.rs new file mode 100644 index 0000000..9bee86d --- /dev/null +++ b/src/builder/makepkg.rs @@ -0,0 +1,134 @@ +use std::fmt::Debug; +use std::path::{Path, PathBuf}; + +use crate::internal::{ + commands::ShellCommand, + error::{AppError, AppResult}, +}; + +#[derive(Default, Debug, Clone)] +pub struct MakePkgBuilder { + directory: PathBuf, + clean: bool, + no_deps: bool, + install: bool, + no_build: bool, + no_confirm: bool, + as_deps: bool, + skip_pgp: bool, + needed: bool, + no_prepare: bool, +} + +impl MakePkgBuilder { + /// Sets the working directory + pub fn directory>(mut self, dir: D) -> Self { + self.directory = dir.as_ref().into(); + + self + } + + pub fn clean(mut self, clean: bool) -> Self { + self.clean = clean; + + self + } + + pub fn no_deps(mut self, no_deps: bool) -> Self { + self.no_deps = no_deps; + + self + } + + pub fn no_build(mut self, no_build: bool) -> Self { + self.no_build = no_build; + + self + } + + pub fn no_prepare(mut self, no_prepare: bool) -> Self { + self.no_prepare = no_prepare; + + self + } + + /// Mark packages as non-explicitly installed + pub fn as_deps(mut self, as_deps: bool) -> Self { + self.as_deps = as_deps; + + self + } + + /// Skip PGP signature checks + pub fn skip_pgp(mut self, skip: bool) -> Self { + self.skip_pgp = skip; + + self + } + + /// Do not reinstall up to date packages + pub fn needed(mut self, needed: bool) -> Self { + self.needed = needed; + + self + } + + /// Executes the makepkg command + #[tracing::instrument(level = "trace")] + pub async fn run(self) -> AppResult<()> { + let mut command = ShellCommand::makepkg().working_dir(self.directory); + + if self.clean { + command = command.arg("-c"); + } + if self.no_deps { + command = command.arg("-d") + } + if self.install { + command = command.arg("-c"); + } + if self.no_build { + command = command.arg("-o"); + } + if self.no_confirm { + command = command.arg("--noconfirm") + } + if self.as_deps { + command = command.arg("--asdeps") + } + if self.skip_pgp { + command = command.arg("--skippgp") + } + if self.needed { + command = command.arg("--needed"); + } + if self.no_prepare { + command = command.arg("--noprepare") + } + + let output = command.wait_with_output().await?; + + if output.status.success() { + Ok(()) + } else { + Err(AppError::Other(output.stderr)) + } + } + + #[tracing::instrument(level = "trace")] + pub async fn package_list + Debug>(dir: D) -> AppResult> { + let result = ShellCommand::makepkg() + .working_dir(dir.as_ref()) + .arg("--packagelist") + .wait_with_output() + .await?; + + if result.status.success() { + let packages = result.stdout.lines().map(PathBuf::from).collect(); + + Ok(packages) + } else { + Err(AppError::Other(result.stderr)) + } + } +} diff --git a/src/builder/mod.rs b/src/builder/mod.rs index cb71352..e3233d0 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -1,2 +1,3 @@ pub mod git; +pub mod makepkg; pub mod pacman; diff --git a/src/builder/pacman.rs b/src/builder/pacman.rs index f7a48b3..4822c25 100644 --- a/src/builder/pacman.rs +++ b/src/builder/pacman.rs @@ -1,8 +1,11 @@ +use std::path::{Path, PathBuf}; + use crate::internal::{commands::ShellCommand, error::AppResult, structs::Options}; #[derive(Debug, Default)] pub struct PacmanInstallBuilder { packages: Vec, + files: Vec, as_deps: bool, no_confirm: bool, } @@ -21,6 +24,13 @@ impl PacmanInstallBuilder { self } + pub fn files, T: AsRef>(mut self, files: I) -> Self { + let mut files = files.into_iter().map(|f| f.as_ref().into()).collect(); + self.files.append(&mut files); + + self + } + pub fn no_confirm(mut self, no_confirm: bool) -> Self { self.no_confirm = no_confirm; @@ -36,7 +46,13 @@ impl PacmanInstallBuilder { #[tracing::instrument(level = "debug")] pub async fn install(self) -> AppResult<()> { - let mut command = ShellCommand::pacman().elevated().arg("-S").arg("--needed"); + let mut command = ShellCommand::pacman().elevated(); + + if !self.packages.is_empty() { + command = command.arg("-S"); + } else if !self.files.is_empty() { + command = command.arg("-U"); + } if self.no_confirm { command = command.arg("--noconfirm") @@ -46,7 +62,12 @@ impl PacmanInstallBuilder { command = command.arg("--asdeps") } - command.args(self.packages).wait_success().await + command + .arg("--needed") + .args(self.packages) + .args(self.files) + .wait_success() + .await } } diff --git a/src/internal/commands.rs b/src/internal/commands.rs index b8e86ad..6af8a0a 100644 --- a/src/internal/commands.rs +++ b/src/internal/commands.rs @@ -1,4 +1,5 @@ use std::ffi::{OsStr, OsString}; +use std::path::{Path, PathBuf}; use std::process::{ExitStatus, Stdio}; use tokio::process::{Child, Command}; @@ -17,6 +18,7 @@ pub struct ShellCommand { command: String, args: Vec, elevated: bool, + working_dir: Option, } impl ShellCommand { @@ -55,6 +57,7 @@ impl ShellCommand { command: command.to_string(), args: Vec::new(), elevated: false, + working_dir: None, } } @@ -77,6 +80,12 @@ impl ShellCommand { self } + pub fn working_dir>(mut self, dir: D) -> Self { + self.working_dir = Some(dir.as_ref().into()); + + self + } + /// Runs the command with sudo pub fn elevated(mut self) -> Self { self.elevated = true; @@ -117,25 +126,30 @@ impl ShellCommand { } fn spawn(self, piped: bool) -> AppResult { + tracing::debug!("Running {} {:?}", self.command, self.args); + let (stdout, stderr) = if piped { (Stdio::piped(), Stdio::piped()) } else { (Stdio::inherit(), Stdio::inherit()) }; - let child = if self.elevated { - Command::new("sudo") - .arg(self.command) - .args(self.args) - .stdout(stdout) - .stderr(stderr) - .spawn()? + let mut command = if self.elevated { + let mut cmd = Command::new("sudo"); + cmd.arg(self.command); + + cmd } else { Command::new(self.command) - .args(self.args) - .stdout(stdout) - .stderr(stderr) - .spawn()? }; + if let Some(dir) = self.working_dir { + command.current_dir(dir); + } + + let child = command + .args(self.args) + .stdout(stdout) + .stderr(stderr) + .spawn()?; Ok(child) } diff --git a/src/internal/error.rs b/src/internal/error.rs index 0b0ee81..442ee80 100644 --- a/src/internal/error.rs +++ b/src/internal/error.rs @@ -14,6 +14,7 @@ pub enum AppError { Other(String), Rpc(aur_rpc::error::RPCError), NonZeroExit, + BuildStepViolation, } impl Display for AppError { @@ -23,6 +24,7 @@ impl Display for AppError { AppError::Rpc(e) => Display::fmt(e, f), AppError::Other(s) => Display::fmt(s, f), AppError::NonZeroExit => Display::fmt("exited with non zero code", f), + AppError::BuildStepViolation => Display::fmt("AUR build violated build steps", f), } } } diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 286c952..1d08558 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -2,9 +2,11 @@ use async_recursion::async_recursion; use aur_rpc::PackageInfo; use crossterm::style::Stylize; use futures::future; -use std::path::PathBuf; +use std::collections::HashSet; +use std::path::{Path, PathBuf}; use crate::builder::git::{GitCloneBuilder, GitPullBuilder}; +use crate::builder::makepkg::MakePkgBuilder; use crate::builder::pacman::PacmanInstallBuilder; use crate::internal::dependencies::DependencyInformation; use crate::internal::error::{AppError, AppResult, SilentUnwrap}; @@ -13,6 +15,53 @@ use crate::internal::utils::get_cache_dir; use crate::logging::get_logger; use crate::{crash, Options}; +#[derive(Debug)] +pub struct BuildContext { + pub package: PackageInfo, + pub step: BuildStep, +} + +#[derive(Debug)] +pub enum BuildStep { + Download, + Build(BuildPath), + Install(PackageArchives), + Done, +} + +#[derive(Debug)] +pub struct BuildPath(pub PathBuf); + +#[derive(Debug)] +pub struct PackageArchives(pub Vec); + +impl From for BuildContext { + fn from(package: PackageInfo) -> Self { + Self { + package, + step: BuildStep::Download, + } + } +} + +impl BuildContext { + pub fn build_path(&self) -> AppResult<&Path> { + if let BuildStep::Build(path) = &self.step { + Ok(&path.0) + } else { + Err(AppError::BuildStepViolation) + } + } + + pub fn packages(&self) -> AppResult<&Vec> { + if let BuildStep::Install(pkgs) = &self.step { + Ok(&pkgs.0) + } else { + Err(AppError::BuildStepViolation) + } + } +} + /// Installs a given list of packages from the aur #[tracing::instrument(level = "trace")] #[async_recursion] @@ -45,16 +94,10 @@ pub async fn aur_install(packages: Vec, options: Options) { ); } - pb.finish_with_message("Found all packages in the aur"); - - get_logger().new_multi_progress(); + get_logger().reset_output_type(); - future::try_join_all(package_info.iter().map(download_aur_source)) - .await - .unwrap(); + pb.finish_with_message("Found all packages in the aur"); - get_logger().reset_output_type(); - tracing::info!("All sources are ready."); get_logger().new_multi_progress(); let dependencies = future::try_join_all(package_info.iter().map(|pkg| async { @@ -66,41 +109,118 @@ pub async fn aur_install(packages: Vec, options: Options) { .await .silent_unwrap(AppExitCode::RpcError); + get_logger().new_multi_progress(); + + let contexts = future::try_join_all( + package_info + .into_iter() + .map(BuildContext::from) + .map(download_aur_source), + ) + .await + .unwrap(); + + get_logger().reset_output_type(); + tracing::info!("All sources are ready."); + let aur_build_dependencies: Vec = dependencies .iter() .flat_map(|d| d.make_depends.aur.clone()) .collect(); - let repo_build_dependencies: Vec = dependencies + let repo_dependencies: HashSet = dependencies .iter() - .flat_map(|d| d.make_depends.repo.clone()) + .flat_map(|d| { + let mut repo_deps = d.make_depends.repo.clone(); + repo_deps.append(&mut d.depends.repo.clone()); + + repo_deps + }) .collect(); get_logger().reset_output_type(); - tracing::info!("Installing repo build dependencies"); - PacmanInstallBuilder::default() - .as_deps(true) - .packages(repo_build_dependencies) - .install() - .await - .silent_unwrap(AppExitCode::PacmanError); + if !repo_dependencies.is_empty() { + tracing::info!("Installing repo dependencies"); + PacmanInstallBuilder::default() + .as_deps(true) + .packages(repo_dependencies) + .install() + .await + .silent_unwrap(AppExitCode::PacmanError); + } - tracing::info!( - "Installing {} build dependencies from the aur", - aur_build_dependencies.len() - ); + if !aur_build_dependencies.is_empty() { + tracing::info!( + "Installing {} build dependencies from the aur", + aur_build_dependencies.len() + ); + install_aur_build_dependencies(aur_build_dependencies) + .await + .unwrap(); + } + + tracing::info!("Installing {} packages", contexts.len()); + + build_and_install( + contexts, + MakePkgBuilder::default(), + PacmanInstallBuilder::default(), + ) + .await + .silent_unwrap(AppExitCode::MakePkgError); +} + +async fn install_aur_build_dependencies(deps: Vec) -> AppResult<()> { get_logger().new_multi_progress(); - future::try_join_all(aur_build_dependencies.iter().map(download_aur_source)) - .await - .unwrap(); + let dep_contexts = future::try_join_all( + deps.into_iter() + .map(BuildContext::from) + .map(download_aur_source), + ) + .await?; + + get_logger().reset_output_type(); + + build_and_install( + dep_contexts, + MakePkgBuilder::default().as_deps(true), + PacmanInstallBuilder::default().as_deps(true), + ) + .await?; + + Ok(()) +} + +#[tracing::instrument(level = "trace")] +async fn build_and_install( + ctxs: Vec, + make_opts: MakePkgBuilder, + install_opts: PacmanInstallBuilder, +) -> AppResult<()> { + tracing::info!("Building packages"); + get_logger().new_multi_progress(); + let ctxs = future::try_join_all( + ctxs.into_iter() + .map(|ctx| build_package(ctx, make_opts.clone())), + ) + .await + .silent_unwrap(AppExitCode::MakePkgError); + get_logger().reset_output_type(); + + tracing::info!("Built {} packages", ctxs.len()); + tracing::info!("Installing packages..."); + + install_packages(ctxs, install_opts).await?; + + Ok(()) } #[tracing::instrument(level = "trace", skip_all)] -async fn download_aur_source(info: &PackageInfo) -> AppResult { +async fn download_aur_source(mut ctx: BuildContext) -> AppResult { let pb = get_logger().new_progress_spinner(); - let pkg_name = &info.metadata.name; + let pkg_name = &ctx.package.metadata.name; pb.set_message(format!("{pkg_name}: Downloading sources")); let cache_dir = get_cache_dir(); @@ -121,8 +241,59 @@ async fn download_aur_source(info: &PackageInfo) -> AppResult { .await?; pb.set_message(format!("{pkg_name}: Downloading and extracting files")); + + MakePkgBuilder::default() + .directory(&pkg_dir) + .no_build(true) + .no_deps(true) + .no_prepare(true) + .skip_pgp(true) + .run() + .await?; } - pb.finish_with_message(format!("{pkg_name} is ready to build")); + pb.finish_with_message(format!("{pkg_name}: Downloaded!")); + ctx.step = BuildStep::Build(BuildPath(pkg_dir)); + + Ok(ctx) +} + +async fn build_package( + mut ctx: BuildContext, + make_opts: MakePkgBuilder, +) -> AppResult { + let pb = get_logger().new_progress_spinner(); + let pkg_name = &ctx.package.metadata.name; + let build_path = ctx.build_path()?; + pb.set_message(format!("{pkg_name}: Building Package")); + + make_opts + .directory(build_path) + .clean(true) + .no_deps(true) + .skip_pgp(true) + .needed(true) + .run() + .await?; + + let packages = MakePkgBuilder::package_list(build_path).await?; + pb.finish_with_message(format!("{pkg_name}: Built!")); + ctx.step = BuildStep::Install(PackageArchives(packages)); + + Ok(ctx) +} + +async fn install_packages( + mut ctxs: Vec, + install_opts: PacmanInstallBuilder, +) -> AppResult> { + let mut packages = Vec::new(); + + for ctx in &mut ctxs { + packages.append(&mut ctx.packages()?.clone()); + ctx.step = BuildStep::Done; + } + + install_opts.files(packages).install().await?; - Ok(pkg_dir) + Ok(ctxs) } From 53cf2125699ad4811150e54e43046b3792e98110 Mon Sep 17 00:00:00 2001 From: trivernis Date: Wed, 31 Aug 2022 21:26:07 +0200 Subject: [PATCH 17/43] [WIP] Fix dependency order and improve build output Signed-off-by: trivernis --- Cargo.lock | 1 + Cargo.toml | 1 + src/builder/makepkg.rs | 26 +++-- src/internal/clean.rs | 13 ++- src/internal/commands.rs | 2 +- src/internal/exit_code.rs | 1 - src/internal/utils.rs | 24 +++++ src/logging/handler.rs | 23 +++- src/logging/mod.rs | 1 + src/logging/output.rs | 75 +++++++++++++ src/operations/aur_install.rs | 191 ++++++++++++++++++++++++++++------ 11 files changed, 305 insertions(+), 53 deletions(-) create mode 100644 src/logging/output.rs diff --git a/Cargo.lock b/Cargo.lock index fd16aec..fbbed4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ dependencies = [ "clap_complete", "color-eyre", "colored", + "console", "crossterm", "dialoguer", "directories", diff --git a/Cargo.toml b/Cargo.toml index 8f8160c..16e5a51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ parking_lot = { version = "0.12.1", features = ["deadlock_detection"] } dialoguer = "0.10.2" lazy-regex = "2.3.0" directories = "4.0.1" +console = "0.15.1" [dependencies.tokio] version = "1.20.1" diff --git a/src/builder/makepkg.rs b/src/builder/makepkg.rs index 9bee86d..e0ee16d 100644 --- a/src/builder/makepkg.rs +++ b/src/builder/makepkg.rs @@ -1,6 +1,8 @@ use std::fmt::Debug; use std::path::{Path, PathBuf}; +use tokio::process::Child; + use crate::internal::{ commands::ShellCommand, error::{AppError, AppResult}, @@ -73,9 +75,23 @@ impl MakePkgBuilder { self } + pub async fn run(self) -> AppResult<()> { + let output = self.build().wait_with_output().await?; + + if output.status.success() { + Ok(()) + } else { + Err(AppError::Other(output.stderr)) + } + } + + pub fn spawn(self) -> AppResult { + self.build().spawn(true) + } + /// Executes the makepkg command #[tracing::instrument(level = "trace")] - pub async fn run(self) -> AppResult<()> { + fn build(self) -> ShellCommand { let mut command = ShellCommand::makepkg().working_dir(self.directory); if self.clean { @@ -106,13 +122,7 @@ impl MakePkgBuilder { command = command.arg("--noprepare") } - let output = command.wait_with_output().await?; - - if output.status.success() { - Ok(()) - } else { - Err(AppError::Other(output.stderr)) - } + command } #[tracing::instrument(level = "trace")] diff --git a/src/internal/clean.rs b/src/internal/clean.rs index b37039e..558f278 100644 --- a/src/internal/clean.rs +++ b/src/internal/clean.rs @@ -1,14 +1,13 @@ -use regex::Regex; - /// Strips packages from versioning and other extraneous information. pub fn clean(a: &[String]) -> Vec { // Strip versioning from package names - let cleaned = a.iter() - .map(|name| - name - .split_once("=") + let cleaned = a + .iter() + .map(|name| { + name.split_once("=") .map(|n| n.0.to_string()) - .unwrap_or(name.to_string())) + .unwrap_or(name.to_string()) + }) .collect(); tracing::debug!("Cleaned: {:?}\nInto: {:?}", a, cleaned); diff --git a/src/internal/commands.rs b/src/internal/commands.rs index 6af8a0a..a93dd7a 100644 --- a/src/internal/commands.rs +++ b/src/internal/commands.rs @@ -125,7 +125,7 @@ impl ShellCommand { }) } - fn spawn(self, piped: bool) -> AppResult { + pub fn spawn(self, piped: bool) -> AppResult { tracing::debug!("Running {} {:?}", self.command, self.args); let (stdout, stderr) = if piped { diff --git a/src/internal/exit_code.rs b/src/internal/exit_code.rs index a6e2ec4..ec7b781 100644 --- a/src/internal/exit_code.rs +++ b/src/internal/exit_code.rs @@ -6,7 +6,6 @@ pub enum AppExitCode { MissingDeps = 3, UserCancellation = 4, PacmanError = 5, - GitError = 6, MakePkgError = 7, RpcError = 9, Other = 63, diff --git a/src/internal/utils.rs b/src/internal/utils.rs index 3f19d94..83b0340 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -3,6 +3,7 @@ use std::path::Path; use std::process::exit; use directories::ProjectDirs; +use textwrap::wrap; use crate::internal::exit_code::AppExitCode; use crate::logging::get_logger; @@ -17,6 +18,17 @@ macro_rules! crash { } } +#[macro_export] +/// Cancelles the process +macro_rules! cancelled { + () => { + crash!( + $crate::internal::exit_code::AppExitCode::UserCancellation, + "Installation cancelled" + ) + }; +} + #[macro_export] /// Macro for prompting the user with a yes/no question. macro_rules! prompt { @@ -59,3 +71,15 @@ fn get_directories() -> &'static ProjectDirs { &*DIRECTORIES } + +pub fn wrap_text>(s: S) -> Vec { + wrap(s.as_ref(), get_wrap_options()) + .into_iter() + .map(String::from) + .collect() +} + +fn get_wrap_options() -> textwrap::Options<'static> { + textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2) + .subsequent_indent(" ") +} diff --git a/src/logging/handler.rs b/src/logging/handler.rs index dfdbb66..8e09795 100644 --- a/src/logging/handler.rs +++ b/src/logging/handler.rs @@ -1,11 +1,12 @@ use colored::Colorize; use indicatif::{MultiProgress, ProgressBar}; use std::{ + fmt::Display, sync::{atomic::AtomicBool, Arc}, time::Duration, }; -use crate::uwu; +use crate::{internal::utils::wrap_text, uwu}; use dialoguer::Confirm; use super::Verbosity; @@ -110,6 +111,22 @@ impl LogHandler { confirm.interact().unwrap() } + pub fn print_list, T: Display>(&self, list: I, separator: &str) { + let lines = list + .into_iter() + .map(|l| self.preformat_msg(l.to_string())) + .fold(String::new(), |acc, line| { + format!("{}{}{}", acc, separator, line) + }); + + let lines = wrap_text(lines).join("\n"); + self.log(lines) + } + + pub fn print_newline(&self) { + self.log(String::from("\n")) + } + pub fn set_verbosity(&self, level: Verbosity) { (*self.level.write()) = level; } @@ -169,10 +186,8 @@ impl LogHandler { fn preformat_msg(&self, msg: String) -> String { let msg = self.apply_uwu(msg); - let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2) - .subsequent_indent(" "); - textwrap::wrap(&msg, opts).join("\n") + wrap_text(msg).join("\n") } fn apply_uwu(&self, msg: String) -> String { diff --git a/src/logging/mod.rs b/src/logging/mod.rs index b93210e..47eb543 100644 --- a/src/logging/mod.rs +++ b/src/logging/mod.rs @@ -12,6 +12,7 @@ use crate::internal::uwu_enabled; use self::handler::LogHandler; pub mod handler; +pub mod output; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Verbosity { diff --git a/src/logging/output.rs b/src/logging/output.rs new file mode 100644 index 0000000..c3910db --- /dev/null +++ b/src/logging/output.rs @@ -0,0 +1,75 @@ +use aur_rpc::PackageInfo; +use console::Alignment; +use crossterm::style::Stylize; + +use crate::internal::dependencies::DependencyInformation; + +use super::get_logger; + +pub fn print_dependency_list(dependencies: &Vec) -> bool { + let (deps_repo, makedeps_repo, deps_aur, makedeps_aur) = dependencies + .iter() + .map(|d| { + ( + d.depends.repo.clone(), + d.make_depends.repo.clone(), + d.depends.aur.clone(), + d.make_depends.aur.clone(), + ) + }) + .fold( + (Vec::new(), Vec::new(), Vec::new(), Vec::new()), + |mut acc, mut deps| { + acc.0.append(&mut deps.0); + acc.1.append(&mut deps.1); + acc.2.append(&mut deps.2); + acc.3.append(&mut deps.3); + + acc + }, + ); + + let mut empty = true; + if !deps_repo.is_empty() { + get_logger().print_newline(); + tracing::info!("Repo dependencies"); + get_logger().print_list(&deps_repo, " "); + empty = false; + } + if !deps_aur.is_empty() { + get_logger().print_newline(); + tracing::info!("AUR dependencies"); + print_aur_package_list(&deps_aur); + empty = false; + } + + if !makedeps_repo.is_empty() { + get_logger().print_newline(); + tracing::info!("Repo make dependencies"); + get_logger().print_list(&makedeps_repo, " "); + empty = false; + } + + if !makedeps_aur.is_empty() { + get_logger().print_newline(); + tracing::info!("AUR make dependencies"); + print_aur_package_list(&makedeps_aur); + empty = false; + } + + empty +} + +pub fn print_aur_package_list(packages: &Vec) { + get_logger().print_list( + packages.iter().map(|pkg| { + format!( + "{} version {} ({} votes)", + console::pad_str(&pkg.metadata.name, 30, Alignment::Left, Some("...")).bold(), + pkg.metadata.version.clone().dim(), + pkg.metadata.num_votes, + ) + }), + "\n ", + ); +} diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 1d08558..b3d26b5 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -2,8 +2,13 @@ use async_recursion::async_recursion; use aur_rpc::PackageInfo; use crossterm::style::Stylize; use futures::future; -use std::collections::HashSet; +use indicatif::ProgressBar; +use std::collections::{HashMap, HashSet}; +use std::mem; use std::path::{Path, PathBuf}; +use std::sync::Arc; +use tokio::io::{AsyncRead, AsyncReadExt, BufReader}; +use tokio::task::JoinHandle; use crate::builder::git::{GitCloneBuilder, GitPullBuilder}; use crate::builder::makepkg::MakePkgBuilder; @@ -11,9 +16,10 @@ use crate::builder::pacman::PacmanInstallBuilder; use crate::internal::dependencies::DependencyInformation; use crate::internal::error::{AppError, AppResult, SilentUnwrap}; use crate::internal::exit_code::AppExitCode; -use crate::internal::utils::get_cache_dir; +use crate::internal::utils::{get_cache_dir, wrap_text}; use crate::logging::get_logger; -use crate::{crash, Options}; +use crate::logging::output::{print_aur_package_list, print_dependency_list}; +use crate::{cancelled, crash, prompt, Options}; #[derive(Debug)] pub struct BuildContext { @@ -66,12 +72,8 @@ impl BuildContext { #[tracing::instrument(level = "trace")] #[async_recursion] pub async fn aur_install(packages: Vec, options: Options) { - let noconfirm = options.noconfirm; - tracing::debug!("Installing from AUR: {:?}", &packages); - tracing::info!("Installing packages {} from the AUR", packages.join(", ")); - let pb = get_logger().new_progress_spinner(); pb.set_message("Fetching package information"); @@ -97,18 +99,33 @@ pub async fn aur_install(packages: Vec, options: Options) { get_logger().reset_output_type(); pb.finish_with_message("Found all packages in the aur"); + print_aur_package_list(&package_info); + + if !options.noconfirm + && !prompt!(default yes, "Do you want to install those packages from the AUR?") + { + cancelled!(); + } + tracing::info!("Downloading aur packages"); get_logger().new_multi_progress(); let dependencies = future::try_join_all(package_info.iter().map(|pkg| async { - get_logger() - .new_progress_spinner() - .set_message(format!("{}: Fetching dependencies", pkg.metadata.name)); + get_logger().new_progress_spinner().set_message(format!( + "{}: Fetching dependencies", + pkg.metadata.name.clone().bold() + )); DependencyInformation::for_package(pkg).await })) .await .silent_unwrap(AppExitCode::RpcError); + if !print_dependency_list(&dependencies) && !options.noconfirm { + get_logger().print_newline(); + if !prompt!(default yes, "Do you want to install those dependencies?") { + cancelled!(); + } + } get_logger().new_multi_progress(); let contexts = future::try_join_all( @@ -123,9 +140,14 @@ pub async fn aur_install(packages: Vec, options: Options) { get_logger().reset_output_type(); tracing::info!("All sources are ready."); - let aur_build_dependencies: Vec = dependencies + let aur_dependencies: Vec = dependencies .iter() - .flat_map(|d| d.make_depends.aur.clone()) + .flat_map(|d| { + let mut deps = d.make_depends.aur.clone(); + deps.append(&mut d.depends.aur.clone()); + + deps + }) .collect(); let repo_dependencies: HashSet = dependencies @@ -138,26 +160,28 @@ pub async fn aur_install(packages: Vec, options: Options) { }) .collect(); - get_logger().reset_output_type(); - if !repo_dependencies.is_empty() { tracing::info!("Installing repo dependencies"); PacmanInstallBuilder::default() .as_deps(true) + .no_confirm(true) .packages(repo_dependencies) .install() .await .silent_unwrap(AppExitCode::PacmanError); } - if !aur_build_dependencies.is_empty() { + if !aur_dependencies.is_empty() { tracing::info!( - "Installing {} build dependencies from the aur", - aur_build_dependencies.len() + "Installing {} dependencies from the aur", + aur_dependencies.len() ); - install_aur_build_dependencies(aur_build_dependencies) - .await - .unwrap(); + let batches = create_dependency_batches(aur_dependencies); + tracing::debug!("aur install batches: {batches:?}"); + + for batch in batches { + install_aur_deps(batch).await.unwrap(); + } } tracing::info!("Installing {} packages", contexts.len()); @@ -165,13 +189,14 @@ pub async fn aur_install(packages: Vec, options: Options) { build_and_install( contexts, MakePkgBuilder::default(), - PacmanInstallBuilder::default(), + PacmanInstallBuilder::default().no_confirm(true), ) .await .silent_unwrap(AppExitCode::MakePkgError); + tracing::info!("Done!"); } -async fn install_aur_build_dependencies(deps: Vec) -> AppResult<()> { +async fn install_aur_deps(deps: Vec) -> AppResult<()> { get_logger().new_multi_progress(); let dep_contexts = future::try_join_all( @@ -221,18 +246,24 @@ async fn build_and_install( async fn download_aur_source(mut ctx: BuildContext) -> AppResult { let pb = get_logger().new_progress_spinner(); let pkg_name = &ctx.package.metadata.name; - pb.set_message(format!("{pkg_name}: Downloading sources")); + pb.set_message(format!("{}: Downloading sources", pkg_name.clone().bold())); let cache_dir = get_cache_dir(); let pkg_dir = cache_dir.join(&pkg_name); if pkg_dir.exists() { - pb.set_message(format!("{pkg_name}: Pulling latest changes {pkg_name}")); + pb.set_message(format!( + "{}: Pulling latest changes", + pkg_name.clone().bold() + )); GitPullBuilder::default().directory(&pkg_dir).pull().await?; } else { let aur_url = crate::internal::rpc::URL; let repository_url = format!("{aur_url}/{pkg_name}"); - pb.set_message(format!("{pkg_name}: Cloning aur repository")); + pb.set_message(format!( + "{}: Cloning aur repository", + pkg_name.clone().bold() + )); GitCloneBuilder::default() .url(repository_url) @@ -240,7 +271,10 @@ async fn download_aur_source(mut ctx: BuildContext) -> AppResult { .clone() .await?; - pb.set_message(format!("{pkg_name}: Downloading and extracting files")); + pb.set_message(format!( + "{}: Downloading and extracting files", + pkg_name.clone().bold() + )); MakePkgBuilder::default() .directory(&pkg_dir) @@ -251,7 +285,11 @@ async fn download_aur_source(mut ctx: BuildContext) -> AppResult { .run() .await?; } - pb.finish_with_message(format!("{pkg_name}: Downloaded!")); + pb.finish_with_message(format!( + "{}: {}", + pkg_name.clone().bold(), + "Downloaded!".green() + )); ctx.step = BuildStep::Build(BuildPath(pkg_dir)); Ok(ctx) @@ -264,19 +302,47 @@ async fn build_package( let pb = get_logger().new_progress_spinner(); let pkg_name = &ctx.package.metadata.name; let build_path = ctx.build_path()?; - pb.set_message(format!("{pkg_name}: Building Package")); + pb.set_message(format!("{}: Building Package", pkg_name.clone().bold())); - make_opts + let mut child = make_opts .directory(build_path) .clean(true) .no_deps(true) .skip_pgp(true) .needed(true) - .run() - .await?; + .spawn()?; + + let stderr = child.stderr.take().unwrap(); + let stdout = child.stdout.take().unwrap(); + let h1 = show_stdio_on_pb(stdout, pb.clone(), { + let pkg_name = pkg_name.clone(); + move |s| format!("{}: {s}", pkg_name.clone().bold()) + }); + let h2 = show_stdio_on_pb(stderr, pb.clone(), { + let pkg_name = pkg_name.clone(); + move |s| format!("{}: {s}", pkg_name.clone().bold()) + }); + + loop { + if let Some(exit_code) = child.try_wait()? { + h1.abort(); + h2.abort(); + + if !exit_code.success() { + pb.set_message(format!( + "{}: {}", + "Build failed!".red(), + pkg_name.clone().bold() + )); + return Err(AppError::from("Failed to build package")); + } else { + break; + } + } + } let packages = MakePkgBuilder::package_list(build_path).await?; - pb.finish_with_message(format!("{pkg_name}: Built!")); + pb.finish_with_message(format!("{}: {}", pkg_name.clone().bold(), "Built!".green())); ctx.step = BuildStep::Install(PackageArchives(packages)); Ok(ctx) @@ -297,3 +363,64 @@ async fn install_packages( Ok(ctxs) } + +#[tracing::instrument(level = "trace")] +fn create_dependency_batches(deps: Vec) -> Vec> { + let mut deps: HashMap = deps + .into_iter() + .map(|d| (d.metadata.name.clone(), d)) + .collect(); + let mut batches = Vec::new(); + + while !deps.is_empty() { + let mut current_batch = HashMap::new(); + + for (key, info) in deps.clone() { + let contains_make_dep = info + .make_depends + .iter() + .any(|d| current_batch.contains_key(d) || deps.contains_key(d)); + + let contains_dep = info + .depends + .iter() + .any(|d| current_batch.contains_key(d) || deps.contains_key(d)); + + if !contains_dep && !contains_make_dep { + deps.remove(&key); + current_batch.insert(key, info); + } + } + + batches.push(current_batch.into_iter().map(|(_, v)| v).collect()); + } + + batches +} + +fn show_stdio_on_pb< + R: AsyncRead + Unpin + Send + 'static, + F: Fn(String) -> String + 'static + Send, +>( + stdout: R, + pb: Arc, + fmt: F, +) -> JoinHandle<()> { + tokio::task::spawn({ + async move { + let mut stdout = BufReader::new(stdout); + let mut line = String::new(); + + while let Ok(ch) = stdout.read_u8().await { + if ch == b'\n' { + let line = fmt(mem::take(&mut line)); + let lines = wrap_text(line); + let line = lines.into_iter().next().unwrap(); + pb.set_message(line); + } else { + line.push(ch as char); + } + } + } + }) +} From d2f645e34fcc125005690d24465a9f7a47f99662 Mon Sep 17 00:00:00 2001 From: trivernis Date: Wed, 31 Aug 2022 21:33:24 +0200 Subject: [PATCH 18/43] Fix the broken parallel build Signed-off-by: trivernis --- src/operations/aur_install.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index b3d26b5..56ef83e 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -323,22 +323,17 @@ async fn build_package( move |s| format!("{}: {s}", pkg_name.clone().bold()) }); - loop { - if let Some(exit_code) = child.try_wait()? { - h1.abort(); - h2.abort(); - - if !exit_code.success() { - pb.set_message(format!( - "{}: {}", - "Build failed!".red(), - pkg_name.clone().bold() - )); - return Err(AppError::from("Failed to build package")); - } else { - break; - } - } + let exit_status = child.wait().await?; + h1.abort(); + h2.abort(); + + if !exit_status.success() { + pb.set_message(format!( + "{}: {}", + "Build failed!".red(), + pkg_name.clone().bold() + )); + return Err(AppError::from("Failed to build package")); } let packages = MakePkgBuilder::package_list(build_path).await?; From ad34e8bf8b627b7a87e1ca5a898be98273de8533 Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 2 Sep 2022 21:29:21 +0200 Subject: [PATCH 19/43] Add filtering for already installed dependencies --- Cargo.lock | 72 +++++++++++----------- Cargo.toml | 2 +- src/builder/makepkg.rs | 1 + src/builder/pacman.rs | 13 +++- src/internal/clean.rs | 4 +- src/internal/dependencies.rs | 50 +++++++++++++--- src/internal/initialise.rs | 112 ----------------------------------- src/internal/mod.rs | 4 -- src/internal/utils.rs | 20 +++++-- src/logging/handler.rs | 2 +- src/logging/output.rs | 4 +- src/main.rs | 4 +- 12 files changed, 111 insertions(+), 177 deletions(-) delete mode 100644 src/internal/initialise.rs diff --git a/Cargo.lock b/Cargo.lock index fbbed4a..0acdd4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,9 +158,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.2.17" +version = "3.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" +checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd" dependencies = [ "atty", "bitflags", @@ -185,9 +185,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.17" +version = "3.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" dependencies = [ "heck", "proc-macro-error", @@ -403,9 +403,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab30e97ab6aacfe635fad58f22c2bb06c8b685f7421eb1e064a729e2a5f481fa" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" dependencies = [ "futures-channel", "futures-core", @@ -418,9 +418,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -428,15 +428,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -445,15 +445,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-macro" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ "proc-macro2", "quote", @@ -462,21 +462,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-channel", "futures-core", @@ -571,9 +571,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -724,9 +724,9 @@ checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" dependencies = [ "autocfg", "scopeguard", @@ -834,9 +834,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" [[package]] name = "openssl" @@ -1245,9 +1245,9 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" [[package]] name = "socket2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10c98bba371b9b22a71a9414e420f92ddeb2369239af08200816169d5e2dd7aa" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -1317,18 +1317,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" dependencies = [ "proc-macro2", "quote", @@ -1372,9 +1372,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.20.1" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42" dependencies = [ "autocfg", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 16e5a51..0da120b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,5 +50,5 @@ directories = "4.0.1" console = "0.15.1" [dependencies.tokio] -version = "1.20.1" +version = "1.21.0" features = ["rt", "rt-multi-thread", "io-std", "io-util", "process", "time", "macros", "tracing", "fs"] diff --git a/src/builder/makepkg.rs b/src/builder/makepkg.rs index e0ee16d..2bd4800 100644 --- a/src/builder/makepkg.rs +++ b/src/builder/makepkg.rs @@ -55,6 +55,7 @@ impl MakePkgBuilder { } /// Mark packages as non-explicitly installed + #[allow(clippy::wrong_self_convention)] pub fn as_deps(mut self, as_deps: bool) -> Self { self.as_deps = as_deps; diff --git a/src/builder/pacman.rs b/src/builder/pacman.rs index 4822c25..2e17ee0 100644 --- a/src/builder/pacman.rs +++ b/src/builder/pacman.rs @@ -81,6 +81,7 @@ pub struct PacmanQueryBuilder { #[derive(Debug)] enum PacmanQueryType { Foreign, + All, Info, } @@ -106,7 +107,11 @@ impl PacmanQueryBuilder { packages: Vec::new(), } } - /// Query for foreign packages + + pub fn all() -> Self { + Self::new(PacmanQueryType::All) + } + pub fn foreign() -> Self { Self::new(PacmanQueryType::Foreign) } @@ -135,12 +140,12 @@ impl PacmanQueryBuilder { self } - #[tracing::instrument(level = "debug")] + #[tracing::instrument(level = "trace")] pub async fn query(self) -> AppResult<()> { self.build_command().wait_success().await } - #[tracing::instrument(level = "debug")] + #[tracing::instrument(level = "trace")] pub async fn query_with_output(self) -> AppResult> { let output = self.build_command().wait_with_output().await?; let packages = output @@ -153,6 +158,7 @@ impl PacmanQueryBuilder { version: version.to_string(), }) .collect(); + tracing::debug!("Query result: {packages:?}"); Ok(packages) } @@ -163,6 +169,7 @@ impl PacmanQueryBuilder { command = match self.query_type { PacmanQueryType::Foreign => command.arg("-m"), PacmanQueryType::Info => command.arg("-i"), + PacmanQueryType::All => command, }; command = command.arg("--color"); diff --git a/src/internal/clean.rs b/src/internal/clean.rs index 558f278..465d996 100644 --- a/src/internal/clean.rs +++ b/src/internal/clean.rs @@ -4,9 +4,9 @@ pub fn clean(a: &[String]) -> Vec { let cleaned = a .iter() .map(|name| { - name.split_once("=") + name.split_once('=') .map(|n| n.0.to_string()) - .unwrap_or(name.to_string()) + .unwrap_or_else(|| name.to_string()) }) .collect(); diff --git a/src/internal/dependencies.rs b/src/internal/dependencies.rs index e767f82..2bdf884 100644 --- a/src/internal/dependencies.rs +++ b/src/internal/dependencies.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use aur_rpc::PackageInfo; use futures::future; -use crate::builder::pacman::PacmanSearchBuilder; +use crate::builder::pacman::{PacmanQueryBuilder, PacmanSearchBuilder}; use super::error::AppResult; use lazy_regex::regex; @@ -71,8 +71,10 @@ impl DependencyInformation { let mut packages_to_resolve: HashSet = package .make_depends .iter() - .filter_map(Self::map_dep_to_name) + .filter_map(|d| Self::map_dep_to_name(d)) .collect(); + + Self::filter_fulfilled_dependencies(&mut packages_to_resolve).await?; let mut already_searched = HashSet::new(); let mut dependencies = DependencyCollection::default(); @@ -91,6 +93,7 @@ impl DependencyInformation { .append(&mut not_found.into_iter().collect()); packages_to_resolve = Self::get_filtered_make_depends(&aur_packages, &already_searched); + Self::filter_fulfilled_dependencies(&mut packages_to_resolve).await?; dependencies.aur.append(&mut aur_packages); } @@ -103,8 +106,9 @@ impl DependencyInformation { let mut packages_to_resolve: HashSet = package .depends .iter() - .filter_map(Self::map_dep_to_name) + .filter_map(|d| Self::map_dep_to_name(d)) .collect(); + Self::filter_fulfilled_dependencies(&mut packages_to_resolve).await?; let mut already_searched = HashSet::new(); let mut dependencies = DependencyCollection::default(); @@ -123,6 +127,7 @@ impl DependencyInformation { .append(&mut not_found.into_iter().collect()); packages_to_resolve = Self::get_filtered_depends(&aur_packages, &already_searched); + Self::filter_fulfilled_dependencies(&mut packages_to_resolve).await?; dependencies.aur.append(&mut aur_packages); } @@ -143,28 +148,46 @@ impl DependencyInformation { } fn get_filtered_make_depends( - aur_packages: &Vec, + aur_packages: &[PackageInfo], searched: &HashSet, ) -> HashSet { aur_packages .iter() - .flat_map(|p| p.make_depends.iter().filter_map(Self::map_dep_to_name)) + .flat_map(|p| { + p.make_depends + .iter() + .filter_map(|d| Self::map_dep_to_name(d)) + }) .filter(|d| !searched.contains(d)) .collect() } fn get_filtered_depends( - aur_packages: &Vec, + aur_packages: &[PackageInfo], searched: &HashSet, ) -> HashSet { aur_packages .iter() - .flat_map(|p| p.depends.iter().filter_map(Self::map_dep_to_name)) + .flat_map(|p| p.depends.iter().filter_map(|d| Self::map_dep_to_name(d))) .filter(|d| !searched.contains(d)) .collect() } - fn map_dep_to_name(dep: &String) -> Option { + async fn filter_fulfilled_dependencies(deps: &mut HashSet) -> AppResult<()> { + let mut fulfilled = HashSet::new(); + + for dep in deps.iter() { + if get_dependency_fulfilled(dep.clone()).await? { + fulfilled.insert(dep.clone()); + } + } + + deps.retain(|pkg| !fulfilled.contains(pkg)); + + Ok(()) + } + + fn map_dep_to_name(dep: &str) -> Option { Dependency::try_from_str(dep).map(|d| d.name) } @@ -205,3 +228,14 @@ impl Dependency { }) } } + +#[tracing::instrument(level = "trace")] +async fn get_dependency_fulfilled(name: String) -> AppResult { + let not_found = PacmanQueryBuilder::all() + .package(name) + .query_with_output() + .await? + .is_empty(); + + Ok(!not_found) +} diff --git a/src/internal/initialise.rs b/src/internal/initialise.rs deleted file mode 100644 index db4305b..0000000 --- a/src/internal/initialise.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::env; -use std::path::Path; -use std::process::Command; - -use crate::{crash, internal::exit_code::AppExitCode}; - -pub fn init() { - let homedir = env::var("HOME").unwrap(); - - if !Path::new(&format!("{}/.local/share/ame", homedir)).exists() { - let r = std::fs::create_dir_all(format!("{}/.local/share/ame", homedir)); - match r { - Ok(_) => { - tracing::debug!("Created path: {}/.local/share/ame", homedir); - } - Err(e) => { - crash!( - AppExitCode::FailedCreatingPaths, - "Couldn't create path: {}/.local/share/ame: {}", - homedir, - e, - ); - } - } - } - - if !Path::new(&format!("{}/.cache/ame/", homedir)).exists() { - let r = std::fs::create_dir_all(format!("{}/.cache/ame", homedir)); - match r { - Ok(_) => { - tracing::debug!("Created path: {}/.cache/ame", homedir); - } - Err(e) => { - crash!( - AppExitCode::FailedCreatingPaths, - "Couldn't create path: {}/.cache/ame: {}", - homedir, - e, - ); - } - } - } else { - let r = std::fs::remove_dir_all(format!("{}/.cache/ame", homedir)); - match r { - Ok(_) => { - tracing::debug!("Removing cache: {}/.cache/ame", homedir); - } - Err(e) => { - crash!( - AppExitCode::FailedCreatingPaths, - "Couldn't remove path: {}/.cache/ame: {}", - homedir, - e, - ); - } - } - let r2 = std::fs::create_dir_all(format!("{}/.cache/ame", homedir)); - match r2 { - Ok(_) => { - tracing::debug!("Created path: {}/.cache/ame", homedir); - } - Err(e2) => { - crash!( - AppExitCode::FailedCreatingPaths, - "Couldn't create path: {}/.cache/ame: {}", - homedir, - e2, - ); - } - } - } - - let r = Command::new("chmod") - .arg("-R") - .arg("770") - .arg(format!("{}/.cache/ame", homedir)) - .status(); - match r { - Ok(_) => { - tracing::debug!("Set correct permissions for path: {}/.cache/ame", homedir); - } - Err(e) => { - crash!( - AppExitCode::FailedCreatingPaths, - "Couldn't set permissions for path: {}/.cache/ame: {}", - homedir, - e, - ); - } - }; - let r = Command::new("chmod") - .arg("-R") - .arg("770") - .arg(format!("{}/.local/share/ame", homedir)) - .status(); - match r { - Ok(_) => { - tracing::debug!( - "Set correct permissions for path: {}/.local/share/ame", - homedir - ); - } - Err(e) => { - crash!( - AppExitCode::FailedCreatingPaths, - "Couldn't set permissions for path: {}/.local/share/ame: {}", - homedir, - e, - ); - } - }; -} diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 2e63968..81b1d0c 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -1,9 +1,6 @@ pub use clean::*; pub use clean::*; pub use detect::*; -pub use initialise::*; -pub use initialise::*; -pub use sort::*; pub use sort::*; pub use sudoloop::*; @@ -15,7 +12,6 @@ mod detect; pub mod error; pub mod exit_code; pub mod fs_utils; -mod initialise; pub mod rpc; mod sort; pub mod structs; diff --git a/src/internal/utils.rs b/src/internal/utils.rs index 83b0340..21a2282 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -10,6 +10,8 @@ use crate::logging::get_logger; use crate::logging::handler::PromptDefault; use lazy_static::lazy_static; +use super::error::{AppError, SilentUnwrap}; + #[macro_export] /// Macro for printing a message and destructively exiting macro_rules! crash { @@ -55,13 +57,11 @@ pub fn prompt_yn(question: String, prompt_default: PromptDefault) -> bool { } pub fn get_cache_dir() -> &'static Path { - let cache_dir = get_directories().cache_dir(); - - if !cache_dir.exists() { - fs::create_dir_all(cache_dir).unwrap(); + lazy_static! { + static ref CACHE_DIR: &'static Path = create_if_not_exist(get_directories().cache_dir()); } - cache_dir + *CACHE_DIR } fn get_directories() -> &'static ProjectDirs { @@ -72,6 +72,16 @@ fn get_directories() -> &'static ProjectDirs { &*DIRECTORIES } +fn create_if_not_exist(dir: &Path) -> &Path { + if !dir.exists() { + fs::create_dir_all(dir) + .map_err(AppError::from) + .silent_unwrap(AppExitCode::FailedCreatingPaths) + } + + dir +} + pub fn wrap_text>(s: S) -> Vec { wrap(s.as_ref(), get_wrap_options()) .into_iter() diff --git a/src/logging/handler.rs b/src/logging/handler.rs index 8e09795..8db545b 100644 --- a/src/logging/handler.rs +++ b/src/logging/handler.rs @@ -145,7 +145,7 @@ impl LogHandler { let mut output_type = self.output_type.write(); if let OutputType::MultiProgress(mp) = &*output_type { - Arc::new(mp.add(pb.clone())) + Arc::new(mp.add(pb)) } else { let pb = Arc::new(pb); *output_type = OutputType::Progress(pb.clone()); diff --git a/src/logging/output.rs b/src/logging/output.rs index c3910db..56e4d02 100644 --- a/src/logging/output.rs +++ b/src/logging/output.rs @@ -6,7 +6,7 @@ use crate::internal::dependencies::DependencyInformation; use super::get_logger; -pub fn print_dependency_list(dependencies: &Vec) -> bool { +pub fn print_dependency_list(dependencies: &[DependencyInformation]) -> bool { let (deps_repo, makedeps_repo, deps_aur, makedeps_aur) = dependencies .iter() .map(|d| { @@ -60,7 +60,7 @@ pub fn print_dependency_list(dependencies: &Vec) -> bool empty } -pub fn print_aur_package_list(packages: &Vec) { +pub fn print_aur_package_list(packages: &[PackageInfo]) { get_logger().print_list( packages.iter().map(|pkg| { format!( diff --git a/src/main.rs b/src/main.rs index 80366a9..51d629b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use internal::error::SilentUnwrap; use crate::args::{InstallArgs, Operation, QueryArgs, RemoveArgs, SearchArgs}; use crate::internal::detect; use crate::internal::exit_code::AppExitCode; -use crate::internal::{init, sort, start_sudoloop, structs::Options}; +use crate::internal::{sort, start_sudoloop, structs::Options}; use clap_complete::{Generator, Shell}; use std::str::FromStr; @@ -35,8 +35,6 @@ async fn main() { asdeps: false, }; - init(); - if args.sudoloop { start_sudoloop().await; } From c7ad9a972501a98700133b534dd5663cfc39bb26 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 11:40:12 +0200 Subject: [PATCH 20/43] Add build log reviewing when package builds fail --- src/builder/makepkg.rs | 10 +++ src/builder/mod.rs | 1 + src/builder/pager.rs | 20 ++++++ src/internal/commands.rs | 8 +++ src/internal/error.rs | 2 + src/logging/mod.rs | 1 + src/logging/piped_stdio.rs | 57 +++++++++++++++ src/operations/aur_install.rs | 130 +++++++++++++++++++++++----------- 8 files changed, 188 insertions(+), 41 deletions(-) create mode 100644 src/builder/pager.rs create mode 100644 src/logging/piped_stdio.rs diff --git a/src/builder/makepkg.rs b/src/builder/makepkg.rs index 2bd4800..9e3582c 100644 --- a/src/builder/makepkg.rs +++ b/src/builder/makepkg.rs @@ -20,6 +20,7 @@ pub struct MakePkgBuilder { skip_pgp: bool, needed: bool, no_prepare: bool, + force: bool, } impl MakePkgBuilder { @@ -76,6 +77,12 @@ impl MakePkgBuilder { self } + pub fn force(mut self, force: bool) -> Self { + self.force = force; + + self + } + pub async fn run(self) -> AppResult<()> { let output = self.build().wait_with_output().await?; @@ -122,6 +129,9 @@ impl MakePkgBuilder { if self.no_prepare { command = command.arg("--noprepare") } + if self.force { + command = command.arg("-f") + } command } diff --git a/src/builder/mod.rs b/src/builder/mod.rs index e3233d0..37d4240 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -1,3 +1,4 @@ pub mod git; pub mod makepkg; pub mod pacman; +pub mod pager; diff --git a/src/builder/pager.rs b/src/builder/pager.rs new file mode 100644 index 0000000..2457fec --- /dev/null +++ b/src/builder/pager.rs @@ -0,0 +1,20 @@ +use std::path::{Path, PathBuf}; + +use crate::internal::{commands::ShellCommand, error::AppResult}; + +#[derive(Default)] +pub struct PagerBuilder { + path: PathBuf, +} + +impl PagerBuilder { + pub fn path>(mut self, path: P) -> Self { + self.path = path.as_ref().into(); + + self + } + + pub async fn open(self) -> AppResult<()> { + ShellCommand::pager().arg(self.path).wait_success().await + } +} diff --git a/src/internal/commands.rs b/src/internal/commands.rs index a93dd7a..4a579aa 100644 --- a/src/internal/commands.rs +++ b/src/internal/commands.rs @@ -1,3 +1,4 @@ +use std::env; use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; use std::process::{ExitStatus, Stdio}; @@ -52,6 +53,12 @@ impl ShellCommand { Self::new("sudo") } + pub fn pager() -> Self { + let pager = env::var("PAGER").unwrap_or_else(|_| String::from("less")); + + Self::new(pager) + } + fn new(command: S) -> Self { Self { command: command.to_string(), @@ -149,6 +156,7 @@ impl ShellCommand { .args(self.args) .stdout(stdout) .stderr(stderr) + .kill_on_drop(true) .spawn()?; Ok(child) diff --git a/src/internal/error.rs b/src/internal/error.rs index 442ee80..f4863a7 100644 --- a/src/internal/error.rs +++ b/src/internal/error.rs @@ -15,6 +15,7 @@ pub enum AppError { Rpc(aur_rpc::error::RPCError), NonZeroExit, BuildStepViolation, + BuildError { pkg_name: String }, } impl Display for AppError { @@ -25,6 +26,7 @@ impl Display for AppError { AppError::Other(s) => Display::fmt(s, f), AppError::NonZeroExit => Display::fmt("exited with non zero code", f), AppError::BuildStepViolation => Display::fmt("AUR build violated build steps", f), + AppError::BuildError { pkg_name } => write!(f, "Failed to build package {pkg_name}"), } } } diff --git a/src/logging/mod.rs b/src/logging/mod.rs index 47eb543..c0086fd 100644 --- a/src/logging/mod.rs +++ b/src/logging/mod.rs @@ -13,6 +13,7 @@ use crate::internal::uwu_enabled; use self::handler::LogHandler; pub mod handler; pub mod output; +pub mod piped_stdio; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Verbosity { diff --git a/src/logging/piped_stdio.rs b/src/logging/piped_stdio.rs new file mode 100644 index 0000000..11343da --- /dev/null +++ b/src/logging/piped_stdio.rs @@ -0,0 +1,57 @@ +use std::mem; + +use tokio::{ + io::{AsyncRead, AsyncReadExt}, + process::{ChildStderr, ChildStdout}, +}; + +use crate::internal::error::{AppError, AppResult}; + +pub struct StdioReader { + stdout: ChildStdout, + stderr: ChildStderr, + stdout_line: Vec, + stderr_line: Vec, +} + +impl StdioReader { + pub fn new(stdout: ChildStdout, stderr: ChildStderr) -> Self { + Self { + stdout, + stderr, + stdout_line: Vec::new(), + stderr_line: Vec::new(), + } + } + + pub async fn read_line(&mut self) -> AppResult { + let line = tokio::select! { + l = Self::read_stdio(&mut self.stdout, &mut self.stdout_line) => {l?} + l = Self::read_stdio(&mut self.stderr, &mut self.stderr_line) => {l?} + }; + + Ok(line) + } + + pub async fn read_stdio( + reader: &mut R, + buf: &mut Vec, + ) -> AppResult { + while let Ok(ch) = reader.read_u8().await { + if ch == b'\n' { + if !buf.is_empty() { + break; + } + } else { + buf.push(ch); + } + } + + let line = mem::take(buf); + if line.is_empty() { + Err(AppError::from("stdio exhausted")) + } else { + Ok(String::from_utf8(line).unwrap()) + } + } +} diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 56ef83e..fffa7ca 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -4,21 +4,24 @@ use crossterm::style::Stylize; use futures::future; use indicatif::ProgressBar; use std::collections::{HashMap, HashSet}; -use std::mem; use std::path::{Path, PathBuf}; use std::sync::Arc; -use tokio::io::{AsyncRead, AsyncReadExt, BufReader}; -use tokio::task::JoinHandle; +use tokio::fs::OpenOptions; +use tokio::io::{AsyncWriteExt, BufWriter}; +use tokio::process::{ChildStderr, ChildStdout}; +use tokio::task; use crate::builder::git::{GitCloneBuilder, GitPullBuilder}; use crate::builder::makepkg::MakePkgBuilder; use crate::builder::pacman::PacmanInstallBuilder; +use crate::builder::pager::PagerBuilder; use crate::internal::dependencies::DependencyInformation; use crate::internal::error::{AppError, AppResult, SilentUnwrap}; use crate::internal::exit_code::AppExitCode; use crate::internal::utils::{get_cache_dir, wrap_text}; use crate::logging::get_logger; use crate::logging::output::{print_aur_package_list, print_dependency_list}; +use crate::logging::piped_stdio::StdioReader; use crate::{cancelled, crash, prompt, Options}; #[derive(Debug)] @@ -186,16 +189,21 @@ pub async fn aur_install(packages: Vec, options: Options) { tracing::info!("Installing {} packages", contexts.len()); - build_and_install( + if let Err(e) = build_and_install( contexts, MakePkgBuilder::default(), PacmanInstallBuilder::default().no_confirm(true), ) .await - .silent_unwrap(AppExitCode::MakePkgError); + { + handle_build_error(e) + .await + .silent_unwrap(AppExitCode::MakePkgError); + } tracing::info!("Done!"); } +#[tracing::instrument(level = "trace")] async fn install_aur_deps(deps: Vec) -> AppResult<()> { get_logger().new_multi_progress(); @@ -208,12 +216,16 @@ async fn install_aur_deps(deps: Vec) -> AppResult<()> { get_logger().reset_output_type(); - build_and_install( + if let Err(e) = build_and_install( dep_contexts, MakePkgBuilder::default().as_deps(true), PacmanInstallBuilder::default().as_deps(true), ) - .await?; + .await + { + handle_build_error(e).await?; + } + get_logger().reset_output_type(); Ok(()) } @@ -230,8 +242,7 @@ async fn build_and_install( ctxs.into_iter() .map(|ctx| build_package(ctx, make_opts.clone())), ) - .await - .silent_unwrap(AppExitCode::MakePkgError); + .await?; get_logger().reset_output_type(); tracing::info!("Built {} packages", ctxs.len()); @@ -295,6 +306,7 @@ async fn download_aur_source(mut ctx: BuildContext) -> AppResult { Ok(ctx) } +#[tracing::instrument(level = "trace")] async fn build_package( mut ctx: BuildContext, make_opts: MakePkgBuilder, @@ -310,22 +322,19 @@ async fn build_package( .no_deps(true) .skip_pgp(true) .needed(true) + .force(true) .spawn()?; let stderr = child.stderr.take().unwrap(); let stdout = child.stdout.take().unwrap(); - let h1 = show_stdio_on_pb(stdout, pb.clone(), { - let pkg_name = pkg_name.clone(); - move |s| format!("{}: {s}", pkg_name.clone().bold()) - }); - let h2 = show_stdio_on_pb(stderr, pb.clone(), { + let handle = task::spawn({ + let pb = pb.clone(); let pkg_name = pkg_name.clone(); - move |s| format!("{}: {s}", pkg_name.clone().bold()) + async move { show_and_log_stdio(stdout, stderr, pb, pkg_name).await } }); let exit_status = child.wait().await?; - h1.abort(); - h2.abort(); + handle.abort(); if !exit_status.success() { pb.set_message(format!( @@ -333,7 +342,9 @@ async fn build_package( "Build failed!".red(), pkg_name.clone().bold() )); - return Err(AppError::from("Failed to build package")); + return Err(AppError::BuildError { + pkg_name: pkg_name.to_owned(), + }); } let packages = MakePkgBuilder::package_list(build_path).await?; @@ -343,6 +354,7 @@ async fn build_package( Ok(ctx) } +#[tracing::instrument(level = "trace")] async fn install_packages( mut ctxs: Vec, install_opts: PacmanInstallBuilder, @@ -393,29 +405,65 @@ fn create_dependency_batches(deps: Vec) -> Vec> { batches } -fn show_stdio_on_pb< - R: AsyncRead + Unpin + Send + 'static, - F: Fn(String) -> String + 'static + Send, ->( - stdout: R, +#[tracing::instrument(level = "trace")] +async fn show_and_log_stdio( + stdout: ChildStdout, + stderr: ChildStderr, pb: Arc, - fmt: F, -) -> JoinHandle<()> { - tokio::task::spawn({ - async move { - let mut stdout = BufReader::new(stdout); - let mut line = String::new(); - - while let Ok(ch) = stdout.read_u8().await { - if ch == b'\n' { - let line = fmt(mem::take(&mut line)); - let lines = wrap_text(line); - let line = lines.into_iter().next().unwrap(); - pb.set_message(line); - } else { - line.push(ch as char); - } - } + package_name: String, +) -> AppResult<()> { + let mut reader = StdioReader::new(stdout, stderr); + let out_file = get_cache_dir().join(format!("{package_name}-build.log")); + let mut out_writer = BufWriter::new( + OpenOptions::new() + .create(true) + .write(true) + .open(out_file) + .await?, + ); + + while let Ok(line) = reader.read_line().await { + let _ = out_writer.write(line.as_bytes()).await?; + let _ = out_writer.write(&[b'\n']).await?; + tracing::trace!("{package_name}: {line}"); + let line = format!("{}: {}", package_name.clone().bold(), line); + let lines = wrap_text(line); + let line = lines.into_iter().next().unwrap(); + pb.set_message(line); + } + out_writer.flush().await?; + + Ok(()) +} + +#[tracing::instrument(level = "trace")] +async fn review_build_log(log_file: &Path) -> AppResult<()> { + if prompt!(default yes, "Do you want to review the build log?") { + PagerBuilder::default().path(log_file).open().await?; + } + + Ok(()) +} + +async fn handle_build_error>(err: E) -> AppResult<()> { + get_logger().reset_output_type(); + let err = err.into(); + + match &err { + AppError::BuildError { pkg_name } => { + get_logger().print_newline(); + tracing::error!("Failed to build package {pkg_name}!"); + let log_path = get_cache_dir().join(format!("{pkg_name}-build.log")); + get_logger().reset_output_type(); + review_build_log(&log_path).await?; + + Err(err) } - }) + e => { + crash!( + AppExitCode::MakePkgError, + "An error occurred while building: {e}" + ) + } + } } From 7aebf35f96b6ab7c297ee440f3db784e8a9068fa Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 12:41:45 +0200 Subject: [PATCH 21/43] Add output suspending when prompting the user or starting a pager --- Cargo.lock | 1 + Cargo.toml | 1 + src/args.rs | 12 +++--- src/builder/pager.rs | 11 +++++- src/internal/commands.rs | 4 ++ src/logging/handler.rs | 69 ++++++++++++++++++++++++++++++++--- src/logging/mod.rs | 5 ++- src/operations/aur_install.rs | 38 +++++++++---------- src/operations/clean.rs | 17 +++++---- 9 files changed, 116 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0acdd4e..b014408 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,7 @@ dependencies = [ "tokio", "toml", "tracing", + "tracing-error", "tracing-subscriber", ] diff --git a/Cargo.toml b/Cargo.toml index 0da120b..cb61561 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ dialoguer = "0.10.2" lazy-regex = "2.3.0" directories = "4.0.1" console = "0.15.1" +tracing-error = "0.2.0" [dependencies.tokio] version = "1.21.0" diff --git a/src/args.rs b/src/args.rs index ace1a36..6bf68d7 100644 --- a/src/args.rs +++ b/src/args.rs @@ -29,19 +29,19 @@ pub struct Args { #[derive(Debug, Clone, Subcommand)] pub enum Operation { /// Installs a package from either the AUR or the Pacman-defined repositories - #[clap(bin_name = "ame", name = "install", visible_aliases = & ["-S"], aliases = & ["-Sa", "-Sr"])] + #[clap(bin_name = "ame", name = "install", visible_aliases = & ["-S", "i"], aliases = & ["-Sa", "-Sr"])] Install(InstallArgs), /// Removes a previously installed package - #[clap(bin_name = "ame", name = "remove", visible_aliases = & ["rm", "-Rs"])] + #[clap(bin_name = "ame", name = "remove", visible_aliases = & ["rm", "r", "-Rs"])] Remove(RemoveArgs), /// Searches for packages matching a regex-supported pattern in the AUR and/or the repos - #[clap(bin_name = "ame", name = "search", visible_aliases = & ["-Ss"], aliases = & ["-Ssa", "-Ssr"])] + #[clap(bin_name = "ame", name = "search", visible_aliases = & ["-Ss", "s"], aliases = & ["-Ssa", "-Ssr"])] Search(SearchArgs), /// Queries installed packages - #[clap(bin_name = "ame", name = "query", visible_aliases = & ["-Q"], aliases = & ["-Qa", "-Qr", "-Qm", "-Qn"])] + #[clap(bin_name = "ame", name = "query", visible_aliases = & ["-Q", "q"], aliases = & ["-Qa", "-Qr", "-Qm", "-Qn"])] Query(QueryArgs), /// Gets info about a package @@ -49,7 +49,7 @@ pub enum Operation { Info(InfoArgs), /// Upgrades locally installed packages to their latest versions (Default) - #[clap(bin_name = "ame", name = "upgrade", visible_aliases = & ["-Syu"])] + #[clap(bin_name = "ame", name = "upgrade", visible_aliases = & ["-Syu", "u"])] Upgrade(UpgradeArgs), /// Generates shell completions for supported shells (bash, fish, elvish, pwsh) @@ -57,7 +57,7 @@ pub enum Operation { GenComp(GenCompArgs), /// Removes all orphaned packages - #[clap(bin_name = "ame", name = "clean", visible_aliases = & ["-Sc"])] + #[clap(bin_name = "ame", name = "clean", visible_aliases = & ["-Sc", "c"])] Clean, /// Runs pacdiff diff --git a/src/builder/pager.rs b/src/builder/pager.rs index 2457fec..73892e3 100644 --- a/src/builder/pager.rs +++ b/src/builder/pager.rs @@ -1,6 +1,9 @@ use std::path::{Path, PathBuf}; -use crate::internal::{commands::ShellCommand, error::AppResult}; +use crate::{ + internal::{commands::ShellCommand, error::AppResult}, + logging::get_logger, +}; #[derive(Default)] pub struct PagerBuilder { @@ -15,6 +18,10 @@ impl PagerBuilder { } pub async fn open(self) -> AppResult<()> { - ShellCommand::pager().arg(self.path).wait_success().await + get_logger().suspend(); + ShellCommand::pager().arg(self.path).wait_success().await?; + get_logger().unsuspend(); + + Ok(()) } } diff --git a/src/internal/commands.rs b/src/internal/commands.rs index 4a579aa..f443aac 100644 --- a/src/internal/commands.rs +++ b/src/internal/commands.rs @@ -53,6 +53,10 @@ impl ShellCommand { Self::new("sudo") } + pub fn rm() -> Self { + Self::new("rm") + } + pub fn pager() -> Self { let pager = env::var("PAGER").unwrap_or_else(|_| String::from("less")); diff --git a/src/logging/handler.rs b/src/logging/handler.rs index 8db545b..cbf56df 100644 --- a/src/logging/handler.rs +++ b/src/logging/handler.rs @@ -1,7 +1,9 @@ use colored::Colorize; -use indicatif::{MultiProgress, ProgressBar}; +use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget}; +use parking_lot::{Mutex, RwLock}; use std::{ fmt::Display, + mem, sync::{atomic::AtomicBool, Arc}, time::Duration, }; @@ -10,7 +12,6 @@ use crate::{internal::utils::wrap_text, uwu}; use dialoguer::Confirm; use super::Verbosity; -use parking_lot::RwLock; const OK_SYMBOL: &str = "❖"; const ERR_SYMBOL: &str = "X"; @@ -41,9 +42,14 @@ pub enum OutputType { Stderr, MultiProgress(Arc), Progress(Arc), + Buffer { + buffer: Arc>>, + suspended: Box, + }, } #[allow(unused)] +#[derive(Clone, Copy, Debug)] pub enum PromptDefault { Yes, No, @@ -93,11 +99,13 @@ impl LogHandler { } /// Prompts the user with a question and a default selection + #[tracing::instrument(level = "trace", skip(self))] pub fn prompt(&self, question: String, p_default: PromptDefault) -> bool { let question = self.preformat_msg(question); let question = format!("{} {}", PROMPT_SYMBOL.purple(), question.bold()); let mut confirm = Confirm::new(); confirm.with_prompt(question); + confirm.wait_for_newline(true); match p_default { PromptDefault::Yes => { @@ -108,7 +116,11 @@ impl LogHandler { } PromptDefault::None => {} } - confirm.interact().unwrap() + self.suspend(); + let result = confirm.interact().unwrap(); + self.unsuspend(); + + result } pub fn print_list, T: Display>(&self, list: I, separator: &str) { @@ -136,6 +148,33 @@ impl LogHandler { self.set_output_type(OutputType::Stdout); } + pub fn suspend(&self) { + let mut output_type = self.output_type.write(); + let mut old_output_type = OutputType::Stdout; + mem::swap(&mut *output_type, &mut old_output_type); + + (*output_type) = OutputType::Buffer { + buffer: Arc::new(Mutex::new(Vec::new())), + suspended: Box::new(old_output_type), + } + } + + pub fn unsuspend(&self) { + let mut buffered = Vec::new(); + { + let mut output_type = self.output_type.write(); + let mut old_output_type = OutputType::Stdout; + mem::swap(&mut *output_type, &mut old_output_type); + + if let OutputType::Buffer { buffer, suspended } = old_output_type { + (*output_type) = *suspended; + buffered = mem::take(&mut *buffer.lock()); + } + } + + buffered.into_iter().for_each(|msg| self.log(msg)); + } + /// Creates a new progress spinner and registers it on the log handler #[tracing::instrument(level = "trace", skip_all)] pub fn new_progress_spinner(&self) -> Arc { @@ -170,8 +209,24 @@ impl LogHandler { /// Sets the output type of the log handler to either stdout/stderr or a progress bar #[tracing::instrument(level = "trace", skip_all)] - pub fn set_output_type(&self, output: OutputType) { - (*self.output_type.write()) = output; + pub fn set_output_type(&self, mut output: OutputType) { + { + let mut output_type = self.output_type.write(); + mem::swap(&mut *output_type, &mut output); + } + + match &mut output { + OutputType::MultiProgress(mp) => mp.set_draw_target(ProgressDrawTarget::hidden()), + OutputType::Progress(p) => p.set_draw_target(ProgressDrawTarget::hidden()), + OutputType::Buffer { + buffer, + suspended: _, + } => { + let buffered = mem::take(&mut *buffer.lock()); + buffered.into_iter().for_each(|c| self.log(c)); + } + _ => {} + } } #[tracing::instrument(level = "trace", skip_all)] @@ -207,6 +262,10 @@ impl LogHandler { let _ = m.println(msg); } OutputType::Progress(p) => p.println(msg), + OutputType::Buffer { + buffer, + suspended: _, + } => buffer.lock().push(msg), }; } } diff --git a/src/logging/mod.rs b/src/logging/mod.rs index c0086fd..16bf270 100644 --- a/src/logging/mod.rs +++ b/src/logging/mod.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use lazy_static::lazy_static; use tracing::Level; +use tracing_error::ErrorLayer; use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; use tracing_subscriber::Registry; @@ -57,7 +58,9 @@ pub fn init_logger(verbosity: Verbosity) { logger.set_uwu_enabled(uwu_enabled()); let ame_layer = AmeFormatLayer::new(logger); - let subscriber = Registry::default().with(ame_layer); + let subscriber = Registry::default() + .with(ErrorLayer::default()) + .with(ame_layer); tracing::subscriber::set_global_default(subscriber).unwrap(); } diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index fffa7ca..9044cc3 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -123,12 +123,7 @@ pub async fn aur_install(packages: Vec, options: Options) { .await .silent_unwrap(AppExitCode::RpcError); - if !print_dependency_list(&dependencies) && !options.noconfirm { - get_logger().print_newline(); - if !prompt!(default yes, "Do you want to install those dependencies?") { - cancelled!(); - } - } + print_dependency_list(&dependencies); get_logger().new_multi_progress(); let contexts = future::try_join_all( @@ -167,8 +162,8 @@ pub async fn aur_install(packages: Vec, options: Options) { tracing::info!("Installing repo dependencies"); PacmanInstallBuilder::default() .as_deps(true) - .no_confirm(true) .packages(repo_dependencies) + .no_confirm(options.noconfirm) .install() .await .silent_unwrap(AppExitCode::PacmanError); @@ -183,7 +178,7 @@ pub async fn aur_install(packages: Vec, options: Options) { tracing::debug!("aur install batches: {batches:?}"); for batch in batches { - install_aur_deps(batch).await.unwrap(); + install_aur_deps(batch, options.noconfirm).await.unwrap(); } } @@ -204,7 +199,7 @@ pub async fn aur_install(packages: Vec, options: Options) { } #[tracing::instrument(level = "trace")] -async fn install_aur_deps(deps: Vec) -> AppResult<()> { +async fn install_aur_deps(deps: Vec, no_confirm: bool) -> AppResult<()> { get_logger().new_multi_progress(); let dep_contexts = future::try_join_all( @@ -219,7 +214,9 @@ async fn install_aur_deps(deps: Vec) -> AppResult<()> { if let Err(e) = build_and_install( dep_contexts, MakePkgBuilder::default().as_deps(true), - PacmanInstallBuilder::default().as_deps(true), + PacmanInstallBuilder::default() + .no_confirm(no_confirm) + .as_deps(true), ) .await { @@ -436,25 +433,15 @@ async fn show_and_log_stdio( Ok(()) } -#[tracing::instrument(level = "trace")] -async fn review_build_log(log_file: &Path) -> AppResult<()> { - if prompt!(default yes, "Do you want to review the build log?") { - PagerBuilder::default().path(log_file).open().await?; - } - - Ok(()) -} - +#[tracing::instrument(level = "trace", skip_all)] async fn handle_build_error>(err: E) -> AppResult<()> { get_logger().reset_output_type(); let err = err.into(); match &err { AppError::BuildError { pkg_name } => { - get_logger().print_newline(); tracing::error!("Failed to build package {pkg_name}!"); let log_path = get_cache_dir().join(format!("{pkg_name}-build.log")); - get_logger().reset_output_type(); review_build_log(&log_path).await?; Err(err) @@ -467,3 +454,12 @@ async fn handle_build_error>(err: E) -> AppResult<()> { } } } + +#[tracing::instrument(level = "trace")] +async fn review_build_log(log_file: &Path) -> AppResult<()> { + if prompt!(default yes, "Do you want to review the build log?") { + PagerBuilder::default().path(log_file).open().await?; + } + + Ok(()) +} diff --git a/src/operations/clean.rs b/src/operations/clean.rs index 4444ead..0ee0744 100644 --- a/src/operations/clean.rs +++ b/src/operations/clean.rs @@ -2,8 +2,11 @@ use tokio::process::Command; use crate::crash; use crate::internal::commands::ShellCommand; + use crate::internal::error::SilentUnwrap; use crate::internal::exit_code::AppExitCode; + +use crate::internal::utils::get_cache_dir; use crate::prompt; use crate::Options; @@ -71,13 +74,13 @@ pub async fn clean(options: Options) { // Prompt the user whether to clear the Amethyst cache let clear_ame_cache = prompt!(default no, "Clear Amethyst's internal PKGBUILD cache?"); if clear_ame_cache { - // Remove ~/.cache/ame - Command::new("rm") - .arg("-rf") - .arg("~/.cache/ame") - .spawn() - .unwrap() - .wait() + let cache_dir = get_cache_dir(); + ShellCommand::rm() + .arg(cache_dir) + .arg("-r") + .arg("-f") + .elevated() + .wait_success() .await .unwrap(); } From 7541e74e876ccc66225e497b74265738ff0ffc53 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 12:50:12 +0200 Subject: [PATCH 22/43] Improve querying of installed packages --- src/builder/pacman.rs | 6 ++++++ src/main.rs | 32 +++++++++++--------------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/builder/pacman.rs b/src/builder/pacman.rs index 2e17ee0..b5ab95f 100644 --- a/src/builder/pacman.rs +++ b/src/builder/pacman.rs @@ -83,6 +83,7 @@ enum PacmanQueryType { Foreign, All, Info, + Native, } #[derive(Clone, Copy, Debug)] @@ -116,6 +117,10 @@ impl PacmanQueryBuilder { Self::new(PacmanQueryType::Foreign) } + pub fn native() -> Self { + Self::new(PacmanQueryType::Native) + } + pub fn info() -> Self { Self::new(PacmanQueryType::Info) } @@ -169,6 +174,7 @@ impl PacmanQueryBuilder { command = match self.query_type { PacmanQueryType::Foreign => command.arg("-m"), PacmanQueryType::Info => command.arg("-i"), + PacmanQueryType::Native => command.arg("-n"), PacmanQueryType::All => command, }; diff --git a/src/main.rs b/src/main.rs index 51d629b..821d7f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use args::{Args, GenCompArgs, InfoArgs}; -use builder::pacman::PacmanQueryBuilder; +use builder::pacman::{PacmanColor, PacmanQueryBuilder}; use clap::Parser; use internal::commands::ShellCommand; use internal::error::SilentUnwrap; @@ -127,29 +127,19 @@ async fn cmd_search(args: SearchArgs, options: Options) { #[tracing::instrument(level = "trace")] async fn cmd_query(args: QueryArgs) { - if args.aur { - ShellCommand::pacman() - .arg("-Qm") - .wait_success() + if args.repo || !args.aur { + tracing::info!("Installed Repo Packages: "); + PacmanQueryBuilder::native() + .color(PacmanColor::Always) + .query() .await .silent_unwrap(AppExitCode::PacmanError); } - if args.repo { - ShellCommand::pacman() - .arg("-Qn") - .wait_success() - .await - .silent_unwrap(AppExitCode::PacmanError); - } - if !args.repo && !args.aur { - ShellCommand::pacman() - .arg("-Qn") - .wait_success() - .await - .silent_unwrap(AppExitCode::PacmanError); - ShellCommand::pacman() - .arg("-Qm") - .wait_success() + if args.aur || !args.repo { + tracing::info!("Installed AUR Packages: "); + PacmanQueryBuilder::foreign() + .color(PacmanColor::Always) + .query() .await .silent_unwrap(AppExitCode::PacmanError); } From 119f4ef8fd744d213ba89d68070017d20a5dce97 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 13:29:44 +0200 Subject: [PATCH 23/43] Change build behaviour to build all packages and handle failed ones later --- src/main.rs | 2 -- src/operations/aur_install.rs | 33 ++++++++++++++++----------------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 821d7f0..89d3371 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,8 +65,6 @@ async fn cmd_install(args: InstallArgs, options: Options) { let packages = args.packages; let sorted = sort(&packages, options).await; - // info!("Attempting to install packages: {}", packages.join(", ")); - if !sorted.repo.is_empty() { operations::install(sorted.repo, options).await; } diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 9044cc3..872e06c 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -211,17 +211,14 @@ async fn install_aur_deps(deps: Vec, no_confirm: bool) -> AppResult get_logger().reset_output_type(); - if let Err(e) = build_and_install( + build_and_install( dep_contexts, MakePkgBuilder::default().as_deps(true), PacmanInstallBuilder::default() .no_confirm(no_confirm) .as_deps(true), ) - .await - { - handle_build_error(e).await?; - } + .await?; get_logger().reset_output_type(); Ok(()) @@ -235,12 +232,19 @@ async fn build_and_install( ) -> AppResult<()> { tracing::info!("Building packages"); get_logger().new_multi_progress(); - let ctxs = future::try_join_all( + let results = future::join_all( ctxs.into_iter() .map(|ctx| build_package(ctx, make_opts.clone())), ) - .await?; + .await; get_logger().reset_output_type(); + let mut ctxs = Vec::new(); + for result in results { + match result { + Ok(ctx) => ctxs.push(ctx), + Err(e) => handle_build_error(e).await?, + } + } tracing::info!("Built {} packages", ctxs.len()); tracing::info!("Installing packages..."); @@ -334,10 +338,10 @@ async fn build_package( handle.abort(); if !exit_status.success() { - pb.set_message(format!( + pb.finish_with_message(format!( "{}: {}", + pkg_name.clone().bold(), "Build failed!".red(), - pkg_name.clone().bold() )); return Err(AppError::BuildError { pkg_name: pkg_name.to_owned(), @@ -438,20 +442,15 @@ async fn handle_build_error>(err: E) -> AppResult<()> { get_logger().reset_output_type(); let err = err.into(); - match &err { + match err { AppError::BuildError { pkg_name } => { tracing::error!("Failed to build package {pkg_name}!"); let log_path = get_cache_dir().join(format!("{pkg_name}-build.log")); review_build_log(&log_path).await?; - Err(err) - } - e => { - crash!( - AppExitCode::MakePkgError, - "An error occurred while building: {e}" - ) + Ok(()) } + e => Err(e), } } From 8b2a62969e194934a9f400dcdf85e5aeaf496a5d Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 17:23:00 +0200 Subject: [PATCH 24/43] Separate multiselect and confirm from logger --- src/interact/macros.rs | 21 ++++ src/interact/mod.rs | 13 +++ src/interact/multi_select.rs | 43 +++++++ src/interact/prompt.rs | 55 +++++++++ src/interact/theme.rs | 211 ++++++++++++++++++++++++++++++++++ src/internal/detect.rs | 18 ++- src/internal/utils.rs | 21 ---- src/logging/handler.rs | 35 ------ src/main.rs | 1 + src/operations/aur_install.rs | 20 +++- 10 files changed, 376 insertions(+), 62 deletions(-) create mode 100644 src/interact/macros.rs create mode 100644 src/interact/mod.rs create mode 100644 src/interact/multi_select.rs create mode 100644 src/interact/prompt.rs create mode 100644 src/interact/theme.rs diff --git a/src/interact/macros.rs b/src/interact/macros.rs new file mode 100644 index 0000000..5804803 --- /dev/null +++ b/src/interact/macros.rs @@ -0,0 +1,21 @@ +#[macro_export] +/// Macro for prompting the user with a yes/no question. +macro_rules! prompt { + (default yes, $($arg:tt)+) => { + $crate::interact::Interact::interact($crate::interact::Prompt::new(format!($($arg)+)).default_yes()) + }; + (default no, $($arg:tt)+) => { + $crate::interact::Interact::interact($crate::interact::Prompt::new(format!($($arg)+)).default_no()) + }; + (no default, $($arg:tt)+) => { + $crate::interact::Interact::interact($crate::interact::Prompt::new(format!($($arg)+))) + } +} + +#[macro_export] +/// Macro for prompting the user with a multi select +macro_rules! multi_select { + ($items:expr, $($arg:tt)+) => { + $crate::interact::Interact::interact($crate::interact::MultiSelect::new(format!($($arg)+)).items($items)) + } +} diff --git a/src/interact/mod.rs b/src/interact/mod.rs new file mode 100644 index 0000000..383ddd7 --- /dev/null +++ b/src/interact/mod.rs @@ -0,0 +1,13 @@ +pub mod macros; +mod multi_select; +mod prompt; +mod theme; + +pub use multi_select::MultiSelect; +pub use prompt::Prompt; + +pub trait Interact { + type Result; + + fn interact(&mut self) -> Self::Result; +} diff --git a/src/interact/multi_select.rs b/src/interact/multi_select.rs new file mode 100644 index 0000000..ea3055c --- /dev/null +++ b/src/interact/multi_select.rs @@ -0,0 +1,43 @@ +use std::mem; + +use crate::logging::get_logger; + +use super::{theme::AmeTheme, Interact}; + +pub struct MultiSelect { + prompt: String, + items: Vec, +} + +impl MultiSelect { + /// Creates a new multi select prompt + pub fn new(prompt: S) -> Self { + Self { + prompt: prompt.to_string(), + items: Vec::new(), + } + } + + /// Adds/replaces the items of this multi select + pub fn items, S: ToString>(&mut self, items: I) -> &mut Self { + self.items = items.into_iter().map(|i| i.to_string()).collect(); + + self + } +} + +impl Interact for MultiSelect { + type Result = Vec; + + fn interact(&mut self) -> Self::Result { + get_logger().suspend(); + let selection = dialoguer::MultiSelect::with_theme(AmeTheme::get()) + .with_prompt(mem::take(&mut self.prompt)) + .items(&self.items) + .interact() + .unwrap(); + get_logger().unsuspend(); + + selection + } +} diff --git a/src/interact/prompt.rs b/src/interact/prompt.rs new file mode 100644 index 0000000..f9b14fa --- /dev/null +++ b/src/interact/prompt.rs @@ -0,0 +1,55 @@ +use std::mem; + +use crate::logging::get_logger; + +use super::{theme::AmeTheme, Interact}; + +pub struct Prompt { + question: String, + default_yes: Option, +} + +impl Prompt { + /// Creates a new prompt + pub fn new(question: Q) -> Self { + Self { + question: question.to_string(), + default_yes: None, + } + } + + /// Sets the prompt to default to yes + pub fn default_yes(&mut self) -> &mut Self { + self.default_yes = Some(true); + + self + } + + /// Sets the prompt to default to yes + pub fn default_no(&mut self) -> &mut Self { + self.default_yes = Some(false); + + self + } +} + +impl Interact for Prompt { + type Result = bool; + + fn interact(&mut self) -> Self::Result { + let mut dialog = dialoguer::Confirm::with_theme(AmeTheme::get()); + + if let Some(def) = self.default_yes.take() { + dialog.default(def); + } + + dialog + .with_prompt(mem::take(&mut self.question)) + .wait_for_newline(true); + get_logger().suspend(); + let result = dialog.interact().unwrap(); + get_logger().unsuspend(); + + result + } +} diff --git a/src/interact/theme.rs b/src/interact/theme.rs new file mode 100644 index 0000000..9a4b412 --- /dev/null +++ b/src/interact/theme.rs @@ -0,0 +1,211 @@ +use crossterm::style::Stylize; +use dialoguer::theme::Theme; + +use crate::internal::utils::wrap_text; + +const ERR_SYMBOL: &str = "X"; +const PROMPT_SYMBOL: &str = "?"; + +pub struct AmeTheme; + +impl AmeTheme { + pub fn get() -> &'static Self { + static AME_THEME: AmeTheme = AmeTheme; + &AME_THEME + } +} + +impl Theme for AmeTheme { + fn format_prompt(&self, f: &mut dyn std::fmt::Write, prompt: &str) -> std::fmt::Result { + let prompt = wrap_text(prompt).join("\n "); + write!(f, "{} {}:", PROMPT_SYMBOL.magenta(), prompt.bold()) + } + + fn format_error(&self, f: &mut dyn std::fmt::Write, err: &str) -> std::fmt::Result { + write!(f, "{} error: {}", ERR_SYMBOL.red(), err) + } + + fn format_confirm_prompt( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + default: Option, + ) -> std::fmt::Result { + let prompt = wrap_text(prompt).join("\n "); + if !prompt.is_empty() { + write!(f, "{} {} ", PROMPT_SYMBOL.magenta(), &prompt.bold())?; + } + match default { + None => write!(f, "[y/n] ")?, + Some(true) => write!(f, "[{}/n] ", "Y".bold())?, + Some(false) => write!(f, "[y/{}] ", "N".bold())?, + } + Ok(()) + } + + fn format_confirm_prompt_selection( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + selection: Option, + ) -> std::fmt::Result { + let prompt = wrap_text(prompt).join("\n "); + let selection = selection.map(|b| if b { "yes" } else { "no" }); + + match selection { + Some(selection) if prompt.is_empty() => { + write!(f, "{}", selection.italic()) + } + Some(selection) => { + write!(f, "{} {}", &prompt.bold(), selection.italic()) + } + None if prompt.is_empty() => Ok(()), + None => { + write!(f, "{}", &prompt.bold()) + } + } + } + + fn format_input_prompt( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + default: Option<&str>, + ) -> std::fmt::Result { + match default { + Some(default) if prompt.is_empty() => { + write!(f, "{} [{}]: ", PROMPT_SYMBOL.magenta(), default) + } + Some(default) => write!( + f, + "{} {} [{}]: ", + PROMPT_SYMBOL.magenta(), + prompt.bold(), + default + ), + None => write!(f, "{} {}: ", PROMPT_SYMBOL.magenta(), prompt.bold()), + } + } + + fn format_input_prompt_selection( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + sel: &str, + ) -> std::fmt::Result { + write!( + f, + "{} {}: {}", + PROMPT_SYMBOL.magenta(), + prompt.bold(), + sel.italic() + ) + } + + fn format_password_prompt( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + ) -> std::fmt::Result { + self.format_input_prompt(f, prompt, None) + } + + fn format_password_prompt_selection( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + ) -> std::fmt::Result { + self.format_input_prompt_selection(f, prompt, "[hidden]") + } + + fn format_select_prompt(&self, f: &mut dyn std::fmt::Write, prompt: &str) -> std::fmt::Result { + self.format_prompt(f, prompt) + } + + fn format_select_prompt_selection( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + sel: &str, + ) -> std::fmt::Result { + self.format_input_prompt_selection(f, prompt, sel) + } + + fn format_multi_select_prompt( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + ) -> std::fmt::Result { + self.format_prompt(f, prompt) + } + + fn format_sort_prompt(&self, f: &mut dyn std::fmt::Write, prompt: &str) -> std::fmt::Result { + self.format_prompt(f, prompt) + } + + fn format_multi_select_prompt_selection( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + selections: &[&str], + ) -> std::fmt::Result { + write!(f, "{}: ", prompt.bold())?; + if selections.is_empty() { + write!(f, "{}", "No selections".italic())?; + } else { + for (idx, sel) in selections.iter().enumerate() { + write!(f, "{}{}", if idx == 0 { "" } else { ", " }, sel)?; + } + } + Ok(()) + } + + fn format_sort_prompt_selection( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + selections: &[&str], + ) -> std::fmt::Result { + self.format_multi_select_prompt_selection(f, prompt, selections) + } + + fn format_select_prompt_item( + &self, + f: &mut dyn std::fmt::Write, + text: &str, + active: bool, + ) -> std::fmt::Result { + write!(f, "{} {}", if active { ">" } else { " " }, text) + } + + fn format_multi_select_prompt_item( + &self, + f: &mut dyn std::fmt::Write, + text: &str, + checked: bool, + active: bool, + ) -> std::fmt::Result { + let active_symbol = if active { ">" } else { " " }; + let checked_symbol = if checked { "x" } else { " " }.magenta(); + write!(f, "{active_symbol} [{checked_symbol}] {text}") + } + + fn format_sort_prompt_item( + &self, + f: &mut dyn std::fmt::Write, + text: &str, + picked: bool, + active: bool, + ) -> std::fmt::Result { + write!( + f, + "{} {}", + match (picked, active) { + (true, true) => "> [x]", + (false, true) => "> [ ]", + (_, false) => " [ ]", + }, + text + ) + } +} diff --git a/src/internal/detect.rs b/src/internal/detect.rs index 3b667f9..9ea88d3 100644 --- a/src/internal/detect.rs +++ b/src/internal/detect.rs @@ -1,3 +1,5 @@ +use crossterm::style::Stylize; + use crate::internal::commands::ShellCommand; use crate::internal::config; use crate::internal::error::SilentUnwrap; @@ -31,13 +33,19 @@ pub async fn detect() { // If pacnew files are found, warn the user and prompt to pacdiff if pacnew.is_empty() { - pb.finish_with_message("No pacnew files found"); + pb.finish_with_message("No .pacnew files found"); get_logger().reset_output_type(); } else { - pb.finish_with_message("It appears that at least one program you have installed / upgraded has installed a .pacnew config file. These are created when you have modified a program's configuration, and a package upgrade could not automatically merge the new file."); + pb.finish_with_message("pacnew files found"); get_logger().reset_output_type(); + tracing::info!( + "It appears that at least one program you have installed / upgraded has installed a .pacnew config file. \ + These are created when you have modified a program's configuration, and a package upgrade could not automatically merge the new file. \ + You can deal with those files by running {}.", + "sudo pacdiff".reset().magenta() + ); - let choice = prompt!(default no, "Would you like Amethyst to run pacdiff to deal with this? You can always deal with this later by running `sudo pacdiff`"); + let choice = prompt!(default no, "Would you like to run pacdiff now?"); if choice { let config = config::read(); if config.base.pacdiff_warn { @@ -49,8 +57,8 @@ pub async fn detect() { } else { tracing::warn!("Pacdiff uses vimdiff by default to edit files for merging. You can focus panes by mousing over them and pressing left click, and scroll up and down using your mouse's scroll wheel (or the arrow keys). To exit vimdiff, press the following key combination: ESC, :qa!, ENTER"); tracing::warn!("You can surpress this warning in the future by setting `pacdiff_warn` to \"false\" in ~/.config/ame/config.toml"); - let cont = prompt!(default no, "Continue?"); - if cont { + + if prompt!(default no, "Continue?") { ShellCommand::pacdiff() .elevated() .wait() diff --git a/src/internal/utils.rs b/src/internal/utils.rs index 21a2282..4f945b9 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -6,8 +6,6 @@ use directories::ProjectDirs; use textwrap::wrap; use crate::internal::exit_code::AppExitCode; -use crate::logging::get_logger; -use crate::logging::handler::PromptDefault; use lazy_static::lazy_static; use super::error::{AppError, SilentUnwrap}; @@ -31,31 +29,12 @@ macro_rules! cancelled { }; } -#[macro_export] -/// Macro for prompting the user with a yes/no question. -macro_rules! prompt { - (default yes, $($arg:tt)+) => { - $crate::internal::utils::prompt_yn(format!($($arg)+), $crate::logging::handler::PromptDefault::Yes) - }; - (default no, $($arg:tt)+) => { - $crate::internal::utils::prompt_yn(format!($($arg)+), $crate::logging::handler::PromptDefault::No) - }; - (no default, $($arg:tt)+) => { - $crate::internal::utils::prompt_yn(format!($($arg)+), $crate::logging::handler::PromptDefault::None) - } -} - /// Logs a message and exits the program with the given exit code. pub fn log_and_crash(msg: String, exit_code: AppExitCode) -> ! { tracing::error!(msg); exit(exit_code as i32); } -/// Prompts the user for a yes/no answer. -pub fn prompt_yn(question: String, prompt_default: PromptDefault) -> bool { - get_logger().prompt(question, prompt_default) -} - pub fn get_cache_dir() -> &'static Path { lazy_static! { static ref CACHE_DIR: &'static Path = create_if_not_exist(get_directories().cache_dir()); diff --git a/src/logging/handler.rs b/src/logging/handler.rs index cbf56df..f3cbcb0 100644 --- a/src/logging/handler.rs +++ b/src/logging/handler.rs @@ -9,7 +9,6 @@ use std::{ }; use crate::{internal::utils::wrap_text, uwu}; -use dialoguer::Confirm; use super::Verbosity; @@ -18,7 +17,6 @@ const ERR_SYMBOL: &str = "X"; const WARN_SYMBOL: &str = "!"; const DEBUG_SYMBOL: &str = "⌘"; const TRACE_SYMBOL: &str = "🗲"; -const PROMPT_SYMBOL: &str = "?"; pub struct LogHandler { level: Arc>, @@ -48,14 +46,6 @@ pub enum OutputType { }, } -#[allow(unused)] -#[derive(Clone, Copy, Debug)] -pub enum PromptDefault { - Yes, - No, - None, -} - impl LogHandler { pub fn log_error(&self, msg: String) { if self.is_loggable(Verbosity::Error) { @@ -98,31 +88,6 @@ impl LogHandler { } } - /// Prompts the user with a question and a default selection - #[tracing::instrument(level = "trace", skip(self))] - pub fn prompt(&self, question: String, p_default: PromptDefault) -> bool { - let question = self.preformat_msg(question); - let question = format!("{} {}", PROMPT_SYMBOL.purple(), question.bold()); - let mut confirm = Confirm::new(); - confirm.with_prompt(question); - confirm.wait_for_newline(true); - - match p_default { - PromptDefault::Yes => { - confirm.default(true); - } - PromptDefault::No => { - confirm.default(false); - } - PromptDefault::None => {} - } - self.suspend(); - let result = confirm.interact().unwrap(); - self.unsuspend(); - - result - } - pub fn print_list, T: Display>(&self, list: I, separator: &str) { let lines = list .into_iter() diff --git a/src/main.rs b/src/main.rs index 89d3371..70a703f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use std::str::FromStr; mod args; mod builder; +mod interact; mod internal; mod logging; mod operations; diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 872e06c..4da6f36 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -3,6 +3,7 @@ use aur_rpc::PackageInfo; use crossterm::style::Stylize; use futures::future; use indicatif::ProgressBar; + use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -22,7 +23,7 @@ use crate::internal::utils::{get_cache_dir, wrap_text}; use crate::logging::get_logger; use crate::logging::output::{print_aur_package_list, print_dependency_list}; use crate::logging::piped_stdio::StdioReader; -use crate::{cancelled, crash, prompt, Options}; +use crate::{cancelled, crash, multi_select, prompt, Options}; #[derive(Debug)] pub struct BuildContext { @@ -88,6 +89,7 @@ pub async fn aur_install(packages: Vec, options: Options) { tracing::debug!("package info = {package_info:?}"); if package_info.len() != packages.len() { + pb.finish_with_message("Couldn't find all packages".red().to_string()); let mut not_found = packages.clone(); package_info .iter() @@ -99,11 +101,19 @@ pub async fn aur_install(packages: Vec, options: Options) { ); } + pb.finish_with_message("All packages found".green().to_string()); get_logger().reset_output_type(); pb.finish_with_message("Found all packages in the aur"); print_aur_package_list(&package_info); + if !options.noconfirm { + let to_review = multi_select!(&packages, "Select packages to review"); + for pkg in to_review.into_iter().filter_map(|i| packages.get(i)) { + review_pkgbuild(pkg).await.unwrap(); + } + } + if !options.noconfirm && !prompt!(default yes, "Do you want to install those packages from the AUR?") { @@ -462,3 +472,11 @@ async fn review_build_log(log_file: &Path) -> AppResult<()> { Ok(()) } + +#[tracing::instrument(level = "trace")] +async fn review_pkgbuild(package: &str) -> AppResult<()> { + let pkgbuild_path = get_cache_dir().join(package).join("PKGBUILD"); + PagerBuilder::default().path(pkgbuild_path).open().await?; + + Ok(()) +} From 0f3133bf16b0eda50cce67a0539fd596a3bd54d3 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 17:26:42 +0200 Subject: [PATCH 25/43] Rename prompts and stuff to ame prompt Signed-off-by: trivernis --- src/interact/macros.rs | 8 ++++---- src/interact/mod.rs | 4 ++-- src/interact/multi_select.rs | 6 +++--- src/interact/prompt.rs | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/interact/macros.rs b/src/interact/macros.rs index 5804803..544f777 100644 --- a/src/interact/macros.rs +++ b/src/interact/macros.rs @@ -2,13 +2,13 @@ /// Macro for prompting the user with a yes/no question. macro_rules! prompt { (default yes, $($arg:tt)+) => { - $crate::interact::Interact::interact($crate::interact::Prompt::new(format!($($arg)+)).default_yes()) + $crate::interact::Interact::interact($crate::interact::AmePrompt::new(format!($($arg)+)).default_yes()) }; (default no, $($arg:tt)+) => { - $crate::interact::Interact::interact($crate::interact::Prompt::new(format!($($arg)+)).default_no()) + $crate::interact::Interact::interact($crate::interact::AmePrompt::new(format!($($arg)+)).default_no()) }; (no default, $($arg:tt)+) => { - $crate::interact::Interact::interact($crate::interact::Prompt::new(format!($($arg)+))) + $crate::interact::Interact::interact($crate::interact::AmePrompt::new(format!($($arg)+))) } } @@ -16,6 +16,6 @@ macro_rules! prompt { /// Macro for prompting the user with a multi select macro_rules! multi_select { ($items:expr, $($arg:tt)+) => { - $crate::interact::Interact::interact($crate::interact::MultiSelect::new(format!($($arg)+)).items($items)) + $crate::interact::Interact::interact($crate::interact::AmeMultiSelect::new(format!($($arg)+)).items($items)) } } diff --git a/src/interact/mod.rs b/src/interact/mod.rs index 383ddd7..a48be3d 100644 --- a/src/interact/mod.rs +++ b/src/interact/mod.rs @@ -3,8 +3,8 @@ mod multi_select; mod prompt; mod theme; -pub use multi_select::MultiSelect; -pub use prompt::Prompt; +pub use multi_select::AmeMultiSelect; +pub use prompt::AmePrompt; pub trait Interact { type Result; diff --git a/src/interact/multi_select.rs b/src/interact/multi_select.rs index ea3055c..d656c46 100644 --- a/src/interact/multi_select.rs +++ b/src/interact/multi_select.rs @@ -4,12 +4,12 @@ use crate::logging::get_logger; use super::{theme::AmeTheme, Interact}; -pub struct MultiSelect { +pub struct AmeMultiSelect { prompt: String, items: Vec, } -impl MultiSelect { +impl AmeMultiSelect { /// Creates a new multi select prompt pub fn new(prompt: S) -> Self { Self { @@ -26,7 +26,7 @@ impl MultiSelect { } } -impl Interact for MultiSelect { +impl Interact for AmeMultiSelect { type Result = Vec; fn interact(&mut self) -> Self::Result { diff --git a/src/interact/prompt.rs b/src/interact/prompt.rs index f9b14fa..64fb768 100644 --- a/src/interact/prompt.rs +++ b/src/interact/prompt.rs @@ -4,12 +4,12 @@ use crate::logging::get_logger; use super::{theme::AmeTheme, Interact}; -pub struct Prompt { +pub struct AmePrompt { question: String, default_yes: Option, } -impl Prompt { +impl AmePrompt { /// Creates a new prompt pub fn new(question: Q) -> Self { Self { @@ -33,7 +33,7 @@ impl Prompt { } } -impl Interact for Prompt { +impl Interact for AmePrompt { type Result = bool; fn interact(&mut self) -> Self::Result { From 98cc3e2388d8e0f8a4a9a656715fe6b441bfd1d5 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 17:40:27 +0200 Subject: [PATCH 26/43] Add confirmation for reinstalling of already installed packages --- src/builder/pacman.rs | 11 ++++++++++- src/logging/output.rs | 36 +++++++++++++++++++++++++++++------ src/operations/aur_install.rs | 11 +++++++---- 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/src/builder/pacman.rs b/src/builder/pacman.rs index b5ab95f..3740dca 100644 --- a/src/builder/pacman.rs +++ b/src/builder/pacman.rs @@ -8,6 +8,7 @@ pub struct PacmanInstallBuilder { files: Vec, as_deps: bool, no_confirm: bool, + needed: bool, } impl PacmanInstallBuilder { @@ -44,6 +45,12 @@ impl PacmanInstallBuilder { self } + pub fn needed(mut self, needed: bool) -> Self { + self.needed = needed; + + self + } + #[tracing::instrument(level = "debug")] pub async fn install(self) -> AppResult<()> { let mut command = ShellCommand::pacman().elevated(); @@ -61,9 +68,11 @@ impl PacmanInstallBuilder { if self.as_deps { command = command.arg("--asdeps") } + if self.needed { + command = command.arg("--needed") + } command - .arg("--needed") .args(self.packages) .args(self.files) .wait_success() diff --git a/src/logging/output.rs b/src/logging/output.rs index 56e4d02..7453285 100644 --- a/src/logging/output.rs +++ b/src/logging/output.rs @@ -1,12 +1,14 @@ +use std::collections::{HashMap, HashSet}; + use aur_rpc::PackageInfo; use console::Alignment; use crossterm::style::Stylize; -use crate::internal::dependencies::DependencyInformation; +use crate::{builder::pacman::PacmanQueryBuilder, internal::dependencies::DependencyInformation}; use super::get_logger; -pub fn print_dependency_list(dependencies: &[DependencyInformation]) -> bool { +pub async fn print_dependency_list(dependencies: &[DependencyInformation]) -> bool { let (deps_repo, makedeps_repo, deps_aur, makedeps_aur) = dependencies .iter() .map(|d| { @@ -39,7 +41,7 @@ pub fn print_dependency_list(dependencies: &[DependencyInformation]) -> bool { if !deps_aur.is_empty() { get_logger().print_newline(); tracing::info!("AUR dependencies"); - print_aur_package_list(&deps_aur); + print_aur_package_list(&deps_aur).await; empty = false; } @@ -53,23 +55,45 @@ pub fn print_dependency_list(dependencies: &[DependencyInformation]) -> bool { if !makedeps_aur.is_empty() { get_logger().print_newline(); tracing::info!("AUR make dependencies"); - print_aur_package_list(&makedeps_aur); + print_aur_package_list(&makedeps_aur).await; empty = false; } empty } -pub fn print_aur_package_list(packages: &[PackageInfo]) { +pub async fn print_aur_package_list(packages: &[PackageInfo]) -> bool { + let pkgs = packages + .iter() + .map(|p| p.metadata.name.clone()) + .collect::>(); + let installed = PacmanQueryBuilder::all() + .query_with_output() + .await + .unwrap() + .into_iter() + .filter(|p| pkgs.contains(&p.name)) + .map(|p| (p.name.clone(), p)) + .collect::>(); + get_logger().print_list( packages.iter().map(|pkg| { format!( - "{} version {} ({} votes)", + "{} version {} ({} votes) {}", console::pad_str(&pkg.metadata.name, 30, Alignment::Left, Some("...")).bold(), pkg.metadata.version.clone().dim(), pkg.metadata.num_votes, + if installed.contains_key(&pkg.metadata.name) { + "(Installed)" + } else { + "" + } + .bold() + .magenta() ) }), "\n ", ); + + !installed.is_empty() } diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 4da6f36..840198f 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -104,8 +104,11 @@ pub async fn aur_install(packages: Vec, options: Options) { pb.finish_with_message("All packages found".green().to_string()); get_logger().reset_output_type(); - pb.finish_with_message("Found all packages in the aur"); - print_aur_package_list(&package_info); + if print_aur_package_list(&package_info).await && !options.noconfirm { + if !prompt!(default yes, "Some packages are already installed. Continue anyway?") { + cancelled!(); + } + } if !options.noconfirm { let to_review = multi_select!(&packages, "Select packages to review"); @@ -133,7 +136,7 @@ pub async fn aur_install(packages: Vec, options: Options) { .await .silent_unwrap(AppExitCode::RpcError); - print_dependency_list(&dependencies); + print_dependency_list(&dependencies).await; get_logger().new_multi_progress(); let contexts = future::try_join_all( @@ -377,7 +380,7 @@ async fn install_packages( ctx.step = BuildStep::Done; } - install_opts.files(packages).install().await?; + install_opts.files(packages).needed(false).install().await?; Ok(ctxs) } From caf2ec3de4ca21def6dddfeda6945582af7795bf Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 18:31:45 +0200 Subject: [PATCH 27/43] Add removing of make dependencies after build --- src/builder/pacman.rs | 39 ++++++++++++- src/internal/dependencies.rs | 50 +++++++++++++++++ src/logging/handler.rs | 2 +- src/logging/output.rs | 22 ++++---- src/operations/aur_install.rs | 101 ++++++++++++++++++++-------------- 5 files changed, 159 insertions(+), 55 deletions(-) diff --git a/src/builder/pacman.rs b/src/builder/pacman.rs index 3740dca..f0fa34f 100644 --- a/src/builder/pacman.rs +++ b/src/builder/pacman.rs @@ -18,8 +18,8 @@ impl PacmanInstallBuilder { .no_confirm(options.noconfirm) } - pub fn packages>(mut self, packages: I) -> Self { - let mut packages = packages.into_iter().collect(); + pub fn packages, S: ToString>(mut self, packages: I) -> Self { + let mut packages = packages.into_iter().map(|p| p.to_string()).collect(); self.packages.append(&mut packages); self @@ -230,3 +230,38 @@ impl PacmanSearchBuilder { ShellCommand::pacman().arg("-Ss").arg(self.query) } } + +#[derive(Default, Debug, Clone)] +pub struct PacmanUninstallBuilder { + packages: Vec, + no_confirm: bool, +} + +impl PacmanUninstallBuilder { + pub fn packages, S: ToString>(mut self, packages: I) -> Self { + let mut packages = packages.into_iter().map(|p| p.to_string()).collect(); + self.packages.append(&mut packages); + + self + } + + pub fn no_confirm(mut self, no_confirm: bool) -> Self { + self.no_confirm = no_confirm; + + self + } + + #[tracing::instrument(level = "trace")] + pub async fn uninstall(self) -> AppResult<()> { + let mut command = ShellCommand::pacman() + .elevated() + .arg("-R") + .args(self.packages); + + if self.no_confirm { + command = command.arg("--noconfirm"); + } + + command.wait_success().await + } +} diff --git a/src/internal/dependencies.rs b/src/internal/dependencies.rs index 2bdf884..790f63d 100644 --- a/src/internal/dependencies.rs +++ b/src/internal/dependencies.rs @@ -205,6 +205,56 @@ impl DependencyInformation { Ok(repo_deps) } + + pub fn make_depends(&self) -> Vec<&str> { + let mut make_depends = Vec::new(); + let mut aur_depends = self + .make_depends + .aur + .iter() + .map(|p| p.metadata.name.as_str()) + .collect::>(); + let mut repo_depends = self + .make_depends + .repo + .iter() + .map(String::as_str) + .collect::>(); + make_depends.append(&mut aur_depends); + make_depends.append(&mut repo_depends); + + make_depends + } + + pub fn all_aur_depends(&self) -> Vec<&PackageInfo> { + let mut depends = Vec::new(); + let mut aur_depends = self.make_depends.aur.iter().collect::>(); + let mut aur_make_depends = self.depends.aur.iter().collect::>(); + depends.append(&mut aur_depends); + depends.append(&mut aur_make_depends); + + depends + } + + pub fn all_repo_depends(&self) -> Vec<&str> { + let mut depends = Vec::new(); + let mut repo_depends = self + .make_depends + .repo + .iter() + .map(String::as_str) + .collect::>(); + let mut repo_make_depends = self + .depends + .repo + .iter() + .map(String::as_str) + .collect::>(); + depends.append(&mut repo_depends); + depends.append(&mut repo_make_depends); + + depends + } } impl Dependency { diff --git a/src/logging/handler.rs b/src/logging/handler.rs index f3cbcb0..0734b34 100644 --- a/src/logging/handler.rs +++ b/src/logging/handler.rs @@ -96,7 +96,7 @@ impl LogHandler { format!("{}{}{}", acc, separator, line) }); - let lines = wrap_text(lines).join("\n"); + let lines = wrap_text(lines).join("\n "); self.log(lines) } diff --git a/src/logging/output.rs b/src/logging/output.rs index 7453285..224a713 100644 --- a/src/logging/output.rs +++ b/src/logging/output.rs @@ -9,14 +9,14 @@ use crate::{builder::pacman::PacmanQueryBuilder, internal::dependencies::Depende use super::get_logger; pub async fn print_dependency_list(dependencies: &[DependencyInformation]) -> bool { - let (deps_repo, makedeps_repo, deps_aur, makedeps_aur) = dependencies + let (mut deps_repo, mut makedeps_repo, deps_aur, makedeps_aur) = dependencies .iter() .map(|d| { ( - d.depends.repo.clone(), - d.make_depends.repo.clone(), - d.depends.aur.clone(), - d.make_depends.aur.clone(), + d.depends.repo.iter().collect(), + d.make_depends.repo.iter().collect(), + d.depends.aur.iter().collect(), + d.make_depends.aur.iter().collect(), ) }) .fold( @@ -30,39 +30,41 @@ pub async fn print_dependency_list(dependencies: &[DependencyInformation]) -> bo acc }, ); + deps_repo.dedup(); + makedeps_repo.dedup(); let mut empty = true; if !deps_repo.is_empty() { - get_logger().print_newline(); tracing::info!("Repo dependencies"); get_logger().print_list(&deps_repo, " "); empty = false; + get_logger().print_newline(); } if !deps_aur.is_empty() { - get_logger().print_newline(); tracing::info!("AUR dependencies"); print_aur_package_list(&deps_aur).await; empty = false; + get_logger().print_newline(); } if !makedeps_repo.is_empty() { - get_logger().print_newline(); tracing::info!("Repo make dependencies"); get_logger().print_list(&makedeps_repo, " "); empty = false; + get_logger().print_newline(); } if !makedeps_aur.is_empty() { - get_logger().print_newline(); tracing::info!("AUR make dependencies"); print_aur_package_list(&makedeps_aur).await; empty = false; + get_logger().print_newline(); } empty } -pub async fn print_aur_package_list(packages: &[PackageInfo]) -> bool { +pub async fn print_aur_package_list(packages: &[&PackageInfo]) -> bool { let pkgs = packages .iter() .map(|p| p.metadata.name.clone()) diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 840198f..e0abd47 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -14,7 +14,7 @@ use tokio::task; use crate::builder::git::{GitCloneBuilder, GitPullBuilder}; use crate::builder::makepkg::MakePkgBuilder; -use crate::builder::pacman::PacmanInstallBuilder; +use crate::builder::pacman::{PacmanInstallBuilder, PacmanUninstallBuilder}; use crate::builder::pager::PagerBuilder; use crate::internal::dependencies::DependencyInformation; use crate::internal::error::{AppError, AppResult, SilentUnwrap}; @@ -54,6 +54,12 @@ impl From for BuildContext { } } +impl From<&PackageInfo> for BuildContext { + fn from(p: &PackageInfo) -> Self { + Self::from(p.to_owned()) + } +} + impl BuildContext { pub fn build_path(&self) -> AppResult<&Path> { if let BuildStep::Build(path) = &self.step { @@ -104,39 +110,35 @@ pub async fn aur_install(packages: Vec, options: Options) { pb.finish_with_message("All packages found".green().to_string()); get_logger().reset_output_type(); - if print_aur_package_list(&package_info).await && !options.noconfirm { - if !prompt!(default yes, "Some packages are already installed. Continue anyway?") { - cancelled!(); - } - } - - if !options.noconfirm { - let to_review = multi_select!(&packages, "Select packages to review"); - for pkg in to_review.into_iter().filter_map(|i| packages.get(i)) { - review_pkgbuild(pkg).await.unwrap(); - } - } - - if !options.noconfirm - && !prompt!(default yes, "Do you want to install those packages from the AUR?") + if print_aur_package_list(&package_info.iter().collect::>()).await + && !options.noconfirm + && !prompt!(default yes, "Some packages are already installed. Continue anyway?") { cancelled!(); } - tracing::info!("Downloading aur packages"); - get_logger().new_multi_progress(); + let pb = get_logger().new_progress_spinner(); + pb.set_message("Fetching package information"); - let dependencies = future::try_join_all(package_info.iter().map(|pkg| async { - get_logger().new_progress_spinner().set_message(format!( - "{}: Fetching dependencies", - pkg.metadata.name.clone().bold() - )); - DependencyInformation::for_package(pkg).await - })) + let dependencies = future::try_join_all( + package_info + .iter() + .map(|pkg| async { DependencyInformation::for_package(pkg).await }), + ) .await .silent_unwrap(AppExitCode::RpcError); + pb.finish_and_clear(); + get_logger().reset_output_type(); print_dependency_list(&dependencies).await; + + if !options.noconfirm + && !prompt!(default yes, "Do you want to install these packages and package dependencies?") + { + cancelled!(); + } + + tracing::info!("Downloading sources"); get_logger().new_multi_progress(); let contexts = future::try_join_all( @@ -151,24 +153,24 @@ pub async fn aur_install(packages: Vec, options: Options) { get_logger().reset_output_type(); tracing::info!("All sources are ready."); - let aur_dependencies: Vec = dependencies - .iter() - .flat_map(|d| { - let mut deps = d.make_depends.aur.clone(); - deps.append(&mut d.depends.aur.clone()); + if !options.noconfirm { + let to_review = multi_select!(&packages, "Select packages to review"); + for pkg in to_review.into_iter().filter_map(|i| packages.get(i)) { + review_pkgbuild(pkg).await.unwrap(); + } + if !prompt!(default yes, "Do you still want to install those packages?") { + cancelled!(); + } + } - deps - }) + let aur_dependencies: Vec<&PackageInfo> = dependencies + .iter() + .flat_map(DependencyInformation::all_aur_depends) .collect(); - let repo_dependencies: HashSet = dependencies + let repo_dependencies: HashSet<&str> = dependencies .iter() - .flat_map(|d| { - let mut repo_deps = d.make_depends.repo.clone(); - repo_deps.append(&mut d.depends.repo.clone()); - - repo_deps - }) + .flat_map(DependencyInformation::all_repo_depends) .collect(); if !repo_dependencies.is_empty() { @@ -208,11 +210,26 @@ pub async fn aur_install(packages: Vec, options: Options) { .await .silent_unwrap(AppExitCode::MakePkgError); } + let make_depends = dependencies + .iter() + .flat_map(DependencyInformation::make_depends) + .collect::>(); + if !make_depends.is_empty() + && !options.noconfirm + && prompt!(default yes, "Do you want to remove the installed make dependencies?") + { + PacmanUninstallBuilder::default() + .packages(make_depends) + .no_confirm(true) + .uninstall() + .await + .silent_unwrap(AppExitCode::PacmanError); + } tracing::info!("Done!"); } #[tracing::instrument(level = "trace")] -async fn install_aur_deps(deps: Vec, no_confirm: bool) -> AppResult<()> { +async fn install_aur_deps(deps: Vec<&PackageInfo>, no_confirm: bool) -> AppResult<()> { get_logger().new_multi_progress(); let dep_contexts = future::try_join_all( @@ -386,8 +403,8 @@ async fn install_packages( } #[tracing::instrument(level = "trace")] -fn create_dependency_batches(deps: Vec) -> Vec> { - let mut deps: HashMap = deps +fn create_dependency_batches(deps: Vec<&PackageInfo>) -> Vec> { + let mut deps: HashMap = deps .into_iter() .map(|d| (d.metadata.name.clone(), d)) .collect(); From 1751f4f39e5a28513886ae749e32e5f9087f5ed3 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 19:03:30 +0200 Subject: [PATCH 28/43] Partially fix base pkg build stuff --- src/internal/dependencies.rs | 3 +++ src/operations/aur_install.rs | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/internal/dependencies.rs b/src/internal/dependencies.rs index 790f63d..cce6eaa 100644 --- a/src/internal/dependencies.rs +++ b/src/internal/dependencies.rs @@ -76,6 +76,7 @@ impl DependencyInformation { Self::filter_fulfilled_dependencies(&mut packages_to_resolve).await?; let mut already_searched = HashSet::new(); + already_searched.insert(package.metadata.name.to_owned()); let mut dependencies = DependencyCollection::default(); while !packages_to_resolve.is_empty() { @@ -108,8 +109,10 @@ impl DependencyInformation { .iter() .filter_map(|d| Self::map_dep_to_name(d)) .collect(); + Self::filter_fulfilled_dependencies(&mut packages_to_resolve).await?; let mut already_searched = HashSet::new(); + already_searched.insert(package.metadata.name.to_owned()); let mut dependencies = DependencyCollection::default(); while !packages_to_resolve.is_empty() { diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index e0abd47..2038f1a 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -288,6 +288,7 @@ async fn build_and_install( async fn download_aur_source(mut ctx: BuildContext) -> AppResult { let pb = get_logger().new_progress_spinner(); let pkg_name = &ctx.package.metadata.name; + let base_pkg = &ctx.package.metadata.package_base; pb.set_message(format!("{}: Downloading sources", pkg_name.clone().bold())); let cache_dir = get_cache_dir(); @@ -301,7 +302,7 @@ async fn download_aur_source(mut ctx: BuildContext) -> AppResult { GitPullBuilder::default().directory(&pkg_dir).pull().await?; } else { let aur_url = crate::internal::rpc::URL; - let repository_url = format!("{aur_url}/{pkg_name}"); + let repository_url = format!("{aur_url}/{base_pkg}"); pb.set_message(format!( "{}: Cloning aur repository", pkg_name.clone().bold() @@ -409,6 +410,7 @@ fn create_dependency_batches(deps: Vec<&PackageInfo>) -> Vec> .map(|d| (d.metadata.name.clone(), d)) .collect(); let mut batches = Vec::new(); + let mut relaxed = false; while !deps.is_empty() { let mut current_batch = HashMap::new(); @@ -424,13 +426,21 @@ fn create_dependency_batches(deps: Vec<&PackageInfo>) -> Vec> .iter() .any(|d| current_batch.contains_key(d) || deps.contains_key(d)); - if !contains_dep && !contains_make_dep { + if (!contains_dep || relaxed) && contains_make_dep { deps.remove(&key); current_batch.insert(key, info); + if relaxed { + break; + } } } - batches.push(current_batch.into_iter().map(|(_, v)| v).collect()); + if current_batch.is_empty() { + relaxed = true; + } else { + batches.push(current_batch.into_iter().map(|(_, v)| v).collect()); + relaxed = false; + } } batches From 9ced074282cfd6d87bce143880bccba74c730a1d Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 19:06:42 +0200 Subject: [PATCH 29/43] Fix typo (lmao) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cb61561..9d425c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "Amethyst" version = "4.0.0" -authors = ["michal ", "axtlos ", "triveris "] +authors = ["michal ", "axtlos ", "trivernis "] edition = "2021" description = "A fast and efficient AUR helper" license-file = "LICENSE.md" From 57d331a31888d4e17847dd5e59e8bd08ee07ae41 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 19:16:30 +0200 Subject: [PATCH 30/43] Fix indentation wrapping of lists Signed-off-by: trivernis --- src/logging/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logging/handler.rs b/src/logging/handler.rs index 0734b34..f3cbcb0 100644 --- a/src/logging/handler.rs +++ b/src/logging/handler.rs @@ -96,7 +96,7 @@ impl LogHandler { format!("{}{}{}", acc, separator, line) }); - let lines = wrap_text(lines).join("\n "); + let lines = wrap_text(lines).join("\n"); self.log(lines) } From c667f5b285cd1c8367db11fa027c19d00954967f Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 20:06:41 +0200 Subject: [PATCH 31/43] Update aur-rpc --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- src/internal/error.rs | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b014408..cf9cde9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,9 +50,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] @@ -90,9 +90,9 @@ dependencies = [ [[package]] name = "aur-rpc" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34866076a1b9a1af170e30ff8bb5f3d82ab8b3c204d8f11f3b93f41d55e8f15" +checksum = "4edbe8eb8dde1e70c5969f980a5a51a6b267d86c49d99c4bd42b7d26a8bb39c5" dependencies = [ "reqwest", "serde", diff --git a/Cargo.toml b/Cargo.toml index 9d425c4..46ad32c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ serde = { version = "1.0.144", default-features = false, features = [ "derive", native-tls = { version = "0.2.10", default-features = false } libc = { version = "0.2.132", default-features = false } async-recursion = "1.0.0" -aur-rpc = "0.1.2" +aur-rpc = "0.1.3" futures = "0.3.23" tracing = "0.1.36" tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } diff --git a/src/internal/error.rs b/src/internal/error.rs index f4863a7..ad4b01a 100644 --- a/src/internal/error.rs +++ b/src/internal/error.rs @@ -65,7 +65,10 @@ impl SilentUnwrap for AppResult { fn silent_unwrap(self, exit_code: AppExitCode) -> T { match self { Ok(val) => val, - Err(_) => crash!(exit_code, "An error occurred"), + Err(e) => { + tracing::debug!("{e}"); + crash!(exit_code, "An error occurred") + } } } } From 00085def6f3e61a98026b13a07dbfcfeed3b5dc8 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 3 Sep 2022 21:00:27 +0200 Subject: [PATCH 32/43] Change .pacnew detection text to bold Signed-off-by: trivernis --- src/internal/detect.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/internal/detect.rs b/src/internal/detect.rs index 9ea88d3..d2d295f 100644 --- a/src/internal/detect.rs +++ b/src/internal/detect.rs @@ -33,10 +33,10 @@ pub async fn detect() { // If pacnew files are found, warn the user and prompt to pacdiff if pacnew.is_empty() { - pb.finish_with_message("No .pacnew files found"); + pb.finish_with_message("No .pacnew files found".bold().to_string()); get_logger().reset_output_type(); } else { - pb.finish_with_message("pacnew files found"); + pb.finish_with_message("pacnew files found".bold().to_string()); get_logger().reset_output_type(); tracing::info!( "It appears that at least one program you have installed / upgraded has installed a .pacnew config file. \ From 03ed4249100875c89a7d35fb215e7e949f197fdd Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 09:34:16 +0200 Subject: [PATCH 33/43] Fix missing prompt for removing conflicted packages Signed-off-by: trivernis --- src/operations/aur_install.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 2038f1a..4eb5307 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -202,7 +202,7 @@ pub async fn aur_install(packages: Vec, options: Options) { if let Err(e) = build_and_install( contexts, MakePkgBuilder::default(), - PacmanInstallBuilder::default().no_confirm(true), + PacmanInstallBuilder::default().no_confirm(options.noconfirm), ) .await { From 6ad7f4d79fc757cb2d1a74d3fd82de5c248b0712 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 10:12:49 +0200 Subject: [PATCH 34/43] Fix installation of make-depends uninstalls depends too --- src/interact/macros.rs | 19 ++++++++++++ src/internal/dependencies.rs | 57 ++++++++++++++--------------------- src/operations/aur_install.rs | 12 ++++---- 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/src/interact/macros.rs b/src/interact/macros.rs index 544f777..085ddd6 100644 --- a/src/interact/macros.rs +++ b/src/interact/macros.rs @@ -19,3 +19,22 @@ macro_rules! multi_select { $crate::interact::Interact::interact($crate::interact::AmeMultiSelect::new(format!($($arg)+)).items($items)) } } + +#[macro_export] +/// Returns a singular or plural expression depending on the given len +macro_rules! numeric { + ($len:expr, $sin:literal[$plu:literal]) => { + if $len == 1 { + format!("{} {}", $len, $sin) + } else { + format!("{} {}{}", $len, $sin, $plu) + } + }; + ($len:expr, $sin:literal or $plu:literal) => { + if $len == 1 { + format!("{} {}", $len, $sin) + } else { + format!("{} {}", $len, plu) + } + }; +} diff --git a/src/internal/dependencies.rs b/src/internal/dependencies.rs index cce6eaa..9501761 100644 --- a/src/internal/dependencies.rs +++ b/src/internal/dependencies.rs @@ -209,54 +209,41 @@ impl DependencyInformation { Ok(repo_deps) } - pub fn make_depends(&self) -> Vec<&str> { - let mut make_depends = Vec::new(); - let mut aur_depends = self - .make_depends + pub fn make_depends(&self) -> HashSet<&str> { + let depends = self.depends(); + self.make_depends .aur .iter() .map(|p| p.metadata.name.as_str()) - .collect::>(); - let mut repo_depends = self - .make_depends - .repo - .iter() - .map(String::as_str) - .collect::>(); - make_depends.append(&mut aur_depends); - make_depends.append(&mut repo_depends); + .chain(self.make_depends.repo.iter().map(String::as_str)) + .filter(|d| !depends.contains(d)) + .collect() + } - make_depends + pub fn depends(&self) -> HashSet<&str> { + self.depends + .aur + .iter() + .map(|d| d.metadata.name.as_str()) + .chain(self.depends.repo.iter().map(String::as_str)) + .collect() } pub fn all_aur_depends(&self) -> Vec<&PackageInfo> { - let mut depends = Vec::new(); - let mut aur_depends = self.make_depends.aur.iter().collect::>(); - let mut aur_make_depends = self.depends.aur.iter().collect::>(); - depends.append(&mut aur_depends); - depends.append(&mut aur_make_depends); - - depends + self.make_depends + .aur + .iter() + .chain(self.depends.aur.iter()) + .collect() } pub fn all_repo_depends(&self) -> Vec<&str> { - let mut depends = Vec::new(); - let mut repo_depends = self - .make_depends - .repo - .iter() - .map(String::as_str) - .collect::>(); - let mut repo_make_depends = self - .depends + self.make_depends .repo .iter() + .chain(self.depends.repo.iter()) .map(String::as_str) - .collect::>(); - depends.append(&mut repo_depends); - depends.append(&mut repo_make_depends); - - depends + .collect() } } diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 4eb5307..828354f 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -23,7 +23,7 @@ use crate::internal::utils::{get_cache_dir, wrap_text}; use crate::logging::get_logger; use crate::logging::output::{print_aur_package_list, print_dependency_list}; use crate::logging::piped_stdio::StdioReader; -use crate::{cancelled, crash, multi_select, prompt, Options}; +use crate::{cancelled, crash, multi_select, numeric, prompt, Options}; #[derive(Debug)] pub struct BuildContext { @@ -186,8 +186,8 @@ pub async fn aur_install(packages: Vec, options: Options) { if !aur_dependencies.is_empty() { tracing::info!( - "Installing {} dependencies from the aur", - aur_dependencies.len() + "Installing {} from the aur", + numeric!(aur_dependencies.len(), "package"["s"]) ); let batches = create_dependency_batches(aur_dependencies); tracing::debug!("aur install batches: {batches:?}"); @@ -197,7 +197,7 @@ pub async fn aur_install(packages: Vec, options: Options) { } } - tracing::info!("Installing {} packages", contexts.len()); + tracing::info!("Installing {}", numeric!(contexts.len(), "package"["s"])); if let Err(e) = build_and_install( contexts, @@ -276,8 +276,8 @@ async fn build_and_install( } } - tracing::info!("Built {} packages", ctxs.len()); - tracing::info!("Installing packages..."); + tracing::info!("Built {}", numeric!(ctxs.len(), "package"["s"])); + tracing::info!("Installing packages"); install_packages(ctxs, install_opts).await?; From f24a711b0bf725d3abd850fbb21bfe96d2abf340 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 10:43:12 +0200 Subject: [PATCH 35/43] Fix build installing all built packages instead of only the specified one @not-my-segfault That should fix your problem with clion --- src/operations/aur_install.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 828354f..9fb9d53 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -379,7 +379,23 @@ async fn build_package( }); } - let packages = MakePkgBuilder::package_list(build_path).await?; + let mut packages = MakePkgBuilder::package_list(build_path).await?; + let match_version = ctx + .package + .metadata + .version + .rsplit_once('_') + .map(|v| v.0) + .unwrap_or(&ctx.package.metadata.version); + let match_name = format!("{pkg_name}-{match_version}"); + tracing::debug!("Match name {match_name}"); + packages.retain(|name| { + name.file_name() + .and_then(|n| n.to_str()) + .unwrap() + .starts_with(&match_name) + }); + tracing::debug!("Archives: {packages:?}"); pb.finish_with_message(format!("{}: {}", pkg_name.clone().bold(), "Built!".green())); ctx.step = BuildStep::Install(PackageArchives(packages)); From a6cb69a998c3fb83c0b73641fde5e4b49a227528 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 11:09:21 +0200 Subject: [PATCH 36/43] Add macros for all output related activities Signed-off-by: trivernis --- src/interact/macros.rs | 57 +++++++++++++++++++++++++++++++++++ src/operations/aur_install.rs | 40 ++++++++++++------------ 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/src/interact/macros.rs b/src/interact/macros.rs index 085ddd6..753338c 100644 --- a/src/interact/macros.rs +++ b/src/interact/macros.rs @@ -22,6 +22,16 @@ macro_rules! multi_select { #[macro_export] /// Returns a singular or plural expression depending on the given len +/// Usage: +/// ```rust +/// let some_list = vec!["a", "b", "c"]; +/// format!("The list has {}", numeric!(some_list.len(), "element"["s"])); +/// // result: The list has 3 elements +/// +/// let some_other_list = vec!["a"]; +/// format!("The list has {}", numeric!(some_other_list.len(), "element"["s"])); +/// // result: The list has 1 element +/// ``` macro_rules! numeric { ($len:expr, $sin:literal[$plu:literal]) => { if $len == 1 { @@ -38,3 +48,50 @@ macro_rules! numeric { } }; } + +#[macro_export] +/// Creates a new multiprogress bar +macro_rules! multi_progress { + () => { + $crate::logging::get_logger().new_multi_progress(); + }; +} + +#[macro_export] +/// Creates a new progress spinner +macro_rules! spinner { + () => { + $crate::logging::get_logger().new_progress_spinner() + }; + ($($arg:tt)+) => { + { + let spinner = $crate::spinner!(); + spinner.set_message(format!($($arg)+)); + spinner + } + } +} + +#[macro_export] +/// Resets the output to normal text output (erases all progress bars and spinners) +macro_rules! normal_output { + () => { + $crate::logging::get_logger().reset_output_type(); + }; +} + +#[macro_export] +/// Suspends the output so that nothing is being written to stdout/stderr +macro_rules! supend_output { + () => { + $crate::loggign::get_logger().suspend(); + }; +} + +#[macro_export] +/// Unsuspends the output and writes everything buffered to stdout/stderr +macro_rules! unsupend_output { + () => { + $crate::loggign::get_logger().unsuspend(); + }; +} diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 9fb9d53..5255e28 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -20,10 +20,13 @@ use crate::internal::dependencies::DependencyInformation; use crate::internal::error::{AppError, AppResult, SilentUnwrap}; use crate::internal::exit_code::AppExitCode; use crate::internal::utils::{get_cache_dir, wrap_text}; -use crate::logging::get_logger; + use crate::logging::output::{print_aur_package_list, print_dependency_list}; use crate::logging::piped_stdio::StdioReader; -use crate::{cancelled, crash, multi_select, numeric, prompt, Options}; +use crate::{ + cancelled, crash, multi_progress, multi_select, normal_output, numeric, prompt, spinner, + Options, +}; #[derive(Debug)] pub struct BuildContext { @@ -84,8 +87,7 @@ impl BuildContext { pub async fn aur_install(packages: Vec, options: Options) { tracing::debug!("Installing from AUR: {:?}", &packages); - let pb = get_logger().new_progress_spinner(); - pb.set_message("Fetching package information"); + let pb = spinner!("Fetching package information"); let package_info = aur_rpc::info(&packages) .await @@ -108,7 +110,7 @@ pub async fn aur_install(packages: Vec, options: Options) { } pb.finish_with_message("All packages found".green().to_string()); - get_logger().reset_output_type(); + normal_output!(); if print_aur_package_list(&package_info.iter().collect::>()).await && !options.noconfirm @@ -117,8 +119,7 @@ pub async fn aur_install(packages: Vec, options: Options) { cancelled!(); } - let pb = get_logger().new_progress_spinner(); - pb.set_message("Fetching package information"); + let pb = spinner!("Fetching package information"); let dependencies = future::try_join_all( package_info @@ -128,7 +129,7 @@ pub async fn aur_install(packages: Vec, options: Options) { .await .silent_unwrap(AppExitCode::RpcError); pb.finish_and_clear(); - get_logger().reset_output_type(); + normal_output!(); print_dependency_list(&dependencies).await; @@ -139,7 +140,7 @@ pub async fn aur_install(packages: Vec, options: Options) { } tracing::info!("Downloading sources"); - get_logger().new_multi_progress(); + multi_progress!(); let contexts = future::try_join_all( package_info @@ -150,7 +151,7 @@ pub async fn aur_install(packages: Vec, options: Options) { .await .unwrap(); - get_logger().reset_output_type(); + normal_output!(); tracing::info!("All sources are ready."); if !options.noconfirm { @@ -230,7 +231,7 @@ pub async fn aur_install(packages: Vec, options: Options) { #[tracing::instrument(level = "trace")] async fn install_aur_deps(deps: Vec<&PackageInfo>, no_confirm: bool) -> AppResult<()> { - get_logger().new_multi_progress(); + multi_progress!(); let dep_contexts = future::try_join_all( deps.into_iter() @@ -239,7 +240,7 @@ async fn install_aur_deps(deps: Vec<&PackageInfo>, no_confirm: bool) -> AppResul ) .await?; - get_logger().reset_output_type(); + normal_output!(); build_and_install( dep_contexts, @@ -249,7 +250,6 @@ async fn install_aur_deps(deps: Vec<&PackageInfo>, no_confirm: bool) -> AppResul .as_deps(true), ) .await?; - get_logger().reset_output_type(); Ok(()) } @@ -261,13 +261,13 @@ async fn build_and_install( install_opts: PacmanInstallBuilder, ) -> AppResult<()> { tracing::info!("Building packages"); - get_logger().new_multi_progress(); + multi_progress!(); let results = future::join_all( ctxs.into_iter() .map(|ctx| build_package(ctx, make_opts.clone())), ) .await; - get_logger().reset_output_type(); + normal_output!(); let mut ctxs = Vec::new(); for result in results { match result { @@ -286,10 +286,9 @@ async fn build_and_install( #[tracing::instrument(level = "trace", skip_all)] async fn download_aur_source(mut ctx: BuildContext) -> AppResult { - let pb = get_logger().new_progress_spinner(); let pkg_name = &ctx.package.metadata.name; let base_pkg = &ctx.package.metadata.package_base; - pb.set_message(format!("{}: Downloading sources", pkg_name.clone().bold())); + let pb = spinner!("{}: Downloading sources", pkg_name.clone().bold()); let cache_dir = get_cache_dir(); let pkg_dir = cache_dir.join(&pkg_name); @@ -343,10 +342,9 @@ async fn build_package( mut ctx: BuildContext, make_opts: MakePkgBuilder, ) -> AppResult { - let pb = get_logger().new_progress_spinner(); let pkg_name = &ctx.package.metadata.name; let build_path = ctx.build_path()?; - pb.set_message(format!("{}: Building Package", pkg_name.clone().bold())); + let pb = spinner!("{}: Building Package", pkg_name.as_str().bold()); let mut child = make_opts .directory(build_path) @@ -371,7 +369,7 @@ async fn build_package( if !exit_status.success() { pb.finish_with_message(format!( "{}: {}", - pkg_name.clone().bold(), + pkg_name.as_str().bold(), "Build failed!".red(), )); return Err(AppError::BuildError { @@ -495,7 +493,7 @@ async fn show_and_log_stdio( #[tracing::instrument(level = "trace", skip_all)] async fn handle_build_error>(err: E) -> AppResult<()> { - get_logger().reset_output_type(); + normal_output!(); let err = err.into(); match err { From 1c3de417e2ae9b4acee44b9cad2c8b648ba7024f Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 12:22:21 +0200 Subject: [PATCH 37/43] Refactor aur install into chained pattern Signed-off-by: trivernis --- src/internal/dependencies.rs | 6 +- src/internal/error.rs | 6 + src/operations/aur_install.rs | 526 ------------------ .../aur_dependency_installation.rs | 76 +++ src/operations/aur_install/aur_download.rs | 44 ++ src/operations/aur_install/aur_fetch.rs | 76 +++ .../aur_install/aur_package_install.rs | 38 ++ src/operations/aur_install/aur_review.rs | 48 ++ src/operations/aur_install/common.rs | 294 ++++++++++ .../aur_install/make_dependency_removal.rs | 35 ++ src/operations/aur_install/mod.rs | 139 +++++ .../repo_dependency_installation.rs | 42 ++ 12 files changed, 802 insertions(+), 528 deletions(-) delete mode 100644 src/operations/aur_install.rs create mode 100644 src/operations/aur_install/aur_dependency_installation.rs create mode 100644 src/operations/aur_install/aur_download.rs create mode 100644 src/operations/aur_install/aur_fetch.rs create mode 100644 src/operations/aur_install/aur_package_install.rs create mode 100644 src/operations/aur_install/aur_review.rs create mode 100644 src/operations/aur_install/common.rs create mode 100644 src/operations/aur_install/make_dependency_removal.rs create mode 100644 src/operations/aur_install/mod.rs create mode 100644 src/operations/aur_install/repo_dependency_installation.rs diff --git a/src/internal/dependencies.rs b/src/internal/dependencies.rs index 9501761..e0a3abd 100644 --- a/src/internal/dependencies.rs +++ b/src/internal/dependencies.rs @@ -5,7 +5,7 @@ use futures::future; use crate::builder::pacman::{PacmanQueryBuilder, PacmanSearchBuilder}; -use super::error::AppResult; +use super::error::{AppError, AppResult}; use lazy_regex::regex; #[derive(Clone, Debug)] @@ -83,7 +83,9 @@ impl DependencyInformation { already_searched.extend(packages_to_resolve.iter().cloned()); Self::extend_by_repo_packages(&mut packages_to_resolve, &mut dependencies).await?; - let mut aur_packages = aur_rpc::info(&packages_to_resolve).await?; + let mut aur_packages = aur_rpc::info(&packages_to_resolve).await.map_err(|_| { + AppError::MissingDependencies(packages_to_resolve.iter().cloned().collect()) + })?; aur_packages.iter().for_each(|p| { packages_to_resolve.remove(&p.metadata.name); }); diff --git a/src/internal/error.rs b/src/internal/error.rs index ad4b01a..68ec955 100644 --- a/src/internal/error.rs +++ b/src/internal/error.rs @@ -16,6 +16,8 @@ pub enum AppError { NonZeroExit, BuildStepViolation, BuildError { pkg_name: String }, + UserCancellation, + MissingDependencies(Vec), } impl Display for AppError { @@ -27,6 +29,10 @@ impl Display for AppError { AppError::NonZeroExit => Display::fmt("exited with non zero code", f), AppError::BuildStepViolation => Display::fmt("AUR build violated build steps", f), AppError::BuildError { pkg_name } => write!(f, "Failed to build package {pkg_name}"), + AppError::UserCancellation => write!(f, "Cancelled by user"), + AppError::MissingDependencies(deps) => { + write!(f, "Missing dependencies {}", deps.join(", ")) + } } } } diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs deleted file mode 100644 index 5255e28..0000000 --- a/src/operations/aur_install.rs +++ /dev/null @@ -1,526 +0,0 @@ -use async_recursion::async_recursion; -use aur_rpc::PackageInfo; -use crossterm::style::Stylize; -use futures::future; -use indicatif::ProgressBar; - -use std::collections::{HashMap, HashSet}; -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use tokio::fs::OpenOptions; -use tokio::io::{AsyncWriteExt, BufWriter}; -use tokio::process::{ChildStderr, ChildStdout}; -use tokio::task; - -use crate::builder::git::{GitCloneBuilder, GitPullBuilder}; -use crate::builder::makepkg::MakePkgBuilder; -use crate::builder::pacman::{PacmanInstallBuilder, PacmanUninstallBuilder}; -use crate::builder::pager::PagerBuilder; -use crate::internal::dependencies::DependencyInformation; -use crate::internal::error::{AppError, AppResult, SilentUnwrap}; -use crate::internal::exit_code::AppExitCode; -use crate::internal::utils::{get_cache_dir, wrap_text}; - -use crate::logging::output::{print_aur_package_list, print_dependency_list}; -use crate::logging::piped_stdio::StdioReader; -use crate::{ - cancelled, crash, multi_progress, multi_select, normal_output, numeric, prompt, spinner, - Options, -}; - -#[derive(Debug)] -pub struct BuildContext { - pub package: PackageInfo, - pub step: BuildStep, -} - -#[derive(Debug)] -pub enum BuildStep { - Download, - Build(BuildPath), - Install(PackageArchives), - Done, -} - -#[derive(Debug)] -pub struct BuildPath(pub PathBuf); - -#[derive(Debug)] -pub struct PackageArchives(pub Vec); - -impl From for BuildContext { - fn from(package: PackageInfo) -> Self { - Self { - package, - step: BuildStep::Download, - } - } -} - -impl From<&PackageInfo> for BuildContext { - fn from(p: &PackageInfo) -> Self { - Self::from(p.to_owned()) - } -} - -impl BuildContext { - pub fn build_path(&self) -> AppResult<&Path> { - if let BuildStep::Build(path) = &self.step { - Ok(&path.0) - } else { - Err(AppError::BuildStepViolation) - } - } - - pub fn packages(&self) -> AppResult<&Vec> { - if let BuildStep::Install(pkgs) = &self.step { - Ok(&pkgs.0) - } else { - Err(AppError::BuildStepViolation) - } - } -} - -/// Installs a given list of packages from the aur -#[tracing::instrument(level = "trace")] -#[async_recursion] -pub async fn aur_install(packages: Vec, options: Options) { - tracing::debug!("Installing from AUR: {:?}", &packages); - - let pb = spinner!("Fetching package information"); - - let package_info = aur_rpc::info(&packages) - .await - .map_err(AppError::from) - .silent_unwrap(AppExitCode::RpcError); - - tracing::debug!("package info = {package_info:?}"); - - if package_info.len() != packages.len() { - pb.finish_with_message("Couldn't find all packages".red().to_string()); - let mut not_found = packages.clone(); - package_info - .iter() - .for_each(|pkg| not_found.retain(|p| pkg.metadata.name != *p)); - crash!( - AppExitCode::MissingDeps, - "Could not find the package: {}", - not_found.join(",").italic(), - ); - } - - pb.finish_with_message("All packages found".green().to_string()); - normal_output!(); - - if print_aur_package_list(&package_info.iter().collect::>()).await - && !options.noconfirm - && !prompt!(default yes, "Some packages are already installed. Continue anyway?") - { - cancelled!(); - } - - let pb = spinner!("Fetching package information"); - - let dependencies = future::try_join_all( - package_info - .iter() - .map(|pkg| async { DependencyInformation::for_package(pkg).await }), - ) - .await - .silent_unwrap(AppExitCode::RpcError); - pb.finish_and_clear(); - normal_output!(); - - print_dependency_list(&dependencies).await; - - if !options.noconfirm - && !prompt!(default yes, "Do you want to install these packages and package dependencies?") - { - cancelled!(); - } - - tracing::info!("Downloading sources"); - multi_progress!(); - - let contexts = future::try_join_all( - package_info - .into_iter() - .map(BuildContext::from) - .map(download_aur_source), - ) - .await - .unwrap(); - - normal_output!(); - tracing::info!("All sources are ready."); - - if !options.noconfirm { - let to_review = multi_select!(&packages, "Select packages to review"); - for pkg in to_review.into_iter().filter_map(|i| packages.get(i)) { - review_pkgbuild(pkg).await.unwrap(); - } - if !prompt!(default yes, "Do you still want to install those packages?") { - cancelled!(); - } - } - - let aur_dependencies: Vec<&PackageInfo> = dependencies - .iter() - .flat_map(DependencyInformation::all_aur_depends) - .collect(); - - let repo_dependencies: HashSet<&str> = dependencies - .iter() - .flat_map(DependencyInformation::all_repo_depends) - .collect(); - - if !repo_dependencies.is_empty() { - tracing::info!("Installing repo dependencies"); - PacmanInstallBuilder::default() - .as_deps(true) - .packages(repo_dependencies) - .no_confirm(options.noconfirm) - .install() - .await - .silent_unwrap(AppExitCode::PacmanError); - } - - if !aur_dependencies.is_empty() { - tracing::info!( - "Installing {} from the aur", - numeric!(aur_dependencies.len(), "package"["s"]) - ); - let batches = create_dependency_batches(aur_dependencies); - tracing::debug!("aur install batches: {batches:?}"); - - for batch in batches { - install_aur_deps(batch, options.noconfirm).await.unwrap(); - } - } - - tracing::info!("Installing {}", numeric!(contexts.len(), "package"["s"])); - - if let Err(e) = build_and_install( - contexts, - MakePkgBuilder::default(), - PacmanInstallBuilder::default().no_confirm(options.noconfirm), - ) - .await - { - handle_build_error(e) - .await - .silent_unwrap(AppExitCode::MakePkgError); - } - let make_depends = dependencies - .iter() - .flat_map(DependencyInformation::make_depends) - .collect::>(); - if !make_depends.is_empty() - && !options.noconfirm - && prompt!(default yes, "Do you want to remove the installed make dependencies?") - { - PacmanUninstallBuilder::default() - .packages(make_depends) - .no_confirm(true) - .uninstall() - .await - .silent_unwrap(AppExitCode::PacmanError); - } - tracing::info!("Done!"); -} - -#[tracing::instrument(level = "trace")] -async fn install_aur_deps(deps: Vec<&PackageInfo>, no_confirm: bool) -> AppResult<()> { - multi_progress!(); - - let dep_contexts = future::try_join_all( - deps.into_iter() - .map(BuildContext::from) - .map(download_aur_source), - ) - .await?; - - normal_output!(); - - build_and_install( - dep_contexts, - MakePkgBuilder::default().as_deps(true), - PacmanInstallBuilder::default() - .no_confirm(no_confirm) - .as_deps(true), - ) - .await?; - - Ok(()) -} - -#[tracing::instrument(level = "trace")] -async fn build_and_install( - ctxs: Vec, - make_opts: MakePkgBuilder, - install_opts: PacmanInstallBuilder, -) -> AppResult<()> { - tracing::info!("Building packages"); - multi_progress!(); - let results = future::join_all( - ctxs.into_iter() - .map(|ctx| build_package(ctx, make_opts.clone())), - ) - .await; - normal_output!(); - let mut ctxs = Vec::new(); - for result in results { - match result { - Ok(ctx) => ctxs.push(ctx), - Err(e) => handle_build_error(e).await?, - } - } - - tracing::info!("Built {}", numeric!(ctxs.len(), "package"["s"])); - tracing::info!("Installing packages"); - - install_packages(ctxs, install_opts).await?; - - Ok(()) -} - -#[tracing::instrument(level = "trace", skip_all)] -async fn download_aur_source(mut ctx: BuildContext) -> AppResult { - let pkg_name = &ctx.package.metadata.name; - let base_pkg = &ctx.package.metadata.package_base; - let pb = spinner!("{}: Downloading sources", pkg_name.clone().bold()); - - let cache_dir = get_cache_dir(); - let pkg_dir = cache_dir.join(&pkg_name); - - if pkg_dir.exists() { - pb.set_message(format!( - "{}: Pulling latest changes", - pkg_name.clone().bold() - )); - GitPullBuilder::default().directory(&pkg_dir).pull().await?; - } else { - let aur_url = crate::internal::rpc::URL; - let repository_url = format!("{aur_url}/{base_pkg}"); - pb.set_message(format!( - "{}: Cloning aur repository", - pkg_name.clone().bold() - )); - - GitCloneBuilder::default() - .url(repository_url) - .directory(&pkg_dir) - .clone() - .await?; - - pb.set_message(format!( - "{}: Downloading and extracting files", - pkg_name.clone().bold() - )); - - MakePkgBuilder::default() - .directory(&pkg_dir) - .no_build(true) - .no_deps(true) - .no_prepare(true) - .skip_pgp(true) - .run() - .await?; - } - pb.finish_with_message(format!( - "{}: {}", - pkg_name.clone().bold(), - "Downloaded!".green() - )); - ctx.step = BuildStep::Build(BuildPath(pkg_dir)); - - Ok(ctx) -} - -#[tracing::instrument(level = "trace")] -async fn build_package( - mut ctx: BuildContext, - make_opts: MakePkgBuilder, -) -> AppResult { - let pkg_name = &ctx.package.metadata.name; - let build_path = ctx.build_path()?; - let pb = spinner!("{}: Building Package", pkg_name.as_str().bold()); - - let mut child = make_opts - .directory(build_path) - .clean(true) - .no_deps(true) - .skip_pgp(true) - .needed(true) - .force(true) - .spawn()?; - - let stderr = child.stderr.take().unwrap(); - let stdout = child.stdout.take().unwrap(); - let handle = task::spawn({ - let pb = pb.clone(); - let pkg_name = pkg_name.clone(); - async move { show_and_log_stdio(stdout, stderr, pb, pkg_name).await } - }); - - let exit_status = child.wait().await?; - handle.abort(); - - if !exit_status.success() { - pb.finish_with_message(format!( - "{}: {}", - pkg_name.as_str().bold(), - "Build failed!".red(), - )); - return Err(AppError::BuildError { - pkg_name: pkg_name.to_owned(), - }); - } - - let mut packages = MakePkgBuilder::package_list(build_path).await?; - let match_version = ctx - .package - .metadata - .version - .rsplit_once('_') - .map(|v| v.0) - .unwrap_or(&ctx.package.metadata.version); - let match_name = format!("{pkg_name}-{match_version}"); - tracing::debug!("Match name {match_name}"); - packages.retain(|name| { - name.file_name() - .and_then(|n| n.to_str()) - .unwrap() - .starts_with(&match_name) - }); - tracing::debug!("Archives: {packages:?}"); - pb.finish_with_message(format!("{}: {}", pkg_name.clone().bold(), "Built!".green())); - ctx.step = BuildStep::Install(PackageArchives(packages)); - - Ok(ctx) -} - -#[tracing::instrument(level = "trace")] -async fn install_packages( - mut ctxs: Vec, - install_opts: PacmanInstallBuilder, -) -> AppResult> { - let mut packages = Vec::new(); - - for ctx in &mut ctxs { - packages.append(&mut ctx.packages()?.clone()); - ctx.step = BuildStep::Done; - } - - install_opts.files(packages).needed(false).install().await?; - - Ok(ctxs) -} - -#[tracing::instrument(level = "trace")] -fn create_dependency_batches(deps: Vec<&PackageInfo>) -> Vec> { - let mut deps: HashMap = deps - .into_iter() - .map(|d| (d.metadata.name.clone(), d)) - .collect(); - let mut batches = Vec::new(); - let mut relaxed = false; - - while !deps.is_empty() { - let mut current_batch = HashMap::new(); - - for (key, info) in deps.clone() { - let contains_make_dep = info - .make_depends - .iter() - .any(|d| current_batch.contains_key(d) || deps.contains_key(d)); - - let contains_dep = info - .depends - .iter() - .any(|d| current_batch.contains_key(d) || deps.contains_key(d)); - - if (!contains_dep || relaxed) && contains_make_dep { - deps.remove(&key); - current_batch.insert(key, info); - if relaxed { - break; - } - } - } - - if current_batch.is_empty() { - relaxed = true; - } else { - batches.push(current_batch.into_iter().map(|(_, v)| v).collect()); - relaxed = false; - } - } - - batches -} - -#[tracing::instrument(level = "trace")] -async fn show_and_log_stdio( - stdout: ChildStdout, - stderr: ChildStderr, - pb: Arc, - package_name: String, -) -> AppResult<()> { - let mut reader = StdioReader::new(stdout, stderr); - let out_file = get_cache_dir().join(format!("{package_name}-build.log")); - let mut out_writer = BufWriter::new( - OpenOptions::new() - .create(true) - .write(true) - .open(out_file) - .await?, - ); - - while let Ok(line) = reader.read_line().await { - let _ = out_writer.write(line.as_bytes()).await?; - let _ = out_writer.write(&[b'\n']).await?; - tracing::trace!("{package_name}: {line}"); - let line = format!("{}: {}", package_name.clone().bold(), line); - let lines = wrap_text(line); - let line = lines.into_iter().next().unwrap(); - pb.set_message(line); - } - out_writer.flush().await?; - - Ok(()) -} - -#[tracing::instrument(level = "trace", skip_all)] -async fn handle_build_error>(err: E) -> AppResult<()> { - normal_output!(); - let err = err.into(); - - match err { - AppError::BuildError { pkg_name } => { - tracing::error!("Failed to build package {pkg_name}!"); - let log_path = get_cache_dir().join(format!("{pkg_name}-build.log")); - review_build_log(&log_path).await?; - - Ok(()) - } - e => Err(e), - } -} - -#[tracing::instrument(level = "trace")] -async fn review_build_log(log_file: &Path) -> AppResult<()> { - if prompt!(default yes, "Do you want to review the build log?") { - PagerBuilder::default().path(log_file).open().await?; - } - - Ok(()) -} - -#[tracing::instrument(level = "trace")] -async fn review_pkgbuild(package: &str) -> AppResult<()> { - let pkgbuild_path = get_cache_dir().join(package).join("PKGBUILD"); - PagerBuilder::default().path(pkgbuild_path).open().await?; - - Ok(()) -} diff --git a/src/operations/aur_install/aur_dependency_installation.rs b/src/operations/aur_install/aur_dependency_installation.rs new file mode 100644 index 0000000..1395137 --- /dev/null +++ b/src/operations/aur_install/aur_dependency_installation.rs @@ -0,0 +1,76 @@ +use aur_rpc::PackageInfo; +use futures::future; + +use crate::{ + builder::{makepkg::MakePkgBuilder, pacman::PacmanInstallBuilder}, + internal::{dependencies::DependencyInformation, error::AppResult}, + multi_progress, normal_output, numeric, + operations::{ + aur_install::common::{build_and_install, create_dependency_batches, download_aur_source}, + BuildContext, + }, +}; + +use super::aur_package_install::AurPackageInstall; + +pub struct AurDependencyInstallation { + pub options: crate::internal::structs::Options, + pub packages: Vec, + pub dependencies: Vec, + pub contexts: Vec, +} + +impl AurDependencyInstallation { + #[tracing::instrument(level = "trace", skip_all)] + pub async fn install_aur_dependencies(self) -> AppResult { + let aur_dependencies: Vec<&PackageInfo> = self + .dependencies + .iter() + .flat_map(DependencyInformation::all_aur_depends) + .collect(); + + if !aur_dependencies.is_empty() { + tracing::info!( + "Installing {} from the aur", + numeric!(aur_dependencies.len(), "package"["s"]) + ); + let batches = create_dependency_batches(aur_dependencies); + tracing::debug!("aur install batches: {batches:?}"); + + for batch in batches { + self.install(batch).await.unwrap(); + } + } + + Ok(AurPackageInstall { + options: self.options, + packages: self.packages, + dependencies: self.dependencies, + contexts: self.contexts, + }) + } + #[tracing::instrument(level = "trace", skip(self))] + pub async fn install(&self, deps: Vec<&PackageInfo>) -> AppResult<()> { + multi_progress!(); + + let dep_contexts = future::try_join_all( + deps.into_iter() + .map(BuildContext::from) + .map(download_aur_source), + ) + .await?; + + normal_output!(); + + build_and_install( + dep_contexts, + MakePkgBuilder::default().as_deps(true), + PacmanInstallBuilder::default() + .no_confirm(self.options.noconfirm) + .as_deps(true), + ) + .await?; + + Ok(()) + } +} diff --git a/src/operations/aur_install/aur_download.rs b/src/operations/aur_install/aur_download.rs new file mode 100644 index 0000000..b5e15dc --- /dev/null +++ b/src/operations/aur_install/aur_download.rs @@ -0,0 +1,44 @@ +use aur_rpc::PackageInfo; + +use futures::future; + +use crate::{ + internal::{dependencies::DependencyInformation, error::AppResult, structs::Options}, + multi_progress, normal_output, + operations::BuildContext, +}; + +use super::aur_review::AurReview; + +pub struct AurDownload { + pub options: Options, + pub package_infos: Vec, + pub packages: Vec, + pub dependencies: Vec, +} + +impl AurDownload { + #[tracing::instrument(level = "trace", skip_all)] + pub async fn download_sources(self) -> AppResult { + tracing::info!("Downloading sources"); + multi_progress!(); + + let contexts = future::try_join_all( + self.package_infos + .into_iter() + .map(BuildContext::from) + .map(super::common::download_aur_source), + ) + .await?; + + normal_output!(); + tracing::info!("All sources are ready."); + + Ok(AurReview { + options: self.options, + packages: self.packages, + dependencies: self.dependencies, + contexts, + }) + } +} diff --git a/src/operations/aur_install/aur_fetch.rs b/src/operations/aur_install/aur_fetch.rs new file mode 100644 index 0000000..ba708f2 --- /dev/null +++ b/src/operations/aur_install/aur_fetch.rs @@ -0,0 +1,76 @@ +use crossterm::style::Stylize; +use futures::future; + +use crate::{ + internal::{ + dependencies::DependencyInformation, + error::{AppError, AppResult}, + structs::Options, + }, + logging::output::{print_aur_package_list, print_dependency_list}, + normal_output, prompt, spinner, +}; + +use super::aur_download::AurDownload; + +pub struct AurFetch { + pub options: Options, + pub packages: Vec, +} + +impl AurFetch { + #[tracing::instrument(level = "trace", skip_all)] + pub async fn fetch_package_info(self) -> AppResult { + let pb = spinner!("Fetching package information"); + + let package_infos = aur_rpc::info(&self.packages).await?; + + tracing::debug!("package info = {package_infos:?}"); + + if package_infos.len() != self.packages.len() { + pb.finish_with_message("Couldn't find all packages".red().to_string()); + let mut not_found = self.packages.clone(); + package_infos + .iter() + .for_each(|pkg| not_found.retain(|p| pkg.metadata.name != *p)); + return Err(AppError::MissingDependencies(not_found)); + } + + pb.finish_with_message("All packages found".green().to_string()); + normal_output!(); + + if print_aur_package_list(&package_infos.iter().collect::>()).await + && !self.options.noconfirm + && !prompt!(default yes, "Some packages are already installed. Continue anyway?") + { + return Err(AppError::UserCancellation); + } + + let pb = spinner!("Fetching package information"); + + let dependencies = future::try_join_all( + package_infos + .iter() + .map(|pkg| async { DependencyInformation::for_package(pkg).await }), + ) + .await?; + + pb.finish_and_clear(); + normal_output!(); + + print_dependency_list(&dependencies).await; + + if !self.options.noconfirm + && !prompt!(default yes, "Do you want to install these packages and package dependencies?") + { + Err(AppError::UserCancellation) + } else { + Ok(AurDownload { + options: self.options, + packages: self.packages, + package_infos, + dependencies, + }) + } + } +} diff --git a/src/operations/aur_install/aur_package_install.rs b/src/operations/aur_install/aur_package_install.rs new file mode 100644 index 0000000..2a2b56b --- /dev/null +++ b/src/operations/aur_install/aur_package_install.rs @@ -0,0 +1,38 @@ +use crate::{ + builder::{makepkg::MakePkgBuilder, pacman::PacmanInstallBuilder}, + internal::{dependencies::DependencyInformation, error::AppResult, structs::Options}, + numeric, + operations::aur_install::{ + common::build_and_install, make_dependency_removal::MakeDependencyRemoval, + }, +}; + +use super::BuildContext; + +pub struct AurPackageInstall { + pub options: Options, + pub packages: Vec, + pub dependencies: Vec, + pub contexts: Vec, +} + +impl AurPackageInstall { + #[tracing::instrument(level = "trace", skip_all)] + pub async fn install_packages(self) -> AppResult { + tracing::info!( + "Installing {}", + numeric!(self.contexts.len(), "package"["s"]) + ); + build_and_install( + self.contexts, + MakePkgBuilder::default(), + PacmanInstallBuilder::default().no_confirm(self.options.noconfirm), + ) + .await?; + + Ok(MakeDependencyRemoval { + options: self.options, + dependencies: self.dependencies, + }) + } +} diff --git a/src/operations/aur_install/aur_review.rs b/src/operations/aur_install/aur_review.rs new file mode 100644 index 0000000..51ce613 --- /dev/null +++ b/src/operations/aur_install/aur_review.rs @@ -0,0 +1,48 @@ +use crate::{ + builder::pager::PagerBuilder, + internal::{ + dependencies::DependencyInformation, + error::{AppError, AppResult}, + structs::Options, + utils::get_cache_dir, + }, + multi_select, prompt, +}; + +use super::{repo_dependency_installation::RepoDependencyInstallation, BuildContext}; + +pub struct AurReview { + pub options: Options, + pub packages: Vec, + pub dependencies: Vec, + pub contexts: Vec, +} + +impl AurReview { + #[tracing::instrument(level = "trace", skip_all)] + pub async fn review_pkgbuild(self) -> AppResult { + if !self.options.noconfirm { + let to_review = multi_select!(&self.packages, "Select packages to review"); + for pkg in to_review.into_iter().filter_map(|i| self.packages.get(i)) { + review_pkgbuild(pkg).await.unwrap(); + } + if !prompt!(default yes, "Do you still want to install those packages?") { + return Err(AppError::UserCancellation); + } + } + Ok(RepoDependencyInstallation { + options: self.options, + packages: self.packages, + dependencies: self.dependencies, + contexts: self.contexts, + }) + } +} + +#[tracing::instrument(level = "trace")] +async fn review_pkgbuild(package: &str) -> AppResult<()> { + let pkgbuild_path = get_cache_dir().join(package).join("PKGBUILD"); + PagerBuilder::default().path(pkgbuild_path).open().await?; + + Ok(()) +} diff --git a/src/operations/aur_install/common.rs b/src/operations/aur_install/common.rs new file mode 100644 index 0000000..2f7c27f --- /dev/null +++ b/src/operations/aur_install/common.rs @@ -0,0 +1,294 @@ +use std::{collections::HashMap, path::Path, sync::Arc}; + +use aur_rpc::PackageInfo; +use crossterm::style::Stylize; +use futures::future; +use indicatif::ProgressBar; +use tokio::{ + fs::OpenOptions, + io::{AsyncWriteExt, BufWriter}, + process::{ChildStderr, ChildStdout}, + task, +}; + +use crate::{ + builder::{ + git::{GitCloneBuilder, GitPullBuilder}, + makepkg::MakePkgBuilder, + pacman::PacmanInstallBuilder, + pager::PagerBuilder, + }, + internal::{ + error::{AppError, AppResult}, + utils::{get_cache_dir, wrap_text}, + }, + logging::piped_stdio::StdioReader, + multi_progress, normal_output, numeric, + operations::PackageArchives, + prompt, spinner, +}; + +use super::{BuildContext, BuildPath, BuildStep}; + +#[tracing::instrument(level = "trace", skip_all)] +pub async fn download_aur_source(mut ctx: BuildContext) -> AppResult { + let pkg_name = &ctx.package.metadata.name; + let base_pkg = &ctx.package.metadata.package_base; + let pb = spinner!("{}: Downloading sources", pkg_name.clone().bold()); + + let cache_dir = get_cache_dir(); + let pkg_dir = cache_dir.join(&pkg_name); + + if pkg_dir.exists() { + pb.set_message(format!( + "{}: Pulling latest changes", + pkg_name.clone().bold() + )); + GitPullBuilder::default().directory(&pkg_dir).pull().await?; + } else { + let aur_url = crate::internal::rpc::URL; + let repository_url = format!("{aur_url}/{base_pkg}"); + pb.set_message(format!( + "{}: Cloning aur repository", + pkg_name.clone().bold() + )); + + GitCloneBuilder::default() + .url(repository_url) + .directory(&pkg_dir) + .clone() + .await?; + + pb.set_message(format!( + "{}: Downloading and extracting files", + pkg_name.clone().bold() + )); + + MakePkgBuilder::default() + .directory(&pkg_dir) + .no_build(true) + .no_deps(true) + .no_prepare(true) + .skip_pgp(true) + .run() + .await?; + } + pb.finish_with_message(format!( + "{}: {}", + pkg_name.clone().bold(), + "Downloaded!".green() + )); + ctx.step = BuildStep::Build(BuildPath(pkg_dir)); + + Ok(ctx) +} + +#[tracing::instrument(level = "trace")] +pub fn create_dependency_batches(deps: Vec<&PackageInfo>) -> Vec> { + let mut deps: HashMap = deps + .into_iter() + .map(|d| (d.metadata.name.clone(), d)) + .collect(); + let mut batches = Vec::new(); + let mut relaxed = false; + + while !deps.is_empty() { + let mut current_batch = HashMap::new(); + + for (key, info) in deps.clone() { + let contains_make_dep = info + .make_depends + .iter() + .any(|d| current_batch.contains_key(d) || deps.contains_key(d)); + + let contains_dep = info + .depends + .iter() + .any(|d| current_batch.contains_key(d) || deps.contains_key(d)); + + if (!contains_dep || relaxed) && contains_make_dep { + deps.remove(&key); + current_batch.insert(key, info); + if relaxed { + break; + } + } + } + + if current_batch.is_empty() { + relaxed = true; + } else { + batches.push(current_batch.into_iter().map(|(_, v)| v).collect()); + relaxed = false; + } + } + + batches +} + +#[tracing::instrument(level = "trace")] +pub async fn build_and_install( + ctxs: Vec, + make_opts: MakePkgBuilder, + install_opts: PacmanInstallBuilder, +) -> AppResult<()> { + tracing::info!("Building packages"); + multi_progress!(); + let results = future::join_all( + ctxs.into_iter() + .map(|ctx| build_package(ctx, make_opts.clone())), + ) + .await; + normal_output!(); + let mut ctxs = Vec::new(); + for result in results { + match result { + Ok(ctx) => ctxs.push(ctx), + Err(e) => handle_build_error(e).await?, + } + } + + tracing::info!("Built {}", numeric!(ctxs.len(), "package"["s"])); + tracing::info!("Installing packages"); + + install_packages(ctxs, install_opts).await?; + + Ok(()) +} + +#[tracing::instrument(level = "trace")] +async fn build_package( + mut ctx: BuildContext, + make_opts: MakePkgBuilder, +) -> AppResult { + let pkg_name = &ctx.package.metadata.name; + let build_path = ctx.build_path()?; + let pb = spinner!("{}: Building Package", pkg_name.as_str().bold()); + + let mut child = make_opts + .directory(build_path) + .clean(true) + .no_deps(true) + .skip_pgp(true) + .needed(true) + .force(true) + .spawn()?; + + let stderr = child.stderr.take().unwrap(); + let stdout = child.stdout.take().unwrap(); + let handle = task::spawn({ + let pb = pb.clone(); + let pkg_name = pkg_name.clone(); + async move { show_and_log_stdio(stdout, stderr, pb, pkg_name).await } + }); + + let exit_status = child.wait().await?; + handle.abort(); + + if !exit_status.success() { + pb.finish_with_message(format!( + "{}: {}", + pkg_name.as_str().bold(), + "Build failed!".red(), + )); + return Err(AppError::BuildError { + pkg_name: pkg_name.to_owned(), + }); + } + + let mut packages = MakePkgBuilder::package_list(build_path).await?; + let match_version = ctx + .package + .metadata + .version + .rsplit_once('_') + .map(|v| v.0) + .unwrap_or(&ctx.package.metadata.version); + let match_name = format!("{pkg_name}-{match_version}"); + tracing::debug!("Match name {match_name}"); + packages.retain(|name| { + name.file_name() + .and_then(|n| n.to_str()) + .unwrap() + .starts_with(&match_name) + }); + tracing::debug!("Archives: {packages:?}"); + pb.finish_with_message(format!("{}: {}", pkg_name.clone().bold(), "Built!".green())); + ctx.step = BuildStep::Install(PackageArchives(packages)); + + Ok(ctx) +} + +#[tracing::instrument(level = "trace")] +async fn install_packages( + mut ctxs: Vec, + install_opts: PacmanInstallBuilder, +) -> AppResult> { + let mut packages = Vec::new(); + + for ctx in &mut ctxs { + packages.append(&mut ctx.packages()?.clone()); + ctx.step = BuildStep::Done; + } + + install_opts.files(packages).needed(false).install().await?; + + Ok(ctxs) +} + +#[tracing::instrument(level = "trace")] +async fn show_and_log_stdio( + stdout: ChildStdout, + stderr: ChildStderr, + pb: Arc, + package_name: String, +) -> AppResult<()> { + let mut reader = StdioReader::new(stdout, stderr); + let out_file = get_cache_dir().join(format!("{package_name}-build.log")); + let mut out_writer = BufWriter::new( + OpenOptions::new() + .create(true) + .write(true) + .open(out_file) + .await?, + ); + + while let Ok(line) = reader.read_line().await { + let _ = out_writer.write(line.as_bytes()).await?; + let _ = out_writer.write(&[b'\n']).await?; + tracing::trace!("{package_name}: {line}"); + let line = format!("{}: {}", package_name.clone().bold(), line); + let lines = wrap_text(line); + let line = lines.into_iter().next().unwrap(); + pb.set_message(line); + } + out_writer.flush().await?; + + Ok(()) +} + +#[tracing::instrument(level = "trace", skip_all)] +async fn handle_build_error>(err: E) -> AppResult<()> { + normal_output!(); + let err = err.into(); + + match err { + AppError::BuildError { pkg_name } => { + tracing::error!("Failed to build package {pkg_name}!"); + let log_path = get_cache_dir().join(format!("{pkg_name}-build.log")); + review_build_log(&log_path).await?; + + Ok(()) + } + e => Err(e), + } +} + +#[tracing::instrument(level = "trace")] +async fn review_build_log(log_file: &Path) -> AppResult<()> { + if prompt!(default yes, "Do you want to review the build log?") { + PagerBuilder::default().path(log_file).open().await?; + } + + Ok(()) +} diff --git a/src/operations/aur_install/make_dependency_removal.rs b/src/operations/aur_install/make_dependency_removal.rs new file mode 100644 index 0000000..cb92cbc --- /dev/null +++ b/src/operations/aur_install/make_dependency_removal.rs @@ -0,0 +1,35 @@ +use crate::{ + builder::pacman::PacmanUninstallBuilder, + internal::{dependencies::DependencyInformation, error::AppResult, structs::Options}, + prompt, +}; + +pub struct MakeDependencyRemoval { + pub options: Options, + pub dependencies: Vec, +} + +impl MakeDependencyRemoval { + #[tracing::instrument(level = "trace", skip_all)] + pub async fn remove_make_deps(self) -> AppResult<()> { + let make_depends = self + .dependencies + .iter() + .flat_map(DependencyInformation::make_depends) + .collect::>(); + if !make_depends.is_empty() + && !self.options.noconfirm + && prompt!(default yes, "Do you want to remove the installed make dependencies?") + { + PacmanUninstallBuilder::default() + .packages(make_depends) + .no_confirm(true) + .uninstall() + .await?; + } + + tracing::info!("Done!"); + + Ok(()) + } +} diff --git a/src/operations/aur_install/mod.rs b/src/operations/aur_install/mod.rs new file mode 100644 index 0000000..60eeaae --- /dev/null +++ b/src/operations/aur_install/mod.rs @@ -0,0 +1,139 @@ +use aur_rpc::PackageInfo; + +use std::path::{Path, PathBuf}; + +use crate::internal::error::{AppError, AppResult}; + +use crate::internal::exit_code::AppExitCode; +use crate::{cancelled, crash, Options}; + +use self::aur_fetch::AurFetch; + +mod aur_dependency_installation; +mod aur_download; +mod aur_fetch; +mod aur_package_install; +mod aur_review; +mod common; +mod make_dependency_removal; +mod repo_dependency_installation; + +#[derive(Debug)] +pub struct BuildContext { + pub package: PackageInfo, + pub step: BuildStep, +} + +#[derive(Debug)] +pub enum BuildStep { + Download, + Build(BuildPath), + Install(PackageArchives), + Done, +} + +#[derive(Debug)] +pub struct BuildPath(pub PathBuf); + +#[derive(Debug)] +pub struct PackageArchives(pub Vec); + +impl From for BuildContext { + fn from(package: PackageInfo) -> Self { + Self { + package, + step: BuildStep::Download, + } + } +} + +impl From<&PackageInfo> for BuildContext { + fn from(p: &PackageInfo) -> Self { + Self::from(p.to_owned()) + } +} + +impl BuildContext { + pub fn build_path(&self) -> AppResult<&Path> { + if let BuildStep::Build(path) = &self.step { + Ok(&path.0) + } else { + Err(AppError::BuildStepViolation) + } + } + + pub fn packages(&self) -> AppResult<&Vec> { + if let BuildStep::Install(pkgs) = &self.step { + Ok(&pkgs.0) + } else { + Err(AppError::BuildStepViolation) + } + } +} + +pub struct AurInstall { + options: Options, + packages: Vec, +} + +impl AurInstall { + pub fn new(options: Options, packages: Vec) -> Self { + Self { options, packages } + } + + pub fn start(self) -> AurFetch { + tracing::debug!("Installing from AUR: {:?}", &self.packages); + AurFetch { + options: self.options, + packages: self.packages, + } + } +} + +/// Installs a given list of packages from the aur +#[tracing::instrument(level = "trace")] +pub async fn aur_install(packages: Vec, options: Options) { + if let Err(e) = aur_install_internal(AurInstall::new(options, packages)).await { + match e { + AppError::Rpc(e) => { + crash!(AppExitCode::RpcError, "AUR RPC Call failed with {e}") + } + AppError::BuildStepViolation => { + crash!(AppExitCode::MakePkgError, "Failed to build") + } + AppError::BuildError { pkg_name } => { + crash!(AppExitCode::MakePkgError, "Failed to build {pkg_name}") + } + AppError::UserCancellation => { + cancelled!(); + } + AppError::MissingDependencies(deps) => { + crash!( + AppExitCode::MissingDeps, + "Missing dependencies {}", + deps.join(", ") + ) + } + _ => crash!(AppExitCode::Other, "Unknown error"), + } + } +} + +async fn aur_install_internal(install: AurInstall) -> AppResult<()> { + install + .start() + .fetch_package_info() + .await? + .download_sources() + .await? + .review_pkgbuild() + .await? + .install_repo_dependencies() + .await? + .install_aur_dependencies() + .await? + .install_packages() + .await? + .remove_make_deps() + .await +} diff --git a/src/operations/aur_install/repo_dependency_installation.rs b/src/operations/aur_install/repo_dependency_installation.rs new file mode 100644 index 0000000..d9e8d26 --- /dev/null +++ b/src/operations/aur_install/repo_dependency_installation.rs @@ -0,0 +1,42 @@ +use std::collections::HashSet; + +use crate::{ + builder::pacman::PacmanInstallBuilder, + internal::{dependencies::DependencyInformation, error::AppResult, structs::Options}, +}; + +use super::{aur_dependency_installation::AurDependencyInstallation, BuildContext}; + +pub struct RepoDependencyInstallation { + pub options: Options, + pub packages: Vec, + pub dependencies: Vec, + pub contexts: Vec, +} + +impl RepoDependencyInstallation { + #[tracing::instrument(level = "trace", skip_all)] + pub async fn install_repo_dependencies(self) -> AppResult { + let repo_dependencies: HashSet<&str> = self + .dependencies + .iter() + .flat_map(DependencyInformation::all_repo_depends) + .collect(); + + if !repo_dependencies.is_empty() { + tracing::info!("Installing repo dependencies"); + PacmanInstallBuilder::default() + .as_deps(true) + .packages(repo_dependencies) + .no_confirm(self.options.noconfirm) + .install() + .await?; + } + Ok(AurDependencyInstallation { + options: self.options, + packages: self.packages, + dependencies: self.dependencies, + contexts: self.contexts, + }) + } +} From 6d2fb4342dd192851582cb57b274465750c440ab Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 12:24:12 +0200 Subject: [PATCH 38/43] Remove context that isn't required Signed-off-by: trivernis --- src/operations/aur_install/aur_dependency_installation.rs | 3 +-- src/operations/aur_install/aur_package_install.rs | 1 - src/operations/aur_install/aur_review.rs | 1 - src/operations/aur_install/repo_dependency_installation.rs | 2 -- 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/operations/aur_install/aur_dependency_installation.rs b/src/operations/aur_install/aur_dependency_installation.rs index 1395137..8a1e1a1 100644 --- a/src/operations/aur_install/aur_dependency_installation.rs +++ b/src/operations/aur_install/aur_dependency_installation.rs @@ -15,7 +15,6 @@ use super::aur_package_install::AurPackageInstall; pub struct AurDependencyInstallation { pub options: crate::internal::structs::Options, - pub packages: Vec, pub dependencies: Vec, pub contexts: Vec, } @@ -44,11 +43,11 @@ impl AurDependencyInstallation { Ok(AurPackageInstall { options: self.options, - packages: self.packages, dependencies: self.dependencies, contexts: self.contexts, }) } + #[tracing::instrument(level = "trace", skip(self))] pub async fn install(&self, deps: Vec<&PackageInfo>) -> AppResult<()> { multi_progress!(); diff --git a/src/operations/aur_install/aur_package_install.rs b/src/operations/aur_install/aur_package_install.rs index 2a2b56b..1d6e642 100644 --- a/src/operations/aur_install/aur_package_install.rs +++ b/src/operations/aur_install/aur_package_install.rs @@ -11,7 +11,6 @@ use super::BuildContext; pub struct AurPackageInstall { pub options: Options, - pub packages: Vec, pub dependencies: Vec, pub contexts: Vec, } diff --git a/src/operations/aur_install/aur_review.rs b/src/operations/aur_install/aur_review.rs index 51ce613..57d1667 100644 --- a/src/operations/aur_install/aur_review.rs +++ b/src/operations/aur_install/aur_review.rs @@ -32,7 +32,6 @@ impl AurReview { } Ok(RepoDependencyInstallation { options: self.options, - packages: self.packages, dependencies: self.dependencies, contexts: self.contexts, }) diff --git a/src/operations/aur_install/repo_dependency_installation.rs b/src/operations/aur_install/repo_dependency_installation.rs index d9e8d26..99780b3 100644 --- a/src/operations/aur_install/repo_dependency_installation.rs +++ b/src/operations/aur_install/repo_dependency_installation.rs @@ -9,7 +9,6 @@ use super::{aur_dependency_installation::AurDependencyInstallation, BuildContext pub struct RepoDependencyInstallation { pub options: Options, - pub packages: Vec, pub dependencies: Vec, pub contexts: Vec, } @@ -34,7 +33,6 @@ impl RepoDependencyInstallation { } Ok(AurDependencyInstallation { options: self.options, - packages: self.packages, dependencies: self.dependencies, contexts: self.contexts, }) From 7a446226ec0fb943345c3bd04ae8a9577cf98b5c Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 12:33:48 +0200 Subject: [PATCH 39/43] Fix tracing output Signed-off-by: trivernis --- src/logging/fmt_layer.rs | 4 ++-- src/logging/handler.rs | 10 ---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/logging/fmt_layer.rs b/src/logging/fmt_layer.rs index 2c187c7..d573904 100644 --- a/src/logging/fmt_layer.rs +++ b/src/logging/fmt_layer.rs @@ -95,8 +95,8 @@ impl LookupSpan<'a>> Layer for AmeFormatLayer { } } - fn on_exit(&self, id: &span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { - let span = ctx.span(id).unwrap(); + fn on_close(&self, id: span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) { + let span = ctx.span(&id).unwrap(); let metadata = span.metadata(); if self.is_enabled(metadata) { diff --git a/src/logging/handler.rs b/src/logging/handler.rs index f3cbcb0..9cf0c22 100644 --- a/src/logging/handler.rs +++ b/src/logging/handler.rs @@ -108,7 +108,6 @@ impl LogHandler { (*self.level.write()) = level; } - #[tracing::instrument(level = "trace", skip_all)] pub fn reset_output_type(&self) { self.set_output_type(OutputType::Stdout); } @@ -141,7 +140,6 @@ impl LogHandler { } /// Creates a new progress spinner and registers it on the log handler - #[tracing::instrument(level = "trace", skip_all)] pub fn new_progress_spinner(&self) -> Arc { let pb = ProgressBar::new_spinner(); pb.enable_steady_tick(Duration::from_millis(250)); @@ -158,7 +156,6 @@ impl LogHandler { } } - #[tracing::instrument(level = "trace", skip_all)] pub fn new_multi_progress(&self) -> Arc { let mp = Arc::new(MultiProgress::new()); self.set_output_type(OutputType::MultiProgress(mp.clone())); @@ -166,14 +163,7 @@ impl LogHandler { mp } - /// Registeres a progress bar on the log handler - #[tracing::instrument(level = "trace", skip_all)] - fn set_progress_bar(&self, pb: Arc) { - self.set_output_type(OutputType::Progress(pb)) - } - /// Sets the output type of the log handler to either stdout/stderr or a progress bar - #[tracing::instrument(level = "trace", skip_all)] pub fn set_output_type(&self, mut output: OutputType) { { let mut output_type = self.output_type.write(); From b83b234363f91e826872b168296224202558f8f0 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 12:35:57 +0200 Subject: [PATCH 40/43] Fix branch names in gh action workflow Signed-off-by: trivernis --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e10aea..5e5a40b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,9 @@ name: Run checks and tests on: workflow_dispatch: push: - branches: [ main, develop, feature/gh-actions ] + branches: [ main, development, feature/gh-actions ] pull_request: - branches: [ main, develop ] + branches: [ main, development ] env: CARGO_TERM_COLOR: always From 847f36ea4030a4ae1c0cf0f69dccf51808518263 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 12:45:48 +0200 Subject: [PATCH 41/43] Fix method visibility and merge some functions Signed-off-by: trivernis --- .../aur_install/aur_dependency_installation.rs | 2 +- src/operations/aur_install/aur_review.rs | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/operations/aur_install/aur_dependency_installation.rs b/src/operations/aur_install/aur_dependency_installation.rs index 8a1e1a1..602cf07 100644 --- a/src/operations/aur_install/aur_dependency_installation.rs +++ b/src/operations/aur_install/aur_dependency_installation.rs @@ -49,7 +49,7 @@ impl AurDependencyInstallation { } #[tracing::instrument(level = "trace", skip(self))] - pub async fn install(&self, deps: Vec<&PackageInfo>) -> AppResult<()> { + async fn install(&self, deps: Vec<&PackageInfo>) -> AppResult<()> { multi_progress!(); let dep_contexts = future::try_join_all( diff --git a/src/operations/aur_install/aur_review.rs b/src/operations/aur_install/aur_review.rs index 57d1667..841a778 100644 --- a/src/operations/aur_install/aur_review.rs +++ b/src/operations/aur_install/aur_review.rs @@ -23,8 +23,10 @@ impl AurReview { pub async fn review_pkgbuild(self) -> AppResult { if !self.options.noconfirm { let to_review = multi_select!(&self.packages, "Select packages to review"); + for pkg in to_review.into_iter().filter_map(|i| self.packages.get(i)) { - review_pkgbuild(pkg).await.unwrap(); + let pkgbuild_path = get_cache_dir().join(pkg).join("PKGBUILD"); + PagerBuilder::default().path(pkgbuild_path).open().await?; } if !prompt!(default yes, "Do you still want to install those packages?") { return Err(AppError::UserCancellation); @@ -37,11 +39,3 @@ impl AurReview { }) } } - -#[tracing::instrument(level = "trace")] -async fn review_pkgbuild(package: &str) -> AppResult<()> { - let pkgbuild_path = get_cache_dir().join(package).join("PKGBUILD"); - PagerBuilder::default().path(pkgbuild_path).open().await?; - - Ok(()) -} From 1ad98ca7809b37de7c4b5fcb81fbbffda290b4f3 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 14:52:17 +0200 Subject: [PATCH 42/43] Add fuzzy file review selector Signed-off-by: trivernis --- Cargo.lock | 11 +++++ Cargo.toml | 3 +- src/builder/makepkg.rs | 2 +- src/builder/pager.rs | 8 +--- src/interact/macros.rs | 34 ++++++++++++++-- src/interact/mod.rs | 6 +++ src/interact/multi_select.rs | 18 ++++---- src/interact/prompt.rs | 8 +--- src/interact/select.rs | 51 +++++++++++++++++++++++ src/interact/theme.rs | 52 +++++++++++++++++++++++- src/internal/error.rs | 2 + src/internal/utils.rs | 6 ++- src/logging/handler.rs | 31 ++++++++++++-- src/operations/aur_install/aur_review.rs | 42 +++++++++++++++++-- src/operations/aur_install/mod.rs | 3 ++ 15 files changed, 239 insertions(+), 38 deletions(-) create mode 100644 src/interact/select.rs diff --git a/Cargo.lock b/Cargo.lock index cf9cde9..c22b6c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,7 @@ dependencies = [ "dialoguer", "directories", "futures", + "fuzzy-matcher", "indicatif", "lazy-regex", "lazy_static", @@ -307,6 +308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92e7e37ecef6857fdc0c0c5d42fd5b0938e46590c2183cc92dd310a6d078eb1" dependencies = [ "console", + "fuzzy-matcher", "tempfile", "zeroize", ] @@ -491,6 +493,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "getrandom" version = "0.2.7" diff --git a/Cargo.toml b/Cargo.toml index 46ad32c..1bfece5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,11 +44,12 @@ color-eyre = { version = "0.6.2", features = ["issue-url", "url"] } indicatif = { version = "0.17.0", features = ["tokio"] } lazy_static = "1.4.0" parking_lot = { version = "0.12.1", features = ["deadlock_detection"] } -dialoguer = "0.10.2" +dialoguer = { version = "0.10.2", features = ["fuzzy-select"] } lazy-regex = "2.3.0" directories = "4.0.1" console = "0.15.1" tracing-error = "0.2.0" +fuzzy-matcher = "0.3.7" [dependencies.tokio] version = "1.21.0" diff --git a/src/builder/makepkg.rs b/src/builder/makepkg.rs index 9e3582c..dd01ca7 100644 --- a/src/builder/makepkg.rs +++ b/src/builder/makepkg.rs @@ -89,7 +89,7 @@ impl MakePkgBuilder { if output.status.success() { Ok(()) } else { - Err(AppError::Other(output.stderr)) + Err(AppError::MakePkg(output.stderr)) } } diff --git a/src/builder/pager.rs b/src/builder/pager.rs index 73892e3..f21f530 100644 --- a/src/builder/pager.rs +++ b/src/builder/pager.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use crate::{ internal::{commands::ShellCommand, error::AppResult}, - logging::get_logger, + with_suspended_output, }; #[derive(Default)] @@ -18,10 +18,6 @@ impl PagerBuilder { } pub async fn open(self) -> AppResult<()> { - get_logger().suspend(); - ShellCommand::pager().arg(self.path).wait_success().await?; - get_logger().unsuspend(); - - Ok(()) + with_suspended_output!({ ShellCommand::pager().arg(self.path).wait_success().await }) } } diff --git a/src/interact/macros.rs b/src/interact/macros.rs index 753338c..fd4f88d 100644 --- a/src/interact/macros.rs +++ b/src/interact/macros.rs @@ -20,6 +20,13 @@ macro_rules! multi_select { } } +#[macro_export] +macro_rules! select_opt { + ($items:expr, $($arg:tt)+) => { + $crate::interact::InteractOpt::interact_opt($crate::interact::AmeFuzzySelect::new(format!($($arg)+)).items($items)) + }; +} + #[macro_export] /// Returns a singular or plural expression depending on the given len /// Usage: @@ -82,16 +89,35 @@ macro_rules! normal_output { #[macro_export] /// Suspends the output so that nothing is being written to stdout/stderr -macro_rules! supend_output { +/// Returns a handle that unsuspend the output when it's dropped +macro_rules! suspend_output { () => { - $crate::loggign::get_logger().suspend(); + $crate::logging::get_logger().suspend() }; } #[macro_export] /// Unsuspends the output and writes everything buffered to stdout/stderr -macro_rules! unsupend_output { +macro_rules! unsuspend_output { + () => { + $crate::logging::get_logger().unsuspend(); + }; +} + +#[macro_export] +/// Suspend all output logging inside the given block +/// Note: This only works as long as the block itself doesn't unsuspend +/// the output +macro_rules! with_suspended_output { + ($expr:block) => {{ + let _handle = $crate::suspend_output!(); + $expr + }}; +} + +#[macro_export] +macro_rules! newline { () => { - $crate::loggign::get_logger().unsuspend(); + $crate::logging::get_logger().print_newline(); }; } diff --git a/src/interact/mod.rs b/src/interact/mod.rs index a48be3d..3ebcf7a 100644 --- a/src/interact/mod.rs +++ b/src/interact/mod.rs @@ -1,13 +1,19 @@ pub mod macros; mod multi_select; mod prompt; +mod select; mod theme; pub use multi_select::AmeMultiSelect; pub use prompt::AmePrompt; +pub use select::AmeFuzzySelect; pub trait Interact { type Result; fn interact(&mut self) -> Self::Result; } + +pub trait InteractOpt: Interact { + fn interact_opt(&mut self) -> Option; +} diff --git a/src/interact/multi_select.rs b/src/interact/multi_select.rs index d656c46..ae04259 100644 --- a/src/interact/multi_select.rs +++ b/src/interact/multi_select.rs @@ -1,6 +1,6 @@ use std::mem; -use crate::logging::get_logger; +use crate::with_suspended_output; use super::{theme::AmeTheme, Interact}; @@ -30,14 +30,12 @@ impl Interact for AmeMultiSelect { type Result = Vec; fn interact(&mut self) -> Self::Result { - get_logger().suspend(); - let selection = dialoguer::MultiSelect::with_theme(AmeTheme::get()) - .with_prompt(mem::take(&mut self.prompt)) - .items(&self.items) - .interact() - .unwrap(); - get_logger().unsuspend(); - - selection + with_suspended_output!({ + dialoguer::MultiSelect::with_theme(AmeTheme::get()) + .with_prompt(mem::take(&mut self.prompt)) + .items(&self.items) + .interact() + .unwrap() + }) } } diff --git a/src/interact/prompt.rs b/src/interact/prompt.rs index 64fb768..202ade1 100644 --- a/src/interact/prompt.rs +++ b/src/interact/prompt.rs @@ -1,6 +1,6 @@ use std::mem; -use crate::logging::get_logger; +use crate::with_suspended_output; use super::{theme::AmeTheme, Interact}; @@ -46,10 +46,6 @@ impl Interact for AmePrompt { dialog .with_prompt(mem::take(&mut self.question)) .wait_for_newline(true); - get_logger().suspend(); - let result = dialog.interact().unwrap(); - get_logger().unsuspend(); - - result + with_suspended_output!({ dialog.interact().unwrap() }) } } diff --git a/src/interact/select.rs b/src/interact/select.rs new file mode 100644 index 0000000..5794b74 --- /dev/null +++ b/src/interact/select.rs @@ -0,0 +1,51 @@ +use std::mem; + +use crate::with_suspended_output; + +use super::{theme::AmeTheme, Interact, InteractOpt}; + +pub struct AmeFuzzySelect { + prompt: String, + items: Vec, +} + +impl AmeFuzzySelect { + /// Creates a new multi select prompt + pub fn new(prompt: S) -> Self { + Self { + prompt: prompt.to_string(), + items: Vec::new(), + } + } + + /// Adds/replaces the items of this multi select + pub fn items, S: ToString>(&mut self, items: I) -> &mut Self { + self.items = items.into_iter().map(|i| i.to_string()).collect(); + + self + } + + fn build(&mut self) -> dialoguer::FuzzySelect { + let mut select = dialoguer::FuzzySelect::with_theme(AmeTheme::get()); + select + .with_prompt(mem::take(&mut self.prompt)) + .items(&self.items) + .default(0); + + select + } +} + +impl Interact for AmeFuzzySelect { + type Result = usize; + + fn interact(&mut self) -> Self::Result { + with_suspended_output!({ self.build().interact().unwrap() }) + } +} + +impl InteractOpt for AmeFuzzySelect { + fn interact_opt(&mut self) -> Option { + with_suspended_output!({ self.build().interact_opt().unwrap() }) + } +} diff --git a/src/interact/theme.rs b/src/interact/theme.rs index 9a4b412..c019921 100644 --- a/src/interact/theme.rs +++ b/src/interact/theme.rs @@ -1,8 +1,8 @@ use crossterm::style::Stylize; use dialoguer::theme::Theme; +use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; use crate::internal::utils::wrap_text; - const ERR_SYMBOL: &str = "X"; const PROMPT_SYMBOL: &str = "?"; @@ -208,4 +208,54 @@ impl Theme for AmeTheme { text ) } + + fn format_fuzzy_select_prompt( + &self, + f: &mut dyn std::fmt::Write, + prompt: &str, + search_term: &str, + cursor_pos: usize, + ) -> std::fmt::Result { + if !prompt.is_empty() { + write!(f, "{} {} ", PROMPT_SYMBOL.magenta(), prompt.bold())?; + } + + if cursor_pos < search_term.len() { + let st_head = search_term[0..cursor_pos].to_string(); + let st_tail = search_term[cursor_pos..search_term.len()].to_string(); + let st_cursor = "|".to_string(); + write!(f, "{}{}{}", st_head, st_cursor, st_tail) + } else { + let cursor = "|".to_string(); + write!(f, "{}{}", search_term, cursor) + } + } + + fn format_fuzzy_select_prompt_item( + &self, + f: &mut dyn std::fmt::Write, + text: &str, + active: bool, + highlight_matches: bool, + matcher: &SkimMatcherV2, + search_term: &str, + ) -> std::fmt::Result { + write!(f, "{} ", if active { ">" } else { " " }.magenta().bold())?; + + if highlight_matches { + if let Some((_score, indices)) = matcher.fuzzy_indices(text, search_term) { + for (idx, c) in text.chars().into_iter().enumerate() { + if indices.contains(&idx) { + write!(f, "{}", c.bold())?; + } else { + write!(f, "{}", c)?; + } + } + + return Ok(()); + } + } + + write!(f, "{}", text) + } } diff --git a/src/internal/error.rs b/src/internal/error.rs index 68ec955..cde2eab 100644 --- a/src/internal/error.rs +++ b/src/internal/error.rs @@ -18,6 +18,7 @@ pub enum AppError { BuildError { pkg_name: String }, UserCancellation, MissingDependencies(Vec), + MakePkg(String), } impl Display for AppError { @@ -33,6 +34,7 @@ impl Display for AppError { AppError::MissingDependencies(deps) => { write!(f, "Missing dependencies {}", deps.join(", ")) } + AppError::MakePkg(msg) => write!(f, "Failed to ru makepkg {msg}"), } } } diff --git a/src/internal/utils.rs b/src/internal/utils.rs index 4f945b9..2d1b640 100644 --- a/src/internal/utils.rs +++ b/src/internal/utils.rs @@ -5,7 +5,7 @@ use std::process::exit; use directories::ProjectDirs; use textwrap::wrap; -use crate::internal::exit_code::AppExitCode; +use crate::{internal::exit_code::AppExitCode, logging::get_logger}; use lazy_static::lazy_static; use super::error::{AppError, SilentUnwrap}; @@ -31,7 +31,9 @@ macro_rules! cancelled { /// Logs a message and exits the program with the given exit code. pub fn log_and_crash(msg: String, exit_code: AppExitCode) -> ! { - tracing::error!(msg); + get_logger().reset_output_type(); + get_logger().log_error(msg); + get_logger().flush(); exit(exit_code as i32); } diff --git a/src/logging/handler.rs b/src/logging/handler.rs index 9cf0c22..0e9ff6b 100644 --- a/src/logging/handler.rs +++ b/src/logging/handler.rs @@ -3,6 +3,7 @@ use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget}; use parking_lot::{Mutex, RwLock}; use std::{ fmt::Display, + io::{self, Write}, mem, sync::{atomic::AtomicBool, Arc}, time::Duration, @@ -10,7 +11,7 @@ use std::{ use crate::{internal::utils::wrap_text, uwu}; -use super::Verbosity; +use super::{get_logger, Verbosity}; const OK_SYMBOL: &str = "❖"; const ERR_SYMBOL: &str = "X"; @@ -46,6 +47,8 @@ pub enum OutputType { }, } +pub struct SuspendHandle; + impl LogHandler { pub fn log_error(&self, msg: String) { if self.is_loggable(Verbosity::Error) { @@ -101,7 +104,7 @@ impl LogHandler { } pub fn print_newline(&self) { - self.log(String::from("\n")) + self.log(String::from("")) } pub fn set_verbosity(&self, level: Verbosity) { @@ -112,7 +115,8 @@ impl LogHandler { self.set_output_type(OutputType::Stdout); } - pub fn suspend(&self) { + #[must_use] + pub fn suspend(&self) -> SuspendHandle { let mut output_type = self.output_type.write(); let mut old_output_type = OutputType::Stdout; mem::swap(&mut *output_type, &mut old_output_type); @@ -120,7 +124,9 @@ impl LogHandler { (*output_type) = OutputType::Buffer { buffer: Arc::new(Mutex::new(Vec::new())), suspended: Box::new(old_output_type), - } + }; + + SuspendHandle } pub fn unsuspend(&self) { @@ -194,6 +200,17 @@ impl LogHandler { (*self.level.read()) >= level } + /// Flushes the output buffer + pub fn flush(&self) { + let output = self.output_type.read(); + match &*output { + OutputType::Stdout => io::stdout().flush().unwrap(), + OutputType::Stderr => io::stderr().flush().unwrap(), + OutputType::Progress(p) => p.tick(), + _ => {} + } + } + fn preformat_msg(&self, msg: String) -> String { let msg = self.apply_uwu(msg); @@ -224,3 +241,9 @@ impl LogHandler { }; } } + +impl Drop for SuspendHandle { + fn drop(&mut self) { + get_logger().unsuspend(); + } +} diff --git a/src/operations/aur_install/aur_review.rs b/src/operations/aur_install/aur_review.rs index 841a778..589140c 100644 --- a/src/operations/aur_install/aur_review.rs +++ b/src/operations/aur_install/aur_review.rs @@ -1,3 +1,5 @@ +use tokio::fs; + use crate::{ builder::pager::PagerBuilder, internal::{ @@ -6,7 +8,7 @@ use crate::{ structs::Options, utils::get_cache_dir, }, - multi_select, prompt, + multi_select, newline, prompt, select_opt, }; use super::{repo_dependency_installation::RepoDependencyInstallation, BuildContext}; @@ -25,8 +27,7 @@ impl AurReview { let to_review = multi_select!(&self.packages, "Select packages to review"); for pkg in to_review.into_iter().filter_map(|i| self.packages.get(i)) { - let pkgbuild_path = get_cache_dir().join(pkg).join("PKGBUILD"); - PagerBuilder::default().path(pkgbuild_path).open().await?; + self.review_single_package(pkg).await?; } if !prompt!(default yes, "Do you still want to install those packages?") { return Err(AppError::UserCancellation); @@ -38,4 +39,39 @@ impl AurReview { contexts: self.contexts, }) } + + async fn review_single_package(&self, pkg: &str) -> AppResult<()> { + newline!(); + tracing::info!("Reviewing {pkg}"); + let mut files_iter = fs::read_dir(get_cache_dir().join(pkg)).await?; + let mut files = Vec::new(); + + while let Some(file) = files_iter.next_entry().await? { + let path = file.path(); + + if path.is_file() { + files.push(file.path()); + } + } + + let file_names = files + .iter() + .map(|f| f.file_name().unwrap()) + .map(|f| f.to_string_lossy()) + .collect::>(); + + while let Some(selection) = select_opt!(&file_names, "Select a file to review") { + if let Some(path) = files.get(selection) { + if let Err(e) = PagerBuilder::default().path(path).open().await { + tracing::debug!("Pager error {e}"); + } + } else { + break; + } + } + + tracing::info!("Done reviewing {pkg}"); + + Ok(()) + } } diff --git a/src/operations/aur_install/mod.rs b/src/operations/aur_install/mod.rs index 60eeaae..eb12fa9 100644 --- a/src/operations/aur_install/mod.rs +++ b/src/operations/aur_install/mod.rs @@ -114,6 +114,9 @@ pub async fn aur_install(packages: Vec, options: Options) { deps.join(", ") ) } + AppError::MakePkg(msg) => { + crash!(AppExitCode::MakePkgError, "makepgk failed {msg}") + } _ => crash!(AppExitCode::Other, "Unknown error"), } } From 219716661da90b9aeccbf022da8df6d81aa03b4f Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 4 Sep 2022 16:22:14 +0200 Subject: [PATCH 43/43] Fix stuff removed from the Cargo.toml when merging --- Cargo.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1bfece5..dc4a413 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,10 +6,8 @@ edition = "2021" description = "A fast and efficient AUR helper" license-file = "LICENSE.md" default-run = "ame" - -[features] -pkg-warner = [] - +keywords = ["aur", "crystal-linux", "pacman", "aur-helper"] +categories = ["command-line-utilities"] [[bin]] name = "ame"