mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-20 12:38:43 +00:00
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:
parent
8777c355c5
commit
223c6ef43a
3 changed files with 61 additions and 82 deletions
|
@ -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!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
90
src/main.rs
90
src/main.rs
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue