diff --git a/crates/bin/src/args.rs b/crates/bin/src/args.rs index b993ed72..055e1e4e 100644 --- a/crates/bin/src/args.rs +++ b/crates/bin/src/args.rs @@ -93,6 +93,20 @@ pub struct Args { )] pub(crate) targets: Option>, + /// Install only the specified binaries. + /// + /// This mirrors the equivalent argument in `cargo install --bin`. + /// + /// If omitted, all binaries are installed. + #[clap( + help_heading = "Package selection", + long, + value_name = "BINARY", + num_args = 1.., + action = clap::ArgAction::Append + )] + pub(crate) bin: Option>, + /// Override Cargo.toml package manifest path. /// /// This skips searching crates.io for a manifest and uses the specified path directly, useful diff --git a/crates/bin/src/entry.rs b/crates/bin/src/entry.rs index 9df479f4..4c4865e4 100644 --- a/crates/bin/src/entry.rs +++ b/crates/bin/src/entry.rs @@ -157,6 +157,10 @@ pub fn install_crates( desired_targets, resolvers, cargo_install_fallback, + bins: args.bin.map(|mut bins| { + bins.sort_unstable(); + bins + }), temp_dir: temp_dir.path().to_owned(), install_path, diff --git a/crates/binstalk/src/ops.rs b/crates/binstalk/src/ops.rs index 8051daaa..88d38fa2 100644 --- a/crates/binstalk/src/ops.rs +++ b/crates/binstalk/src/ops.rs @@ -2,6 +2,7 @@ use std::{path::PathBuf, sync::Arc, time::Duration}; +use compact_str::CompactString; use semver::VersionReq; use crate::{ @@ -45,6 +46,9 @@ pub struct Options { pub resolvers: Vec, pub cargo_install_fallback: bool, + /// If provided, the names are sorted. + pub bins: Option>, + pub temp_dir: PathBuf, pub install_path: PathBuf, pub cargo_root: Option, diff --git a/crates/binstalk/src/ops/resolve/resolution.rs b/crates/binstalk/src/ops/resolve/resolution.rs index 75d4f398..0e53dbc9 100644 --- a/crates/binstalk/src/ops/resolve/resolution.rs +++ b/crates/binstalk/src/ops/resolve/resolution.rs @@ -1,5 +1,6 @@ use std::{borrow::Cow, env, ffi::OsStr, fmt, iter, path::Path, sync::Arc}; +use binstalk_bins::BinFile; use command_group::AsyncCommandGroup; use compact_str::{CompactString, ToCompactString}; use either::Either; @@ -87,14 +88,27 @@ impl ResolutionFetch { current_version: self.new_version, source: self.source, target: self.fetcher.target().to_compact_string(), - bins: self - .bin_files - .into_iter() - .map(|bin| bin.base_name) - .collect(), + bins: Self::resolve_bins(&opts.bins, self.bin_files), }) } + fn resolve_bins( + user_specified_bins: &Option>, + crate_bin_files: Vec, + ) -> Vec { + // We need to filter crate_bin_files by user_specified_bins in case the prebuilt doesn't + // have featured-gated (optional) binary (gated behind feature). + crate_bin_files + .into_iter() + .map(|bin| bin.base_name) + .filter(|bin_name| { + user_specified_bins + .as_ref() + .map_or(true, |bins| bins.binary_search(bin_name).is_ok()) + }) + .collect() + } + pub fn print(&self, opts: &Options) { let fetcher = &self.fetcher; let bin_files = &self.bin_files; @@ -185,6 +199,12 @@ impl ResolutionSource { cmd.arg("--no-track"); } + if let Some(bins) = &opts.bins { + for bin in bins { + cmd.arg("--bin").arg(bin); + } + } + debug!("Running `{}`", format_cmd(&cmd)); if !opts.dry_run { diff --git a/e2e-tests/specific-binaries.sh b/e2e-tests/specific-binaries.sh new file mode 100755 index 00000000..bc3d92c8 --- /dev/null +++ b/e2e-tests/specific-binaries.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -euxo pipefail + +unset CARGO_INSTALL_ROOT + +CARGO_HOME=$(mktemp -d 2>/dev/null || mktemp -d -t 'cargo-home') +export CARGO_HOME +export PATH="$CARGO_HOME/bin:$PATH" + +# Install a specific binary, ensuring we don't fallback to source. +"./$1" binstall --no-confirm taplo-cli --bin taplo + +# Verify that the binary was installed and is executable +if ! command -v taplo >/dev/null 2>&1; then + echo "taplo was not installed" + exit 1 +fi + +# Run the binary to check it works +taplo --version + +# Install a specific binary, but always compile from source. +"./$1" binstall --no-confirm ripgrep --bin rg --strategies compile + +# Verify that the binary was installed and is executable +if ! command -v rg >/dev/null 2>&1; then + echo "rg was not installed" + exit 1 +fi + +# Run the binary to check it works +rg --version diff --git a/justfile b/justfile index e9cb9f53..b63c682c 100644 --- a/justfile +++ b/justfile @@ -224,6 +224,7 @@ e2e-test-signing: (e2e-test "signing") e2e-test-continue-on-failure: (e2e-test "continue-on-failure") e2e-test-private-github-repo: (e2e-test "private-github-repo") e2e-test-self-install: (e2e-test "self-install") +e2e-test-specific-binaries: (e2e-test "specific-binaries") # WinTLS (Windows in CI) does not have TLS 1.3 support [windows] @@ -232,7 +233,8 @@ e2e-test-tls: (e2e-test "tls" "1.2") [macos] e2e-test-tls: (e2e-test "tls" "1.2") (e2e-test "tls" "1.3") -e2e-tests: e2e-test-live e2e-test-manifest-path e2e-test-git e2e-test-other-repos e2e-test-strategies e2e-test-version-syntax e2e-test-upgrade e2e-test-tls e2e-test-self-upgrade-no-symlink e2e-test-uninstall e2e-test-subcrate e2e-test-no-track e2e-test-registries e2e-test-signing e2e-test-continue-on-failure e2e-test-private-github-repo e2e-test-self-install +# e2e-test-self-install needs to be the last task to run, as it would consume the cargo-binstall binary +e2e-tests: e2e-test-live e2e-test-manifest-path e2e-test-git e2e-test-other-repos e2e-test-strategies e2e-test-version-syntax e2e-test-upgrade e2e-test-tls e2e-test-self-upgrade-no-symlink e2e-test-uninstall e2e-test-subcrate e2e-test-no-track e2e-test-registries e2e-test-signing e2e-test-continue-on-failure e2e-test-private-github-repo e2e-test-specific-binaries e2e-test-self-install unit-tests: print-env cargo test --no-run --target {{target}}