cargo-binstall/crates/binstalk/src/ops/resolve/resolution.rs
Jiahao XU 3c5641610a
feat: Improve resolution logging and verbose logging (#1341)
* feat: Improve resolution logging

Fixed #1336

Log target of the pre-built binaries which will be installed.

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>

* Fix args parsing: `quiet` & `verbose` cannot be set at the same time

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>

* feat: Implies `--log-level debug` if `--verbose` is set

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>

---------

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
2023-09-02 09:38:24 +00:00

221 lines
5.9 KiB
Rust

use std::{borrow::Cow, env, ffi::OsStr, fmt, iter, path::Path, sync::Arc};
use command_group::AsyncCommandGroup;
use compact_str::{CompactString, ToCompactString};
use either::Either;
use itertools::Itertools;
use semver::Version;
use tokio::process::Command;
use tracing::{debug, error, info, warn};
use crate::{
bins,
errors::BinstallError,
fetchers::Fetcher,
manifests::crate_info::{CrateInfo, CrateSource},
ops::Options,
};
pub struct ResolutionFetch {
pub fetcher: Arc<dyn Fetcher>,
pub new_version: Version,
pub name: CompactString,
pub version_req: CompactString,
pub bin_files: Vec<bins::BinFile>,
}
pub struct ResolutionSource {
pub name: CompactString,
pub version: CompactString,
}
pub enum Resolution {
Fetch(Box<ResolutionFetch>),
InstallFromSource(ResolutionSource),
AlreadyUpToDate,
}
impl Resolution {
pub fn print(&self, opts: &Options) {
match self {
Resolution::Fetch(fetch) => {
fetch.print(opts);
}
Resolution::InstallFromSource(source) => {
source.print();
}
Resolution::AlreadyUpToDate => (),
}
}
}
impl ResolutionFetch {
pub fn install(self, opts: &Options) -> Result<CrateInfo, BinstallError> {
type InstallFp = fn(&bins::BinFile) -> Result<(), bins::Error>;
let (install_bin, install_link): (InstallFp, InstallFp) = match (opts.no_track, opts.force)
{
(true, true) | (false, _) => (bins::BinFile::install_bin, bins::BinFile::install_link),
(true, false) => (
bins::BinFile::install_bin_noclobber,
bins::BinFile::install_link_noclobber,
),
};
info!("Installing binaries...");
for file in &self.bin_files {
install_bin(file)?;
}
// Generate symlinks
if !opts.no_symlinks {
for file in &self.bin_files {
install_link(file)?;
}
}
Ok(CrateInfo {
name: self.name,
version_req: self.version_req,
current_version: self.new_version,
source: CrateSource::cratesio_registry(),
target: self.fetcher.target().to_compact_string(),
bins: self
.bin_files
.into_iter()
.map(|bin| bin.base_name)
.collect(),
})
}
pub fn print(&self, opts: &Options) {
let fetcher = &self.fetcher;
let bin_files = &self.bin_files;
let name = &self.name;
let new_version = &self.new_version;
let target = fetcher.target();
debug!(
"Found a binary install source: {} ({target})",
fetcher.source_name(),
);
warn!(
"The package {name} v{new_version} ({target}) has been downloaded from {}{}",
if fetcher.is_third_party() {
"third-party source "
} else {
""
},
fetcher.source_name()
);
info!("This will install the following binaries:");
for file in bin_files {
info!(" - {}", file.preview_bin());
}
if !opts.no_symlinks {
info!("And create (or update) the following symlinks:");
for file in bin_files {
info!(" - {}", file.preview_link());
}
}
}
}
impl ResolutionSource {
pub async fn install(self, opts: Arc<Options>) -> Result<(), BinstallError> {
let desired_targets = opts.desired_targets.get().await;
let target = desired_targets
.first()
.ok_or(BinstallError::NoViableTargets)?;
let name = &self.name;
let version = &self.version;
let cargo = env::var_os("CARGO")
.map(Cow::Owned)
.unwrap_or_else(|| Cow::Borrowed(OsStr::new("cargo")));
debug!(
"Running `{} install {name} --version {version} --target {target}`",
Path::new(&cargo).display(),
);
let mut cmd = Command::new(cargo);
cmd.arg("install")
.arg(name)
.arg("--version")
.arg(version)
.arg("--target")
.arg(target)
.kill_on_drop(true);
if opts.quiet {
cmd.arg("--quiet");
}
if opts.force {
cmd.arg("--force");
}
if opts.locked {
cmd.arg("--locked");
}
if let Some(cargo_root) = &opts.cargo_root {
cmd.arg("--root").arg(cargo_root);
}
if opts.no_track {
cmd.arg("--no-track");
}
if !opts.dry_run {
let mut child = opts
.jobserver_client
.get()
.await?
.configure_and_run(&mut cmd, |cmd| cmd.group_spawn())?;
debug!("Spawned command pid={:?}", child.id());
let status = child.wait().await?;
if status.success() {
info!("Cargo finished successfully");
Ok(())
} else {
error!("Cargo errored! {status:?}");
Err(BinstallError::SubProcess {
command: format_cmd(&cmd).to_string().into_boxed_str(),
status,
})
}
} else {
info!("Dry-run: running `{}`", format_cmd(&cmd));
Ok(())
}
}
pub fn print(&self) {
warn!(
"The package {} v{} will be installed from source (with cargo)",
self.name, self.version
)
}
}
fn format_cmd(cmd: &Command) -> impl fmt::Display + '_ {
let cmd = cmd.as_std();
let program = Either::Left(Path::new(cmd.get_program()).display());
let program_args = cmd
.get_args()
.map(OsStr::to_string_lossy)
.map(Either::Right);
iter::once(program).chain(program_args).format(" ")
}