playing with version matching

Can't use semver because crates.io hides alpha versions? not sure how this works in cargo
This commit is contained in:
ryan 2020-12-30 18:13:38 +13:00
parent 8777c355c5
commit 223c6ef43a
3 changed files with 61 additions and 82 deletions

View file

@ -5,7 +5,6 @@ use std::path::{Path, PathBuf};
use log::{debug, error}; use log::{debug, error};
use crates_io_api::AsyncClient; use crates_io_api::AsyncClient;
use semver::Version;
use crate::PkgFmt; use crate::PkgFmt;
use crate::helpers::*; use crate::helpers::*;
@ -13,7 +12,6 @@ use crate::helpers::*;
/// Fetch a crate by name and version from crates.io /// 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> { 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 // Build crates.io api client and fetch info
// TODO: support git-based fetches (whole repo name rather than just crate name)
let api_client = AsyncClient::new("cargo-binstall (https://github.com/ryankurte/cargo-binstall)", Duration::from_millis(100))?; let api_client = AsyncClient::new("cargo-binstall (https://github.com/ryankurte/cargo-binstall)", Duration::from_millis(100))?;
debug!("Fetching information for crate: '{}'", name); debug!("Fetching information for crate: '{}'", name);
@ -34,15 +32,9 @@ pub async fn fetch_crate_cratesio(name: &str, version: Option<&str>, temp_dir: &
}; };
// Fetch crates.io information for the specified version // Fetch crates.io information for the specified version
// TODO: Filter by semver matches instead of literal match // Note it is not viable to use a semver match here as crates.io
let mut versions = info.versions.clone(); // appears to elide alpha and yanked versions in the generic response...
versions.sort_by(|a, b| { let versions = info.versions.clone();
let ver_a = Version::parse(&a.num).unwrap();
let ver_b = Version::parse(&b.num).unwrap();
ver_a.partial_cmp(&ver_b).unwrap()
} );
let version = match versions.iter().find(|v| v.num == version_num) { let version = match versions.iter().find(|v| v.num == version_num) {
Some(v) => v, Some(v) => v,
None => { None => {
@ -78,3 +70,4 @@ pub async fn fetch_crate_gh_releases(_name: &str, _version: Option<&str>, _temp_
unimplemented!(); unimplemented!();
} }

View file

@ -35,6 +35,11 @@ pub enum PkgFmt {
Bin, Bin,
} }
impl Default for PkgFmt {
fn default() -> Self {
Self::Tgz
}
}
/// Metadata for binary installation use. /// Metadata for binary installation use.
/// ///
@ -49,6 +54,7 @@ pub struct Meta {
pub pkg_name: Option<String>, pub pkg_name: Option<String>,
/// Format override for package downloads /// Format override for package downloads
#[serde(default)]
pub pkg_fmt: Option<PkgFmt>, pub pkg_fmt: Option<PkgFmt>,
#[serde(default)] #[serde(default)]
@ -56,7 +62,7 @@ pub struct Meta {
pub pkg_bins: Vec<String>, pub pkg_bins: Vec<String>,
/// Public key for package verification (base64 encoded) /// Public key for package verification (base64 encoded)
pub pkg_pub_key: Option<String>, pub pub_key: Option<String>,
} }
/// Template for constructing download paths /// Template for constructing download paths

View file

@ -19,7 +19,7 @@ struct Options {
#[structopt()] #[structopt()]
name: String, name: String,
/// Package version to instal /// Package version to install
#[structopt(long)] #[structopt(long)]
version: Option<String>, version: Option<String>,
@ -32,9 +32,6 @@ struct Options {
#[structopt(long)] #[structopt(long)]
install_path: Option<String>, install_path: Option<String>,
#[structopt(flatten)]
overrides: Overrides,
/// Do not cleanup temporary files on success /// Do not cleanup temporary files on success
#[structopt(long)] #[structopt(long)]
no_cleanup: bool, no_cleanup: bool,
@ -47,41 +44,18 @@ struct Options {
#[structopt(long)] #[structopt(long)]
no_symlinks: bool, no_symlinks: bool,
/// Override manifest source.
/// This skips searching crates.io for a manifest and uses
/// the specified path directly, useful for debugging and
/// when adding `binstall` support.
#[structopt(long)]
manifest_path: Option<PathBuf>,
/// Utility log level /// Utility log level
#[structopt(long, default_value = "info")] #[structopt(long, default_value = "info")]
log_level: LevelFilter, log_level: LevelFilter,
} }
#[derive(Debug, StructOpt)]
pub struct Overrides {
/// Override the package name.
/// This is only useful for diagnostics when using the default `pkg_url`
/// as you can otherwise customise this in the path.
/// Defaults to the crate name.
#[structopt(long)]
pkg_name: Option<String>,
/// Override the package path template.
/// If no `metadata.pkg_url` key is set or `--pkg-url` argument provided, this
/// defaults to `{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.tgz`
#[structopt(long)]
pkg_url: Option<String>,
/// Override format for binary file download.
/// Defaults to `tgz`
#[structopt(long)]
pkg_fmt: Option<PkgFmt>,
/// Override manifest source.
/// This skips searching crates.io for a manifest and uses
/// the specified path directly, useful for debugging
#[structopt(long)]
manifest_path: Option<PathBuf>,
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), anyhow::Error> { async fn main() -> Result<(), anyhow::Error> {
@ -106,11 +80,18 @@ async fn main() -> Result<(), anyhow::Error> {
// Create a temporary directory for downloads etc. // Create a temporary directory for downloads etc.
let temp_dir = TempDir::new("cargo-binstall")?; let temp_dir = TempDir::new("cargo-binstall")?;
info!("Installing package: '{}'", opts.name);
// Fetch crate via crates.io, git, or use a local manifest path // 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: work out which of these to do based on `opts.name`
let crate_path = match opts.overrides.manifest_path { // TODO: support git-based fetches (whole repo name rather than just crate name)
Some(p) => p, let crate_path = match opts.manifest_path {
None => fetch_crate_cratesio(&opts.name, opts.version.as_deref(), temp_dir.path()).await?, Some(p) => {
p
},
None => {
fetch_crate_cratesio(&opts.name, opts.version.as_deref(), temp_dir.path()).await?
},
}; };
// Read cargo manifest // Read cargo manifest
@ -129,37 +110,31 @@ async fn main() -> Result<(), anyhow::Error> {
debug!("Retrieved metadata: {:?}", meta); debug!("Retrieved metadata: {:?}", meta);
// Select which package path to use // Select which package path to use
let pkg_url = match (opts.overrides.pkg_url, meta.as_ref().map(|m| m.pkg_url.clone() ).flatten()) { let pkg_url = match meta.as_ref().map(|m| m.pkg_url.clone() ).flatten() {
(Some(p), _) => { Some(m) => {
info!("Using package url override: '{}'", p); debug!("Using package url: '{}'", &m);
p
},
(_, Some(m)) => {
info!("Using package url: '{}'", &m);
m m
}, },
_ => { _ => {
info!("No `pkg-url` key found in Cargo.toml or `--pkg-url` argument provided"); debug!("No `pkg-url` key found in Cargo.toml or `--pkg-url` argument provided");
info!("Using default url: {}", DEFAULT_PKG_PATH); debug!("Using default url: {}", DEFAULT_PKG_PATH);
DEFAULT_PKG_PATH.to_string() DEFAULT_PKG_PATH.to_string()
}, },
}; };
// Select bin format to use // Select bin format to use
let pkg_fmt = match (opts.overrides.pkg_fmt, meta.as_ref().map(|m| m.pkg_fmt.clone() ).flatten()) { let pkg_fmt = match meta.as_ref().map(|m| m.pkg_fmt.clone() ).flatten() {
(Some(o), _) => o, Some(m) => m.clone(),
(_, Some(m)) => m.clone(),
_ => PkgFmt::Tgz, _ => PkgFmt::Tgz,
}; };
// Override package name if required // Override package name if required
let pkg_name = match (&opts.overrides.pkg_name, meta.as_ref().map(|m| m.pkg_name.clone() ).flatten()) { let pkg_name = match meta.as_ref().map(|m| m.pkg_name.clone() ).flatten() {
(Some(o), _) => o.clone(), Some(m) => m,
(_, Some(m)) => m,
_ => opts.name.clone(), _ => opts.name.clone(),
}; };
// Generate context for interpolation // Generate context for URL interpolation
let ctx = Context { let ctx = Context {
name: pkg_name.to_string(), name: pkg_name.to_string(),
repo: package.repository, repo: package.repository,
@ -190,8 +165,10 @@ async fn main() -> Result<(), anyhow::Error> {
let pkg_path = temp_dir.path().join(format!("pkg-{}.{}", pkg_name, pkg_fmt)); let pkg_path = temp_dir.path().join(format!("pkg-{}.{}", pkg_name, pkg_fmt));
download(&rendered, pkg_path.to_str().unwrap()).await?; download(&rendered, pkg_path.to_str().unwrap()).await?;
#[cfg(incomplete)]
{
// Fetch and check package signature if available // Fetch and check package signature if available
if let Some(pub_key) = meta.as_ref().map(|m| m.pkg_pub_key.clone() ).flatten() { if let Some(pub_key) = meta.as_ref().map(|m| m.pub_key.clone() ).flatten() {
debug!("Found public key: {}", pub_key); debug!("Found public key: {}", pub_key);
// Generate signature file URL // Generate signature file URL
@ -211,6 +188,7 @@ async fn main() -> Result<(), anyhow::Error> {
} else { } else {
warn!("No public key found, package signature could not be validated"); warn!("No public key found, package signature could not be validated");
} }
}
// Extract files // Extract files
let bin_path = temp_dir.path().join(format!("bin-{}", pkg_name)); let bin_path = temp_dir.path().join(format!("bin-{}", pkg_name));
@ -230,7 +208,7 @@ async fn main() -> Result<(), anyhow::Error> {
let name = source.file_name().map(|v| v.to_str()).flatten().unwrap().to_string(); let name = source.file_name().map(|v| v.to_str()).flatten().unwrap().to_string();
// Trim target and version from name if included in binary file name // Trim target and version from name if included in binary file name
let base_name = name.replace(&format!("-{}", ctx.target), "") let base_name = name.replace(&format!("-{}", TARGET), "")
.replace(&format!("-v{}", ctx.version), "") .replace(&format!("-v{}", ctx.version), "")
.replace(&format!("-{}", ctx.version), ""); .replace(&format!("-{}", ctx.version), "");
@ -262,6 +240,8 @@ async fn main() -> Result<(), anyhow::Error> {
return Ok(()) return Ok(())
} }
info!("Installing binaries...");
// Install binaries // Install binaries
for (_name, source, dest, _link) in &bin_files { for (_name, source, dest, _link) in &bin_files {
// TODO: check if file already exists // TODO: check if file already exists