Refactor: Extract new crate binstalk-manifests (#511)

* Refactor: Extract new crate binstalk-manifests
* Fix clippy warning in mod `binstall_crates_v1`
* Rm unused deps in binstalk
* Update release-pr

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2022-11-08 11:50:02 +11:00 committed by GitHub
parent 565be9ae4c
commit fc6d9ab956
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 96 additions and 47 deletions

View file

@ -0,0 +1,30 @@
[package]
name = "binstalk-manifests"
description = "The binstall toolkit for manipulating with manifest"
repository = "https://github.com/cargo-bins/cargo-binstall"
documentation = "https://docs.rs/binstalk-manifests"
version = "0.1.0"
rust-version = "1.61.0"
authors = ["ryan <ryan@kurte.nz>"]
edition = "2021"
license = "GPL-3.0"
[dependencies]
compact_str = { version = "0.6.0", features = ["serde"] }
fs-lock = { version = "0.1.0", path = "../fs-lock" }
home = "0.5.4"
miette = "5.4.1"
once_cell = "1.16.0"
semver = { version = "1.0.14", features = ["serde"] }
serde = { version = "1.0.147", features = ["derive"] }
serde-tuple-vec-map = "1.0.1"
serde_json = "1.0.87"
strum = "0.24.1"
strum_macros = "0.24.3"
thiserror = "1.0.37"
toml_edit = { version = "0.15.0", features = ["easy"] }
url = { version = "2.3.1", features = ["serde"] }
[dev-dependencies]
detect-targets = { version = "0.1.2", path = "../detect-targets" }
tempfile = "3.3.0"

View file

@ -0,0 +1,262 @@
//! Binstall's `crates-v1.json` manifest.
//!
//! This manifest is used by Binstall to record which crates were installed, and may be used by
//! other (third party) tooling to act upon these crates (e.g. upgrade them, list them, etc).
//!
//! The format is a series of JSON object concatenated together. It is _not_ NLJSON, though writing
//! NLJSON to the file will be understood fine.
use std::{
collections::{btree_set, BTreeSet},
fs,
io::{self, Seek, Write},
iter::{IntoIterator, Iterator},
path::{Path, PathBuf},
};
use fs_lock::FileLock;
use home::cargo_home;
use miette::Diagnostic;
use serde::Serialize;
use thiserror::Error;
use crate::helpers::create_if_not_exist;
use super::crate_info::CrateInfo;
#[derive(Debug, Diagnostic, Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
SerdeJsonParse(#[from] serde_json::Error),
}
pub fn append_to_path<Iter>(path: impl AsRef<Path>, iter: Iter) -> Result<(), Error>
where
Iter: IntoIterator<Item = CrateInfo>,
{
let mut file = FileLock::new_exclusive(create_if_not_exist(path.as_ref())?)?;
// Move the cursor to EOF
file.seek(io::SeekFrom::End(0))?;
write_to(&mut file, &mut iter.into_iter())
}
pub fn append<Iter>(iter: Iter) -> Result<(), Error>
where
Iter: IntoIterator<Item = CrateInfo>,
{
append_to_path(default_path()?, iter)
}
pub fn write_to(
file: &mut FileLock,
iter: &mut dyn Iterator<Item = CrateInfo>,
) -> Result<(), Error> {
let writer = io::BufWriter::with_capacity(512, file);
let mut ser = serde_json::Serializer::new(writer);
for item in iter {
item.serialize(&mut ser)?;
}
ser.into_inner().flush()?;
Ok(())
}
pub fn default_path() -> Result<PathBuf, Error> {
let dir = cargo_home()?.join("binstall");
fs::create_dir_all(&dir)?;
Ok(dir.join("crates-v1.json"))
}
#[derive(Debug)]
pub struct Records {
file: FileLock,
/// Use BTreeSet to dedup the metadata
data: BTreeSet<CrateInfo>,
}
impl Records {
fn load_impl(&mut self) -> Result<(), Error> {
let reader = io::BufReader::with_capacity(1024, &mut self.file);
let stream_deser = serde_json::Deserializer::from_reader(reader).into_iter();
for res in stream_deser {
let item = res?;
self.data.replace(item);
}
Ok(())
}
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
let mut this = Self {
file: FileLock::new_exclusive(create_if_not_exist(path.as_ref())?)?,
data: BTreeSet::default(),
};
this.load_impl()?;
Ok(this)
}
pub fn load() -> Result<Self, Error> {
Self::load_from_path(default_path()?)
}
/// **Warning: This will overwrite all existing records!**
pub fn overwrite(mut self) -> Result<(), Error> {
self.file.rewind()?;
write_to(&mut self.file, &mut self.data.into_iter())?;
let len = self.file.stream_position()?;
self.file.set_len(len)?;
Ok(())
}
pub fn get(&self, value: impl AsRef<str>) -> Option<&CrateInfo> {
self.data.get(value.as_ref())
}
pub fn contains(&self, value: impl AsRef<str>) -> bool {
self.data.contains(value.as_ref())
}
/// Adds a value to the set.
/// If the set did not have an equal element present, true is returned.
/// If the set did have an equal element present, false is returned,
/// and the entry is not updated.
pub fn insert(&mut self, value: CrateInfo) -> bool {
self.data.insert(value)
}
pub fn replace(&mut self, value: CrateInfo) -> Option<CrateInfo> {
self.data.replace(value)
}
pub fn remove(&mut self, value: impl AsRef<str>) -> bool {
self.data.remove(value.as_ref())
}
pub fn take(&mut self, value: impl AsRef<str>) -> Option<CrateInfo> {
self.data.take(value.as_ref())
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl<'a> IntoIterator for &'a Records {
type Item = &'a CrateInfo;
type IntoIter = btree_set::Iter<'a, CrateInfo>;
fn into_iter(self) -> Self::IntoIter {
self.data.iter()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::crate_info::CrateSource;
use compact_str::CompactString;
use detect_targets::TARGET;
use semver::Version;
use tempfile::NamedTempFile;
macro_rules! assert_records_eq {
($records:expr, $metadata_set:expr) => {
assert_eq!($records.len(), $metadata_set.len());
for (record, metadata) in $records.into_iter().zip($metadata_set.iter()) {
assert_eq!(record, metadata);
}
};
}
#[test]
fn rw_test() {
let target = CompactString::from(TARGET);
let named_tempfile = NamedTempFile::new().unwrap();
let path = named_tempfile.path();
let metadata_vec = [
CrateInfo {
name: "a".into(),
version_req: "*".into(),
current_version: Version::new(0, 1, 0),
source: CrateSource::cratesio_registry(),
target: target.clone(),
bins: vec!["1".into(), "2".into()],
other: Default::default(),
},
CrateInfo {
name: "b".into(),
version_req: "0.1.0".into(),
current_version: Version::new(0, 1, 0),
source: CrateSource::cratesio_registry(),
target: target.clone(),
bins: vec!["1".into(), "2".into()],
other: Default::default(),
},
CrateInfo {
name: "a".into(),
version_req: "*".into(),
current_version: Version::new(0, 2, 0),
source: CrateSource::cratesio_registry(),
target: target.clone(),
bins: vec!["1".into()],
other: Default::default(),
},
];
append_to_path(path, metadata_vec.clone()).unwrap();
let mut iter = metadata_vec.into_iter();
iter.next().unwrap();
let mut metadata_set: BTreeSet<_> = iter.collect();
let mut records = Records::load_from_path(path).unwrap();
assert_records_eq!(&records, &metadata_set);
records.remove("b");
assert_eq!(records.len(), metadata_set.len() - 1);
records.overwrite().unwrap();
metadata_set.remove("b");
let records = Records::load_from_path(path).unwrap();
assert_records_eq!(&records, &metadata_set);
// Drop the exclusive file lock
drop(records);
let new_metadata = CrateInfo {
name: "b".into(),
version_req: "0.1.0".into(),
current_version: Version::new(0, 1, 1),
source: CrateSource::cratesio_registry(),
target,
bins: vec!["1".into(), "2".into()],
other: Default::default(),
};
append_to_path(path, [new_metadata.clone()]).unwrap();
metadata_set.insert(new_metadata);
let records = Records::load_from_path(path).unwrap();
assert_records_eq!(&records, &metadata_set);
}
}

View file

@ -0,0 +1,159 @@
//! Cargo's `.crates.toml` manifest.
//!
//! This manifest is used by Cargo to record which crates were installed by `cargo-install` and by
//! other Cargo (first and third party) tooling to act upon these crates (e.g. upgrade them, list
//! them, etc).
//!
//! Binstall writes to this manifest when installing a crate, for interoperability with the Cargo
//! ecosystem.
use std::{
collections::BTreeMap,
fs::File,
io::{self, Seek},
iter::IntoIterator,
path::{Path, PathBuf},
};
use compact_str::CompactString;
use fs_lock::FileLock;
use home::cargo_home;
use miette::Diagnostic;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::helpers::create_if_not_exist;
use super::crate_info::CrateInfo;
mod crate_version_source;
use crate_version_source::*;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct CratesToml {
v1: BTreeMap<String, Vec<CompactString>>,
}
impl CratesToml {
pub fn default_path() -> Result<PathBuf, CratesTomlParseError> {
Ok(cargo_home()?.join(".crates.toml"))
}
pub fn load() -> Result<Self, CratesTomlParseError> {
Self::load_from_path(Self::default_path()?)
}
pub fn load_from_reader<R: io::Read>(mut reader: R) -> Result<Self, CratesTomlParseError> {
let mut vec = Vec::new();
reader.read_to_end(&mut vec)?;
Ok(toml_edit::easy::from_slice(&vec)?)
}
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, CratesTomlParseError> {
let file = File::open(path)?;
Self::load_from_reader(file)
}
pub fn insert(&mut self, cvs: &CrateVersionSource, bins: Vec<CompactString>) {
self.v1.insert(cvs.to_string(), bins);
}
pub fn write(&self) -> Result<(), CratesTomlParseError> {
self.write_to_path(Self::default_path()?)
}
pub fn write_to_writer<W: io::Write>(&self, mut writer: W) -> Result<(), CratesTomlParseError> {
let data = toml_edit::easy::to_vec(&self)?;
writer.write_all(&data)?;
Ok(())
}
pub fn write_to_file(&self, file: &mut File) -> Result<(), CratesTomlParseError> {
self.write_to_writer(&mut *file)?;
let pos = file.stream_position()?;
file.set_len(pos)?;
Ok(())
}
pub fn write_to_path(&self, path: impl AsRef<Path>) -> Result<(), CratesTomlParseError> {
let mut file = File::create(path)?;
self.write_to_file(&mut file)
}
pub fn append_to_path<'a, Iter>(
path: impl AsRef<Path>,
iter: Iter,
) -> Result<(), CratesTomlParseError>
where
Iter: IntoIterator<Item = &'a CrateInfo>,
{
let mut file = FileLock::new_exclusive(create_if_not_exist(path.as_ref())?)?;
let mut c1 = if file.metadata()?.len() != 0 {
Self::load_from_reader(&mut *file)?
} else {
Self::default()
};
for metadata in iter {
c1.insert(&CrateVersionSource::from(metadata), metadata.bins.clone());
}
file.rewind()?;
c1.write_to_file(&mut file)?;
Ok(())
}
pub fn append<'a, Iter>(iter: Iter) -> Result<(), CratesTomlParseError>
where
Iter: IntoIterator<Item = &'a CrateInfo>,
{
Self::append_to_path(Self::default_path()?, iter)
}
}
#[derive(Debug, Diagnostic, Error)]
pub enum CratesTomlParseError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
TomlParse(#[from] toml_edit::easy::de::Error),
#[error(transparent)]
TomlWrite(#[from] toml_edit::easy::ser::Error),
#[error(transparent)]
CvsParse(#[from] CvsParseError),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crate_info::CrateSource;
use detect_targets::TARGET;
use semver::Version;
use tempfile::TempDir;
#[test]
fn test_empty() {
let tempdir = TempDir::new().unwrap();
let path = tempdir.path().join("crates-v1.toml");
CratesToml::append_to_path(
&path,
&[CrateInfo {
name: "cargo-binstall".into(),
version_req: "*".into(),
current_version: Version::new(0, 11, 1),
source: CrateSource::cratesio_registry(),
target: TARGET.into(),
bins: vec!["cargo-binstall".into()],
other: Default::default(),
}],
)
.unwrap();
}
}

View file

@ -0,0 +1,148 @@
use std::{borrow::Cow, fmt, str::FromStr};
use compact_str::CompactString;
use miette::Diagnostic;
use semver::Version;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
use url::Url;
use crate::{
crate_info::{CrateInfo, CrateSource, SourceType},
helpers::cratesio_url,
};
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub struct CrateVersionSource {
pub name: CompactString,
pub version: Version,
pub source: Source,
}
impl From<&CrateInfo> for CrateVersionSource {
fn from(metadata: &CrateInfo) -> Self {
super::CrateVersionSource {
name: metadata.name.clone(),
version: metadata.current_version.clone(),
source: Source::from(&metadata.source),
}
}
}
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum Source {
Git(Url),
Path(Url),
Registry(Url),
}
impl Source {
pub fn cratesio_registry() -> Source {
Self::Registry(cratesio_url().clone())
}
}
impl From<&CrateSource> for Source {
fn from(source: &CrateSource) -> Self {
use SourceType::*;
let url = source.url.clone();
match source.source_type {
Git => Self::Git(url),
Path => Self::Path(url),
Registry => Self::Registry(url),
}
}
}
impl FromStr for CrateVersionSource {
type Err = CvsParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.splitn(3, ' ').collect::<Vec<_>>()[..] {
[name, version, source] => {
let version = version.parse()?;
let source = match source
.trim_matches(&['(', ')'][..])
.splitn(2, '+')
.collect::<Vec<_>>()[..]
{
["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.into(),
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for CrateVersionSource {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = Cow::<'_, str>::deserialize(deserializer)?;
Self::from_str(&s).map_err(serde::de::Error::custom)
}
}

View file

@ -0,0 +1,113 @@
//! The format of the `[package.metadata.binstall]` manifest.
//!
//! This manifest defines how a particular binary crate may be installed by Binstall.
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[doc(inline)]
pub use package_formats::*;
mod package_formats;
/// `binstall` metadata container
///
/// Required to nest metadata under `package.metadata.binstall`
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Meta {
pub binstall: Option<PkgMeta>,
}
/// Metadata for binary installation use.
///
/// Exposed via `[package.metadata]` in `Cargo.toml`
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default)]
pub struct PkgMeta {
/// URL template for package downloads
pub pkg_url: Option<String>,
/// Format for package downloads
pub pkg_fmt: Option<PkgFmt>,
/// Path template for binary files in packages
pub bin_dir: Option<String>,
/// Public key for package verification (base64 encoded)
pub pub_key: Option<String>,
/// Target specific overrides
pub overrides: BTreeMap<String, PkgOverride>,
}
impl PkgMeta {
/// Merge configuration overrides into object
pub fn merge(&mut self, pkg_override: &PkgOverride) {
if let Some(o) = &pkg_override.pkg_url {
self.pkg_url = Some(o.clone());
}
if let Some(o) = &pkg_override.pkg_fmt {
self.pkg_fmt = Some(*o);
}
if let Some(o) = &pkg_override.bin_dir {
self.bin_dir = Some(o.clone());
}
}
/// Merge configuration overrides into object
///
/// * `pkg_overrides` - ordered in preference
pub fn merge_overrides<'a, It>(&self, pkg_overrides: It) -> Self
where
It: IntoIterator<Item = &'a PkgOverride> + Clone,
{
Self {
pkg_url: pkg_overrides
.clone()
.into_iter()
.find_map(|pkg_override| pkg_override.pkg_url.clone())
.or_else(|| self.pkg_url.clone()),
pkg_fmt: pkg_overrides
.clone()
.into_iter()
.find_map(|pkg_override| pkg_override.pkg_fmt)
.or(self.pkg_fmt),
bin_dir: pkg_overrides
.into_iter()
.find_map(|pkg_override| pkg_override.bin_dir.clone())
.or_else(|| self.bin_dir.clone()),
pub_key: self.pub_key.clone(),
overrides: Default::default(),
}
}
}
/// Target specific overrides for binary installation
///
/// Exposed via `[package.metadata.TARGET]` in `Cargo.toml`
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default)]
pub struct PkgOverride {
/// URL template override for package downloads
pub pkg_url: Option<String>,
/// Format override for package downloads
pub pkg_fmt: Option<PkgFmt>,
/// Path template override for binary files in packages
pub bin_dir: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct BinMeta {
/// Binary name
pub name: String,
/// Binary template path (within package)
pub path: String,
}

View file

@ -0,0 +1,93 @@
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumIter, EnumString};
/// Binary format enumeration
#[derive(
Debug, Display, Copy, Clone, Eq, PartialEq, Serialize, Deserialize, EnumString, EnumIter,
)]
#[serde(rename_all = "snake_case")]
#[strum(ascii_case_insensitive)]
pub enum PkgFmt {
/// Download format is TAR (uncompressed)
Tar,
/// Download format is TAR + Bzip2
Tbz2,
/// Download format is TGZ (TAR + GZip)
Tgz,
/// Download format is TAR + XZ
Txz,
/// Download format is TAR + Zstd
Tzstd,
/// Download format is Zip
Zip,
/// Download format is raw / binary
Bin,
}
impl Default for PkgFmt {
fn default() -> Self {
Self::Tgz
}
}
impl PkgFmt {
/// If self is one of the tar based formats, return Some.
pub fn decompose(self) -> PkgFmtDecomposed {
match self {
PkgFmt::Tar => PkgFmtDecomposed::Tar(TarBasedFmt::Tar),
PkgFmt::Tbz2 => PkgFmtDecomposed::Tar(TarBasedFmt::Tbz2),
PkgFmt::Tgz => PkgFmtDecomposed::Tar(TarBasedFmt::Tgz),
PkgFmt::Txz => PkgFmtDecomposed::Tar(TarBasedFmt::Txz),
PkgFmt::Tzstd => PkgFmtDecomposed::Tar(TarBasedFmt::Tzstd),
PkgFmt::Bin => PkgFmtDecomposed::Bin,
PkgFmt::Zip => PkgFmtDecomposed::Zip,
}
}
/// List of possible file extensions for the format
/// (with prefix `.`).
pub fn extensions(self) -> &'static [&'static str] {
match self {
PkgFmt::Tar => &[".tar"],
PkgFmt::Tbz2 => &[".tbz2", ".tar.bz2"],
PkgFmt::Tgz => &[".tgz", ".tar.gz"],
PkgFmt::Txz => &[".txz", ".tar.xz"],
PkgFmt::Tzstd => &[".tzstd", ".tzst", ".tar.zst"],
PkgFmt::Bin => &[".bin", ".exe", ""],
PkgFmt::Zip => &[".zip"],
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum PkgFmtDecomposed {
Tar(TarBasedFmt),
Bin,
Zip,
}
#[derive(Debug, Display, Copy, Clone, Eq, PartialEq)]
pub enum TarBasedFmt {
/// Download format is TAR (uncompressed)
Tar,
/// Download format is TAR + Bzip2
Tbz2,
/// Download format is TGZ (TAR + GZip)
Tgz,
/// Download format is TAR + XZ
Txz,
/// Download format is TAR + Zstd
Tzstd,
}
impl From<TarBasedFmt> for PkgFmt {
fn from(fmt: TarBasedFmt) -> Self {
match fmt {
TarBasedFmt::Tar => PkgFmt::Tar,
TarBasedFmt::Tbz2 => PkgFmt::Tbz2,
TarBasedFmt::Tgz => PkgFmt::Tgz,
TarBasedFmt::Txz => PkgFmt::Txz,
TarBasedFmt::Tzstd => PkgFmt::Tzstd,
}
}
}

View file

@ -0,0 +1,83 @@
//! Common structure for crate information for post-install manifests.
use std::{borrow, cmp, hash};
use compact_str::CompactString;
use semver::Version;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::helpers::cratesio_url;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CrateInfo {
pub name: CompactString,
pub version_req: CompactString,
pub current_version: Version,
pub source: CrateSource,
pub target: CompactString,
pub bins: Vec<CompactString>,
/// Forwards compatibility. Unknown keys from future versions
/// will be stored here and retained when the file is saved.
///
/// We use an `Vec` here since it is never accessed in Rust.
#[serde(flatten, with = "tuple_vec_map")]
pub other: Vec<(CompactString, serde_json::Value)>,
}
impl borrow::Borrow<str> for CrateInfo {
fn borrow(&self) -> &str {
&self.name
}
}
impl PartialEq for CrateInfo {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for CrateInfo {}
impl PartialOrd for CrateInfo {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
self.name.partial_cmp(&other.name)
}
}
impl Ord for CrateInfo {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.name.cmp(&other.name)
}
}
impl hash::Hash for CrateInfo {
fn hash<H>(&self, state: &mut H)
where
H: hash::Hasher,
{
self.name.hash(state)
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub enum SourceType {
Git,
Path,
Registry,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CrateSource {
pub source_type: SourceType,
pub url: Url,
}
impl CrateSource {
pub fn cratesio_registry() -> CrateSource {
Self {
source_type: SourceType::Registry,
url: cratesio_url().clone(),
}
}
}

View file

@ -0,0 +1,25 @@
use std::{fs, io, path::Path};
use once_cell::sync::Lazy;
use url::Url;
/// Returned file is readable and writable.
pub(crate) fn create_if_not_exist(path: impl AsRef<Path>) -> io::Result<fs::File> {
let path = path.as_ref();
let mut options = fs::File::options();
options.read(true).write(true);
options
.clone()
.create_new(true)
.open(path)
.or_else(|_| options.open(path))
}
pub(crate) fn cratesio_url() -> &'static Url {
static CRATESIO: Lazy<Url, fn() -> Url> =
Lazy::new(|| Url::parse("https://github.com/rust-lang/crates.io-index").unwrap());
&CRATESIO
}

View file

@ -0,0 +1,16 @@
//! Manifest formats and utilities.
//!
//! There are three types of manifests Binstall may deal with:
//! - manifests that define how to fetch and install a package
//! ([Cargo.toml's `[metadata.binstall]`][cargo_toml_binstall]);
//! - manifests that record which packages _are_ installed
//! ([Cargo's `.crates.toml`][cargo_crates_v1] and
//! [Binstall's `.crates-v1.json`][binstall_crates_v1]);
//! - manifests that specify which packages _to_ install (currently none).
mod helpers;
pub mod binstall_crates_v1;
pub mod cargo_crates_v1;
pub mod cargo_toml_binstall;
pub mod crate_info;