[WIP] Fix dependency order and improve build output

Signed-off-by: trivernis <trivernis@protonmail.com>
i18n
trivernis 2 years ago
parent e0f58e1aba
commit 53cf212569
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

1
Cargo.lock generated

@ -12,6 +12,7 @@ dependencies = [
"clap_complete",
"color-eyre",
"colored",
"console",
"crossterm",
"dialoguer",
"directories",

@ -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"

@ -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<Child> {
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")]

@ -1,14 +1,13 @@
use regex::Regex;
/// Strips packages from versioning and other extraneous information.
pub fn clean(a: &[String]) -> Vec<String> {
// 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);

@ -125,7 +125,7 @@ impl ShellCommand {
})
}
fn spawn(self, piped: bool) -> AppResult<Child> {
pub fn spawn(self, piped: bool) -> AppResult<Child> {
tracing::debug!("Running {} {:?}", self.command, self.args);
let (stdout, stderr) = if piped {

@ -6,7 +6,6 @@ pub enum AppExitCode {
MissingDeps = 3,
UserCancellation = 4,
PacmanError = 5,
GitError = 6,
MakePkgError = 7,
RpcError = 9,
Other = 63,

@ -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: AsRef<str>>(s: S) -> Vec<String> {
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(" ")
}

@ -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<I: IntoIterator<Item = T>, 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 {

@ -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 {

@ -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<DependencyInformation>) -> 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<PackageInfo>) {
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 ",
);
}

@ -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<String>, 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<String>, 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<String>, options: Options) {
get_logger().reset_output_type();
tracing::info!("All sources are ready.");
let aur_build_dependencies: Vec<PackageInfo> = dependencies
let aur_dependencies: Vec<PackageInfo> = 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<String> = dependencies
@ -138,26 +160,28 @@ pub async fn aur_install(packages: Vec<String>, 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<String>, 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<PackageInfo>) -> AppResult<()> {
async fn install_aur_deps(deps: Vec<PackageInfo>) -> 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<BuildContext> {
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<BuildContext> {
.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<BuildContext> {
.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<PackageInfo>) -> Vec<Vec<PackageInfo>> {
let mut deps: HashMap<String, PackageInfo> = 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<ProgressBar>,
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);
}
}
}
})
}

Loading…
Cancel
Save