You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
336 lines
9.3 KiB
Rust
336 lines
9.3 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
path::{Path, PathBuf},
|
|
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,
|
|
},
|
|
crash, fl,
|
|
internal::{
|
|
alpm::{Alpm, PackageFrom},
|
|
error::{AppError, AppResult},
|
|
exit_code::AppExitCode,
|
|
utils::{get_cache_dir, wrap_text},
|
|
},
|
|
logging::piped_stdio::StdioReader,
|
|
multi_progress, normal_output,
|
|
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<BuildContext> {
|
|
let pkg_name = &ctx.package.metadata.name;
|
|
let base_pkg = &ctx.package.metadata.package_base;
|
|
let pb = spinner!(
|
|
"{}: {}",
|
|
pkg_name.clone().bold(),
|
|
fl!("downloading-sources")
|
|
);
|
|
|
|
let cache_dir = get_cache_dir();
|
|
let pkg_dir = cache_dir.join(&pkg_name);
|
|
|
|
if pkg_dir.exists() {
|
|
pb.set_message(format!(
|
|
"{}: {}",
|
|
pkg_name.clone().bold(),
|
|
fl!("pulling-latest-changes")
|
|
));
|
|
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!(
|
|
"{}: {}",
|
|
pkg_name.clone().bold(),
|
|
fl!("cloning-aur-repo")
|
|
));
|
|
|
|
GitCloneBuilder::default()
|
|
.url(repository_url)
|
|
.directory(&pkg_dir)
|
|
.clone()
|
|
.await?;
|
|
|
|
pb.set_message(format!(
|
|
"{}: {}",
|
|
pkg_name.clone().bold(),
|
|
fl!("down-and-ext-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.clone().bold(),
|
|
format!("{}", fl!("downloaded")).green()
|
|
));
|
|
ctx.step = BuildStep::Build(BuildPath(pkg_dir));
|
|
|
|
Ok(ctx)
|
|
}
|
|
|
|
#[tracing::instrument(level = "trace")]
|
|
pub 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();
|
|
let mut relaxed = false;
|
|
|
|
while !deps.is_empty() {
|
|
let mut current_batch = HashMap::new();
|
|
|
|
for (key, info) in deps.clone() {
|
|
tracing::trace!("Testing {key}");
|
|
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 {
|
|
tracing::debug!("Dependency found with relaxed search");
|
|
break;
|
|
}
|
|
} else {
|
|
tracing::debug!(
|
|
"Cannot install {} before {:?} and {:?}",
|
|
key,
|
|
info.make_depends,
|
|
info.depends
|
|
);
|
|
}
|
|
}
|
|
|
|
if current_batch.is_empty() {
|
|
if relaxed {
|
|
crash!(AppExitCode::Other, "{}", fl!("dependency-cycle"));
|
|
}
|
|
|
|
relaxed = true;
|
|
} else {
|
|
tracing::debug!("Created batch {current_batch:?}");
|
|
batches.push(current_batch.into_iter().map(|(_, v)| v).collect());
|
|
relaxed = false;
|
|
}
|
|
}
|
|
tracing::debug!("Created {} batches", batches.len());
|
|
|
|
batches
|
|
}
|
|
|
|
#[tracing::instrument(level = "trace")]
|
|
pub async fn build_and_install(
|
|
ctxs: Vec<BuildContext>,
|
|
make_opts: MakePkgBuilder,
|
|
install_opts: PacmanInstallBuilder,
|
|
) -> AppResult<()> {
|
|
tracing::info!("{}", fl!("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!(
|
|
"{} {} {}",
|
|
fl!("built"),
|
|
ctxs.len(),
|
|
fl!("packages", pkgNum = ctxs.len())
|
|
);
|
|
tracing::info!("{}", fl!("installing-packages"));
|
|
|
|
install_packages(ctxs, install_opts).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(level = "trace")]
|
|
async fn build_package(
|
|
mut ctx: BuildContext,
|
|
make_opts: MakePkgBuilder,
|
|
) -> AppResult<BuildContext> {
|
|
let pkg_name = &ctx.package.metadata.name;
|
|
let build_path = ctx.build_path()?;
|
|
let pb = spinner!("{}: {}", pkg_name.as_str().bold(), fl!("building-package"));
|
|
|
|
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(),
|
|
format!("{}", fl!("build-failed")).red(),
|
|
));
|
|
return Err(AppError::BuildError {
|
|
pkg_name: pkg_name.to_owned(),
|
|
});
|
|
}
|
|
|
|
let archives = MakePkgBuilder::package_list(build_path).await?;
|
|
tracing::debug!("Archives: {archives:?}");
|
|
|
|
let mut pkgs_produced: HashMap<String, PathBuf> = HashMap::new();
|
|
let alpm = Alpm::new()?;
|
|
|
|
for archive in archives {
|
|
let pkg = alpm.load(PackageFrom::File(archive.clone()))?;
|
|
let name = pkg.name().to_owned();
|
|
pkgs_produced.insert(name, archive);
|
|
}
|
|
tracing::debug!("Produced packages: {pkgs_produced:#?}");
|
|
|
|
let pkg_to_install = pkgs_produced.get(pkg_name).ok_or_else(|| {
|
|
AppError::Other(format!(
|
|
"{}",
|
|
fl!("couldnt-find-pkg-produced", pkg = pkg_name.clone())
|
|
))
|
|
})?;
|
|
|
|
pb.finish_with_message(format!(
|
|
"{}: {}!",
|
|
pkg_name.clone().bold(),
|
|
format!("{}", fl!("built")).green()
|
|
));
|
|
ctx.step = BuildStep::Install(PackageArchives(vec![pkg_to_install.to_path_buf()]));
|
|
|
|
Ok(ctx)
|
|
}
|
|
|
|
#[tracing::instrument(level = "trace")]
|
|
async fn install_packages(
|
|
mut ctxs: Vec<BuildContext>,
|
|
install_opts: PacmanInstallBuilder,
|
|
) -> AppResult<Vec<BuildContext>> {
|
|
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<ProgressBar>,
|
|
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, 2);
|
|
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<E: Into<AppError>>(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, "{}", fl!("review-build-log")) {
|
|
PagerBuilder::default().path(log_file).open().await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|