use std::env::set_current_dir; use std::path::Path; use std::process::Command; use std::{env, fs}; 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, warn, Options}; fn list(dir: &str) -> Vec { println!("{}", dir); let dirs = fs::read_dir(Path::new(&dir)).unwrap(); let dirs: Vec = dirs .map(|dir| { dir.unwrap() .path() .to_str() .unwrap() .to_string() .split('/') .collect::>() .last() .unwrap() .to_string() }) .collect(); dirs } fn mktemp() -> String { let tempdir = Command::new("mktemp") .args(&["-d", "/tmp/ame.XXXXXX.tmp"]) .output() .unwrap() .stdout; String::from_utf8(tempdir).unwrap().trim().to_string() } pub fn aur_install(a: Vec, options: Options, orig_cachedir: String) { // Initialise variables let url = crate::internal::rpc::URL; let cachedir = if !options.toplevel || !orig_cachedir.is_empty() { orig_cachedir.clone() } else { mktemp() }; let verbosity = options.verbosity; let noconfirm = options.noconfirm; if verbosity >= 1 { log!("Installing from AUR: {:?}", &a); } info!("Installing packages {} from the AUR", a.join(", ")); let mut failed: Vec = vec![]; for package in a { let dirs = list(&cachedir); if dirs.contains(&package) { continue; } // Query AUR for package info let rpcres = rpcinfo(package); if !rpcres.found { // If package isn't found, break break; } // Get package name let pkg = &rpcres.package.as_ref().unwrap().name; if verbosity >= 1 { log!("Cloning {} into cachedir", pkg); } info!("Cloning package source"); // Clone package into cachedir set_current_dir(Path::new(&cachedir)).unwrap(); ShellCommand::git() .arg("clone") .arg(format!("{}/{}", url, pkg)) .wait() .silent_unwrap(AppExitCode::GitError); if verbosity >= 1 { log!( "Cloned {} into cachedir, moving on to resolving dependencies", pkg ); log!( "Raw dependencies for package {} are:\n{:?}", pkg, rpcres.package.as_ref().unwrap().depends.join(", ") ); log!( "Raw makedepends for package {} are:\n{:?}", pkg, rpcres.package.as_ref().unwrap().make_depends.join(", ") ); } // Sort dependencies and makedepends if verbosity >= 1 { log!("Sorting dependencies and makedepends"); } let mut sorted = crate::internal::sort(&rpcres.package.as_ref().unwrap().depends, options); let mut md_sorted = crate::internal::sort(&rpcres.package.as_ref().unwrap().make_depends, options); if verbosity >= 1 { log!("Sorted dependencies for {} are:\n{:?}", pkg, &sorted); log!("Sorted makedepends for {} are:\n{:?}", pkg, &md_sorted); } // Create newopts struct for installing dependencies let newopts = Options { verbosity, noconfirm, asdeps: true, toplevel: false, }; // Get a list of installed packages let installed = ShellCommand::pacman() .elevated() .args(&["-Qq"]) .wait_with_output() .silent_unwrap(AppExitCode::PacmanError) .stdout .split_whitespace() .collect::>() .iter() .map(|s| s.to_string()) .collect::>(); // Remove installed packages from sorted dependencies and makedepends if verbosity >= 1 { log!("Removing installed packages from sorted dependencies and makedepends"); } sorted.aur.retain(|x| !installed.contains(x)); sorted.repo.retain(|x| !installed.contains(x)); md_sorted.aur.retain(|x| !installed.contains(x)); md_sorted.repo.retain(|x| !installed.contains(x)); // Remove packages from sorted dependencies if they are already in the cachedir if verbosity >= 1 { log!("Removing packages from sorted dependencies if they are already in the cachedir"); } let dirs = list(&cachedir); sorted.aur.retain(|x| !dirs.contains(x)); // If dependencies are not found in AUR or repos, crash if !sorted.nf.is_empty() || !md_sorted.nf.is_empty() { crash!( AppExitCode::MissingDeps, "Could not find dependencies {} for package {}, aborting", sorted.nf.join(", "), pkg, ); } if !noconfirm { // Prompt user to view PKGBUILD let p0 = prompt!(default false, "Would you like to review and/or edit {}'s PKGBUILD (and any adjacent build files if present)?", pkg); if p0 { info!("This will drop you into a standard `bash` shell in the package's cache directory. If any changes are made, you will be prompted whether to save them to your home directory. To stop reviewing/editing, just run `exit`"); let p1 = prompt!(default true, "Continue?" ); if p1 { let cdir = env::current_dir().unwrap().to_str().unwrap().to_string(); set_current_dir(Path::new(&format!("{}/{}", &cachedir, pkg))).unwrap(); ShellCommand::bash().wait().unwrap(); set_current_dir(Path::new(&cdir)).unwrap(); // Prompt user to save changes let p2 = prompt!(default false, "Save changes to package {}?", pkg ); if p2 { // Save changes to ~/.local/share let dest = format!( "{}-saved-{}", pkg, chrono::Local::now() .naive_local() .format("%Y-%m-%d_%H-%M-%S") ); Command::new("cp") .arg("-r") .arg(format!("{}/{}", cachedir, pkg)) .arg(format!( "{}/.local/share/ame/{}", env::var("HOME").unwrap(), dest )) .spawn() .unwrap() .wait() .unwrap(); // Alert user info!("Saved changes to ~/.local/share/ame/{}", dest); }; } } } // Prompt user to continue let p = prompt!(default true, "Would you still like to install {}?", pkg); if !p { // If not, crash if orig_cachedir.is_empty() { fs::remove_dir_all(format!("{}/{}", cachedir, pkg)).unwrap(); } crash!(AppExitCode::UserCancellation, "Not proceeding"); }; info!("Moving on to install dependencies"); // Install dependencies and makedepends if !sorted.repo.is_empty() { crate::operations::install(sorted.repo, newopts); } if !sorted.aur.is_empty() { crate::operations::aur_install(sorted.aur, newopts, cachedir.clone()); } if !md_sorted.repo.is_empty() { crate::operations::install(md_sorted.repo, newopts); } if !md_sorted.aur.is_empty() { crate::operations::aur_install(md_sorted.aur, newopts, cachedir.clone()); } // Build makepkg args let mut makepkg_args = vec!["-rcd", "--skippgp", "--needed"]; if options.asdeps { makepkg_args.push("--asdeps") } if options.noconfirm { makepkg_args.push("--noconfirm") } info!("Building time!"); // Enter cachedir and build package set_current_dir(format!("{}/{}", cachedir, pkg)).unwrap(); let status = ShellCommand::makepkg() .args(makepkg_args) .wait() .silent_unwrap(AppExitCode::MakePkgError); if !status.success() && status.code().unwrap() != 13 { // If build failed, push to failed vec failed.push(pkg.clone()); return; } // Return to cachedir set_current_dir(&cachedir).unwrap(); if options.toplevel { // Install all packages from cachedir except `pkg` using --asdeps let dirs = list(&cachedir); // Get a list of packages in cachedir if dirs.len() > 1 { info!("Installing all AUR dependencies"); std::process::Command::new("bash") .args(&[ "-cO", "extglob", format!("sudo pacman -U --asdeps {}/!({})/*.zst", cachedir, pkg).as_str(), ]) .spawn() .unwrap() .wait() .unwrap(); } // Install package explicitly info!("Installing {}", pkg); std::process::Command::new("bash") .args(&[ "-c", format!("sudo pacman -U {}/{}/*.zst", cachedir, pkg).as_str(), ]) .spawn() .unwrap() .wait() .unwrap(); } } // If any packages failed to build, warn user with failed packages if !failed.is_empty() { let failed_str = format!("{}.failed", cachedir); warn!( "Failed to build packages {}, keeping cache directory at {} for manual inspection", failed.join(", "), if orig_cachedir.is_empty() { &cachedir } else { &failed_str } ); if orig_cachedir.is_empty() { Command::new("mv") .args(&[&cachedir, &format!("{}.failed", cachedir)]) .spawn() .unwrap() .wait() .unwrap(); } } else if options.toplevel && orig_cachedir.is_empty() { rm_rf::remove(&cachedir).unwrap_or_else(|e| crash!(AppExitCode::Other, "Could not remove cache directory at {}: {}. This could be a permissions issue with fakeroot, try running `sudo rm -rf {}`", cachedir, e, cachedir) ); } }