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")]
|
#[cfg(feature = "wally")]
|
||||||
sourcemap_generator: None,
|
sourcemap_generator: None,
|
||||||
|
overrides: Default::default(),
|
||||||
|
|
||||||
dependencies: Default::default(),
|
dependencies: Default::default(),
|
||||||
peer_dependencies: Default::default(),
|
peer_dependencies: Default::default(),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, BTreeSet, HashSet, VecDeque},
|
collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
@ -16,7 +16,7 @@ use crate::{
|
||||||
DependencySpecifier, PackageRef,
|
DependencySpecifier, PackageRef,
|
||||||
},
|
},
|
||||||
index::{Index, IndexFileEntry, IndexPackageError},
|
index::{Index, IndexFileEntry, IndexPackageError},
|
||||||
manifest::{DependencyType, Manifest, Realm},
|
manifest::{DependencyType, Manifest, OverrideKey, Realm},
|
||||||
package_name::PackageName,
|
package_name::PackageName,
|
||||||
project::{get_index, get_index_by_url, Project, ReadLockfileError},
|
project::{get_index, get_index_by_url, Project, ReadLockfileError},
|
||||||
DEV_PACKAGES_FOLDER, INDEX_FOLDER, PACKAGES_FOLDER, SERVER_PACKAGES_FOLDER,
|
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)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct RootLockfileNode {
|
pub struct RootLockfileNode {
|
||||||
|
/// Dependency overrides
|
||||||
|
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||||
|
pub overrides: BTreeMap<OverrideKey, DependencySpecifier>,
|
||||||
|
|
||||||
/// The specifiers of the root packages
|
/// The specifiers of the root packages
|
||||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||||
pub specifiers: PackageMap<DependencySpecifier>,
|
pub specifiers: PackageMap<DependencySpecifier>,
|
||||||
|
@ -203,17 +207,19 @@ pub enum ResolveError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Manifest {
|
impl Manifest {
|
||||||
/// Resolves the dependency graph for the project
|
fn missing_dependencies(
|
||||||
pub fn dependency_graph(
|
|
||||||
&self,
|
&self,
|
||||||
project: &mut Project,
|
root: &mut RootLockfileNode,
|
||||||
locked: bool,
|
locked: bool,
|
||||||
) -> Result<RootLockfileNode, ResolveError> {
|
project: &Project,
|
||||||
debug!("resolving dependency graph for project {}", self.name);
|
) -> Result<Vec<(DependencySpecifier, DependencyType)>, ResolveError> {
|
||||||
// try to reuse versions (according to semver specifiers) to decrease the amount of downloads and storage
|
Ok(if let Some(old_root) = project.lockfile()? {
|
||||||
let mut root = RootLockfileNode::default();
|
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");
|
debug!("lockfile found, resolving dependencies from it");
|
||||||
let mut missing = Vec::new();
|
let mut missing = Vec::new();
|
||||||
|
|
||||||
|
@ -237,10 +243,13 @@ impl Manifest {
|
||||||
.or_default()
|
.or_default()
|
||||||
.insert(version.clone(), specifier.unwrap().clone());
|
.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() {
|
while let Some((resolved_package, depth)) = queue.pop_front() {
|
||||||
debug!("resolved {resolved_package} from lockfile");
|
debug!(
|
||||||
|
"{}resolved {resolved_package} from lockfile",
|
||||||
|
"\t".repeat(depth)
|
||||||
|
);
|
||||||
|
|
||||||
root.children
|
root.children
|
||||||
.entry(name.clone())
|
.entry(name.clone())
|
||||||
|
@ -260,7 +269,7 @@ impl Manifest {
|
||||||
.and_then(|v| v.get(dep_version));
|
.and_then(|v| v.get(dep_version));
|
||||||
|
|
||||||
match dep {
|
match dep {
|
||||||
Some(dep) => queue.push_back(dep),
|
Some(dep) => queue.push_back((dep, depth + 1)),
|
||||||
// the lockfile is out of date
|
// the lockfile is out of date
|
||||||
None => return Err(ResolveError::OutOfDateLockfile),
|
None => return Err(ResolveError::OutOfDateLockfile),
|
||||||
}
|
}
|
||||||
|
@ -299,21 +308,45 @@ impl Manifest {
|
||||||
} else {
|
} else {
|
||||||
debug!("no lockfile found, resolving all dependencies");
|
debug!("no lockfile found, resolving all dependencies");
|
||||||
self.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...");
|
debug!("no dependencies left to resolve, finishing...");
|
||||||
return Ok(root);
|
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()
|
.into_iter()
|
||||||
.map(|(specifier, dep_type)| (specifier, dep_type, None))
|
.map(|(specifier, dep_type)| (specifier, dep_type, None, vec![]))
|
||||||
.collect::<VecDeque<_>>();
|
.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 {
|
let (pkg_ref, default_realm, dependencies) = match &specifier {
|
||||||
DependencySpecifier::Registry(registry_dependency) => {
|
DependencySpecifier::Registry(registry_dependency) => {
|
||||||
let index = if dependant.is_none() {
|
let index = if dependant.is_none() {
|
||||||
|
@ -331,8 +364,10 @@ impl Manifest {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"resolved registry dependency {} to {}",
|
"{}resolved registry dependency {} to {}",
|
||||||
registry_dependency.name, entry.version
|
"\t".repeat(depth),
|
||||||
|
registry_dependency.name,
|
||||||
|
entry.version
|
||||||
);
|
);
|
||||||
|
|
||||||
(
|
(
|
||||||
|
@ -350,7 +385,8 @@ impl Manifest {
|
||||||
git_dependency.resolve(project.cache_dir(), project.indices())?;
|
git_dependency.resolve(project.cache_dir(), project.indices())?;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"resolved git dependency {} to {url}#{rev}",
|
"{}resolved git dependency {} to {url}#{rev}",
|
||||||
|
"\t".repeat(depth),
|
||||||
git_dependency.repo
|
git_dependency.repo
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -383,8 +419,10 @@ impl Manifest {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"resolved wally dependency {} to {}",
|
"{}resolved wally dependency {} to {}",
|
||||||
wally_dependency.name, entry.version
|
"\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 {
|
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((
|
queue.push_back((
|
||||||
specifier,
|
overridden.cloned().unwrap_or(specifier),
|
||||||
ty,
|
ty,
|
||||||
Some((pkg_ref.name(), pkg_ref.version().clone())),
|
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 std::{collections::BTreeMap, fmt::Display, fs::read, str::FromStr};
|
||||||
|
|
||||||
|
use cfg_if::cfg_if;
|
||||||
use relative_path::RelativePathBuf;
|
use relative_path::RelativePathBuf;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::{Deserialize, Serialize};
|
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
|
/// The manifest of a package
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Manifest {
|
pub struct Manifest {
|
||||||
|
@ -144,6 +184,9 @@ pub struct Manifest {
|
||||||
#[cfg(feature = "wally")]
|
#[cfg(feature = "wally")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub sourcemap_generator: Option<String>,
|
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
|
/// The dependencies of the package
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
@ -292,6 +335,7 @@ impl Manifest {
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
)]),
|
)]),
|
||||||
sourcemap_generator: None,
|
sourcemap_generator: None,
|
||||||
|
overrides: BTreeMap::new(),
|
||||||
|
|
||||||
dependencies,
|
dependencies,
|
||||||
peer_dependencies: Vec::new(),
|
peer_dependencies: Vec::new(),
|
||||||
|
|
|
@ -130,7 +130,7 @@ const SEPARATOR: char = '/';
|
||||||
const ESCAPED_SEPARATOR: char = '+';
|
const ESCAPED_SEPARATOR: char = '+';
|
||||||
|
|
||||||
macro_rules! name_impl {
|
macro_rules! name_impl {
|
||||||
($Name:ident, $Error:ident, $Visitor:ident, $validate:expr, $prefix:expr) => {
|
($Name:ident, $Error:ident, $validate:expr, $prefix:expr) => {
|
||||||
impl $Name {
|
impl $Name {
|
||||||
/// Creates a new package name
|
/// Creates a new package name
|
||||||
pub fn new(scope: &str, name: &str) -> Result<Self, $Error> {
|
pub fn new(scope: &str, name: &str) -> Result<Self, $Error> {
|
||||||
|
@ -209,36 +209,34 @@ macro_rules! name_impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for $Visitor {
|
|
||||||
type Value = $Name;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
formatter,
|
|
||||||
"a string in the format `{}scope{SEPARATOR}name`",
|
|
||||||
$prefix
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
|
||||||
v.parse().map_err(|e| E::custom(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for $Name {
|
impl<'de> Deserialize<'de> for $Name {
|
||||||
fn deserialize<D: serde::Deserializer<'de>>(
|
fn deserialize<D: serde::Deserializer<'de>>(
|
||||||
deserializer: D,
|
deserializer: D,
|
||||||
) -> Result<$Name, D::Error> {
|
) -> Result<$Name, D::Error> {
|
||||||
deserializer.deserialize_str($Visitor)
|
struct NameVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for NameVisitor {
|
||||||
|
type Value = $Name;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
formatter,
|
||||||
|
"a string in the format `{}scope{SEPARATOR}name`",
|
||||||
|
$prefix
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
|
||||||
|
v.parse().map_err(E::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(NameVisitor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StandardPackageNameVisitor;
|
|
||||||
#[cfg(feature = "wally")]
|
|
||||||
struct WallyPackageNameVisitor;
|
|
||||||
|
|
||||||
/// A package name
|
/// A package name
|
||||||
#[derive(Serialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Serialize, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
|
@ -323,7 +321,6 @@ impl From<WallyPackageName> for PackageName {
|
||||||
name_impl!(
|
name_impl!(
|
||||||
StandardPackageName,
|
StandardPackageName,
|
||||||
StandardPackageNameValidationError,
|
StandardPackageNameValidationError,
|
||||||
StandardPackageNameVisitor,
|
|
||||||
validate_part,
|
validate_part,
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
@ -332,7 +329,6 @@ name_impl!(
|
||||||
name_impl!(
|
name_impl!(
|
||||||
WallyPackageName,
|
WallyPackageName,
|
||||||
WallyPackageNameValidationError,
|
WallyPackageNameValidationError,
|
||||||
WallyPackageNameVisitor,
|
|
||||||
validate_wally_part,
|
validate_wally_part,
|
||||||
"wally#"
|
"wally#"
|
||||||
);
|
);
|
||||||
|
|
|
@ -43,6 +43,7 @@ fn test_resolves_package() {
|
||||||
indices: Default::default(),
|
indices: Default::default(),
|
||||||
#[cfg(feature = "wally")]
|
#[cfg(feature = "wally")]
|
||||||
sourcemap_generator: None,
|
sourcemap_generator: None,
|
||||||
|
overrides: Default::default(),
|
||||||
|
|
||||||
dependencies: vec![],
|
dependencies: vec![],
|
||||||
peer_dependencies: vec![],
|
peer_dependencies: vec![],
|
||||||
|
@ -83,6 +84,7 @@ fn test_resolves_package() {
|
||||||
indices: Default::default(),
|
indices: Default::default(),
|
||||||
#[cfg(feature = "wally")]
|
#[cfg(feature = "wally")]
|
||||||
sourcemap_generator: None,
|
sourcemap_generator: None,
|
||||||
|
overrides: Default::default(),
|
||||||
|
|
||||||
dependencies: vec![specifier.clone()],
|
dependencies: vec![specifier.clone()],
|
||||||
peer_dependencies: vec![specifier_2.clone()],
|
peer_dependencies: vec![specifier_2.clone()],
|
||||||
|
|
Loading…
Add table
Reference in a new issue