Refactor, improvements and bugfix for resolution and installation process (#619)

* Refactor: Extract new mod `ops::resolve::resolution`
* Refactor: Extract new type `ResolutionFetch`
* Refactor: Extract new type `ResolutionSource`
* Improve `Resolution::print`: Provides more details on which crate
   is the resolution for.
* Make `Resolution::print` a pub fn
* Refactor: Extract new fn `ResolutionFetch::install`
* Refactor: Extract new async fn `ResolutionSource::install`
* Optimize `ResolutionSource::install` avoiding `OsStr::to_string_lossy`.
* Add new dep command-group v2.0.0 to binstalk with feat with-tokio
* Fix `ResolutionSource::install`: Use `AsyncCommandGroup::group_spawn`
   instead of `tokio::process::Command::spawn` to ensure all the processes
   spanwed by the `cargo` process can be terminated with one signal sent to
   the `cargo` process.
* Fix printing resolution: Make sure they are printed without interleaving
* Refactor `entry::install_crates`
* Improve dry-run output for `ResolutionSource::install`
* Refactor: Extract new fn `ResolutionFetch::print`
* Refactor: Extract new fn `ResolutionSource::print`
* Optimize `Resolution`: Box unit variant `Fetch`
* Improve formatting of `tokio::process::Command`
   Prints out sth like `cargo install ...` instead of the `Debug::fmt`.
* Improve dry-run output

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2022-12-28 11:18:07 +11:00 committed by GitHub
parent 611485de52
commit 6bdb26930e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 323 additions and 289 deletions

View file

@ -0,0 +1,198 @@
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> {
info!("Installing binaries...");
for file in &self.bin_files {
file.install_bin()?;
}
// Generate symlinks
if !opts.no_symlinks {
for file in &self.bin_files {
file.install_link()?;
}
}
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;
debug!(
"Found a binary install source: {} ({})",
fetcher.source_name(),
fetcher.target()
);
warn!(
"The package {name} v{new_version} will be 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.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(" ")
}