mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-05-06 04:00:02 +00:00
fix version matching, now works with semver
This commit is contained in:
parent
a6c70b41e2
commit
ef6a3d0ef7
4 changed files with 210 additions and 45 deletions
|
@ -2,45 +2,90 @@
|
|||
use std::time::Duration;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use log::{debug, error};
|
||||
use log::{debug};
|
||||
use anyhow::{Context, anyhow};
|
||||
use semver::{Version, VersionReq};
|
||||
|
||||
use crates_io_api::AsyncClient;
|
||||
|
||||
use crate::PkgFmt;
|
||||
use crate::helpers::*;
|
||||
|
||||
fn find_version<'a, V: Iterator<Item=&'a str>>(requirement: &str, version_iter: V) -> Result<String, anyhow::Error> {
|
||||
// Parse version requirement
|
||||
let version_req = VersionReq::parse(requirement)?;
|
||||
|
||||
// Filter for matching versions
|
||||
let mut filtered: Vec<_> = version_iter.filter(|v| {
|
||||
// Remove leading `v` for git tags
|
||||
let ver_str = match v.strip_prefix("s") {
|
||||
Some(v) => v,
|
||||
None => v,
|
||||
};
|
||||
|
||||
// Parse out version
|
||||
let ver = match Version::parse(ver_str) {
|
||||
Ok(sv) => sv,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
debug!("Version: {:?}", ver);
|
||||
|
||||
// Filter by version match
|
||||
version_req.matches(&ver)
|
||||
}).collect();
|
||||
|
||||
// Sort by highest matching version
|
||||
filtered.sort_by(|a, b| {
|
||||
let a = Version::parse(a).unwrap();
|
||||
let b = Version::parse(b).unwrap();
|
||||
|
||||
b.partial_cmp(&a).unwrap()
|
||||
});
|
||||
|
||||
debug!("Filtered: {:?}", filtered);
|
||||
|
||||
// Return highest version
|
||||
match filtered.get(0) {
|
||||
Some(v) => Ok(v.to_string()),
|
||||
None => Err(anyhow!("No matching version for requirement: '{}'", version_req))
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch a crate by name and version from crates.io
|
||||
pub async fn fetch_crate_cratesio(name: &str, version: Option<&str>, temp_dir: &Path) -> Result<PathBuf, anyhow::Error> {
|
||||
// Build crates.io api client and fetch info
|
||||
let api_client = AsyncClient::new("cargo-binstall (https://github.com/ryankurte/cargo-binstall)", Duration::from_millis(100))?;
|
||||
pub async fn fetch_crate_cratesio(name: &str, version_req: &str, temp_dir: &Path) -> Result<PathBuf, anyhow::Error> {
|
||||
|
||||
debug!("Fetching information for crate: '{}'", name);
|
||||
// Fetch / update index
|
||||
debug!("Updating crates.io index");
|
||||
let index = crates_index::Index::new_cargo_default();
|
||||
index.retrieve_or_update()?;
|
||||
|
||||
// Fetch overall crate info
|
||||
let info = match api_client.get_crate(name.as_ref()).await {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
error!("Error fetching information for crate {}: {}", name, e);
|
||||
return Err(e.into())
|
||||
// Lookup crate in index
|
||||
debug!("Looking up crate information");
|
||||
let base_info = match index.crate_(name) {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
return Err(anyhow::anyhow!("Error fetching information for crate {}", name));
|
||||
}
|
||||
};
|
||||
|
||||
// Use specified or latest version
|
||||
let version_num = match version {
|
||||
Some(v) => v.to_string(),
|
||||
None => info.crate_data.max_version,
|
||||
};
|
||||
// Locate matching version
|
||||
let version_iter = base_info.versions().iter().map(|v| v.version() );
|
||||
let version_name = find_version(version_req, version_iter)?;
|
||||
|
||||
// Build crates.io api client
|
||||
let api_client = AsyncClient::new("cargo-binstall (https://github.com/ryankurte/cargo-binstall)", Duration::from_millis(100))?;
|
||||
|
||||
// Fetch crates.io information for the specified version
|
||||
// Note it is not viable to use a semver match here as crates.io
|
||||
// appears to elide alpha and yanked versions in the generic response...
|
||||
let versions = info.versions.clone();
|
||||
let version = match versions.iter().find(|v| v.num == version_num) {
|
||||
// Fetch online crate information
|
||||
let crate_info = api_client.get_crate(name.as_ref()).await
|
||||
.context("Error fetching crate information")?;
|
||||
|
||||
// Fetch information for the filtered version
|
||||
let version = match crate_info.versions.iter().find(|v| v.num == version_name) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
error!("No crates.io information found for crate: '{}' version: '{}'",
|
||||
name, version_num);
|
||||
return Err(anyhow::anyhow!("No crate information found"));
|
||||
return Err(anyhow::anyhow!("No information found for crate: '{}' version: '{}'",
|
||||
name, version_name));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -58,7 +103,7 @@ pub async fn fetch_crate_cratesio(name: &str, version: Option<&str>, temp_dir: &
|
|||
// Decompress downloaded tgz
|
||||
debug!("Decompressing crate archive");
|
||||
extract(&tgz_path, PkgFmt::Tgz, &temp_dir)?;
|
||||
let crate_path = temp_dir.join(format!("{}-{}", name, version_num));
|
||||
let crate_path = temp_dir.join(format!("{}-{}", name, version_name));
|
||||
|
||||
// Return crate directory
|
||||
Ok(crate_path)
|
||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -18,8 +18,8 @@ struct Options {
|
|||
name: String,
|
||||
|
||||
/// Filter for package version to install
|
||||
#[structopt(long)]
|
||||
version: Option<String>,
|
||||
#[structopt(long, default_value = "*")]
|
||||
version: String,
|
||||
|
||||
/// Override binary target, ignoring compiled version
|
||||
#[structopt(long, default_value = TARGET)]
|
||||
|
@ -30,14 +30,6 @@ struct Options {
|
|||
#[structopt(long)]
|
||||
install_path: Option<String>,
|
||||
|
||||
/// Do not cleanup temporary files on success
|
||||
#[structopt(long)]
|
||||
no_cleanup: bool,
|
||||
|
||||
/// Disable interactive mode / confirmation
|
||||
#[structopt(long)]
|
||||
no_confirm: bool,
|
||||
|
||||
/// Disable symlinking / versioned updates
|
||||
#[structopt(long)]
|
||||
no_symlinks: bool,
|
||||
|
@ -46,6 +38,14 @@ struct Options {
|
|||
#[structopt(long)]
|
||||
dry_run: bool,
|
||||
|
||||
/// Disable interactive mode / confirmation
|
||||
#[structopt(long)]
|
||||
no_confirm: bool,
|
||||
|
||||
/// Do not cleanup temporary files on success
|
||||
#[structopt(long)]
|
||||
no_cleanup: bool,
|
||||
|
||||
/// Override manifest source.
|
||||
/// This skips searching crates.io for a manifest and uses
|
||||
/// the specified path directly, useful for debugging and
|
||||
|
@ -59,7 +59,6 @@ struct Options {
|
|||
}
|
||||
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
|
||||
|
@ -91,7 +90,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
// TODO: support git-based fetches (whole repo name rather than just crate name)
|
||||
let manifest_path = match opts.manifest_path.clone() {
|
||||
Some(p) => p,
|
||||
None => fetch_crate_cratesio(&opts.name, opts.version.as_deref(), temp_dir.path()).await?,
|
||||
None => fetch_crate_cratesio(&opts.name, &opts.version, temp_dir.path()).await?,
|
||||
};
|
||||
|
||||
debug!("Reading manifest: {}", manifest_path.display());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue