mirror of
https://github.com/pesde-pkg/pesde.git
synced 2025-04-05 11:20:55 +01:00
feat: ✨ add dependency overrides
This commit is contained in:
parent
3a061a9fbe
commit
875379ecbe
5 changed files with 140 additions and 50 deletions
|
@ -417,6 +417,7 @@ pub fn root_command(cmd: Command) -> anyhow::Result<()> {
|
|||
)]),
|
||||
#[cfg(feature = "wally")]
|
||||
sourcemap_generator: None,
|
||||
overrides: Default::default(),
|
||||
|
||||
dependencies: Default::default(),
|
||||
peer_dependencies: Default::default(),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashSet, VecDeque},
|
||||
collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
|
||||
fmt::Display,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ use crate::{
|
|||
DependencySpecifier, PackageRef,
|
||||
},
|
||||
index::{Index, IndexFileEntry, IndexPackageError},
|
||||
manifest::{DependencyType, Manifest, Realm},
|
||||
manifest::{DependencyType, Manifest, OverrideKey, Realm},
|
||||
package_name::PackageName,
|
||||
project::{get_index, get_index_by_url, Project, ReadLockfileError},
|
||||
DEV_PACKAGES_FOLDER, INDEX_FOLDER, PACKAGES_FOLDER, SERVER_PACKAGES_FOLDER,
|
||||
|
@ -29,6 +29,10 @@ pub type PackageMap<T> = BTreeMap<PackageName, BTreeMap<Version, T>>;
|
|||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RootLockfileNode {
|
||||
/// Dependency overrides
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub overrides: BTreeMap<OverrideKey, DependencySpecifier>,
|
||||
|
||||
/// The specifiers of the root packages
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub specifiers: PackageMap<DependencySpecifier>,
|
||||
|
@ -203,17 +207,19 @@ pub enum ResolveError {
|
|||
}
|
||||
|
||||
impl Manifest {
|
||||
/// Resolves the dependency graph for the project
|
||||
pub fn dependency_graph(
|
||||
fn missing_dependencies(
|
||||
&self,
|
||||
project: &mut Project,
|
||||
root: &mut RootLockfileNode,
|
||||
locked: bool,
|
||||
) -> Result<RootLockfileNode, ResolveError> {
|
||||
debug!("resolving dependency graph for project {}", self.name);
|
||||
// try to reuse versions (according to semver specifiers) to decrease the amount of downloads and storage
|
||||
let mut root = RootLockfileNode::default();
|
||||
project: &Project,
|
||||
) -> Result<Vec<(DependencySpecifier, DependencyType)>, ResolveError> {
|
||||
Ok(if let Some(old_root) = project.lockfile()? {
|
||||
if self.overrides != old_root.overrides {
|
||||
// TODO: resolve only the changed dependencies (will this be worth it?)
|
||||
debug!("overrides have changed, resolving all dependencies");
|
||||
return Ok(self.dependencies());
|
||||
}
|
||||
|
||||
let graph = if let Some(old_root) = project.lockfile()? {
|
||||
debug!("lockfile found, resolving dependencies from it");
|
||||
let mut missing = Vec::new();
|
||||
|
||||
|
@ -237,10 +243,13 @@ impl Manifest {
|
|||
.or_default()
|
||||
.insert(version.clone(), specifier.unwrap().clone());
|
||||
|
||||
let mut queue = VecDeque::from([resolved_package]);
|
||||
let mut queue = VecDeque::from([(resolved_package, 0usize)]);
|
||||
|
||||
while let Some(resolved_package) = queue.pop_front() {
|
||||
debug!("resolved {resolved_package} from lockfile");
|
||||
while let Some((resolved_package, depth)) = queue.pop_front() {
|
||||
debug!(
|
||||
"{}resolved {resolved_package} from lockfile",
|
||||
"\t".repeat(depth)
|
||||
);
|
||||
|
||||
root.children
|
||||
.entry(name.clone())
|
||||
|
@ -260,7 +269,7 @@ impl Manifest {
|
|||
.and_then(|v| v.get(dep_version));
|
||||
|
||||
match dep {
|
||||
Some(dep) => queue.push_back(dep),
|
||||
Some(dep) => queue.push_back((dep, depth + 1)),
|
||||
// the lockfile is out of date
|
||||
None => return Err(ResolveError::OutOfDateLockfile),
|
||||
}
|
||||
|
@ -299,21 +308,45 @@ impl Manifest {
|
|||
} else {
|
||||
debug!("no lockfile found, resolving all dependencies");
|
||||
self.dependencies()
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves the dependency graph for the project
|
||||
pub fn dependency_graph(
|
||||
&self,
|
||||
project: &mut Project,
|
||||
locked: bool,
|
||||
) -> Result<RootLockfileNode, ResolveError> {
|
||||
debug!("resolving dependency graph for project {}", self.name);
|
||||
// try to reuse versions (according to semver specifiers) to decrease the amount of downloads and storage
|
||||
let mut root = RootLockfileNode {
|
||||
overrides: self.overrides.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if graph.is_empty() {
|
||||
let missing_dependencies = self.missing_dependencies(&mut root, locked, project)?;
|
||||
|
||||
if missing_dependencies.is_empty() {
|
||||
debug!("no dependencies left to resolve, finishing...");
|
||||
return Ok(root);
|
||||
}
|
||||
|
||||
debug!("resolving {} dependencies from index", graph.len());
|
||||
let overrides = self
|
||||
.overrides
|
||||
.iter()
|
||||
.flat_map(|(k, spec)| k.0.iter().map(|path| (path, spec.clone())))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut queue = graph
|
||||
debug!("resolving {} dependencies", missing_dependencies.len());
|
||||
|
||||
let mut queue = missing_dependencies
|
||||
.into_iter()
|
||||
.map(|(specifier, dep_type)| (specifier, dep_type, None))
|
||||
.map(|(specifier, dep_type)| (specifier, dep_type, None, vec![]))
|
||||
.collect::<VecDeque<_>>();
|
||||
|
||||
while let Some((specifier, dep_type, dependant)) = queue.pop_front() {
|
||||
while let Some((specifier, dep_type, dependant, mut path)) = queue.pop_front() {
|
||||
let depth = path.len();
|
||||
|
||||
let (pkg_ref, default_realm, dependencies) = match &specifier {
|
||||
DependencySpecifier::Registry(registry_dependency) => {
|
||||
let index = if dependant.is_none() {
|
||||
|
@ -331,8 +364,10 @@ impl Manifest {
|
|||
)?;
|
||||
|
||||
debug!(
|
||||
"resolved registry dependency {} to {}",
|
||||
registry_dependency.name, entry.version
|
||||
"{}resolved registry dependency {} to {}",
|
||||
"\t".repeat(depth),
|
||||
registry_dependency.name,
|
||||
entry.version
|
||||
);
|
||||
|
||||
(
|
||||
|
@ -350,7 +385,8 @@ impl Manifest {
|
|||
git_dependency.resolve(project.cache_dir(), project.indices())?;
|
||||
|
||||
debug!(
|
||||
"resolved git dependency {} to {url}#{rev}",
|
||||
"{}resolved git dependency {} to {url}#{rev}",
|
||||
"\t".repeat(depth),
|
||||
git_dependency.repo
|
||||
);
|
||||
|
||||
|
@ -383,8 +419,10 @@ impl Manifest {
|
|||
)?;
|
||||
|
||||
debug!(
|
||||
"resolved wally dependency {} to {}",
|
||||
wally_dependency.name, entry.version
|
||||
"{}resolved wally dependency {} to {}",
|
||||
"\t".repeat(depth),
|
||||
wally_dependency.name,
|
||||
entry.version
|
||||
);
|
||||
|
||||
(
|
||||
|
@ -473,11 +511,20 @@ impl Manifest {
|
|||
},
|
||||
);
|
||||
|
||||
path.push(pkg_ref.name().to_string());
|
||||
|
||||
for (specifier, ty) in dependencies {
|
||||
let overridden = overrides.iter().find_map(|(k_path, spec)| {
|
||||
(&path == &k_path[..k_path.len() - 1]
|
||||
&& k_path.get(k_path.len() - 1) == Some(&specifier.name()))
|
||||
.then_some(spec)
|
||||
});
|
||||
|
||||
queue.push_back((
|
||||
specifier,
|
||||
overridden.cloned().unwrap_or(specifier),
|
||||
ty,
|
||||
Some((pkg_ref.name(), pkg_ref.version().clone())),
|
||||
path.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use cfg_if::cfg_if;
|
||||
use std::{collections::BTreeMap, fmt::Display, fs::read, str::FromStr};
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use relative_path::RelativePathBuf;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -108,6 +108,46 @@ impl FromStr for Realm {
|
|||
}
|
||||
}
|
||||
|
||||
/// A key to override dependencies
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct OverrideKey(pub Vec<Vec<String>>);
|
||||
|
||||
impl Serialize for OverrideKey {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str(
|
||||
&self
|
||||
.0
|
||||
.iter()
|
||||
.map(|overrides| {
|
||||
overrides
|
||||
.iter()
|
||||
.map(String::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(">")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for OverrideKey {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let s = String::deserialize(deserializer)?;
|
||||
let mut key = Vec::new();
|
||||
for overrides in s.split(',') {
|
||||
key.push(
|
||||
overrides
|
||||
.split('>')
|
||||
.map(|s| String::from_str(s).map_err(serde::de::Error::custom))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(OverrideKey(key))
|
||||
}
|
||||
}
|
||||
|
||||
/// The manifest of a package
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Manifest {
|
||||
|
@ -144,6 +184,9 @@ pub struct Manifest {
|
|||
#[cfg(feature = "wally")]
|
||||
#[serde(default)]
|
||||
pub sourcemap_generator: Option<String>,
|
||||
/// Dependency overrides
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub overrides: BTreeMap<OverrideKey, DependencySpecifier>,
|
||||
|
||||
/// The dependencies of the package
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
|
@ -292,6 +335,7 @@ impl Manifest {
|
|||
"".to_string(),
|
||||
)]),
|
||||
sourcemap_generator: None,
|
||||
overrides: BTreeMap::new(),
|
||||
|
||||
dependencies,
|
||||
peer_dependencies: Vec::new(),
|
||||
|
|
|
@ -130,7 +130,7 @@ const SEPARATOR: char = '/';
|
|||
const ESCAPED_SEPARATOR: char = '+';
|
||||
|
||||
macro_rules! name_impl {
|
||||
($Name:ident, $Error:ident, $Visitor:ident, $validate:expr, $prefix:expr) => {
|
||||
($Name:ident, $Error:ident, $validate:expr, $prefix:expr) => {
|
||||
impl $Name {
|
||||
/// Creates a new package name
|
||||
pub fn new(scope: &str, name: &str) -> Result<Self, $Error> {
|
||||
|
@ -209,7 +209,13 @@ macro_rules! name_impl {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de> Visitor<'de> for $Visitor {
|
||||
impl<'de> Deserialize<'de> for $Name {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<$Name, D::Error> {
|
||||
struct NameVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for NameVisitor {
|
||||
type Value = $Name;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
|
@ -221,24 +227,16 @@ macro_rules! name_impl {
|
|||
}
|
||||
|
||||
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||
v.parse().map_err(|e| E::custom(e))
|
||||
v.parse().map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for $Name {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<$Name, D::Error> {
|
||||
deserializer.deserialize_str($Visitor)
|
||||
deserializer.deserialize_str(NameVisitor)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct StandardPackageNameVisitor;
|
||||
#[cfg(feature = "wally")]
|
||||
struct WallyPackageNameVisitor;
|
||||
|
||||
/// A package name
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[serde(untagged)]
|
||||
|
@ -323,7 +321,6 @@ impl From<WallyPackageName> for PackageName {
|
|||
name_impl!(
|
||||
StandardPackageName,
|
||||
StandardPackageNameValidationError,
|
||||
StandardPackageNameVisitor,
|
||||
validate_part,
|
||||
""
|
||||
);
|
||||
|
@ -332,7 +329,6 @@ name_impl!(
|
|||
name_impl!(
|
||||
WallyPackageName,
|
||||
WallyPackageNameValidationError,
|
||||
WallyPackageNameVisitor,
|
||||
validate_wally_part,
|
||||
"wally#"
|
||||
);
|
||||
|
|
|
@ -43,6 +43,7 @@ fn test_resolves_package() {
|
|||
indices: Default::default(),
|
||||
#[cfg(feature = "wally")]
|
||||
sourcemap_generator: None,
|
||||
overrides: Default::default(),
|
||||
|
||||
dependencies: vec![],
|
||||
peer_dependencies: vec![],
|
||||
|
@ -83,6 +84,7 @@ fn test_resolves_package() {
|
|||
indices: Default::default(),
|
||||
#[cfg(feature = "wally")]
|
||||
sourcemap_generator: None,
|
||||
overrides: Default::default(),
|
||||
|
||||
dependencies: vec![specifier.clone()],
|
||||
peer_dependencies: vec![specifier_2.clone()],
|
||||
|
|
Loading…
Add table
Reference in a new issue