diff --git a/src/errors.rs b/src/errors.rs index 4c7e5cad1..ec19f29a1 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -147,8 +147,7 @@ pub enum BinstallError { /// /// This may be the case when using the `--version` option. /// - /// Note that using `--version 1.2.3` is interpreted as the requirement `^1.2.3` as per - /// Cargo.toml rules. If you want the exact version 1.2.3, use `--version '=1.2.3'`. + /// Note that using `--version 1.2.3` is interpreted as the requirement `=1.2.3`. /// /// - Code: `binstall::version::mismatch` /// - Exit: 82 @@ -167,21 +166,15 @@ pub enum BinstallError { v: semver::Version, }, - /// Warning: The resolved version may not be what was meant. - /// - /// This occurs when using the `--version` option with a bare version, like `--version 1.2.3`. - /// That is parsed as the semver requirement `^1.2.3`, but the user may have expected that to - /// be an exact version (which should be specified with `--version '=1.2.3'`. - /// - /// - Code: `binstall::version::warning` - /// - Exit: none (runtime warning only) - #[error("version semantic mismatch: {ver} <> {req}")] + /// This occurs when you specified `--version` while also using + /// form `$crate_name@$ver` tp specify version requirements. + #[error("duplicate version requirements")] #[diagnostic( - severity(warning), - code(binstall::version::warning), - help("You specified `--version {req}` but the package resolved that to '{ver}'.\nUse `--version '={req}'` if you want an exact match.") + severity(error), + code(binstall::version::requirement), + help("Remove the `--version req` or simply use `$crate_name`") )] - VersionWarning { ver: String, req: String }, + DuplicateVersionReq, } impl BinstallError { @@ -208,7 +201,7 @@ impl BinstallError { VersionReq { .. } => 81, VersionMismatch { .. } => 82, VersionUnavailable { .. } => 83, - VersionWarning { .. } => unimplemented!("BUG: warnings do not terminate"), + DuplicateVersionReq => 84, }; // reserved codes diff --git a/src/helpers.rs b/src/helpers.rs index b7614e2f5..254119b81 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -34,6 +34,9 @@ pub use path_ext::*; mod tls_version; pub use tls_version::TLSVersion; +mod crate_name; +pub use crate_name::CrateName; + /// Load binstall metadata from the crate `Cargo.toml` at the provided path pub fn load_manifest_path>( manifest_path: P, diff --git a/src/helpers/crate_name.rs b/src/helpers/crate_name.rs new file mode 100644 index 000000000..b7eb1077e --- /dev/null +++ b/src/helpers/crate_name.rs @@ -0,0 +1,39 @@ +use std::convert::Infallible; +use std::fmt; +use std::str::FromStr; + +#[derive(Debug)] +pub struct CrateName { + pub name: String, + pub version: Option, +} + +impl fmt::Display for CrateName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name)?; + + if let Some(version) = &self.version { + write!(f, "@{version}")?; + } + + Ok(()) + } +} + +impl FromStr for CrateName { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(if let Some((name, version)) = s.split_once('@') { + CrateName { + name: name.to_string(), + version: Some(version.to_string()), + } + } else { + CrateName { + name: s.to_string(), + version: None, + } + }) + } +} diff --git a/src/main.rs b/src/main.rs index 24cf69646..b5a8af311 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,6 @@ use std::{ ffi::OsString, path::{Path, PathBuf}, process::{ExitCode, Termination}, - str::FromStr, time::{Duration, Instant}, }; @@ -36,14 +35,14 @@ struct Options { /// /// This must be a crates.io package name. #[clap(value_name = "crate")] - name: String, + crate_name: CrateName, /// Semver filter to select the package version to install. /// /// This is in Cargo.toml dependencies format: `--version 1.2.3` is equivalent to /// `--version "^1.2.3"`. Use `=1.2.3` to install a specific version. - #[clap(long, default_value = "*")] - version: String, + #[clap(long)] + version: Option, /// Override binary target set. /// @@ -237,34 +236,34 @@ async fn entry() -> Result<()> { .map_err(BinstallError::from) .wrap_err("Creating a temporary directory failed.")?; - info!("Installing package: '{}'", opts.name); + info!("Installing package: '{}'", opts.crate_name); + + let mut version = match (&opts.crate_name.version, &opts.version) { + (Some(version), None) => version.to_string(), + (None, Some(version)) => version.to_string(), + (Some(_), Some(_)) => Err(BinstallError::DuplicateVersionReq)?, + (None, None) => "*".to_string(), + }; + + if version + .chars() + .next() + .map(|ch| ch.is_ascii_digit()) + .unwrap_or(false) + { + version.insert(0, '='); + } // Fetch crate via crates.io, git, or use a local manifest path // TODO: work out which of these to do based on `opts.name` // TODO: support git-based fetches (whole repo name rather than just crate name) let manifest = match opts.manifest_path.clone() { Some(manifest_path) => load_manifest_path(manifest_path.join("Cargo.toml"))?, - None => fetch_crate_cratesio(&client, &opts.name, &opts.version).await?, + None => fetch_crate_cratesio(&client, &opts.crate_name.name, &version).await?, }; let package = manifest.package.unwrap(); - let is_plain_version = semver::Version::from_str(&opts.version).is_ok(); - if is_plain_version && package.version != opts.version { - warn!("Warning!"); - eprintln!( - "{:?}", - miette::Report::new(BinstallError::VersionWarning { - ver: package.version.clone(), - req: opts.version.clone() - }) - ); - - if !opts.dry_run { - uithread.confirm().await?; - } - } - let (mut meta, binaries) = ( package .metadata @@ -312,7 +311,9 @@ async fn entry() -> Result<()> { meta.merge(&cli_overrides); // Generate temporary binary path - let bin_path = temp_dir.path().join(format!("bin-{}", opts.name)); + let bin_path = temp_dir + .path() + .join(format!("bin-{}", opts.crate_name.name)); debug!("Using temporary binary path: {}", bin_path.display()); let bin_files = collect_bin_files( @@ -363,6 +364,7 @@ async fn entry() -> Result<()> { opts, package, temp_dir, + version, &bin_path, &bin_files, ) @@ -438,6 +440,7 @@ async fn install_from_package( opts: Options, package: Package, temp_dir: TempDir, + version: String, bin_path: &Path, bin_files: &[bins::BinFile], ) -> Result<()> { @@ -478,7 +481,7 @@ async fn install_from_package( } let cvs = metafiles::CrateVersionSource { - name: opts.name, + name: opts.crate_name.name, version: package.version.parse().into_diagnostic()?, source: metafiles::Source::Registry( url::Url::parse("https://github.com/rust-lang/crates.io-index").unwrap(), @@ -521,7 +524,7 @@ async fn install_from_package( c2.insert( cvs.clone(), metafiles::v2::CrateInfo { - version_req: Some(opts.version), + version_req: Some(version), bins, profile: "release".into(), target: fetcher.target().to_string(),