From e5502cb80b3d870469c8b714c9b37e93319959cf Mon Sep 17 00:00:00 2001 From: Christof Weickhardt Date: Sat, 30 Apr 2022 18:01:25 +0000 Subject: [PATCH] feat: use in memory extration --- Cargo.lock | 173 ++++++++-------------------------- Cargo.toml | 3 +- src/bins.rs | 62 +++++++----- src/drivers.rs | 24 +++-- src/fetchers.rs | 25 ++--- src/fetchers/gh_crate_meta.rs | 43 +++++---- src/fetchers/quickinstall.rs | 6 +- src/helpers.rs | 131 +++++++++++++------------ src/lib.rs | 22 ++--- src/main.rs | 92 +++++++----------- 10 files changed, 244 insertions(+), 337 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1fffb57..420d8066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,22 +138,13 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "camino" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3132262930b0522068049f5870a856ab8affc80c70d08b6ecb785771a6fc23" -dependencies = [ - "serde", -] - [[package]] name = "cargo-binstall" version = "0.7.0" dependencies = [ "anyhow", "async-trait", - "cargo_metadata", + "bytes", "cargo_toml", "crates-index", "crates_io_api", @@ -169,7 +160,6 @@ dependencies = [ "strum", "strum_macros", "tar", - "tempdir", "tinytemplate", "tokio", "url", @@ -177,28 +167,6 @@ dependencies = [ "zip", ] -[[package]] -name = "cargo-platform" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", -] - [[package]] name = "cargo_toml" version = "0.11.5" @@ -475,12 +443,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "futures" version = "0.3.21" @@ -593,9 +555,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" +checksum = "5e77a14ffc6ba4ad5188d6cf428894c4fcfda725326b37558f35bb677e712cec" dependencies = [ "bitflags", "libc", @@ -818,15 +780,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.124" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "libgit2-sys" -version = "0.13.2+1.4.2" +version = "0.13.3+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" +checksum = "c24d36c3ac9b9996a2418d6bf428cc0bc5d1a814a84303fc60986088c5ed60de" dependencies = [ "cc", "libc", @@ -900,9 +862,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" @@ -962,9 +924,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -1041,9 +1003,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if", "libc", @@ -1059,7 +1021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" dependencies = [ "base64ct", - "rand_core 0.6.3", + "rand_core", "subtle", ] @@ -1141,34 +1103,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.3" @@ -1199,15 +1133,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.2.13" @@ -1245,15 +1170,6 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "reqwest" version = "0.11.10" @@ -1367,24 +1283,21 @@ name = "semver" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" -dependencies = [ - "serde", -] [[package]] name = "serde" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", @@ -1393,9 +1306,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944" dependencies = [ "itoa", "ryu", @@ -1559,9 +1472,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" dependencies = [ "proc-macro2", "quote", @@ -1579,16 +1492,6 @@ dependencies = [ "xattr", ] -[[package]] -name = "tempdir" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -dependencies = [ - "rand", - "remove_dir_all", -] - [[package]] name = "termcolor" version = "1.1.3" @@ -1609,18 +1512,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -2004,9 +1907,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ "windows_aarch64_msvc", "windows_i686_gnu", @@ -2017,33 +1920,33 @@ dependencies = [ [[package]] name = "windows_aarch64_msvc" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" -version = "0.34.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "winreg" diff --git a/Cargo.toml b/Cargo.toml index 70bd46b5..8bb541ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ pkg-fmt = "zip" [dependencies] anyhow = "1.0.57" async-trait = "0.1.52" -cargo_metadata = "0.14.2" +bytes = "1.1.0" cargo_toml = "0.11.4" crates-index = "0.18.7" crates_io_api = { version = "0.8.0", default-features = false, features = ["rustls"] } @@ -36,7 +36,6 @@ structopt = "0.3.26" strum = "0.24.0" strum_macros = "0.24.0" tar = "0.4.38" -tempdir = "0.3.7" tinytemplate = "1.2.1" tokio = { version = "1.18.0", features = [ "full" ] } url = "2.2.2" diff --git a/src/bins.rs b/src/bins.rs index d3b6468b..3d09de54 100644 --- a/src/bins.rs +++ b/src/bins.rs @@ -1,14 +1,18 @@ -use std::path::{Path, PathBuf}; +use std::{ + fs::File, + io::Write, + path::{Path, PathBuf}, +}; use cargo_toml::Product; use log::debug; use serde::Serialize; -use crate::{PkgFmt, PkgMeta, Template}; +use crate::{extract_file, PkgFmt, PkgMeta, Template}; pub struct BinFile { pub base_name: String, - pub source: PathBuf, + pub source: Option, pub dest: PathBuf, pub link: PathBuf, } @@ -35,19 +39,21 @@ impl BinFile { // Generate install paths // Source path is the download dir + the generated binary path - let source_file_path = ctx.render(&data.meta.bin_dir)?; let source = if data.meta.pkg_fmt == PkgFmt::Bin { - data.bin_path.clone() + None } else { - data.bin_path.join(&source_file_path) + Some(PathBuf::from(ctx.render(&data.meta.bin_dir)?)) }; // Destination path is the install dir + base-name-version{.extension} - let dest_file_path = ctx.render("{ bin }-v{ version }{ binary-ext }")?; - let dest = data.install_path.join(dest_file_path); + let dest = data + .install_path + .join(ctx.render("{ bin }-v{ version }{ binary-ext }")?); // Link at install dir + base-name{.extension} - let link = data.install_path.join(&ctx.render("{ bin }{ binary-ext }")?); + let link = data + .install_path + .join(&ctx.render("{ bin }{ binary-ext }")?); Ok(Self { base_name, @@ -58,12 +64,7 @@ impl BinFile { } 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.dest.display()) } pub fn preview_link(&self) -> String { @@ -75,14 +76,28 @@ impl BinFile { ) } - pub fn install_bin(&self) -> Result<(), anyhow::Error> { - // TODO: check if file already exists - debug!( - "Copy file from '{}' to '{}'", - self.source.display(), - self.dest.display() - ); - std::fs::copy(&self.source, &self.dest)?; + pub fn is_binary_already_installed(&self) -> bool { + self.dest.exists() + } + + pub fn install_bin(&self, data: &bytes::Bytes, pkg_fmt: PkgFmt) -> Result<(), anyhow::Error> { + debug!("Writing file to '{}'", self.dest.display()); + + // Extract files + let bin_file = if pkg_fmt != PkgFmt::Bin { + extract_file(data, pkg_fmt, self.source.as_ref().unwrap().to_path_buf())? + } else { + data.to_vec() + }; + + if let Some(parent_dir) = self.dest.parent() { + std::fs::create_dir_all(parent_dir)?; + } + + // Will truncate existing file + let mut file = File::create(&self.dest)?; + file.write_all(&bin_file)?; + file.flush().unwrap(); #[cfg(target_family = "unix")] { @@ -135,7 +150,6 @@ pub struct Data { pub version: String, pub repo: Option, pub meta: PkgMeta, - pub bin_path: PathBuf, pub install_path: PathBuf, } diff --git a/src/drivers.rs b/src/drivers.rs index d39525d1..3f37d667 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -2,13 +2,16 @@ use std::path::{Path, PathBuf}; use std::time::Duration; use anyhow::{anyhow, Context}; +use cargo_toml::Manifest; use log::debug; use semver::{Version, VersionReq}; use crates_io_api::AsyncClient; use crate::helpers::*; +use crate::Meta; use crate::PkgFmt; +use std::str::FromStr; fn find_version<'a, V: Iterator>( requirement: &str, @@ -21,7 +24,7 @@ fn find_version<'a, V: Iterator>( let mut filtered: Vec<_> = version_iter .filter(|v| { // Remove leading `v` for git tags - let ver_str = match v.strip_prefix("s") { + let ver_str = match v.strip_prefix('s') { Some(v) => v, None => v, }; @@ -60,11 +63,10 @@ fn find_version<'a, V: Iterator>( } /// Fetch a crate by name and version from crates.io -pub async fn fetch_crate_cratesio( +pub async fn fetch_manifest_cratesio( name: &str, version_req: &str, - temp_dir: &Path, -) -> Result { +) -> Result, anyhow::Error> { // Fetch / update index debug!("Updating crates.io index"); let mut index = crates_index::Index::new_cargo_default()?; @@ -119,21 +121,23 @@ pub async fn fetch_crate_cratesio( 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)); + let crate_url = format!("https://crates.io{}", version.dl_path); debug!("Fetching crate from: {}", crate_url); // Download crate - download(&crate_url, &tgz_path).await?; + let crate_data = download(&crate_url).await?; // Decompress downloaded tgz debug!("Decompressing crate archive"); - extract(&tgz_path, PkgFmt::Tgz, &temp_dir)?; - let crate_path = temp_dir.join(format!("{}-{}", name, version_name)); + let raw_manifest = extract_file( + &crate_data, + PkgFmt::Tgz, + PathBuf::from_str(&format!("{}-{}/Cargo.toml", name, version_name)).unwrap(), + )?; // Return crate directory - Ok(crate_path) + Ok(Manifest::::from_slice_with_metadata(&raw_manifest)?) } /// Fetch a crate by name and version from github diff --git a/src/fetchers.rs b/src/fetchers.rs index e4474f12..2205f4ac 100644 --- a/src/fetchers.rs +++ b/src/fetchers.rs @@ -1,5 +1,3 @@ -use std::path::Path; - pub use gh_crate_meta::*; pub use log::debug; pub use quickinstall::*; @@ -17,7 +15,7 @@ pub trait Fetcher { Self: Sized; /// Fetch a package - async fn fetch(&self, dst: &Path) -> Result<(), anyhow::Error>; + async fn fetch(&self) -> Result; /// Check if a package is available for download async fn check(&self) -> Result; @@ -54,15 +52,18 @@ impl MultiFetcher { pub async fn first_available(&self) -> Option<&dyn Fetcher> { for fetcher in &self.fetchers { - if fetcher.check().await.unwrap_or_else(|err| { - debug!( - "Error while checking fetcher {}: {}", - fetcher.source_name(), - err - ); - false - }) { - return Some(&**fetcher); + match fetcher.check().await { + Ok(true) => { + return Some(&**fetcher); + } + Ok(false) => {} + Err(err) => { + debug!( + "Error while checking fetcher {}: {}", + fetcher.source_name(), + err + ); + } } } diff --git a/src/fetchers/gh_crate_meta.rs b/src/fetchers/gh_crate_meta.rs index d4e1150b..1f4fa495 100644 --- a/src/fetchers/gh_crate_meta.rs +++ b/src/fetchers/gh_crate_meta.rs @@ -1,5 +1,3 @@ -use std::path::Path; - use log::{debug, info}; use reqwest::Method; use serde::Serialize; @@ -16,7 +14,7 @@ impl GhCrateMeta { fn url(&self) -> Result { let ctx = Context::from_data(&self.data); debug!("Using context: {:?}", ctx); - Ok(ctx.render_url(&self.data.meta.pkg_url)?) + ctx.render_url(&self.data.meta.pkg_url) } } @@ -32,10 +30,10 @@ impl super::Fetcher for GhCrateMeta { remote_exists(url.as_str(), Method::HEAD).await } - async fn fetch(&self, dst: &Path) -> Result<(), anyhow::Error> { + async fn fetch(&self) -> Result { let url = self.url()?; info!("Downloading package from: '{url}'"); - download(url.as_str(), dst).await + download(url.as_str()).await } fn pkg_fmt(&self) -> PkgFmt { @@ -171,8 +169,11 @@ mod test { #[test] fn different_url() { - let mut meta = PkgMeta::default(); - meta.pkg_url = "{ repo }/releases/download/v{ version }/sx128x-util-{ target }-v{ version }.{ archive-format }".to_string(); + let meta = PkgMeta { + pkg_url: "{ repo }/releases/download/v{ version }/sx128x-util-{ target }-v{ version }.{ archive-format }".to_string(), + ..PkgMeta::default() + }; + let data = Data { name: "radio-sx128x".to_string(), target: "x86_64-unknown-linux-gnu".to_string(), @@ -190,8 +191,11 @@ mod test { #[test] fn deprecated_format() { - let mut meta = PkgMeta::default(); - meta.pkg_url = "{ repo }/releases/download/v{ version }/sx128x-util-{ target }-v{ version }.{ format }".to_string(); + let meta = PkgMeta { + pkg_url: "{ repo }/releases/download/v{ version }/sx128x-util-{ target }-v{ version }.{ format }".to_string(), + ..PkgMeta::default() + }; + let data = Data { name: "radio-sx128x".to_string(), target: "x86_64-unknown-linux-gnu".to_string(), @@ -209,11 +213,13 @@ mod test { #[test] fn different_ext() { - let mut meta = PkgMeta::default(); - meta.pkg_url = - "{ repo }/releases/download/v{ version }/{ name }-v{ version }-{ target }.tar.xz" - .to_string(); - meta.pkg_fmt = PkgFmt::Txz; + let meta = PkgMeta { + pkg_url: + "{ repo }/releases/download/v{ version }/{ name }-v{ version }-{ target }.tar.xz" + .to_string(), + pkg_fmt: PkgFmt::Txz, + ..PkgMeta::default() + }; let data = Data { name: "cargo-watch".to_string(), target: "aarch64-apple-darwin".to_string(), @@ -231,9 +237,12 @@ mod test { #[test] fn no_archive() { - let mut meta = PkgMeta::default(); - meta.pkg_url = "{ repo }/releases/download/v{ version }/{ name }-v{ version }-{ target }{ binary-ext }".to_string(); - meta.pkg_fmt = PkgFmt::Bin; + let meta = PkgMeta{ + pkg_fmt: PkgFmt::Bin, + pkg_url: "{ repo }/releases/download/v{ version }/{ name }-v{ version }-{ target }{ binary-ext }".to_string(), + ..PkgMeta::default() + }; + let data = Data { name: "cargo-watch".to_string(), target: "aarch64-pc-windows-msvc".to_string(), diff --git a/src/fetchers/quickinstall.rs b/src/fetchers/quickinstall.rs index b04a8ec2..5e3d654d 100644 --- a/src/fetchers/quickinstall.rs +++ b/src/fetchers/quickinstall.rs @@ -1,5 +1,3 @@ -use std::path::Path; - use log::info; use reqwest::Method; @@ -32,10 +30,10 @@ impl super::Fetcher for QuickInstall { remote_exists(&url, Method::HEAD).await } - async fn fetch(&self, dst: &Path) -> Result<(), anyhow::Error> { + async fn fetch(&self) -> Result { let url = self.package_url(); info!("Downloading package from: '{url}'"); - download(&url, &dst).await + download(&url).await } fn pkg_fmt(&self) -> PkgFmt { diff --git a/src/helpers.rs b/src/helpers.rs index 3b6c7505..425080c2 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,10 +1,14 @@ -use std::path::{Path, PathBuf}; +use std::{ + io::Cursor, + path::{Path, PathBuf}, +}; use log::{debug, error, info}; use cargo_toml::Manifest; use flate2::read::GzDecoder; use serde::Serialize; +use std::io::Read; use tar::Archive; use tinytemplate::TinyTemplate; use xz2::read::XzDecoder; @@ -15,10 +19,20 @@ 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> { - debug!("Reading manifest: {}", manifest_path.as_ref().display()); +pub fn load_manifest_path(mut manifest_path: PathBuf) -> Result, anyhow::Error> { + debug!("Reading manifest: {}", manifest_path.display()); + + if manifest_path.is_dir() { + manifest_path = manifest_path.join("Cargo.toml"); + } + + if !manifest_path.exists() { + error!( + "Manifest at '{:?}' could not be found", + manifest_path.display() + ); + return Err(anyhow::anyhow!("Manifest could not be found")); + } // Load and parse manifest (this checks file system for binary output names) let manifest = Manifest::::from_path_with_metadata(manifest_path)?; @@ -33,7 +47,7 @@ pub async fn remote_exists(url: &str, method: reqwest::Method) -> Result>(url: &str, path: P) -> Result<(), anyhow::Error> { +pub async fn download(url: &str) -> Result { debug!("Downloading from: '{}'", url); let resp = reqwest::get(url).await?; @@ -43,90 +57,89 @@ pub async fn download>(url: &str, path: P) -> Result<(), anyhow:: return Err(anyhow::anyhow!(resp.status())); } - let bytes = resp.bytes().await?; + Ok(resp.bytes().await?) +} - let path = path.as_ref(); - debug!("Download OK, writing to file: '{}'", path.display()); +// Extracts a file at path from tar archive to byte vector +fn extract_file_from_tar_archive( + tar_archive: &mut Archive, + path: PathBuf, +) -> Result, anyhow::Error> { + let mut file = tar_archive + .entries()? + .find(|e| { + if let Ok(value) = e.as_ref() { + if let Ok(file_path) = value.path() { + file_path == path + } else { + false + } + } else { + false + } + }) + .unwrap()?; - std::fs::create_dir_all(path.parent().unwrap())?; - std::fs::write(&path, bytes)?; + let mut buffer = Vec::with_capacity(file.size() as usize); - Ok(()) + file.read_to_end(&mut buffer)?; + + Ok(buffer) } /// Extract files from the specified source onto the specified path -pub fn extract, P: AsRef>( - source: S, +pub fn extract_file( + source: &bytes::Bytes, fmt: PkgFmt, - path: P, -) -> Result<(), anyhow::Error> { + file: PathBuf, +) -> 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 '{:?}'", source.as_ref(),); - let dat = std::fs::File::open(source)?; - let mut tar = Archive::new(dat); + let mut tar = Archive::new(&source[..]); - tar.unpack(path)?; + extract_file_from_tar_archive(&mut tar, file) } PkgFmt::Tgz => { // Extract to install dir - debug!( - "Decompressing from tgz archive '{:?}' to `{:?}`", - source.as_ref(), - path.as_ref() - ); + debug!("Decompressing from tgz archive '{:?}'", source.as_ref()); - let dat = std::fs::File::open(source)?; - let tar = GzDecoder::new(dat); + let tar = GzDecoder::new(&source[..]); let mut tgz = Archive::new(tar); - tgz.unpack(path)?; + extract_file_from_tar_archive(&mut tgz, file) } PkgFmt::Txz => { // Extract to install dir - debug!( - "Decompressing from txz archive '{:?}' to `{:?}`", - source.as_ref(), - path.as_ref() - ); + debug!("Decompressing from txz archive '{:?}'", source.as_ref()); - let dat = std::fs::File::open(source)?; - let tar = XzDecoder::new(dat); + let tar = XzDecoder::new(&source[..]); let mut txz = Archive::new(tar); - txz.unpack(path)?; + extract_file_from_tar_archive(&mut txz, file) } PkgFmt::Zip => { // Extract to install dir - debug!( - "Decompressing from zip archive '{:?}' to `{:?}`", - source.as_ref(), - path.as_ref() - ); + debug!("Decompressing from zip archive '{:?}'", source.as_ref()); - let dat = std::fs::File::open(source)?; - let mut zip = ZipArchive::new(dat)?; + let mut zip = ZipArchive::new(Cursor::new(&source[..]))?; - zip.extract(path)?; + let mut file = zip.by_name(file.to_str().unwrap())?; + + let mut buffer = Vec::with_capacity(file.size() as usize); + + file.read_to_end(&mut buffer)?; + + Ok(buffer) } PkgFmt::Bin => { - debug!( - "Copying binary '{:?}' to `{:?}`", - source.as_ref(), - path.as_ref() - ); + debug!("Copying binary '{:?}'", source.as_ref()); // Copy to install dir - std::fs::copy(source, path)?; + Ok(source.to_vec()) } - }; - - Ok(()) + } } /// Fetch install path from environment @@ -162,7 +175,7 @@ pub fn get_install_path>(install_path: Option

) -> Option, } -impl Default for PkgOverride { - fn default() -> Self { - Self { - pkg_url: None, - pkg_fmt: None, - bin_dir: None, - } - } -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct BinMeta { @@ -157,9 +147,9 @@ mod test { let mut manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); manifest_dir.push_str("/Cargo.toml"); - let manifest = load_manifest_path(&manifest_dir).expect("Error parsing metadata"); + let manifest = load_manifest_path(manifest_dir.into()).expect("Error parsing metadata"); let package = manifest.package.unwrap(); - let meta = package.metadata.map(|m| m.binstall).flatten().unwrap(); + let meta = package.metadata.and_then(|m| m.binstall).unwrap(); assert_eq!(&package.name, "cargo-binstall"); diff --git a/src/main.rs b/src/main.rs index 3ec69068..e64e1290 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ use cargo_toml::{Package, Product}; use log::{debug, error, info, warn, LevelFilter}; use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode}; use structopt::StructOpt; -use tempdir::TempDir; use tokio::process::Command; use cargo_binstall::{ @@ -46,10 +45,6 @@ struct Options { #[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 @@ -88,21 +83,17 @@ async fn main() -> Result<(), anyhow::Error> { ) .unwrap(); - // Create a temporary directory for downloads etc. - let temp_dir = TempDir::new("cargo-binstall")?; - info!("Installing package: '{}'", opts.name); + debug!("Reading manifest"); // 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: 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, temp_dir.path()).await?, + let manifest = match opts.manifest_path.clone() { + Some(p) => load_manifest_path(p)?, + None => fetch_manifest_cratesio(&opts.name, &opts.version).await?, }; - debug!("Reading manifest: {}", manifest_path.display()); - let manifest = load_manifest_path(manifest_path.join("Cargo.toml"))?; let package = manifest.package.unwrap(); let is_plain_version = semver::Version::from_str(&opts.version).is_ok(); @@ -122,9 +113,8 @@ async fn main() -> Result<(), anyhow::Error> { package .metadata .as_ref() - .map(|m| m.binstall.clone()) - .flatten() - .unwrap_or(PkgMeta::default()), + .and_then(|m| m.binstall.clone()) + .unwrap_or_default(), manifest.bin, ); @@ -142,12 +132,6 @@ 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)); - debug!("Using temporary download path: {}", pkg_path.display()); - let fetcher_data = Data { name: package.name.clone(), target: opts.target.clone(), @@ -163,17 +147,7 @@ async fn main() -> Result<(), anyhow::Error> { match fetchers.first_available().await { Some(fetcher) => { - install_from_package( - binaries, - fetcher, - install_path, - meta, - opts, - package, - pkg_path, - temp_dir, - ) - .await + install_from_package(binaries, fetcher, install_path, meta, opts, package).await } None => install_from_source(opts, package).await, } @@ -186,8 +160,6 @@ async fn install_from_package( mut meta: PkgMeta, opts: Options, package: Package, - pkg_path: PathBuf, - temp_dir: TempDir, ) -> Result<(), anyhow::Error> { // Prompt user for third-party source if fetcher.is_third_party() { @@ -212,11 +184,12 @@ async fn install_from_package( } // Download package - if opts.dry_run { + let binary = if opts.dry_run { info!("Dry run, not downloading package"); + None } else { - fetcher.fetch(&pkg_path).await?; - } + Some(fetcher.fetch().await?) + }; #[cfg(incomplete)] { @@ -242,24 +215,11 @@ async fn install_from_package( } } - let bin_path = temp_dir.path().join(format!("bin-{}", opts.name)); - debug!("Using temporary binary path: {}", bin_path.display()); - - if !opts.dry_run { - // Extract files - extract(&pkg_path, fetcher.pkg_fmt(), &bin_path)?; - - // Bypass cleanup if disabled - if opts.no_cleanup { - let _ = temp_dir.into_path(); - } - - if binaries.is_empty() { - error!("No binaries specified (or inferred from file system)"); - return Err(anyhow::anyhow!( - "No binaries specified (or inferred from file system)" - )); - } + if !opts.dry_run && binaries.is_empty() { + error!("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 @@ -270,7 +230,6 @@ async fn install_from_package( version: package.version.clone(), repo: package.repository.clone(), meta, - bin_path, install_path, }; @@ -304,7 +263,24 @@ async fn install_from_package( info!("Installing binaries..."); for file in &bin_files { - file.install_bin()?; + if file.is_binary_already_installed() { + if opts.no_confirm { + warn!( + "The binary at {} does already exist, going to overwrite it", + file.dest.display() + ); + } else { + warn!( + "The binary at {} does already exist, would you like to overwrite it?", + file.dest.display() + ); + if !confirm()? { + continue; + } + } + } + + file.install_bin(binary.as_ref().unwrap(), fetcher.pkg_fmt())?; } // Generate symlinks