diff --git a/build.rs b/build.rs index a77a878b..a6f29869 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,3 @@ - // Fetch build target and define this for the compiler fn main() { println!( diff --git a/src/bins.rs b/src/bins.rs index 26c33865..d443e9a3 100644 --- a/src/bins.rs +++ b/src/bins.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use cargo_toml::Product; use serde::Serialize; -use crate::{Template, PkgFmt, PkgMeta}; +use crate::{PkgFmt, PkgMeta, Template}; pub struct BinFile { pub base_name: String, @@ -17,12 +17,16 @@ impl BinFile { let base_name = product.name.clone().unwrap(); // Generate binary path via interpolation - let ctx = Context { + let ctx = Context { name: &data.name, repo: data.repo.as_ref().map(|s| &s[..]), - target: &data.target, + target: &data.target, version: &data.version, - format: if data.target.contains("windows") { ".exe" } else { "" }, + format: if data.target.contains("windows") { + ".exe" + } else { + "" + }, bin: &base_name, }; @@ -36,21 +40,36 @@ impl BinFile { }; // Destination path is the install dir + base-name-version{.format} - let dest_file_path = ctx.render("{ bin }-v{ version }{ format }")?; + let dest_file_path = ctx.render("{ bin }-v{ version }{ format }")?; let dest = data.install_path.join(dest_file_path); // Link at install dir + base name let link = data.install_path.join(&base_name); - Ok(Self { base_name, source, dest, link }) + Ok(Self { + base_name, + source, + dest, + link, + }) } pub fn preview_bin(&self) -> String { - format!("{} ({} -> {})", self.base_name, self.source.file_name().unwrap().to_string_lossy(), self.dest.display()) + format!( + "{} ({} -> {})", + self.base_name, + self.source.file_name().unwrap().to_string_lossy(), + self.dest.display() + ) } pub fn preview_link(&self) -> String { - format!("{} ({} -> {})", self.base_name, self.dest.display(), self.link.display()) + format!( + "{} ({} -> {})", + self.base_name, + self.dest.display(), + self.link.display() + ) } pub fn install_bin(&self) -> Result<(), anyhow::Error> { @@ -103,4 +122,4 @@ struct Context<'c> { pub bin: &'c str, } -impl<'c> Template for Context<'c> {} \ No newline at end of file +impl<'c> Template for Context<'c> {} diff --git a/src/drivers.rs b/src/drivers.rs index f10b1448..a1f55bd2 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -1,39 +1,43 @@ - -use std::time::Duration; use std::path::{Path, PathBuf}; +use std::time::Duration; -use log::{debug}; -use anyhow::{Context, anyhow}; +use anyhow::{anyhow, Context}; +use log::debug; use semver::{Version, VersionReq}; use crates_io_api::AsyncClient; -use crate::PkgFmt; use crate::helpers::*; +use crate::PkgFmt; -fn find_version<'a, V: Iterator>(requirement: &str, version_iter: V) -> Result { +fn find_version<'a, V: Iterator>( + requirement: &str, + version_iter: V, +) -> Result { // 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, - }; + 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, - }; + // Parse out version + let ver = match Version::parse(ver_str) { + Ok(sv) => sv, + Err(_) => return false, + }; - debug!("Version: {:?}", ver); + debug!("Version: {:?}", ver); - // Filter by version match - version_req.matches(&ver) - }).collect(); + // Filter by version match + version_req.matches(&ver) + }) + .collect(); // Sort by highest matching version filtered.sort_by(|a, b| { @@ -48,13 +52,19 @@ fn find_version<'a, V: Iterator>(requirement: &str, version_iter: // Return highest version match filtered.get(0) { Some(v) => Ok(v.to_string()), - None => Err(anyhow!("No matching version for requirement: '{}'", version_req)) + 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_req: &str, temp_dir: &Path) -> Result { - +pub async fn fetch_crate_cratesio( + name: &str, + version_req: &str, + temp_dir: &Path, +) -> Result { // Fetch / update index debug!("Updating crates.io index"); let mut index = crates_index::Index::new_cargo_default()?; @@ -65,37 +75,48 @@ pub async fn fetch_crate_cratesio(name: &str, version_req: &str, temp_dir: &Path let base_info = match index.crate_(name) { Some(i) => i, None => { - return Err(anyhow::anyhow!("Error fetching information for crate {}", name)); + return Err(anyhow::anyhow!( + "Error fetching information for crate {}", + name + )); } }; // Locate matching version - let version_iter = base_info.versions().iter().map(|v| v.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))?; + let api_client = AsyncClient::new( + "cargo-binstall (https://github.com/ryankurte/cargo-binstall)", + Duration::from_millis(100), + )?; // Fetch online crate information - let crate_info = api_client.get_crate(name.as_ref()).await + 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 => { - return Err(anyhow::anyhow!("No information found for crate: '{}' version: '{}'", - name, version_name)); + return Err(anyhow::anyhow!( + "No information found for crate: '{}' version: '{}'", + name, + version_name + )); } }; debug!("Found information for crate version: '{}'", version.num); - + // Download crate to temporary dir (crates.io or git?) let crate_url = format!("https://crates.io/{}", version.dl_path); let tgz_path = temp_dir.join(format!("{}.tgz", name)); - debug!("Fetching crate from: {}", crate_url); + debug!("Fetching crate from: {}", crate_url); // Download crate download(&crate_url, &tgz_path).await?; @@ -111,8 +132,10 @@ pub async fn fetch_crate_cratesio(name: &str, version_req: &str, temp_dir: &Path /// Fetch a crate by name and version from github /// TODO: implement this -pub async fn fetch_crate_gh_releases(_name: &str, _version: Option<&str>, _temp_dir: &Path) -> Result { - +pub async fn fetch_crate_gh_releases( + _name: &str, + _version: Option<&str>, + _temp_dir: &Path, +) -> Result { unimplemented!(); } - diff --git a/src/fetchers.rs b/src/fetchers.rs index 288340cb..c2c4b052 100644 --- a/src/fetchers.rs +++ b/src/fetchers.rs @@ -11,11 +11,13 @@ mod quickinstall; #[async_trait::async_trait] pub trait Fetcher { /// Create a new fetcher from some data - async fn new(data: &Data) -> Result, anyhow::Error> where Self: Sized; + async fn new(data: &Data) -> Result, anyhow::Error> + where + Self: Sized; /// Fetch a package async fn fetch(&self, dst: &Path) -> Result<(), anyhow::Error>; - + /// Check if a package is available for download async fn check(&self) -> Result; } @@ -46,7 +48,7 @@ impl MultiFetcher { return Some(&**fetcher); } } - + None } -} \ No newline at end of file +} diff --git a/src/fetchers/gh_release.rs b/src/fetchers/gh_release.rs index 4b0cf9b0..386f0ab4 100644 --- a/src/fetchers/gh_release.rs +++ b/src/fetchers/gh_release.rs @@ -4,8 +4,8 @@ use log::{debug, info}; use reqwest::Method; use serde::Serialize; -use crate::{download, remote_exists, Template}; use super::Data; +use crate::{download, remote_exists, Template}; pub struct GhRelease { url: String, @@ -15,16 +15,18 @@ pub struct GhRelease { impl super::Fetcher for GhRelease { async fn new(data: &Data) -> Result, anyhow::Error> { // Generate context for URL interpolation - let ctx = Context { + let ctx = Context { name: &data.name, repo: data.repo.as_ref().map(|s| &s[..]), - target: &data.target, + target: &data.target, version: &data.version, format: data.meta.pkg_fmt.to_string(), }; debug!("Using context: {:?}", ctx); - Ok(Box::new(Self { url: ctx.render(&data.meta.pkg_url)? })) + Ok(Box::new(Self { + url: ctx.render(&data.meta.pkg_url)?, + })) } async fn check(&self) -> Result { @@ -48,4 +50,4 @@ struct Context<'c> { pub format: String, } -impl<'c> Template for Context<'c> {} \ No newline at end of file +impl<'c> Template for Context<'c> {} diff --git a/src/fetchers/quickinstall.rs b/src/fetchers/quickinstall.rs index e65e6ff6..fed27fa3 100644 --- a/src/fetchers/quickinstall.rs +++ b/src/fetchers/quickinstall.rs @@ -3,8 +3,8 @@ use std::path::Path; use log::info; use reqwest::Method; -use crate::{download, remote_exists}; use super::Data; +use crate::{download, remote_exists}; pub struct QuickInstall { url: String, @@ -28,4 +28,4 @@ impl super::Fetcher for QuickInstall { info!("Downloading package from: '{}'", self.url); download(&self.url, dst).await } -} \ No newline at end of file +} diff --git a/src/helpers.rs b/src/helpers.rs index 4ce11389..a94437a6 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,9 +1,8 @@ - use std::path::{Path, PathBuf}; -use log::{debug, info, error}; +use log::{debug, error, info}; -use cargo_toml::{Manifest}; +use cargo_toml::Manifest; use flate2::read::GzDecoder; use serde::Serialize; use tar::Archive; @@ -11,12 +10,14 @@ use tinytemplate::TinyTemplate; use xz2::read::XzDecoder; use zip::read::ZipArchive; -use crate::{Meta}; +use crate::Meta; use super::PkgFmt; /// Load binstall metadata from the crate `Cargo.toml` at the provided path -pub fn load_manifest_path>(manifest_path: P) -> Result, anyhow::Error> { +pub fn load_manifest_path>( + manifest_path: P, +) -> Result, anyhow::Error> { debug!("Reading manifest: {}", manifest_path.as_ref().display()); // Load and parse manifest (this checks file system for binary output names) @@ -33,7 +34,6 @@ pub async fn remote_exists(url: &str, method: reqwest::Method) -> Result>(url: &str, path: P) -> Result<(), anyhow::Error> { - debug!("Downloading from: '{}'", url); let resp = reqwest::get(url).await?; @@ -53,51 +53,75 @@ pub async fn download>(url: &str, path: P) -> Result<(), anyhow:: } /// Extract files from the specified source onto the specified path -pub fn extract, P: AsRef>(source: S, fmt: PkgFmt, path: P) -> Result<(), anyhow::Error> { +pub fn extract, P: AsRef>( + source: S, + fmt: PkgFmt, + path: P, +) -> Result<(), anyhow::Error> { match fmt { PkgFmt::Tar => { // Extract to install dir - debug!("Extracting from tar archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref()); + debug!( + "Extracting from tar archive '{:?}' to `{:?}`", + source.as_ref(), + path.as_ref() + ); let dat = std::fs::File::open(source)?; let mut tar = Archive::new(dat); tar.unpack(path)?; - }, + } PkgFmt::Tgz => { // Extract to install dir - debug!("Decompressing from tgz archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref()); + debug!( + "Decompressing from tgz archive '{:?}' to `{:?}`", + source.as_ref(), + path.as_ref() + ); let dat = std::fs::File::open(source)?; let tar = GzDecoder::new(dat); let mut tgz = Archive::new(tar); tgz.unpack(path)?; - }, + } PkgFmt::Txz => { // Extract to install dir - debug!("Decompressing from txz archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref()); + debug!( + "Decompressing from txz archive '{:?}' to `{:?}`", + source.as_ref(), + path.as_ref() + ); let dat = std::fs::File::open(source)?; let tar = XzDecoder::new(dat); let mut txz = Archive::new(tar); txz.unpack(path)?; - }, + } PkgFmt::Zip => { // Extract to install dir - debug!("Decompressing from zip archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref()); + debug!( + "Decompressing from zip archive '{:?}' to `{:?}`", + source.as_ref(), + path.as_ref() + ); let dat = std::fs::File::open(source)?; let mut zip = ZipArchive::new(dat)?; zip.extract(path)?; - }, + } PkgFmt::Bin => { - debug!("Copying binary '{:?}' to `{:?}`", source.as_ref(), path.as_ref()); + debug!( + "Copying binary '{:?}' to `{:?}`", + source.as_ref(), + path.as_ref() + ); // Copy to install dir std::fs::copy(source, path)?; - }, + } }; Ok(()) @@ -108,7 +132,7 @@ pub fn extract, P: AsRef>(source: S, fmt: PkgFmt, path: P) pub fn get_install_path>(install_path: Option

) -> Option { // Command line override first first if let Some(p) = install_path { - return Some(PathBuf::from(p.as_ref())) + return Some(PathBuf::from(p.as_ref())); } // Environmental variables @@ -151,15 +175,17 @@ pub fn confirm() -> Result { match input.as_str().trim() { "yes" => Ok(true), "no" => Ok(false), - _ => { - Err(anyhow::anyhow!("Valid options are 'yes', 'no', please try again")) - } + _ => Err(anyhow::anyhow!( + "Valid options are 'yes', 'no', please try again" + )), } } pub trait Template: Serialize { fn render(&self, template: &str) -> Result - where Self: Sized { + where + Self: Sized, + { // Create template instance let mut tt = TinyTemplate::new(); @@ -169,4 +195,4 @@ pub trait Template: Serialize { // Render output Ok(tt.render("path", self)?) } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index bd668427..cf32973b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ - use std::collections::HashMap; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use strum_macros::{Display, EnumString, EnumVariantNames}; pub mod helpers; @@ -13,20 +12,20 @@ pub use drivers::*; pub mod bins; pub mod fetchers; - /// Compiled target triple, used as default for binary fetching pub const TARGET: &'static str = env!("TARGET"); /// Default package path template (may be overridden in package Cargo.toml) -pub const DEFAULT_PKG_URL: &'static str = "{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ format }"; +pub const DEFAULT_PKG_URL: &'static str = + "{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ format }"; /// Default binary name template (may be overridden in package Cargo.toml) pub const DEFAULT_BIN_PATH: &'static str = "{ name }-{ target }-v{ version }/{ bin }{ format }"; - /// Binary format enumeration -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] -#[derive(Display, EnumString, EnumVariantNames)] +#[derive( + Debug, Copy, Clone, PartialEq, Serialize, Deserialize, Display, EnumString, EnumVariantNames, +)] #[strum(serialize_all = "snake_case")] #[serde(rename_all = "snake_case")] pub enum PkgFmt { @@ -132,7 +131,6 @@ impl Default for PkgOverride { } } - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct BinMeta { @@ -144,7 +142,7 @@ pub struct BinMeta { #[cfg(test)] mod test { - use crate::{load_manifest_path}; + use crate::load_manifest_path; use cargo_toml::Product; @@ -161,7 +159,7 @@ mod test { let manifest = load_manifest_path(&manifest_dir).expect("Error parsing metadata"); let package = manifest.package.unwrap(); - let meta = package.metadata.map(|m| m.binstall ).flatten().unwrap(); + let meta = package.metadata.map(|m| m.binstall).flatten().unwrap(); assert_eq!(&package.name, "cargo-binstall"); @@ -172,14 +170,12 @@ mod test { assert_eq!( manifest.bin.as_slice(), - &[ - Product{ - name: Some("cargo-binstall".to_string()), - path: Some("src/main.rs".to_string()), - edition: Some(cargo_toml::Edition::E2018), - ..Default::default() - }, - ], + &[Product { + name: Some("cargo-binstall".to_string()), + path: Some("src/main.rs".to_string()), + edition: Some(cargo_toml::Edition::E2018), + ..Default::default() + },], ); } } diff --git a/src/main.rs b/src/main.rs index 0b6f72c3..2eab84a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,17 @@ -use std::path::{PathBuf}; +use std::path::PathBuf; -use log::{debug, info, warn, error, LevelFilter}; -use simplelog::{TermLogger, ConfigBuilder, TerminalMode, ColorChoice}; +use log::{debug, error, info, warn, LevelFilter}; +use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode}; use structopt::StructOpt; use tempdir::TempDir; -use cargo_binstall::{*, fetchers::{GhRelease, Data, Fetcher, QuickInstall, MultiFetcher}, bins}; - +use cargo_binstall::{ + bins, + fetchers::{Data, Fetcher, GhRelease, MultiFetcher, QuickInstall}, + *, +}; #[derive(Debug, StructOpt)] struct Options { @@ -48,7 +51,7 @@ struct Options { /// Override manifest source. /// This skips searching crates.io for a manifest and uses - /// the specified path directly, useful for debugging and + /// the specified path directly, useful for debugging and /// when adding `binstall` support. #[structopt(long)] manifest_path: Option, @@ -58,10 +61,8 @@ struct Options { log_level: LevelFilter, } - #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - // Filter extraneous arg when invoked by cargo // `cargo run -- --help` gives ["target/debug/cargo-binstall", "--help"] // `cargo binstall --help` gives ["/home/ryan/.cargo/bin/cargo-binstall", "binstall", "--help"] @@ -78,7 +79,13 @@ async fn main() -> Result<(), anyhow::Error> { log_config.add_filter_ignore("hyper".to_string()); log_config.add_filter_ignore("reqwest".to_string()); log_config.set_location_level(LevelFilter::Off); - TermLogger::init(opts.log_level, log_config.build(), TerminalMode::Mixed, ColorChoice::Auto).unwrap(); + TermLogger::init( + opts.log_level, + log_config.build(), + TerminalMode::Mixed, + ColorChoice::Auto, + ) + .unwrap(); // Create a temporary directory for downloads etc. let temp_dir = TempDir::new("cargo-binstall")?; @@ -92,13 +99,17 @@ async fn main() -> Result<(), anyhow::Error> { Some(p) => p, None => fetch_crate_cratesio(&opts.name, &opts.version, temp_dir.path()).await?, }; - + debug!("Reading manifest: {}", manifest_path.display()); let manifest = load_manifest_path(manifest_path.join("Cargo.toml"))?; let package = manifest.package.unwrap(); let (mut meta, binaries) = ( - package.metadata.map(|m| m.binstall ).flatten().unwrap_or(PkgMeta::default()), + package + .metadata + .map(|m| m.binstall) + .flatten() + .unwrap_or(PkgMeta::default()), manifest.bin, ); @@ -117,7 +128,9 @@ async fn main() -> Result<(), anyhow::Error> { debug!("Using install path: {}", install_path.display()); // Compute temporary directory for downloads - let pkg_path = temp_dir.path().join(format!("pkg-{}.{}", opts.name, meta.pkg_fmt)); + let pkg_path = temp_dir + .path() + .join(format!("pkg-{}.{}", opts.name, meta.pkg_fmt)); debug!("Using temporary download path: {}", pkg_path.display()); let fetcher_data = Data { @@ -144,7 +157,7 @@ async fn main() -> Result<(), anyhow::Error> { #[cfg(incomplete)] { // Fetch and check package signature if available - if let Some(pub_key) = meta.as_ref().map(|m| m.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); // Generate signature file URL @@ -160,7 +173,6 @@ async fn main() -> Result<(), anyhow::Error> { // TODO: do the signature check unimplemented!() - } else { warn!("No public key found, package signature could not be validated"); } @@ -177,7 +189,9 @@ async fn main() -> Result<(), anyhow::Error> { if binaries.len() == 0 { error!("No binaries specified (or inferred from file system)"); - return Err(anyhow::anyhow!("No binaries specified (or inferred from file system)")); + return Err(anyhow::anyhow!( + "No binaries specified (or inferred from file system)" + )); } // List files to be installed @@ -192,7 +206,8 @@ async fn main() -> Result<(), anyhow::Error> { install_path, }; - let bin_files = binaries.iter() + let bin_files = binaries + .iter() .map(|p| bins::BinFile::from_product(&bin_data, p)) .collect::, anyhow::Error>>()?; @@ -211,7 +226,7 @@ async fn main() -> Result<(), anyhow::Error> { if !opts.no_confirm && !confirm()? { warn!("Installation cancelled"); - return Ok(()) + return Ok(()); } info!("Installing binaries..."); @@ -225,7 +240,7 @@ async fn main() -> Result<(), anyhow::Error> { file.install_link()?; } } - + info!("Installation complete!"); Ok(()) -} \ No newline at end of file +}