diff --git a/Cargo.lock b/Cargo.lock index 3a5a3bc7..9fad27a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,6 +151,7 @@ dependencies = [ "scopeguard", "semver", "serde", + "serde_json", "simplelog", "strum", "strum_macros", @@ -1204,9 +1205,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ "itoa", "ryu", diff --git a/Cargo.toml b/Cargo.toml index aa0ee739..453e0d2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ reqwest = { version = "0.11.11", features = ["rustls-tls", "stream"], default-fe scopeguard = "1.1.0" semver = "1.0.10" serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.82" simplelog = "0.12.0" strum = "0.24.1" strum_macros = "0.24.2" diff --git a/src/main.rs b/src/main.rs index fffdccd0..4b331aa4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use std::{ + collections::BTreeSet, ffi::OsString, path::PathBuf, process::{ExitCode, Termination}, @@ -454,20 +455,13 @@ 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?; - } + let cvs = metafiles::CrateVersionSource { + name: opts.name, + version: package.version.parse().into_diagnostic()?, + source: metafiles::Source::Registry( + url::Url::parse("https://github.com/rust-lang/crates.io-index").unwrap(), + ), + }; info!("Installing binaries..."); block_in_place(|| { @@ -490,6 +484,30 @@ async fn install_from_package( }); } + let bins: BTreeSet = bin_files.iter().map(|bin| bin.base_name.clone()).collect(); + + debug!("Writing .crates.toml"); + if let Ok(mut c1) = metafiles::v1::CratesToml::load() { + c1.insert(cvs.clone(), bins.clone()); + c1.write()?; + } + + debug!("Writing .crates2.json"); + if let Ok(mut c2) = metafiles::v2::Crates2Json::load() { + c2.insert( + cvs.clone(), + metafiles::v2::CrateInfo { + version_req: Some(opts.version), + bins, + profile: "release".into(), + target: fetcher.target().to_string(), + rustc: format!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")), + ..Default::default() + }, + ); + c2.write()?; + } + Ok(()) }) } diff --git a/src/metafiles.rs b/src/metafiles.rs index 013d31be..06323a52 100644 --- a/src/metafiles.rs +++ b/src/metafiles.rs @@ -1,173 +1,5 @@ -use std::{ - collections::{BTreeMap, BTreeSet}, - fmt, - path::{Path, PathBuf}, - str::FromStr, -}; +mod cvs; +pub use cvs::*; -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}"), - } - } -} +pub mod v1; +pub mod v2; diff --git a/src/metafiles/cvs.rs b/src/metafiles/cvs.rs new file mode 100644 index 00000000..a111d02f --- /dev/null +++ b/src/metafiles/cvs.rs @@ -0,0 +1,111 @@ +use std::{fmt, str::FromStr}; + +use miette::Diagnostic; +use semver::Version; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use thiserror::Error; +use url::Url; + +#[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}"), + } + } +} + +impl Serialize for CrateVersionSource { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for CrateVersionSource { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Self::from_str(&String::deserialize(deserializer)?).map_err(serde::de::Error::custom) + } +} diff --git a/src/metafiles/v1.rs b/src/metafiles/v1.rs new file mode 100644 index 00000000..1180814a --- /dev/null +++ b/src/metafiles/v1.rs @@ -0,0 +1,67 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + fs, + path::{Path, PathBuf}, + str::FromStr, +}; + +use miette::Diagnostic; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use super::CrateVersionSource; + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct CratesToml { + v1: BTreeMap>, +} + +impl CratesToml { + pub fn default_path() -> Result { + Ok(home::cargo_home()?.join(".crates.toml")) + } + + pub fn load() -> Result { + Self::load_from_path(Self::default_path()?) + } + + pub fn load_from_path(path: impl AsRef) -> Result { + let file = fs::read_to_string(path)?; + Self::from_str(&file) + } + + pub fn insert(&mut self, cvs: CrateVersionSource, bins: BTreeSet) { + self.v1.insert(cvs, bins); + } + + pub fn write(&self) -> Result<(), CratesTomlParseError> { + self.write_to_path(Self::default_path()?) + } + + pub fn write_to_path(&self, path: impl AsRef) -> Result<(), CratesTomlParseError> { + fs::write(path, &toml::to_vec(&self)?)?; + Ok(()) + } +} + +impl FromStr for CratesToml { + type Err = CratesTomlParseError; + fn from_str(s: &str) -> Result { + Ok(toml::from_str(s)?) + } +} + +#[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] super::CvsParseError), +} diff --git a/src/metafiles/v2.rs b/src/metafiles/v2.rs new file mode 100644 index 00000000..70c4e46e --- /dev/null +++ b/src/metafiles/v2.rs @@ -0,0 +1,78 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + fs, + path::{Path, PathBuf}, +}; + +use miette::Diagnostic; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use super::CrateVersionSource; + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct Crates2Json { + pub installs: BTreeMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct CrateInfo { + #[serde(default)] + pub version_req: Option, + + #[serde(default)] + pub bins: BTreeSet, + + #[serde(default)] + pub features: BTreeSet, + + #[serde(default)] + pub all_features: bool, + + #[serde(default)] + pub no_default_features: bool, + + pub profile: String, + pub target: String, + pub rustc: String, +} + +impl Crates2Json { + pub fn default_path() -> Result { + Ok(home::cargo_home()?.join(".crates2.json")) + } + + pub fn load() -> Result { + Self::load_from_path(Self::default_path()?) + } + + pub fn load_from_path(path: impl AsRef) -> Result { + let file = fs::read_to_string(path)?; + Ok(serde_json::from_str(&file)?) + } + + pub fn insert(&mut self, cvs: CrateVersionSource, info: CrateInfo) { + self.installs.insert(cvs, info); + } + + pub fn write(&self) -> Result<(), Crates2JsonParseError> { + self.write_to_path(Self::default_path()?) + } + + pub fn write_to_path(&self, path: impl AsRef) -> Result<(), Crates2JsonParseError> { + fs::write(path, &serde_json::to_vec(&self)?)?; + Ok(()) + } +} + +#[derive(Debug, Diagnostic, Error)] +pub enum Crates2JsonParseError { + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error(transparent)] + Json(#[from] serde_json::Error), + + #[error(transparent)] + CvsParse(#[from] super::CvsParseError), +}