From bef4fbcb02ab2aec8eacea2c9084b14b5d4a3f8c Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 29 Aug 2022 21:01:26 +0200 Subject: [PATCH] [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) }