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 crates_io_api::AsyncClient;
use semver::Version;
use crate::PkgFmt;
use crate::helpers::*;
@ -13,7 +12,6 @@ use crate::helpers::*;
/// 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
// 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))?;
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
// TODO: Filter by semver matches instead of literal match
let mut versions = info.versions.clone();
versions.sort_by(|a, b| {
let ver_a = Version::parse(&a.num).unwrap();
let ver_b = Version::parse(&b.num).unwrap();
ver_a.partial_cmp(&ver_b).unwrap()
} );
// 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) {
Some(v) => v,
None => {
@ -78,3 +70,4 @@ pub async fn fetch_crate_gh_releases(_name: &str, _version: Option<&str>, _temp_
unimplemented!();
}

View file

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

View file

@ -19,7 +19,7 @@ struct Options {
#[structopt()]
name: String,
/// Package version to instal
/// Package version to install
#[structopt(long)]
version: Option<String>,
@ -32,9 +32,6 @@ struct Options {
#[structopt(long)]
install_path: Option<String>,
#[structopt(flatten)]
overrides: Overrides,
/// Do not cleanup temporary files on success
#[structopt(long)]
no_cleanup: bool,
@ -47,41 +44,18 @@ struct Options {
#[structopt(long)]
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
#[structopt(long, default_value = "info")]
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]
async fn main() -> Result<(), anyhow::Error> {
@ -106,11 +80,18 @@ async fn main() -> Result<(), anyhow::Error> {
// Create a temporary directory for downloads etc.
let temp_dir = TempDir::new("cargo-binstall")?;
info!("Installing package: '{}'", opts.name);
// Fetch crate via crates.io, git, or use a local manifest path
// TODO: work out which of these to do based on `opts.name`
let crate_path = match opts.overrides.manifest_path {
Some(p) => p,
None => fetch_crate_cratesio(&opts.name, opts.version.as_deref(), temp_dir.path()).await?,
// TODO: support git-based fetches (whole repo name rather than just crate name)
let crate_path = match opts.manifest_path {
Some(p) => {
p
},
None => {
fetch_crate_cratesio(&opts.name, opts.version.as_deref(), temp_dir.path()).await?
},
};
// Read cargo manifest
@ -129,37 +110,31 @@ async fn main() -> Result<(), anyhow::Error> {
debug!("Retrieved metadata: {:?}", meta);
// Select which package path to use
let pkg_url = match (opts.overrides.pkg_url, meta.as_ref().map(|m| m.pkg_url.clone() ).flatten()) {
(Some(p), _) => {
info!("Using package url override: '{}'", p);
p
},
(_, Some(m)) => {
info!("Using package url: '{}'", &m);
let pkg_url = match meta.as_ref().map(|m| m.pkg_url.clone() ).flatten() {
Some(m) => {
debug!("Using package url: '{}'", &m);
m
},
_ => {
info!("No `pkg-url` key found in Cargo.toml or `--pkg-url` argument provided");
info!("Using default url: {}", DEFAULT_PKG_PATH);
debug!("No `pkg-url` key found in Cargo.toml or `--pkg-url` argument provided");
debug!("Using default url: {}", DEFAULT_PKG_PATH);
DEFAULT_PKG_PATH.to_string()
},
};
// Select bin format to use
let pkg_fmt = match (opts.overrides.pkg_fmt, meta.as_ref().map(|m| m.pkg_fmt.clone() ).flatten()) {
(Some(o), _) => o,
(_, Some(m)) => m.clone(),
let pkg_fmt = match meta.as_ref().map(|m| m.pkg_fmt.clone() ).flatten() {
Some(m) => m.clone(),
_ => PkgFmt::Tgz,
};
// Override package name if required
let pkg_name = match (&opts.overrides.pkg_name, meta.as_ref().map(|m| m.pkg_name.clone() ).flatten()) {
(Some(o), _) => o.clone(),
(_, Some(m)) => m,
let pkg_name = match meta.as_ref().map(|m| m.pkg_name.clone() ).flatten() {
Some(m) => m,
_ => opts.name.clone(),
};
// Generate context for interpolation
// Generate context for URL interpolation
let ctx = Context {
name: pkg_name.to_string(),
repo: package.repository,
@ -190,26 +165,29 @@ async fn main() -> Result<(), anyhow::Error> {
let pkg_path = temp_dir.path().join(format!("pkg-{}.{}", pkg_name, pkg_fmt));
download(&rendered, pkg_path.to_str().unwrap()).await?;
// Fetch and check package signature if available
if let Some(pub_key) = meta.as_ref().map(|m| m.pkg_pub_key.clone() ).flatten() {
debug!("Found public key: {}", pub_key);
#[cfg(incomplete)]
{
// Fetch and check package signature if available
if let Some(pub_key) = meta.as_ref().map(|m| m.pub_key.clone() ).flatten() {
debug!("Found public key: {}", pub_key);
// Generate signature file URL
let mut sig_ctx = ctx.clone();
sig_ctx.format = "sig".to_string();
let sig_url = sig_ctx.render(&pkg_url)?;
// Generate signature file URL
let mut sig_ctx = ctx.clone();
sig_ctx.format = "sig".to_string();
let sig_url = sig_ctx.render(&pkg_url)?;
debug!("Fetching signature file: {}", sig_url);
debug!("Fetching signature file: {}", sig_url);
// Download signature file
let sig_path = temp_dir.path().join(format!("{}.sig", pkg_name));
download(&sig_url, &sig_path).await?;
// Download signature file
let sig_path = temp_dir.path().join(format!("{}.sig", pkg_name));
download(&sig_url, &sig_path).await?;
// TODO: do the signature check
unimplemented!()
// TODO: do the signature check
unimplemented!()
} else {
warn!("No public key found, package signature could not be validated");
} else {
warn!("No public key found, package signature could not be validated");
}
}
// Extract files
@ -230,7 +208,7 @@ async fn main() -> Result<(), anyhow::Error> {
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
let base_name = name.replace(&format!("-{}", ctx.target), "")
let base_name = name.replace(&format!("-{}", TARGET), "")
.replace(&format!("-v{}", ctx.version), "")
.replace(&format!("-{}", ctx.version), "");
@ -262,6 +240,8 @@ async fn main() -> Result<(), anyhow::Error> {
return Ok(())
}
info!("Installing binaries...");
// Install binaries
for (_name, source, dest, _link) in &bin_files {
// TODO: check if file already exists