diff --git a/.github/scripts/tests.sh b/.github/scripts/tests.sh index 5088bbf7..5714932d 100755 --- a/.github/scripts/tests.sh +++ b/.github/scripts/tests.sh @@ -77,16 +77,13 @@ cargo binstall --help >/dev/null "./$1" binstall --no-confirm cargo-binstall@0.12.0 | grep -q 'cargo-binstall v0.12.0 is already installed' "./$1" binstall --no-confirm cargo-binstall@^0.12.0 | grep -q -v 'cargo-binstall v0.12.0 is already installed' -# to force failure if falling back to source -# FIXME: remove/replace once #136 lands -export PATH="$test_resources/fake-cargo:$PATH" - # Test default GitLab pkg-url templates "./$1" binstall \ --force \ --manifest-path "$test_resources/gitlab-test-Cargo.toml" \ --log-level debug \ --no-confirm \ + --disable-strategies compile \ cargo-binstall # Test default BitBucket pkg-url templates @@ -95,6 +92,7 @@ export PATH="$test_resources/fake-cargo:$PATH" --manifest-path "$test_resources/bitbucket-test-Cargo.toml" \ --log-level debug \ --no-confirm \ + --disable-strategies compile \ cargo-binstall # Test default Github pkg-url templates, @@ -104,4 +102,25 @@ export PATH="$test_resources/fake-cargo:$PATH" --manifest-path "$test_resources/github-test-Cargo2.toml" \ --log-level debug \ --no-confirm \ + --disable-strategies compile \ cargo-binstall + +## Test --disable-strategies +set +e + +"./$1" binstall --no-confirm --disable-strategies quick-install,compile cargo-update +exit_code="$?" + +if [ "$exit_code" != 94 ]; then + echo "Expected exit code 94, but actual exit code $exit_code" + exit 1 +fi + +## Test --strategies +"./$1" binstall --no-confirm --strategies crate-meta-data cargo-update +exit_code="$?" + +if [ "$exit_code" != 94 ]; then + echo "Expected exit code 94, but actual exit code $exit_code" + exit 1 +fi diff --git a/crates/bin/src/args.rs b/crates/bin/src/args.rs index 9ca2ae30..899ced0a 100644 --- a/crates/bin/src/args.rs +++ b/crates/bin/src/args.rs @@ -129,6 +129,19 @@ pub struct Args { #[clap(help_heading = "Overrides", long, default_value_t = RateLimit::default())] pub rate_limit: RateLimit, + /// Specify the strategies to be used, + /// binstall will run the strategies specified in order. + /// + /// Default value is "crate-meta-data,quick-install,compile". + #[clap(help_heading = "Overrides", long, value_delimiter(','))] + pub strategies: Vec, + + /// Disable the strategies specified. + /// If a strategy is specified in `--strategies` and `--disable-strategies`, + /// then it will be removed. + #[clap(help_heading = "Overrides", long, value_delimiter(','))] + pub disable_strategies: Vec, + /// Disable symlinking / versioned updates. /// /// By default, Binstall will install a binary named `-` in the install path, and @@ -282,6 +295,18 @@ impl Default for RateLimit { } } +/// Strategy for installing the package +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)] +pub enum Strategy { + /// Attempt to download official pre-built artifacts using + /// information provided in `Cargo.toml`. + CrateMetaData, + /// Query third-party QuickInstall for the crates. + QuickInstall, + /// Build the crates from source using `cargo-build`. + Compile, +} + pub fn parse() -> Result { // Filter extraneous arg when invoked by cargo // `cargo run -- --help` gives ["target/debug/cargo-binstall", "--help"] diff --git a/crates/bin/src/entry.rs b/crates/bin/src/entry.rs index 4518694b..ee2ae23c 100644 --- a/crates/bin/src/entry.rs +++ b/crates/bin/src/entry.rs @@ -1,7 +1,8 @@ -use std::{fs, path::Path, sync::Arc, time::Duration}; +use std::{fs, mem, path::Path, sync::Arc, time::Duration}; use binstalk::{ errors::BinstallError, + fetchers::{Fetcher, GhCrateMeta, QuickInstall}, get_desired_targets, helpers::{jobserver_client::LazyJobserverClient, remote::Client, tasks::AutoAbortJoinHandle}, manifests::{ @@ -16,9 +17,61 @@ use log::{debug, error, info, warn, LevelFilter}; use miette::{miette, Result, WrapErr}; use tokio::task::block_in_place; -use crate::{args::Args, install_path, ui::UIThread}; +use crate::{ + args::{Args, Strategy}, + install_path, + ui::UIThread, +}; pub async fn install_crates(mut args: Args, jobserver_client: LazyJobserverClient) -> Result<()> { + let mut strategies = vec![]; + + // Remove duplicate strategies + for strategy in mem::take(&mut args.strategies) { + if !strategies.contains(&strategy) { + strategies.push(strategy); + } + } + + // Default strategies if empty + if strategies.is_empty() { + strategies = vec![ + Strategy::CrateMetaData, + Strategy::QuickInstall, + Strategy::Compile, + ]; + } + + let disable_strategies = mem::take(&mut args.disable_strategies); + + let mut strategies: Vec = if !disable_strategies.is_empty() { + strategies + .into_iter() + .filter(|strategy| !disable_strategies.contains(strategy)) + .collect() + } else { + strategies + }; + + if strategies.is_empty() { + return Err(BinstallError::InvalidStrategies(&"No strategy is provided").into()); + } + + let cargo_install_fallback = *strategies.last().unwrap() == Strategy::Compile; + + if cargo_install_fallback { + strategies.pop().unwrap(); + } + + let resolver: Vec<_> = strategies + .into_iter() + .map(|strategy| match strategy { + Strategy::CrateMetaData => GhCrateMeta::new, + Strategy::QuickInstall => QuickInstall::new, + Strategy::Compile => unreachable!(), + }) + .collect(); + let cli_overrides = PkgOverride { pkg_url: args.pkg_url.take(), pkg_fmt: args.pkg_fmt.take(), @@ -138,6 +191,7 @@ pub async fn install_crates(mut args: Args, jobserver_client: LazyJobserverClien cli_overrides, desired_targets, quiet: args.log_level == LevelFilter::Off, + resolver, }); let tasks: Vec<_> = if !args.dry_run && !args.no_confirm { @@ -208,7 +262,13 @@ pub async fn install_crates(mut args: Args, jobserver_client: LazyJobserverClien ) .await?; - ops::install::install(resolution, opts, jobserver_client).await + if !cargo_install_fallback + && matches!(resolution, Resolution::InstallFromSource { .. }) + { + Err(BinstallError::NoFallbackToCargoInstall) + } else { + ops::install::install(resolution, opts, jobserver_client).await + } }) }) .collect() diff --git a/crates/binstalk/src/errors.rs b/crates/binstalk/src/errors.rs index 351ba02b..5dae3396 100644 --- a/crates/binstalk/src/errors.rs +++ b/crates/binstalk/src/errors.rs @@ -310,6 +310,22 @@ pub enum BinstallError { #[diagnostic(severity(error), code(binstall::SourceFilePath))] EmptySourceFilePath, + /// Invalid strategies configured. + /// + /// - Code: `binstall::strategies` + /// - Exit: 93 + #[error("Invalid strategies configured: {0}")] + #[diagnostic(severity(error), code(binstall::strategies))] + InvalidStrategies(&'static &'static str), + + /// Fallback to `cargo-install` is disabled. + /// + /// - Code: `binstall::no_fallback_to_cargo_install` + /// - Exit: 94 + #[error("Fallback to cargo-install is disabled")] + #[diagnostic(severity(error), code(binstall::no_fallback_to_cargo_install))] + NoFallbackToCargoInstall, + /// A wrapped error providing the context of which crate the error is about. #[error("for crate {crate_name}")] CrateContext { @@ -348,6 +364,8 @@ impl BinstallError { DuplicateSourceFilePath { .. } => 90, InvalidSourceFilePath { .. } => 91, EmptySourceFilePath => 92, + InvalidStrategies(..) => 93, + NoFallbackToCargoInstall => 94, CrateContext { error, .. } => error.exit_number(), }; diff --git a/crates/binstalk/src/ops.rs b/crates/binstalk/src/ops.rs index b7475750..582d3911 100644 --- a/crates/binstalk/src/ops.rs +++ b/crates/binstalk/src/ops.rs @@ -1,14 +1,21 @@ //! Concrete Binstall operations. -use std::path::PathBuf; +use std::{path::PathBuf, sync::Arc}; use semver::VersionReq; -use crate::{manifests::cargo_toml_binstall::PkgOverride, DesiredTargets}; +use crate::{ + fetchers::{Data, Fetcher}, + helpers::remote::Client, + manifests::cargo_toml_binstall::PkgOverride, + DesiredTargets, +}; pub mod install; pub mod resolve; +pub type Resolver = fn(&Client, &Arc) -> Arc; + pub struct Options { pub no_symlinks: bool, pub dry_run: bool, @@ -18,4 +25,5 @@ pub struct Options { pub cli_overrides: PkgOverride, pub desired_targets: DesiredTargets, pub quiet: bool, + pub resolver: Vec, } diff --git a/crates/binstalk/src/ops/resolve.rs b/crates/binstalk/src/ops/resolve.rs index 3335a130..1e494e5c 100644 --- a/crates/binstalk/src/ops/resolve.rs +++ b/crates/binstalk/src/ops/resolve.rs @@ -18,7 +18,7 @@ use crate::{ bins, drivers::fetch_crate_cratesio, errors::BinstallError, - fetchers::{Data, Fetcher, GhCrateMeta, QuickInstall}, + fetchers::{Data, Fetcher}, helpers::{remote::Client, tasks::AutoAbortJoinHandle}, manifests::cargo_toml_binstall::{Meta, PkgMeta}, }; @@ -211,7 +211,7 @@ async fn resolve_inner( meta: target_meta, }) }) - .cartesian_product([GhCrateMeta::new, QuickInstall::new]) + .cartesian_product(&opts.resolver) .map(|(fetcher_data, f)| { let fetcher = f(&client, &fetcher_data); (