feat: use in memory extration

This commit is contained in:
Christof Weickhardt 2022-04-30 18:01:25 +00:00
parent 2d68a74637
commit e5502cb80b
10 changed files with 244 additions and 337 deletions

173
Cargo.lock generated
View file

@ -138,22 +138,13 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "camino"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f3132262930b0522068049f5870a856ab8affc80c70d08b6ecb785771a6fc23"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "cargo-binstall" name = "cargo-binstall"
version = "0.7.0" version = "0.7.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"cargo_metadata", "bytes",
"cargo_toml", "cargo_toml",
"crates-index", "crates-index",
"crates_io_api", "crates_io_api",
@ -169,7 +160,6 @@ dependencies = [
"strum", "strum",
"strum_macros", "strum_macros",
"tar", "tar",
"tempdir",
"tinytemplate", "tinytemplate",
"tokio", "tokio",
"url", "url",
@ -177,28 +167,6 @@ dependencies = [
"zip", "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]] [[package]]
name = "cargo_toml" name = "cargo_toml"
version = "0.11.5" version = "0.11.5"
@ -475,12 +443,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.21" version = "0.3.21"
@ -593,9 +555,9 @@ dependencies = [
[[package]] [[package]]
name = "git2" name = "git2"
version = "0.14.2" version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" checksum = "5e77a14ffc6ba4ad5188d6cf428894c4fcfda725326b37558f35bb677e712cec"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"libc", "libc",
@ -818,15 +780,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.124" version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
[[package]] [[package]]
name = "libgit2-sys" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" checksum = "c24d36c3ac9b9996a2418d6bf428cc0bc5d1a814a84303fc60986088c5ed60de"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -900,9 +862,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.4.1" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
@ -962,9 +924,9 @@ dependencies = [
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.44" version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"num-traits", "num-traits",
@ -1041,9 +1003,9 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.2" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -1059,7 +1021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8"
dependencies = [ dependencies = [
"base64ct", "base64ct",
"rand_core 0.6.3", "rand_core",
"subtle", "subtle",
] ]
@ -1141,34 +1103,6 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.6.3" version = "0.6.3"
@ -1199,15 +1133,6 @@ dependencies = [
"num_cpus", "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]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.13" version = "0.2.13"
@ -1245,15 +1170,6 @@ version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 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]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.10" version = "0.11.10"
@ -1367,24 +1283,21 @@ name = "semver"
version = "1.0.7" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.136" version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.136" version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1393,9 +1306,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.79" version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -1559,9 +1472,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.91" version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1579,16 +1492,6 @@ dependencies = [
"xattr", "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]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.1.3" version = "1.1.3"
@ -1609,18 +1512,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.30" version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.30" version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2004,9 +1907,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [ dependencies = [
"windows_aarch64_msvc", "windows_aarch64_msvc",
"windows_i686_gnu", "windows_i686_gnu",
@ -2017,33 +1920,33 @@ dependencies = [
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.34.0" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]] [[package]]
name = "winreg" name = "winreg"

View file

@ -21,7 +21,7 @@ pkg-fmt = "zip"
[dependencies] [dependencies]
anyhow = "1.0.57" anyhow = "1.0.57"
async-trait = "0.1.52" async-trait = "0.1.52"
cargo_metadata = "0.14.2" bytes = "1.1.0"
cargo_toml = "0.11.4" cargo_toml = "0.11.4"
crates-index = "0.18.7" crates-index = "0.18.7"
crates_io_api = { version = "0.8.0", default-features = false, features = ["rustls"] } 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 = "0.24.0"
strum_macros = "0.24.0" strum_macros = "0.24.0"
tar = "0.4.38" tar = "0.4.38"
tempdir = "0.3.7"
tinytemplate = "1.2.1" tinytemplate = "1.2.1"
tokio = { version = "1.18.0", features = [ "full" ] } tokio = { version = "1.18.0", features = [ "full" ] }
url = "2.2.2" url = "2.2.2"

View file

@ -1,14 +1,18 @@
use std::path::{Path, PathBuf}; use std::{
fs::File,
io::Write,
path::{Path, PathBuf},
};
use cargo_toml::Product; use cargo_toml::Product;
use log::debug; use log::debug;
use serde::Serialize; use serde::Serialize;
use crate::{PkgFmt, PkgMeta, Template}; use crate::{extract_file, PkgFmt, PkgMeta, Template};
pub struct BinFile { pub struct BinFile {
pub base_name: String, pub base_name: String,
pub source: PathBuf, pub source: Option<PathBuf>,
pub dest: PathBuf, pub dest: PathBuf,
pub link: PathBuf, pub link: PathBuf,
} }
@ -35,19 +39,21 @@ impl BinFile {
// Generate install paths // Generate install paths
// Source path is the download dir + the generated binary path // 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 { let source = if data.meta.pkg_fmt == PkgFmt::Bin {
data.bin_path.clone() None
} else { } 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} // Destination path is the install dir + base-name-version{.extension}
let dest_file_path = ctx.render("{ bin }-v{ version }{ binary-ext }")?; let dest = data
let dest = data.install_path.join(dest_file_path); .install_path
.join(ctx.render("{ bin }-v{ version }{ binary-ext }")?);
// Link at install dir + base-name{.extension} // 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 { Ok(Self {
base_name, base_name,
@ -58,12 +64,7 @@ impl BinFile {
} }
pub fn preview_bin(&self) -> String { pub fn preview_bin(&self) -> String {
format!( format!("{} ({})", self.base_name, self.dest.display())
"{} ({} -> {})",
self.base_name,
self.source.file_name().unwrap().to_string_lossy(),
self.dest.display()
)
} }
pub fn preview_link(&self) -> String { pub fn preview_link(&self) -> String {
@ -75,14 +76,28 @@ impl BinFile {
) )
} }
pub fn install_bin(&self) -> Result<(), anyhow::Error> { pub fn is_binary_already_installed(&self) -> bool {
// TODO: check if file already exists self.dest.exists()
debug!( }
"Copy file from '{}' to '{}'",
self.source.display(), pub fn install_bin(&self, data: &bytes::Bytes, pkg_fmt: PkgFmt) -> Result<(), anyhow::Error> {
self.dest.display() debug!("Writing file to '{}'", self.dest.display());
);
std::fs::copy(&self.source, &self.dest)?; // 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")] #[cfg(target_family = "unix")]
{ {
@ -135,7 +150,6 @@ pub struct Data {
pub version: String, pub version: String,
pub repo: Option<String>, pub repo: Option<String>,
pub meta: PkgMeta, pub meta: PkgMeta,
pub bin_path: PathBuf,
pub install_path: PathBuf, pub install_path: PathBuf,
} }

View file

@ -2,13 +2,16 @@ use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use cargo_toml::Manifest;
use log::debug; use log::debug;
use semver::{Version, VersionReq}; use semver::{Version, VersionReq};
use crates_io_api::AsyncClient; use crates_io_api::AsyncClient;
use crate::helpers::*; use crate::helpers::*;
use crate::Meta;
use crate::PkgFmt; use crate::PkgFmt;
use std::str::FromStr;
fn find_version<'a, V: Iterator<Item = &'a str>>( fn find_version<'a, V: Iterator<Item = &'a str>>(
requirement: &str, requirement: &str,
@ -21,7 +24,7 @@ fn find_version<'a, V: Iterator<Item = &'a str>>(
let mut filtered: Vec<_> = version_iter let mut filtered: Vec<_> = version_iter
.filter(|v| { .filter(|v| {
// Remove leading `v` for git tags // 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, Some(v) => v,
None => v, None => v,
}; };
@ -60,11 +63,10 @@ fn find_version<'a, V: Iterator<Item = &'a str>>(
} }
/// 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( pub async fn fetch_manifest_cratesio(
name: &str, name: &str,
version_req: &str, version_req: &str,
temp_dir: &Path, ) -> Result<Manifest<Meta>, anyhow::Error> {
) -> Result<PathBuf, anyhow::Error> {
// Fetch / update index // Fetch / update index
debug!("Updating crates.io index"); debug!("Updating crates.io index");
let mut index = crates_index::Index::new_cargo_default()?; 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); debug!("Found information for crate version: '{}'", version.num);
// Download crate to temporary dir (crates.io or git?) // Download crate to temporary dir (crates.io or git?)
let crate_url = format!("https://crates.io/{}", version.dl_path); 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
download(&crate_url, &tgz_path).await?; let crate_data = download(&crate_url).await?;
// Decompress downloaded tgz // Decompress downloaded tgz
debug!("Decompressing crate archive"); debug!("Decompressing crate archive");
extract(&tgz_path, PkgFmt::Tgz, &temp_dir)?; let raw_manifest = extract_file(
let crate_path = temp_dir.join(format!("{}-{}", name, version_name)); &crate_data,
PkgFmt::Tgz,
PathBuf::from_str(&format!("{}-{}/Cargo.toml", name, version_name)).unwrap(),
)?;
// Return crate directory // Return crate directory
Ok(crate_path) Ok(Manifest::<Meta>::from_slice_with_metadata(&raw_manifest)?)
} }
/// Fetch a crate by name and version from github /// Fetch a crate by name and version from github

View file

@ -1,5 +1,3 @@
use std::path::Path;
pub use gh_crate_meta::*; pub use gh_crate_meta::*;
pub use log::debug; pub use log::debug;
pub use quickinstall::*; pub use quickinstall::*;
@ -17,7 +15,7 @@ pub trait Fetcher {
Self: Sized; Self: Sized;
/// Fetch a package /// Fetch a package
async fn fetch(&self, dst: &Path) -> Result<(), anyhow::Error>; async fn fetch(&self) -> Result<bytes::Bytes, anyhow::Error>;
/// Check if a package is available for download /// Check if a package is available for download
async fn check(&self) -> Result<bool, anyhow::Error>; async fn check(&self) -> Result<bool, anyhow::Error>;
@ -54,15 +52,18 @@ impl MultiFetcher {
pub async fn first_available(&self) -> Option<&dyn Fetcher> { pub async fn first_available(&self) -> Option<&dyn Fetcher> {
for fetcher in &self.fetchers { for fetcher in &self.fetchers {
if fetcher.check().await.unwrap_or_else(|err| { match fetcher.check().await {
debug!( Ok(true) => {
"Error while checking fetcher {}: {}", return Some(&**fetcher);
fetcher.source_name(), }
err Ok(false) => {}
); Err(err) => {
false debug!(
}) { "Error while checking fetcher {}: {}",
return Some(&**fetcher); fetcher.source_name(),
err
);
}
} }
} }

View file

@ -1,5 +1,3 @@
use std::path::Path;
use log::{debug, info}; use log::{debug, info};
use reqwest::Method; use reqwest::Method;
use serde::Serialize; use serde::Serialize;
@ -16,7 +14,7 @@ impl GhCrateMeta {
fn url(&self) -> Result<Url, anyhow::Error> { fn url(&self) -> Result<Url, anyhow::Error> {
let ctx = Context::from_data(&self.data); let ctx = Context::from_data(&self.data);
debug!("Using context: {:?}", ctx); 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 remote_exists(url.as_str(), Method::HEAD).await
} }
async fn fetch(&self, dst: &Path) -> Result<(), anyhow::Error> { async fn fetch(&self) -> Result<bytes::Bytes, anyhow::Error> {
let url = self.url()?; let url = self.url()?;
info!("Downloading package from: '{url}'"); info!("Downloading package from: '{url}'");
download(url.as_str(), dst).await download(url.as_str()).await
} }
fn pkg_fmt(&self) -> PkgFmt { fn pkg_fmt(&self) -> PkgFmt {
@ -171,8 +169,11 @@ mod test {
#[test] #[test]
fn different_url() { fn different_url() {
let mut meta = PkgMeta::default(); let meta = PkgMeta {
meta.pkg_url = "{ repo }/releases/download/v{ version }/sx128x-util-{ target }-v{ version }.{ archive-format }".to_string(); pkg_url: "{ repo }/releases/download/v{ version }/sx128x-util-{ target }-v{ version }.{ archive-format }".to_string(),
..PkgMeta::default()
};
let data = Data { let data = Data {
name: "radio-sx128x".to_string(), name: "radio-sx128x".to_string(),
target: "x86_64-unknown-linux-gnu".to_string(), target: "x86_64-unknown-linux-gnu".to_string(),
@ -190,8 +191,11 @@ mod test {
#[test] #[test]
fn deprecated_format() { fn deprecated_format() {
let mut meta = PkgMeta::default(); let meta = PkgMeta {
meta.pkg_url = "{ repo }/releases/download/v{ version }/sx128x-util-{ target }-v{ version }.{ format }".to_string(); pkg_url: "{ repo }/releases/download/v{ version }/sx128x-util-{ target }-v{ version }.{ format }".to_string(),
..PkgMeta::default()
};
let data = Data { let data = Data {
name: "radio-sx128x".to_string(), name: "radio-sx128x".to_string(),
target: "x86_64-unknown-linux-gnu".to_string(), target: "x86_64-unknown-linux-gnu".to_string(),
@ -209,11 +213,13 @@ mod test {
#[test] #[test]
fn different_ext() { fn different_ext() {
let mut meta = PkgMeta::default(); let meta = PkgMeta {
meta.pkg_url = pkg_url:
"{ repo }/releases/download/v{ version }/{ name }-v{ version }-{ target }.tar.xz" "{ repo }/releases/download/v{ version }/{ name }-v{ version }-{ target }.tar.xz"
.to_string(); .to_string(),
meta.pkg_fmt = PkgFmt::Txz; pkg_fmt: PkgFmt::Txz,
..PkgMeta::default()
};
let data = Data { let data = Data {
name: "cargo-watch".to_string(), name: "cargo-watch".to_string(),
target: "aarch64-apple-darwin".to_string(), target: "aarch64-apple-darwin".to_string(),
@ -231,9 +237,12 @@ mod test {
#[test] #[test]
fn no_archive() { fn no_archive() {
let mut meta = PkgMeta::default(); let meta = PkgMeta{
meta.pkg_url = "{ repo }/releases/download/v{ version }/{ name }-v{ version }-{ target }{ binary-ext }".to_string(); pkg_fmt: PkgFmt::Bin,
meta.pkg_fmt = PkgFmt::Bin; pkg_url: "{ repo }/releases/download/v{ version }/{ name }-v{ version }-{ target }{ binary-ext }".to_string(),
..PkgMeta::default()
};
let data = Data { let data = Data {
name: "cargo-watch".to_string(), name: "cargo-watch".to_string(),
target: "aarch64-pc-windows-msvc".to_string(), target: "aarch64-pc-windows-msvc".to_string(),

View file

@ -1,5 +1,3 @@
use std::path::Path;
use log::info; use log::info;
use reqwest::Method; use reqwest::Method;
@ -32,10 +30,10 @@ impl super::Fetcher for QuickInstall {
remote_exists(&url, Method::HEAD).await remote_exists(&url, Method::HEAD).await
} }
async fn fetch(&self, dst: &Path) -> Result<(), anyhow::Error> { async fn fetch(&self) -> Result<bytes::Bytes, anyhow::Error> {
let url = self.package_url(); let url = self.package_url();
info!("Downloading package from: '{url}'"); info!("Downloading package from: '{url}'");
download(&url, &dst).await download(&url).await
} }
fn pkg_fmt(&self) -> PkgFmt { fn pkg_fmt(&self) -> PkgFmt {

View file

@ -1,10 +1,14 @@
use std::path::{Path, PathBuf}; use std::{
io::Cursor,
path::{Path, PathBuf},
};
use log::{debug, error, info}; use log::{debug, error, info};
use cargo_toml::Manifest; use cargo_toml::Manifest;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use serde::Serialize; use serde::Serialize;
use std::io::Read;
use tar::Archive; use tar::Archive;
use tinytemplate::TinyTemplate; use tinytemplate::TinyTemplate;
use xz2::read::XzDecoder; use xz2::read::XzDecoder;
@ -15,10 +19,20 @@ use crate::Meta;
use super::PkgFmt; use super::PkgFmt;
/// Load binstall metadata from the crate `Cargo.toml` at the provided path /// Load binstall metadata from the crate `Cargo.toml` at the provided path
pub fn load_manifest_path<P: AsRef<Path>>( pub fn load_manifest_path(mut manifest_path: PathBuf) -> Result<Manifest<Meta>, anyhow::Error> {
manifest_path: P, debug!("Reading manifest: {}", manifest_path.display());
) -> Result<Manifest<Meta>, anyhow::Error> {
debug!("Reading manifest: {}", manifest_path.as_ref().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) // Load and parse manifest (this checks file system for binary output names)
let manifest = Manifest::<Meta>::from_path_with_metadata(manifest_path)?; let manifest = Manifest::<Meta>::from_path_with_metadata(manifest_path)?;
@ -33,7 +47,7 @@ pub async fn remote_exists(url: &str, method: reqwest::Method) -> Result<bool, a
} }
/// Download a file from the provided URL to the provided path /// Download a file from the provided URL to the provided path
pub async fn download<P: AsRef<Path>>(url: &str, path: P) -> Result<(), anyhow::Error> { pub async fn download(url: &str) -> Result<bytes::Bytes, anyhow::Error> {
debug!("Downloading from: '{}'", url); debug!("Downloading from: '{}'", url);
let resp = reqwest::get(url).await?; let resp = reqwest::get(url).await?;
@ -43,90 +57,89 @@ pub async fn download<P: AsRef<Path>>(url: &str, path: P) -> Result<(), anyhow::
return Err(anyhow::anyhow!(resp.status())); return Err(anyhow::anyhow!(resp.status()));
} }
let bytes = resp.bytes().await?; Ok(resp.bytes().await?)
}
let path = path.as_ref(); // Extracts a file at path from tar archive to byte vector
debug!("Download OK, writing to file: '{}'", path.display()); fn extract_file_from_tar_archive<T: std::io::Read>(
tar_archive: &mut Archive<T>,
path: PathBuf,
) -> Result<Vec<u8>, 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())?; let mut buffer = Vec::with_capacity(file.size() as usize);
std::fs::write(&path, bytes)?;
Ok(()) file.read_to_end(&mut buffer)?;
Ok(buffer)
} }
/// Extract files from the specified source onto the specified path /// Extract files from the specified source onto the specified path
pub fn extract<S: AsRef<Path>, P: AsRef<Path>>( pub fn extract_file(
source: S, source: &bytes::Bytes,
fmt: PkgFmt, fmt: PkgFmt,
path: P, file: PathBuf,
) -> Result<(), anyhow::Error> { ) -> Result<Vec<u8>, anyhow::Error> {
match fmt { match fmt {
PkgFmt::Tar => { PkgFmt::Tar => {
// Extract to install dir // Extract to install dir
debug!( debug!("Extracting from tar archive '{:?}'", source.as_ref(),);
"Extracting from tar archive '{:?}' to `{:?}`",
source.as_ref(),
path.as_ref()
);
let dat = std::fs::File::open(source)?; let mut tar = Archive::new(&source[..]);
let mut tar = Archive::new(dat);
tar.unpack(path)?; extract_file_from_tar_archive(&mut tar, file)
} }
PkgFmt::Tgz => { PkgFmt::Tgz => {
// Extract to install dir // Extract to install dir
debug!( debug!("Decompressing from tgz archive '{:?}'", source.as_ref());
"Decompressing from tgz archive '{:?}' to `{:?}`",
source.as_ref(),
path.as_ref()
);
let dat = std::fs::File::open(source)?; let tar = GzDecoder::new(&source[..]);
let tar = GzDecoder::new(dat);
let mut tgz = Archive::new(tar); let mut tgz = Archive::new(tar);
tgz.unpack(path)?; extract_file_from_tar_archive(&mut tgz, file)
} }
PkgFmt::Txz => { PkgFmt::Txz => {
// Extract to install dir // Extract to install dir
debug!( debug!("Decompressing from txz archive '{:?}'", source.as_ref());
"Decompressing from txz archive '{:?}' to `{:?}`",
source.as_ref(),
path.as_ref()
);
let dat = std::fs::File::open(source)?; let tar = XzDecoder::new(&source[..]);
let tar = XzDecoder::new(dat);
let mut txz = Archive::new(tar); let mut txz = Archive::new(tar);
txz.unpack(path)?; extract_file_from_tar_archive(&mut txz, file)
} }
PkgFmt::Zip => { PkgFmt::Zip => {
// Extract to install dir // Extract to install dir
debug!( debug!("Decompressing from zip archive '{:?}'", source.as_ref());
"Decompressing from zip archive '{:?}' to `{:?}`",
source.as_ref(),
path.as_ref()
);
let dat = std::fs::File::open(source)?; let mut zip = ZipArchive::new(Cursor::new(&source[..]))?;
let mut zip = ZipArchive::new(dat)?;
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 => { PkgFmt::Bin => {
debug!( debug!("Copying binary '{:?}'", source.as_ref());
"Copying binary '{:?}' to `{:?}`",
source.as_ref(),
path.as_ref()
);
// Copy to install dir // Copy to install dir
std::fs::copy(source, path)?; Ok(source.to_vec())
} }
}; }
Ok(())
} }
/// Fetch install path from environment /// Fetch install path from environment
@ -162,7 +175,7 @@ pub fn get_install_path<P: AsRef<Path>>(install_path: Option<P>) -> Option<PathB
// Local executable dir if no cargo is found // Local executable dir if no cargo is found
if let Some(d) = dirs::executable_dir() { if let Some(d) = dirs::executable_dir() {
debug!("Fallback to {}", d.display()); debug!("Fallback to {}", d.display());
return Some(d.into()); return Some(d);
} }
None None
@ -192,7 +205,7 @@ pub trait Template: Serialize {
let mut tt = TinyTemplate::new(); let mut tt = TinyTemplate::new();
// Add template to instance // Add template to instance
tt.add_template("path", &template)?; tt.add_template("path", template)?;
// Render output // Render output
Ok(tt.render("path", self)?) Ok(tt.render("path", self)?)

View file

@ -13,14 +13,14 @@ pub mod bins;
pub mod fetchers; pub mod fetchers;
/// Compiled target triple, used as default for binary fetching /// Compiled target triple, used as default for binary fetching
pub const TARGET: &'static str = env!("TARGET"); pub const TARGET: &str = env!("TARGET");
/// Default package path template (may be overridden in package Cargo.toml) /// Default package path template (may be overridden in package Cargo.toml)
pub const DEFAULT_PKG_URL: &'static str = pub const DEFAULT_PKG_URL: &str =
"{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ archive-format }"; "{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ archive-format }";
/// Default binary name template (may be overridden in package Cargo.toml) /// Default binary name template (may be overridden in package Cargo.toml)
pub const DEFAULT_BIN_DIR: &'static str = "{ name }-{ target }-v{ version }/{ bin }{ binary-ext }"; pub const DEFAULT_BIN_DIR: &str = "{ name }-{ target }-v{ version }/{ bin }{ binary-ext }";
/// Binary format enumeration /// Binary format enumeration
#[derive( #[derive(
@ -108,7 +108,7 @@ impl PkgMeta {
/// Target specific overrides for binary installation /// Target specific overrides for binary installation
/// ///
/// Exposed via `[package.metadata.TARGET]` in `Cargo.toml` /// Exposed via `[package.metadata.TARGET]` in `Cargo.toml`
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case", default)] #[serde(rename_all = "kebab-case", default)]
pub struct PkgOverride { pub struct PkgOverride {
/// URL template override for package downloads /// URL template override for package downloads
@ -121,16 +121,6 @@ pub struct PkgOverride {
pub bin_dir: Option<String>, pub bin_dir: Option<String>,
} }
impl Default for PkgOverride {
fn default() -> Self {
Self {
pkg_url: None,
pkg_fmt: None,
bin_dir: None,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct BinMeta { pub struct BinMeta {
@ -157,9 +147,9 @@ mod test {
let mut manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); let mut manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
manifest_dir.push_str("/Cargo.toml"); 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 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"); assert_eq!(&package.name, "cargo-binstall");

View file

@ -4,7 +4,6 @@ use cargo_toml::{Package, Product};
use log::{debug, error, info, warn, LevelFilter}; use log::{debug, error, info, warn, LevelFilter};
use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode}; use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode};
use structopt::StructOpt; use structopt::StructOpt;
use tempdir::TempDir;
use tokio::process::Command; use tokio::process::Command;
use cargo_binstall::{ use cargo_binstall::{
@ -46,10 +45,6 @@ struct Options {
#[structopt(long)] #[structopt(long)]
no_confirm: bool, no_confirm: bool,
/// Do not cleanup temporary files on success
#[structopt(long)]
no_cleanup: bool,
/// Override manifest source. /// Override manifest source.
/// This skips searching crates.io for a manifest and uses /// 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
@ -88,21 +83,17 @@ async fn main() -> Result<(), anyhow::Error> {
) )
.unwrap(); .unwrap();
// Create a temporary directory for downloads etc.
let temp_dir = TempDir::new("cargo-binstall")?;
info!("Installing package: '{}'", opts.name); info!("Installing package: '{}'", opts.name);
debug!("Reading manifest");
// 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`
// TODO: support git-based fetches (whole repo name rather than just crate name) // TODO: support git-based fetches (whole repo name rather than just crate name)
let manifest_path = match opts.manifest_path.clone() { let manifest = match opts.manifest_path.clone() {
Some(p) => p, Some(p) => load_manifest_path(p)?,
None => fetch_crate_cratesio(&opts.name, &opts.version, temp_dir.path()).await?, 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 package = manifest.package.unwrap();
let is_plain_version = semver::Version::from_str(&opts.version).is_ok(); let is_plain_version = semver::Version::from_str(&opts.version).is_ok();
@ -122,9 +113,8 @@ async fn main() -> Result<(), anyhow::Error> {
package package
.metadata .metadata
.as_ref() .as_ref()
.map(|m| m.binstall.clone()) .and_then(|m| m.binstall.clone())
.flatten() .unwrap_or_default(),
.unwrap_or(PkgMeta::default()),
manifest.bin, manifest.bin,
); );
@ -142,12 +132,6 @@ async fn main() -> Result<(), anyhow::Error> {
})?; })?;
debug!("Using install path: {}", install_path.display()); 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 { let fetcher_data = Data {
name: package.name.clone(), name: package.name.clone(),
target: opts.target.clone(), target: opts.target.clone(),
@ -163,17 +147,7 @@ async fn main() -> Result<(), anyhow::Error> {
match fetchers.first_available().await { match fetchers.first_available().await {
Some(fetcher) => { Some(fetcher) => {
install_from_package( install_from_package(binaries, fetcher, install_path, meta, opts, package).await
binaries,
fetcher,
install_path,
meta,
opts,
package,
pkg_path,
temp_dir,
)
.await
} }
None => install_from_source(opts, package).await, None => install_from_source(opts, package).await,
} }
@ -186,8 +160,6 @@ async fn install_from_package(
mut meta: PkgMeta, mut meta: PkgMeta,
opts: Options, opts: Options,
package: Package<Meta>, package: Package<Meta>,
pkg_path: PathBuf,
temp_dir: TempDir,
) -> Result<(), anyhow::Error> { ) -> Result<(), anyhow::Error> {
// Prompt user for third-party source // Prompt user for third-party source
if fetcher.is_third_party() { if fetcher.is_third_party() {
@ -212,11 +184,12 @@ async fn install_from_package(
} }
// Download package // Download package
if opts.dry_run { let binary = if opts.dry_run {
info!("Dry run, not downloading package"); info!("Dry run, not downloading package");
None
} else { } else {
fetcher.fetch(&pkg_path).await?; Some(fetcher.fetch().await?)
} };
#[cfg(incomplete)] #[cfg(incomplete)]
{ {
@ -242,24 +215,11 @@ async fn install_from_package(
} }
} }
let bin_path = temp_dir.path().join(format!("bin-{}", opts.name)); if !opts.dry_run && binaries.is_empty() {
debug!("Using temporary binary path: {}", bin_path.display()); error!("No binaries specified (or inferred from file system)");
return Err(anyhow::anyhow!(
if !opts.dry_run { "No binaries specified (or inferred from file system)"
// 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)"
));
}
} }
// List files to be installed // List files to be installed
@ -270,7 +230,6 @@ async fn install_from_package(
version: package.version.clone(), version: package.version.clone(),
repo: package.repository.clone(), repo: package.repository.clone(),
meta, meta,
bin_path,
install_path, install_path,
}; };
@ -304,7 +263,24 @@ async fn install_from_package(
info!("Installing binaries..."); info!("Installing binaries...");
for file in &bin_files { 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 // Generate symlinks