mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-21 04:58:42 +00:00
refactoring to library
This commit is contained in:
parent
0c72b89627
commit
8f7f7f5530
7 changed files with 263 additions and 184 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -130,6 +130,7 @@ dependencies = [
|
||||||
"flate2",
|
"flate2",
|
||||||
"log",
|
"log",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"simplelog",
|
"simplelog",
|
||||||
|
|
|
@ -27,3 +27,5 @@ strum_macros = "0.20.1"
|
||||||
strum = "0.20.0"
|
strum = "0.20.0"
|
||||||
dirs = "3.0.1"
|
dirs = "3.0.1"
|
||||||
serde_derive = "1.0.118"
|
serde_derive = "1.0.118"
|
||||||
|
#github = "0.1.2"
|
||||||
|
semver = "0.11.0"
|
||||||
|
|
|
@ -38,7 +38,7 @@ Cargo metadata is used to avoid the need for an additional centralised index or
|
||||||
First you'll need to install `cargo-binstall` either via `cargo install cargo-binstall` (and it'll have to compile, sorry...), or by grabbing a pre-compiled version from the [releases](https://github.com/ryankurte/cargo-binstall/releases) page and putting that somewhere on your path. It's like there's a problem we're trying to solve?
|
First you'll need to install `cargo-binstall` either via `cargo install cargo-binstall` (and it'll have to compile, sorry...), or by grabbing a pre-compiled version from the [releases](https://github.com/ryankurte/cargo-binstall/releases) page and putting that somewhere on your path. It's like there's a problem we're trying to solve?
|
||||||
|
|
||||||
Once a project supports `binstall` you can then install binaries via `cargo binstall NAME` where `NAME` is the name of the crate. This will then fetch the metadata for the provided crate, lookup the associated binary file, and download this onto your system.
|
Once a project supports `binstall` you can then install binaries via `cargo binstall NAME` where `NAME` is the name of the crate. This will then fetch the metadata for the provided crate, lookup the associated binary file, and download this onto your system.
|
||||||
By default the latest version is installed, which can be overridden using the `--version` argument, and packages are installed to `$HOME/.cargo/bin` as is consistent with `cargo install`, which can be overridden via the `--install-path` argument. As always `--help` will show available options.
|
By default the latest version from is installed, which can be overridden using the `--version` argument, and packages are installed to `$HOME/.cargo/bin` as is consistent with `cargo install`, which can be overridden via the `--install-path` argument. As always `--help` will show available options.
|
||||||
|
|
||||||
We hope the defaults will work without configuration in _some_ cases, however, different projects have wildly different CI and build output configurations. You will likely need to add some cargo metadata to support `binstall` in your project, see [Supporting Binary Installation](#Supporting-Binary-Installation) for details.
|
We hope the defaults will work without configuration in _some_ cases, however, different projects have wildly different CI and build output configurations. You will likely need to add some cargo metadata to support `binstall` in your project, see [Supporting Binary Installation](#Supporting-Binary-Installation) for details.
|
||||||
|
|
||||||
|
|
80
src/drivers.rs
Normal file
80
src/drivers.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use log::{debug, error};
|
||||||
|
|
||||||
|
use crates_io_api::AsyncClient;
|
||||||
|
use semver::Version;
|
||||||
|
|
||||||
|
use crate::PkgFmt;
|
||||||
|
use crate::helpers::*;
|
||||||
|
|
||||||
|
/// Fetch a crate by name and version from crates.io
|
||||||
|
pub async fn fetch_crate_cratesio(name: &str, version: Option<&str>, temp_dir: &Path) -> Result<PathBuf, anyhow::Error> {
|
||||||
|
// Build crates.io api client and fetch info
|
||||||
|
// TODO: support git-based fetches (whole repo name rather than just crate name)
|
||||||
|
let api_client = AsyncClient::new("cargo-binstall (https://github.com/ryankurte/cargo-binstall)", Duration::from_millis(100))?;
|
||||||
|
|
||||||
|
debug!("Fetching information for crate: '{}'", name);
|
||||||
|
|
||||||
|
// Fetch overall crate info
|
||||||
|
let info = match api_client.get_crate(name.as_ref()).await {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error fetching information for crate {}: {}", name, e);
|
||||||
|
return Err(e.into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use specified or latest version
|
||||||
|
let version_num = match version {
|
||||||
|
Some(v) => v.to_string(),
|
||||||
|
None => info.crate_data.max_version,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch crates.io information for the specified version
|
||||||
|
// TODO: Filter by semver matches instead of literal match
|
||||||
|
let mut versions = info.versions.clone();
|
||||||
|
versions.sort_by(|a, b| {
|
||||||
|
let ver_a = Version::parse(&a.num).unwrap();
|
||||||
|
let ver_b = Version::parse(&b.num).unwrap();
|
||||||
|
|
||||||
|
ver_a.partial_cmp(&ver_b).unwrap()
|
||||||
|
} );
|
||||||
|
|
||||||
|
let version = match versions.iter().find(|v| v.num == version_num) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => {
|
||||||
|
error!("No crates.io information found for crate: '{}' version: '{}'",
|
||||||
|
name, version_num);
|
||||||
|
return Err(anyhow::anyhow!("No crate information found"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Download crate
|
||||||
|
download(&crate_url, &tgz_path).await?;
|
||||||
|
|
||||||
|
// Decompress downloaded tgz
|
||||||
|
debug!("Decompressing crate archive");
|
||||||
|
extract(&tgz_path, PkgFmt::Tgz, &temp_dir)?;
|
||||||
|
let crate_path = temp_dir.join(format!("{}-{}", name, version_num));
|
||||||
|
|
||||||
|
// Return crate directory
|
||||||
|
Ok(crate_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<PathBuf, anyhow::Error> {
|
||||||
|
|
||||||
|
unimplemented!();
|
||||||
|
}
|
65
src/helpers.rs
Normal file
65
src/helpers.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use log::{debug, error};
|
||||||
|
|
||||||
|
use flate2::read::GzDecoder;
|
||||||
|
use tar::Archive;
|
||||||
|
|
||||||
|
|
||||||
|
use super::PkgFmt;
|
||||||
|
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
|
||||||
|
debug!("Downloading from: '{}'", url);
|
||||||
|
|
||||||
|
let resp = reqwest::get(url).await?;
|
||||||
|
|
||||||
|
if !resp.status().is_success() {
|
||||||
|
error!("Download error: {}", resp.status());
|
||||||
|
return Err(anyhow::anyhow!(resp.status()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = resp.bytes().await?;
|
||||||
|
|
||||||
|
debug!("Download OK, writing to file: '{:?}'", path.as_ref());
|
||||||
|
|
||||||
|
std::fs::write(&path, bytes)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract files from the specified source onto the specified path
|
||||||
|
pub fn extract<S: AsRef<Path>, P: AsRef<Path>>(source: S, fmt: PkgFmt, path: P) -> Result<(), anyhow::Error> {
|
||||||
|
match fmt {
|
||||||
|
PkgFmt::Tar => {
|
||||||
|
// Extract to install dir
|
||||||
|
debug!("Extracting from 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 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::Bin => {
|
||||||
|
debug!("Copying data from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
||||||
|
// Copy to install dir
|
||||||
|
std::fs::copy(source, path)?;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
75
src/lib.rs
Normal file
75
src/lib.rs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
use structopt::StructOpt;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use strum_macros::{Display, EnumString, EnumVariantNames};
|
||||||
|
use tinytemplate::TinyTemplate;
|
||||||
|
|
||||||
|
|
||||||
|
pub mod helpers;
|
||||||
|
pub use helpers::*;
|
||||||
|
|
||||||
|
pub mod drivers;
|
||||||
|
pub use drivers::*;
|
||||||
|
|
||||||
|
|
||||||
|
/// Compiled target triple, used as default for binary fetching
|
||||||
|
pub const TARGET: &'static str = env!("TARGET");
|
||||||
|
|
||||||
|
/// Default package path for use if no path is specified
|
||||||
|
pub const DEFAULT_PKG_PATH: &'static str = "{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ format }";
|
||||||
|
|
||||||
|
|
||||||
|
/// Binary format enumeration
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[derive(Display, EnumString, EnumVariantNames)]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum PkgFmt {
|
||||||
|
/// Download format is TAR (uncompressed)
|
||||||
|
Tar,
|
||||||
|
/// Download format is TGZ (TAR + GZip)
|
||||||
|
Tgz,
|
||||||
|
/// Download format is raw / binary
|
||||||
|
Bin,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Metadata for binary installation use.
|
||||||
|
///
|
||||||
|
/// Exposed via `[package.metadata]` in `Cargo.toml`
|
||||||
|
#[derive(Clone, Debug, StructOpt, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct Meta {
|
||||||
|
/// Path template override for binary downloads
|
||||||
|
pub pkg_url: Option<String>,
|
||||||
|
/// Package name override for binary downloads
|
||||||
|
pub pkg_name: Option<String>,
|
||||||
|
/// Format override for binary downloads
|
||||||
|
pub pkg_fmt: Option<PkgFmt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Template for constructing download paths
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct Context {
|
||||||
|
pub name: String,
|
||||||
|
pub repo: Option<String>,
|
||||||
|
pub target: String,
|
||||||
|
pub version: String,
|
||||||
|
pub format: PkgFmt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
/// Render the context into the provided template
|
||||||
|
pub fn render(&self, template: &str) -> Result<String, anyhow::Error> {
|
||||||
|
// Create template instance
|
||||||
|
let mut tt = TinyTemplate::new();
|
||||||
|
|
||||||
|
// Add template to instance
|
||||||
|
tt.add_template("path", &template)?;
|
||||||
|
|
||||||
|
// Render output
|
||||||
|
let rendered = tt.render("path", self)?;
|
||||||
|
|
||||||
|
Ok(rendered)
|
||||||
|
}
|
||||||
|
}
|
222
src/main.rs
222
src/main.rs
|
@ -1,62 +1,57 @@
|
||||||
use std::time::Duration;
|
use std::path::{PathBuf};
|
||||||
use std::path::{PathBuf, Path};
|
|
||||||
|
|
||||||
use log::{debug, info, error, LevelFilter};
|
use log::{debug, info, error, LevelFilter};
|
||||||
use simplelog::{TermLogger, ConfigBuilder, TerminalMode};
|
use simplelog::{TermLogger, ConfigBuilder, TerminalMode};
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
use crates_io_api::AsyncClient;
|
|
||||||
use cargo_toml::Manifest;
|
use cargo_toml::Manifest;
|
||||||
|
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
use flate2::read::GzDecoder;
|
|
||||||
use tar::Archive;
|
|
||||||
|
|
||||||
use tinytemplate::TinyTemplate;
|
use cargo_binstall::*;
|
||||||
|
|
||||||
/// Compiled target triple, used as default for binary fetching
|
|
||||||
const TARGET: &'static str = env!("TARGET");
|
|
||||||
|
|
||||||
/// Default binary path for use if no path is specified
|
|
||||||
const DEFAULT_BIN_PATH: &'static str = "{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.{ format }";
|
|
||||||
|
|
||||||
/// Binary format enumeration
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[derive(strum_macros::Display, strum_macros::EnumString, strum_macros::EnumVariantNames)]
|
|
||||||
#[strum(serialize_all = "snake_case")]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum PkgFmt {
|
|
||||||
/// Download format is TAR (uncompressed)
|
|
||||||
Tar,
|
|
||||||
/// Download format is TGZ (TAR + GZip)
|
|
||||||
Tgz,
|
|
||||||
/// Download format is raw / binary
|
|
||||||
Bin,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
struct Options {
|
struct Options {
|
||||||
/// Crate name to install
|
/// Package name or URL for installation
|
||||||
|
/// This must be either a crates.io package name or github or gitlab url
|
||||||
#[structopt()]
|
#[structopt()]
|
||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
/// Crate version to install
|
/// Package version to instal
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
version: Option<String>,
|
version: Option<String>,
|
||||||
|
|
||||||
/// Override the package path template.
|
/// Override binary target, ignoring compiled version
|
||||||
/// If no `metadata.pkg_url` key is set or `--pkg-url` argument provided, this
|
#[structopt(long, default_value = TARGET)]
|
||||||
/// defaults to `{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.tgz`
|
target: String,
|
||||||
#[structopt(long)]
|
|
||||||
pkg_url: Option<String>,
|
|
||||||
|
|
||||||
/// Override format for binary file download.
|
/// Override format for binary file download.
|
||||||
/// Defaults to `tgz`
|
/// Defaults to `tgz`
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
pkg_fmt: Option<PkgFmt>,
|
pkg_fmt: Option<PkgFmt>,
|
||||||
|
|
||||||
|
/// Override install path for downloaded binary.
|
||||||
|
/// Defaults to `$HOME/.cargo/bin`
|
||||||
|
#[structopt(long)]
|
||||||
|
install_path: Option<String>,
|
||||||
|
|
||||||
|
#[structopt(flatten)]
|
||||||
|
overrides: Overrides,
|
||||||
|
|
||||||
|
/// Do not cleanup temporary files on success
|
||||||
|
#[structopt(long)]
|
||||||
|
no_cleanup: bool,
|
||||||
|
|
||||||
|
/// Utility log level
|
||||||
|
#[structopt(long, default_value = "info")]
|
||||||
|
log_level: LevelFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
pub struct Overrides {
|
||||||
|
|
||||||
/// Override the package name.
|
/// Override the package name.
|
||||||
/// This is only useful for diagnostics when using the default `pkg_url`
|
/// This is only useful for diagnostics when using the default `pkg_url`
|
||||||
/// as you can otherwise customise this in the path.
|
/// as you can otherwise customise this in the path.
|
||||||
|
@ -64,53 +59,20 @@ struct Options {
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
pkg_name: Option<String>,
|
pkg_name: Option<String>,
|
||||||
|
|
||||||
/// Override install path for downloaded binary.
|
/// Override the package path template.
|
||||||
/// Defaults to `$HOME/.cargo/bin`
|
/// If no `metadata.pkg_url` key is set or `--pkg-url` argument provided, this
|
||||||
|
/// defaults to `{ repo }/releases/download/v{ version }/{ name }-{ target }-v{ version }.tgz`
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
install_path: Option<String>,
|
pkg_url: Option<String>,
|
||||||
|
|
||||||
/// Override binary target, ignoring compiled version
|
|
||||||
#[structopt(long, default_value = TARGET)]
|
|
||||||
target: String,
|
|
||||||
|
|
||||||
/// 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
|
/// the specified path directly, useful for debugging
|
||||||
#[structopt(long)]
|
#[structopt(long)]
|
||||||
manifest_path: Option<PathBuf>,
|
manifest_path: Option<PathBuf>,
|
||||||
|
|
||||||
/// Utility log level
|
|
||||||
#[structopt(long, default_value = "info")]
|
|
||||||
log_level: LevelFilter,
|
|
||||||
|
|
||||||
/// Do not cleanup temporary files on success
|
|
||||||
#[structopt(long)]
|
|
||||||
no_cleanup: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Metadata for cargo-binstall exposed via cargo.toml
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub struct Meta {
|
|
||||||
/// Path template override for binary downloads
|
|
||||||
pub pkg_url: Option<String>,
|
|
||||||
/// Package name override for binary downloads
|
|
||||||
pub pkg_name: Option<String>,
|
|
||||||
/// Format override for binary downloads
|
|
||||||
pub pkg_fmt: Option<PkgFmt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Template for constructing download paths
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
pub struct Context {
|
|
||||||
name: String,
|
|
||||||
repo: Option<String>,
|
|
||||||
target: String,
|
|
||||||
version: String,
|
|
||||||
format: PkgFmt,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), anyhow::Error> {
|
async fn main() -> Result<(), anyhow::Error> {
|
||||||
|
@ -138,7 +100,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
|
|
||||||
// Fetch crate via crates.io, git, or use a local manifest path
|
// Fetch crate via crates.io, git, or use a local manifest path
|
||||||
// TODO: work out which of these to do based on `opts.name`
|
// TODO: work out which of these to do based on `opts.name`
|
||||||
let crate_path = match opts.manifest_path {
|
let crate_path = match opts.overrides.manifest_path {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
None => fetch_crate_cratesio(&opts.name, opts.version.as_deref(), temp_dir.path()).await?,
|
None => fetch_crate_cratesio(&opts.name, opts.version.as_deref(), temp_dir.path()).await?,
|
||||||
};
|
};
|
||||||
|
@ -158,8 +120,8 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
let meta = package.metadata;
|
let meta = package.metadata;
|
||||||
debug!("Retrieved metadata: {:?}", meta);
|
debug!("Retrieved metadata: {:?}", meta);
|
||||||
|
|
||||||
// Select which binary path to use
|
// Select which package path to use
|
||||||
let pkg_url = match (opts.pkg_url, meta.as_ref().map(|m| m.pkg_url.clone() ).flatten()) {
|
let pkg_url = match (opts.overrides.pkg_url, meta.as_ref().map(|m| m.pkg_url.clone() ).flatten()) {
|
||||||
(Some(p), _) => {
|
(Some(p), _) => {
|
||||||
info!("Using package url override: '{}'", p);
|
info!("Using package url override: '{}'", p);
|
||||||
p
|
p
|
||||||
|
@ -170,8 +132,8 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
info!("No `pkg-url` key found in Cargo.toml or `--pkg-url` argument provided");
|
info!("No `pkg-url` key found in Cargo.toml or `--pkg-url` argument provided");
|
||||||
info!("Using default url: {}", DEFAULT_BIN_PATH);
|
info!("Using default url: {}", DEFAULT_PKG_PATH);
|
||||||
DEFAULT_BIN_PATH.to_string()
|
DEFAULT_PKG_PATH.to_string()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -183,7 +145,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Override package name if required
|
// Override package name if required
|
||||||
let pkg_name = match (&opts.pkg_name, meta.as_ref().map(|m| m.pkg_name.clone() ).flatten()) {
|
let pkg_name = match (&opts.overrides.pkg_name, meta.as_ref().map(|m| m.pkg_name.clone() ).flatten()) {
|
||||||
(Some(o), _) => o.clone(),
|
(Some(o), _) => o.clone(),
|
||||||
(_, Some(m)) => m,
|
(_, Some(m)) => m,
|
||||||
_ => opts.name.clone(),
|
_ => opts.name.clone(),
|
||||||
|
@ -201,9 +163,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
debug!("Using context: {:?}", ctx);
|
debug!("Using context: {:?}", ctx);
|
||||||
|
|
||||||
// Interpolate version / target / etc.
|
// Interpolate version / target / etc.
|
||||||
let mut tt = TinyTemplate::new();
|
let rendered = ctx.render(&pkg_url)?;
|
||||||
tt.add_template("path", &pkg_url)?;
|
|
||||||
let rendered = tt.render("path", &ctx)?;
|
|
||||||
|
|
||||||
info!("Downloading package from: '{}'", rendered);
|
info!("Downloading package from: '{}'", rendered);
|
||||||
|
|
||||||
|
@ -238,111 +198,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Download a file from the provided URL to the provided path
|
|
||||||
async fn download<P: AsRef<Path>>(url: &str, path: P) -> Result<(), anyhow::Error> {
|
|
||||||
|
|
||||||
debug!("Downloading from: '{}'", url);
|
|
||||||
|
|
||||||
let resp = reqwest::get(url).await?;
|
|
||||||
|
|
||||||
if !resp.status().is_success() {
|
|
||||||
error!("Download error: {}", resp.status());
|
|
||||||
return Err(anyhow::anyhow!(resp.status()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes = resp.bytes().await?;
|
|
||||||
|
|
||||||
debug!("Download OK, writing to file: '{:?}'", path.as_ref());
|
|
||||||
|
|
||||||
std::fs::write(&path, bytes)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract<S: AsRef<Path>, P: AsRef<Path>>(source: S, fmt: PkgFmt, path: P) -> Result<(), anyhow::Error> {
|
|
||||||
match fmt {
|
|
||||||
PkgFmt::Tar => {
|
|
||||||
// Extract to install dir
|
|
||||||
debug!("Extracting from 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 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::Bin => {
|
|
||||||
debug!("Copying data from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
|
||||||
// Copy to install dir
|
|
||||||
std::fs::copy(source, path)?;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch a crate by name and version from crates.io
|
|
||||||
async fn fetch_crate_cratesio(name: &str, version: Option<&str>, temp_dir: &Path) -> Result<PathBuf, anyhow::Error> {
|
|
||||||
// Build crates.io api client and fetch info
|
|
||||||
// TODO: support git-based fetches (whole repo name rather than just crate name)
|
|
||||||
let api_client = AsyncClient::new("cargo-binstall (https://github.com/ryankurte/cargo-binstall)", Duration::from_millis(100))?;
|
|
||||||
|
|
||||||
info!("Fetching information for crate: '{}'", name);
|
|
||||||
|
|
||||||
// Fetch overall crate info
|
|
||||||
let info = match api_client.get_crate(name.as_ref()).await {
|
|
||||||
Ok(i) => i,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Error fetching information for crate {}: {}", name, e);
|
|
||||||
return Err(e.into())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use specified or latest version
|
|
||||||
let version_num = match version {
|
|
||||||
Some(v) => v.to_string(),
|
|
||||||
None => info.crate_data.max_version,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch crates.io information for the specified version
|
|
||||||
// TODO: could do a semver match and sort here?
|
|
||||||
let version = match info.versions.iter().find(|v| v.num == version_num) {
|
|
||||||
Some(v) => v,
|
|
||||||
None => {
|
|
||||||
error!("No crates.io information found for crate: '{}' version: '{}'",
|
|
||||||
name, version_num);
|
|
||||||
return Err(anyhow::anyhow!("No crate information found"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("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);
|
|
||||||
|
|
||||||
// Download crate
|
|
||||||
download(&crate_url, &tgz_path).await?;
|
|
||||||
|
|
||||||
// Decompress downloaded tgz
|
|
||||||
debug!("Decompressing crate archive");
|
|
||||||
extract(&tgz_path, PkgFmt::Tgz, &temp_dir)?;
|
|
||||||
let crate_path = temp_dir.join(format!("{}-{}", name, version_num));
|
|
||||||
|
|
||||||
// Return crate directory
|
|
||||||
Ok(crate_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch install path
|
/// Fetch install path
|
||||||
/// roughly follows https://doc.rust-lang.org/cargo/commands/cargo-install.html#description
|
/// roughly follows https://doc.rust-lang.org/cargo/commands/cargo-install.html#description
|
||||||
|
|
Loading…
Add table
Reference in a new issue