From 1c2d005fd42c6425a0f589b562613063eb096613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fe=CC=81lix=20Saparelli?= Date: Mon, 4 Jul 2022 23:53:47 +1200 Subject: [PATCH] Write to .crates.toml --- Cargo.lock | 11 +++ Cargo.toml | 2 + src/helpers.rs | 2 +- src/lib.rs | 1 + src/main.rs | 20 +++++- src/metafiles.rs | 173 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 src/metafiles.rs diff --git a/Cargo.lock b/Cargo.lock index e03717bb..3a5a3bc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,6 +143,7 @@ dependencies = [ "flate2", "futures-util", "guess_host_triple", + "home", "log", "miette", "once_cell", @@ -158,6 +159,7 @@ dependencies = [ "thiserror", "tinytemplate", "tokio", + "toml", "url", "xz2", "zip", @@ -595,6 +597,15 @@ dependencies = [ "libc", ] +[[package]] +name = "home" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" +dependencies = [ + "winapi", +] + [[package]] name = "http" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index f121f595..aa0ee739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ crates_io_api = { version = "0.8.0", default-features = false, features = ["rust dirs = "4.0.0" flate2 = { version = "1.0.24", features = ["zlib-ng"], default-features = false } futures-util = { version = "0.3.21", default-features = false } +home = "0.5.3" log = "0.4.14" miette = { version = "5.1.0", features = ["fancy-no-backtrace"] } once_cell = "1.12.0" @@ -43,6 +44,7 @@ tempfile = "3.3.0" thiserror = "1.0.31" tinytemplate = "1.2.1" tokio = { version = "1.19.1", features = ["rt-multi-thread", "process", "sync"], default-features = false } +toml = "0.5.9" url = "2.2.2" xz2 = "0.1.6" diff --git a/src/helpers.rs b/src/helpers.rs index e476464c..4be08709 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -6,7 +6,7 @@ use cargo_toml::Manifest; use futures_util::stream::Stream; use log::debug; use once_cell::sync::OnceCell; -use reqwest::{Client, ClientBuilder, Method, Response, tls}; +use reqwest::{tls, Client, ClientBuilder, Method, Response}; use serde::Serialize; use tinytemplate::TinyTemplate; use tokio::task::block_in_place; diff --git a/src/lib.rs b/src/lib.rs index 2ec0eb1b..eb475ac4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub use helpers::*; pub mod bins; pub mod fetchers; +pub mod metafiles; mod target; pub use target::*; diff --git a/src/main.rs b/src/main.rs index f5684031..fffdccd0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -201,7 +201,10 @@ async fn entry() -> Result<()> { // Initialize REQWESTGLOBALCONFIG REQWESTGLOBALCONFIG - .set(ReqwestConfig { secure: opts.secure, min_tls: opts.min_tls_version.map(|v| v.into()) }) + .set(ReqwestConfig { + secure: opts.secure, + min_tls: opts.min_tls_version.map(|v| v.into()), + }) .unwrap(); // Setup logging @@ -451,6 +454,21 @@ async fn install_from_package( uithread.confirm().await?; + debug!("Writing .crates.toml"); + if let Ok(mut ctoml) = metafiles::CratesToml::load().await { + ctoml.insert( + metafiles::CrateVersionSource { + name: opts.name.into(), + version: package.version.parse().into_diagnostic()?, + source: metafiles::Source::Registry( + url::Url::parse("https://github.com/rust-lang/crates.io-index").unwrap(), + ), + }, + bin_files.iter().map(|bin| bin.base_name.clone()), + ); + ctoml.write().await?; + } + info!("Installing binaries..."); block_in_place(|| { for file in &bin_files { diff --git a/src/metafiles.rs b/src/metafiles.rs new file mode 100644 index 00000000..013d31be --- /dev/null +++ b/src/metafiles.rs @@ -0,0 +1,173 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt, + path::{Path, PathBuf}, + str::FromStr, +}; + +use miette::Diagnostic; +use semver::Version; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use tokio::fs; +use url::Url; + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct CratesTomlRaw { + #[serde(default)] + pub v1: BTreeMap>, +} + +#[derive(Clone, Debug, Default)] +pub struct CratesToml(BTreeMap>); + +impl CratesToml { + pub fn default_path() -> Result { + Ok(home::cargo_home()?.join(".crates.toml")) + } + + pub async fn load() -> Result { + Self::load_from_path(Self::default_path()?).await + } + + pub async fn load_from_path(path: impl AsRef) -> Result { + let file = fs::read_to_string(path).await?; + Self::from_str(&file) + } + + pub fn insert(&mut self, cvs: CrateVersionSource, bins: impl Iterator) { + self.0.insert(cvs, bins.collect()); + } + + pub async fn write(&self) -> Result<(), CratesTomlParseError> { + self.write_to_path(Self::default_path()?).await + } + + pub async fn write_to_path(&self, path: impl AsRef) -> Result<(), CratesTomlParseError> { + let raw = CratesTomlRaw { + v1: self + .0 + .iter() + .map(|(cvs, bins)| (cvs.to_string(), bins.clone())) + .collect(), + }; + + fs::write(path, &toml::to_vec(&raw)?).await?; + Ok(()) + } +} + +impl FromStr for CratesToml { + type Err = CratesTomlParseError; + fn from_str(s: &str) -> Result { + let raw: CratesTomlRaw = toml::from_str(s).unwrap(); + + Ok(Self( + raw.v1 + .into_iter() + .map(|(name, bins)| CrateVersionSource::from_str(&name).map(|cvs| (cvs, bins))) + .collect::>()?, + )) + } +} + +#[derive(Debug, Diagnostic, Error)] +pub enum CratesTomlParseError { + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error(transparent)] + TomlParse(#[from] toml::de::Error), + + #[error(transparent)] + TomlWrite(#[from] toml::ser::Error), + + #[error(transparent)] + CvsParse(#[from] CvsParseError), +} + +#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] +pub struct CrateVersionSource { + pub name: String, + pub version: Version, + pub source: Source, +} + +#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] +pub enum Source { + Git(Url), + Path(Url), + Registry(Url), +} + +impl FromStr for CrateVersionSource { + type Err = CvsParseError; + fn from_str(s: &str) -> Result { + match s.splitn(3, ' ').collect::>()[..] { + [name, version, source] => { + let version = version.parse()?; + let source = match source + .trim_matches(&['(', ')'][..]) + .splitn(2, '+') + .collect::>()[..] + { + ["git", url] => Source::Git(Url::parse(url)?), + ["path", url] => Source::Path(Url::parse(url)?), + ["registry", url] => Source::Registry(Url::parse(url)?), + [kind, arg] => { + return Err(CvsParseError::UnknownSourceType { + kind: kind.to_string(), + arg: arg.to_string(), + }) + } + _ => return Err(CvsParseError::BadSource), + }; + Ok(Self { + name: name.to_string(), + version, + source, + }) + } + _ => Err(CvsParseError::BadFormat), + } + } +} + +#[derive(Debug, Diagnostic, Error)] +pub enum CvsParseError { + #[error(transparent)] + UrlParse(#[from] url::ParseError), + + #[error(transparent)] + VersionParse(#[from] semver::Error), + + #[error("unknown source type {kind}+{arg}")] + UnknownSourceType { kind: String, arg: String }, + + #[error("bad source format")] + BadSource, + + #[error("bad CVS format")] + BadFormat, +} + +impl fmt::Display for CrateVersionSource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + name, + version, + source, + } = &self; + write!(f, "{name} {version} ({source})") + } +} + +impl fmt::Display for Source { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Source::Git(url) => write!(f, "git+{url}"), + Source::Path(url) => write!(f, "path+{url}"), + Source::Registry(url) => write!(f, "registry+{url}"), + } + } +}