Refactor: Extract cargo_toml_workspace as a new crate (#1287)

To reduce codegen time of `binstalk` and also enable others to reuse
this crate.

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2023-08-12 22:05:10 +10:00 committed by GitHub
parent c57356e870
commit 8ff13c1b36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 290 additions and 47 deletions

View file

@ -14,12 +14,11 @@ async-trait = "0.1.68"
base16 = "0.2.1"
binstalk-downloader = { version = "0.7.0", path = "../binstalk-downloader", default-features = false, features = ["gh-api-client"] }
binstalk-types = { version = "0.5.0", path = "../binstalk-types" }
cargo_toml = "0.15.3"
cargo-toml-workspace = { version = "0.0.0", path = "../cargo-toml-workspace" }
command-group = { version = "2.1.0", features = ["with-tokio"] }
compact_str = { version = "0.7.0", features = ["serde"] }
detect-targets = { version = "0.1.10", path = "../detect-targets" }
either = "1.8.1"
glob = "0.3.1"
home = "0.5.5"
itertools = "0.11.0"
jobslot = { version = "0.2.11", features = ["tokio"] }

View file

@ -1,7 +1,6 @@
use std::{str::FromStr, sync::Arc};
use base16::DecodeError as Base16DecodeError;
use cargo_toml::Manifest;
use compact_str::CompactString;
use leon::{ParseError, RenderError};
use miette::Diagnostic;
@ -11,7 +10,10 @@ use thiserror::Error as ThisError;
use crate::{
errors::BinstallError,
helpers::remote::{Client, Error as RemoteError, Url, UrlParseError},
helpers::{
cargo_toml::Manifest,
remote::{Client, Error as RemoteError, Url, UrlParseError},
},
manifests::cargo_toml_binstall::Meta,
};

View file

@ -1,7 +1,6 @@
use std::borrow::Cow;
use base16::{decode as decode_base16, encode_lower as encode_base16};
use cargo_toml::Manifest;
use compact_str::{format_compact, CompactString, ToCompactString};
use leon::{Template, Values};
use semver::{Version, VersionReq};
@ -15,6 +14,7 @@ use crate::{
errors::BinstallError,
helpers::{
bytes::Bytes,
cargo_toml::Manifest,
download::{DataVerifier, Download},
remote::{Client, Url},
},

View file

@ -1,5 +1,4 @@
use binstalk_downloader::remote::Error as RemoteError;
use cargo_toml::Manifest;
use compact_str::{CompactString, ToCompactString};
use semver::{Comparator, Op as ComparatorOp, Version as SemVersion, VersionReq};
use serde::Deserialize;
@ -12,7 +11,10 @@ use tracing::debug;
use crate::{
drivers::registry::{parse_manifest, MatchedVersion, RegistryError},
errors::BinstallError,
helpers::remote::{Client, Url},
helpers::{
cargo_toml::Manifest,
remote::{Client, Url},
},
manifests::cargo_toml_binstall::Meta,
};

View file

@ -1,6 +1,5 @@
use std::{io, path::PathBuf, sync::Arc};
use cargo_toml::Manifest;
use compact_str::{CompactString, ToCompactString};
use once_cell::sync::OnceCell;
use semver::VersionReq;
@ -16,6 +15,7 @@ use crate::{
},
errors::BinstallError,
helpers::{
cargo_toml::Manifest,
git::{GitUrl, Repository},
remote::Client,
},

View file

@ -1,4 +1,3 @@
use cargo_toml::Manifest;
use compact_str::CompactString;
use semver::VersionReq;
use serde_json::Deserializer as JsonDeserializer;
@ -11,7 +10,10 @@ use crate::{
RegistryConfig, RegistryError,
},
errors::BinstallError,
helpers::remote::{Client, Error as RemoteError},
helpers::{
cargo_toml::Manifest,
remote::{Client, Error as RemoteError},
},
manifests::cargo_toml_binstall::Meta,
};

View file

@ -4,7 +4,7 @@ use std::{
path::Path,
};
use cargo_toml::AbstractFilesystem;
use crate::helpers::cargo_toml::AbstractFilesystem;
use normalize_path::NormalizePath;
/// This type stores the filesystem structure for the crate tarball

View file

@ -1,6 +1,5 @@
use std::path::{Path, PathBuf};
use cargo_toml::{Manifest, Value};
use normalize_path::NormalizePath;
use tokio::io::AsyncReadExt;
use tracing::debug;
@ -8,7 +7,10 @@ use tracing::debug;
use super::vfs::Vfs;
use crate::{
errors::BinstallError,
helpers::download::{DownloadError, TarEntriesVisitor, TarEntry},
helpers::{
cargo_toml::{Manifest, Value},
download::{DownloadError, TarEntriesVisitor, TarEntry},
},
manifests::cargo_toml_binstall::Meta,
};

View file

@ -7,7 +7,6 @@ use std::{
use binstalk_downloader::{
download::DownloadError, gh_api_client::GhApiError, remote::Error as RemoteError,
};
use cargo_toml::Error as CargoTomlError;
use compact_str::CompactString;
use miette::{Diagnostic, Report};
use target_lexicon::ParseError as TargetTripleParseError;
@ -17,7 +16,9 @@ use tracing::{error, warn};
use crate::{
drivers::{InvalidRegistryError, RegistryError},
helpers::cargo_toml_workspace::LoadManifestFromWSError,
helpers::{
cargo_toml::Error as CargoTomlError, cargo_toml_workspace::Error as LoadManifestFromWSError,
},
};
#[derive(Debug, Error)]
@ -500,3 +501,9 @@ impl From<InvalidRegistryError> for BinstallError {
BinstallError::RegistryParseError(Box::new(e))
}
}
impl From<LoadManifestFromWSError> for BinstallError {
fn from(e: LoadManifestFromWSError) -> Self {
BinstallError::LoadManifestFromWSError(Box::new(e))
}
}

View file

@ -1,4 +1,3 @@
pub(crate) mod cargo_toml_workspace;
pub(crate) mod futures_resolver;
pub mod jobserver_client;
pub mod remote;
@ -11,6 +10,7 @@ pub(crate) use binstalk_downloader::{bytes, download};
#[cfg(feature = "git")]
pub(crate) use binstalk_downloader::git;
pub(crate) use cargo_toml_workspace::{self, cargo_toml};
pub(crate) fn is_universal_macos(target: &str) -> bool {
["universal-apple-darwin", "universal2-apple-darwin"].contains(&target)

View file

@ -1,271 +0,0 @@
use std::{
io, mem,
path::{Path, PathBuf},
};
use cargo_toml::{Error as CargoTomlError, Manifest};
use compact_str::CompactString;
use glob::PatternError;
use normalize_path::NormalizePath;
use thiserror::Error as ThisError;
use tracing::{debug, instrument, warn};
use crate::{errors::BinstallError, manifests::cargo_toml_binstall::Meta};
/// Load binstall metadata `Cargo.toml` from workspace at the provided path
///
/// WARNING: This is a blocking operation.
///
/// * `workspace_path` - can be a directory (path to workspace) or
/// a file (path to `Cargo.toml`).
pub fn load_manifest_from_workspace(
workspace_path: impl AsRef<Path>,
crate_name: impl AsRef<str>,
) -> Result<Manifest<Meta>, BinstallError> {
fn inner(workspace_path: &Path, crate_name: &str) -> Result<Manifest<Meta>, BinstallError> {
load_manifest_from_workspace_inner(workspace_path, crate_name).map_err(|inner| {
Box::new(LoadManifestFromWSError {
workspace_path: workspace_path.into(),
crate_name: crate_name.into(),
inner,
})
.into()
})
}
inner(workspace_path.as_ref(), crate_name.as_ref())
}
#[derive(Debug, ThisError)]
#[error("Failed to load {crate_name} from {}: {inner}", workspace_path.display())]
pub struct LoadManifestFromWSError {
workspace_path: Box<Path>,
crate_name: CompactString,
#[source]
inner: LoadManifestFromWSErrorInner,
}
#[derive(Debug, ThisError)]
enum LoadManifestFromWSErrorInner {
#[error("Invalid pattern in workspace.members or workspace.exclude: {0}")]
PatternError(#[from] PatternError),
#[error("Invalid pattern `{0}`: It must be relative and point within current dir")]
InvalidPatternError(CompactString),
#[error("Failed to parse cargo manifest: {0}")]
CargoManifest(#[from] CargoTomlError),
#[error("I/O error: {0}")]
Io(#[from] io::Error),
#[error("Not found")]
NotFound,
}
#[instrument]
fn load_manifest_from_workspace_inner(
workspace_path: &Path,
crate_name: &str,
) -> Result<Manifest<Meta>, LoadManifestFromWSErrorInner> {
debug!(
"Loading manifest of crate {crate_name} from workspace: {}",
workspace_path.display()
);
let manifest_path = if workspace_path.is_file() {
workspace_path.to_owned()
} else {
workspace_path.join("Cargo.toml")
};
let mut manifest_paths = vec![manifest_path];
while let Some(manifest_path) = manifest_paths.pop() {
let manifest = Manifest::<Meta>::from_path_with_metadata(&manifest_path)?;
let name = manifest.package.as_ref().map(|p| &*p.name);
debug!(
"Loading from {}, manifest.package.name = {:#?}",
manifest_path.display(),
name
);
if name == Some(crate_name) {
return Ok(manifest);
}
if let Some(ws) = manifest.workspace {
let excludes = ws.exclude;
let members = ws.members;
if members.is_empty() {
continue;
}
let exclude_patterns = excludes
.into_iter()
.map(|pat| Pattern::new(&pat))
.collect::<Result<Vec<_>, _>>()?;
let workspace_path = manifest_path.parent().unwrap();
for member in members {
for path in Pattern::new(&member)?.glob_dirs(workspace_path)? {
if !exclude_patterns
.iter()
.any(|exclude| exclude.matches_with_trailing(&path))
{
manifest_paths.push(workspace_path.join(path).join("Cargo.toml"));
}
}
}
}
}
Err(LoadManifestFromWSErrorInner::NotFound)
}
struct Pattern(Vec<glob::Pattern>);
impl Pattern {
fn new(pat: &str) -> Result<Self, LoadManifestFromWSErrorInner> {
Path::new(pat)
.try_normalize()
.ok_or_else(|| LoadManifestFromWSErrorInner::InvalidPatternError(pat.into()))?
.iter()
.map(|c| glob::Pattern::new(c.to_str().unwrap()))
.collect::<Result<Vec<_>, _>>()
.map_err(Into::into)
.map(Self)
}
/// * `glob_path` - path to dir to glob for
/// return paths relative to `glob_path`.
fn glob_dirs(&self, glob_path: &Path) -> Result<Vec<PathBuf>, LoadManifestFromWSErrorInner> {
let mut paths = vec![PathBuf::new()];
for pattern in &self.0 {
if paths.is_empty() {
break;
}
for path in mem::take(&mut paths) {
let p = glob_path.join(&path);
let res = p.read_dir();
if res.is_err() && !p.is_dir() {
continue;
}
drop(p);
for res in res? {
let entry = res?;
let is_dir = entry
.file_type()
.map(|file_type| file_type.is_dir() || file_type.is_symlink())
.unwrap_or(false);
if !is_dir {
continue;
}
let filename = entry.file_name();
if filename != "." // Ignore current dir
&& filename != ".." // Ignore parent dir
&& pattern.matches(&filename.to_string_lossy())
{
paths.push(path.join(filename));
}
}
}
}
Ok(paths)
}
/// Return `true` if `path` matches the pattern.
/// It will still return `true` even if there are some trailing components.
fn matches_with_trailing(&self, path: &Path) -> bool {
let mut iter = path.iter().map(|os_str| os_str.to_string_lossy());
for pattern in &self.0 {
match iter.next() {
Some(s) if pattern.matches(&s) => (),
_ => return false,
}
}
true
}
}
#[cfg(test)]
mod test {
use std::fs::create_dir_all as mkdir;
use tempfile::TempDir;
use super::*;
#[test]
fn test_glob_dirs() {
let pattern = Pattern::new("*/*/q/*").unwrap();
let tempdir = TempDir::new().unwrap();
mkdir(tempdir.as_ref().join("a/b/c/efe")).unwrap();
mkdir(tempdir.as_ref().join("a/b/q/ww")).unwrap();
mkdir(tempdir.as_ref().join("d/233/q/d")).unwrap();
let mut paths = pattern.glob_dirs(tempdir.as_ref()).unwrap();
paths.sort_unstable();
assert_eq!(
paths,
vec![PathBuf::from("a/b/q/ww"), PathBuf::from("d/233/q/d")]
);
}
#[test]
fn test_matches_with_trailing() {
let pattern = Pattern::new("*/*/q/*").unwrap();
assert!(pattern.matches_with_trailing(Path::new("a/b/q/d/")));
assert!(pattern.matches_with_trailing(Path::new("a/b/q/d")));
assert!(pattern.matches_with_trailing(Path::new("a/b/q/d/234")));
assert!(pattern.matches_with_trailing(Path::new("a/234/q/d/234")));
assert!(!pattern.matches_with_trailing(Path::new("")));
assert!(!pattern.matches_with_trailing(Path::new("a/")));
assert!(!pattern.matches_with_trailing(Path::new("a/234")));
assert!(!pattern.matches_with_trailing(Path::new("a/234/q")));
}
#[test]
fn test_load() {
let p = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.join("e2e-tests/manifests/workspace");
let manifest = load_manifest_from_workspace(&p, "cargo-binstall").unwrap();
let package = manifest.package.unwrap();
assert_eq!(package.name, "cargo-binstall");
assert_eq!(package.version.as_ref().unwrap(), "0.12.0");
assert_eq!(manifest.bin.len(), 1);
assert_eq!(manifest.bin[0].name.as_deref().unwrap(), "cargo-binstall");
assert_eq!(manifest.bin[0].path.as_deref().unwrap(), "src/main.rs");
let err = load_manifest_from_workspace_inner(&p, "cargo-binstall2").unwrap_err();
assert!(
matches!(err, LoadManifestFromWSErrorInner::NotFound),
"{:#?}",
err
);
let manifest = load_manifest_from_workspace(&p, "cargo-watch").unwrap();
let package = manifest.package.unwrap();
assert_eq!(package.name, "cargo-watch");
assert_eq!(package.version.as_ref().unwrap(), "8.4.0");
assert_eq!(manifest.bin.len(), 1);
assert_eq!(manifest.bin[0].name.as_deref().unwrap(), "cargo-watch");
}
}

View file

@ -7,7 +7,6 @@ use std::{
sync::Arc,
};
use cargo_toml::Manifest;
use compact_str::{CompactString, ToCompactString};
use itertools::Itertools;
use leon::Template;
@ -22,8 +21,8 @@ use crate::{
errors::{BinstallError, VersionParseError},
fetchers::{Data, Fetcher, TargetData},
helpers::{
self, cargo_toml_workspace::load_manifest_from_workspace, download::ExtractedFiles,
remote::Client, target_triple::TargetTriple,
self, cargo_toml::Manifest, cargo_toml_workspace::load_manifest_from_workspace,
download::ExtractedFiles, remote::Client, target_triple::TargetTriple,
},
manifests::cargo_toml_binstall::{Meta, PkgMeta, PkgOverride},
ops::{CargoTomlFetchOverride, Options},
@ -381,7 +380,7 @@ impl PackageInfo {
let dir = TempDir::new()?;
helpers::git::Repository::shallow_clone(git_url, dir.as_ref())?;
load_manifest_from_workspace(dir.as_ref(), &name)
load_manifest_from_workspace(dir.as_ref(), &name).map_err(BinstallError::from)
})
.await??
}

View file

@ -1,5 +1,5 @@
use binstalk::ops::resolve::load_manifest_path;
use cargo_toml::Product;
use cargo_toml_workspace::cargo_toml::{Edition, Product};
use std::path::PathBuf;
#[test]
@ -24,7 +24,7 @@ fn parse_meta() {
&[Product {
name: Some("cargo-binstall".to_string()),
path: Some("src/main.rs".to_string()),
edition: cargo_toml::Edition::E2021,
edition: Edition::E2021,
..Default::default()
},],
);