summaryrefslogtreecommitdiff
path: root/cli/npm/resolution/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/npm/resolution/mod.rs')
-rw-r--r--cli/npm/resolution/mod.rs265
1 files changed, 137 insertions, 128 deletions
diff --git a/cli/npm/resolution/mod.rs b/cli/npm/resolution/mod.rs
index ec6ee0dda..90bf16c43 100644
--- a/cli/npm/resolution/mod.rs
+++ b/cli/npm/resolution/mod.rs
@@ -1,11 +1,13 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::HashSet;
+use deno_core::anyhow::Context;
use deno_core::error::AnyError;
-use deno_core::futures;
use deno_core::parking_lot::RwLock;
+use deno_graph::npm::NpmPackageNv;
use deno_graph::npm::NpmPackageReq;
use deno_graph::semver::Version;
use serde::Deserialize;
@@ -17,12 +19,11 @@ use crate::args::Lockfile;
use self::graph::GraphDependencyResolver;
use self::snapshot::NpmPackagesPartitioned;
-use super::cache::should_sync_download;
use super::cache::NpmPackageCacheFolderId;
use super::registry::NpmPackageVersionDistInfo;
-use super::registry::RealNpmRegistryApi;
-use super::NpmRegistryApi;
+use super::registry::NpmRegistryApi;
+mod common;
mod graph;
mod snapshot;
mod specifier;
@@ -40,25 +41,20 @@ pub struct NpmPackageNodeIdDeserializationError {
/// A resolved unique identifier for an npm package. This contains
/// the resolved name, version, and peer dependency resolution identifiers.
-#[derive(
- Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize,
-)]
-pub struct NpmPackageNodeId {
- pub name: String,
- pub version: Version,
- pub peer_dependencies: Vec<NpmPackageNodeId>,
+#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub struct NpmPackageId {
+ pub nv: NpmPackageNv,
+ pub peer_dependencies: Vec<NpmPackageId>,
}
-impl NpmPackageNodeId {
- #[allow(unused)]
- pub fn scope(&self) -> Option<&str> {
- if self.name.starts_with('@') && self.name.contains('/') {
- self.name.split('/').next()
- } else {
- None
- }
+// Custom debug implementation for more concise test output
+impl std::fmt::Debug for NpmPackageId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.as_serialized())
}
+}
+impl NpmPackageId {
pub fn as_serialized(&self) -> String {
self.as_serialized_with_level(0)
}
@@ -68,11 +64,11 @@ impl NpmPackageNodeId {
let mut result = format!(
"{}@{}",
if level == 0 {
- self.name.to_string()
+ self.nv.name.to_string()
} else {
- self.name.replace('/', "+")
+ self.nv.name.replace('/', "+")
},
- self.version
+ self.nv.version
);
for peer in &self.peer_dependencies {
// unfortunately we can't do something like `_3` when
@@ -136,7 +132,7 @@ impl NpmPackageNodeId {
fn parse_peers_at_level<'a>(
level: usize,
- ) -> impl Fn(&'a str) -> ParseResult<'a, Vec<NpmPackageNodeId>> {
+ ) -> impl Fn(&'a str) -> ParseResult<'a, Vec<NpmPackageId>> {
move |mut input| {
let mut peers = Vec::new();
while let Ok((level_input, _)) = parse_level_at_level(level)(input) {
@@ -151,7 +147,7 @@ impl NpmPackageNodeId {
fn parse_id_at_level<'a>(
level: usize,
- ) -> impl Fn(&'a str) -> ParseResult<'a, NpmPackageNodeId> {
+ ) -> impl Fn(&'a str) -> ParseResult<'a, NpmPackageId> {
move |input| {
let (input, (name, version)) = parse_name_and_version(input)?;
let name = if level > 0 {
@@ -163,9 +159,8 @@ impl NpmPackageNodeId {
parse_peers_at_level(level + 1)(input)?;
Ok((
input,
- NpmPackageNodeId {
- name,
- version,
+ NpmPackageId {
+ nv: NpmPackageNv { name, version },
peer_dependencies,
},
))
@@ -179,17 +174,26 @@ impl NpmPackageNodeId {
}
})
}
+}
+
+impl Ord for NpmPackageId {
+ fn cmp(&self, other: &Self) -> Ordering {
+ match self.nv.cmp(&other.nv) {
+ Ordering::Equal => self.peer_dependencies.cmp(&other.peer_dependencies),
+ ordering => ordering,
+ }
+ }
+}
- pub fn display(&self) -> String {
- // Don't implement std::fmt::Display because we don't
- // want this to be used by accident in certain scenarios.
- format!("{}@{}", self.name, self.version)
+impl PartialOrd for NpmPackageId {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct NpmResolutionPackage {
- pub id: NpmPackageNodeId,
+ pub pkg_id: NpmPackageId,
/// The peer dependency resolution can differ for the same
/// package (name and version) depending on where it is in
/// the resolution tree. This copy index indicates which
@@ -198,21 +202,20 @@ pub struct NpmResolutionPackage {
pub dist: NpmPackageVersionDistInfo,
/// Key is what the package refers to the other package as,
/// which could be different from the package name.
- pub dependencies: HashMap<String, NpmPackageNodeId>,
+ pub dependencies: HashMap<String, NpmPackageId>,
}
impl NpmResolutionPackage {
pub fn get_package_cache_folder_id(&self) -> NpmPackageCacheFolderId {
NpmPackageCacheFolderId {
- name: self.id.name.clone(),
- version: self.id.version.clone(),
+ nv: self.pkg_id.nv.clone(),
copy_index: self.copy_index,
}
}
}
pub struct NpmResolution {
- api: RealNpmRegistryApi,
+ api: NpmRegistryApi,
snapshot: RwLock<NpmResolutionSnapshot>,
update_semaphore: tokio::sync::Semaphore,
}
@@ -228,7 +231,7 @@ impl std::fmt::Debug for NpmResolution {
impl NpmResolution {
pub fn new(
- api: RealNpmRegistryApi,
+ api: NpmRegistryApi,
initial_snapshot: Option<NpmResolutionSnapshot>,
) -> Self {
Self {
@@ -246,9 +249,8 @@ impl NpmResolution {
let _permit = self.update_semaphore.acquire().await?;
let snapshot = self.snapshot.read().clone();
- let snapshot = self
- .add_package_reqs_to_snapshot(package_reqs, snapshot)
- .await?;
+ let snapshot =
+ add_package_reqs_to_snapshot(&self.api, package_reqs, snapshot).await?;
*self.snapshot.write() = snapshot;
Ok(())
@@ -272,88 +274,28 @@ impl NpmResolution {
} else {
snapshot
};
- let snapshot = self
- .add_package_reqs_to_snapshot(
- package_reqs.into_iter().collect(),
- snapshot,
- )
- .await?;
+ let snapshot = add_package_reqs_to_snapshot(
+ &self.api,
+ package_reqs.into_iter().collect(),
+ snapshot,
+ )
+ .await?;
*self.snapshot.write() = snapshot;
Ok(())
}
- async fn add_package_reqs_to_snapshot(
- &self,
- package_reqs: Vec<NpmPackageReq>,
- snapshot: NpmResolutionSnapshot,
- ) -> Result<NpmResolutionSnapshot, AnyError> {
- // convert the snapshot to a traversable graph
- let mut graph = Graph::from_snapshot(snapshot);
-
- // go over the top level package names first, then down the
- // tree one level at a time through all the branches
- let mut unresolved_tasks = Vec::with_capacity(package_reqs.len());
- let mut resolving_package_names =
- HashSet::with_capacity(package_reqs.len());
- for package_req in &package_reqs {
- if graph.has_package_req(package_req) {
- // skip analyzing this package, as there's already a matching top level package
- continue;
- }
- if !resolving_package_names.insert(package_req.name.clone()) {
- continue; // already resolving
- }
-
- // cache the package info up front in parallel
- if should_sync_download() {
- // for deterministic test output
- self.api.package_info(&package_req.name).await?;
- } else {
- let api = self.api.clone();
- let package_name = package_req.name.clone();
- unresolved_tasks.push(tokio::task::spawn(async move {
- // This is ok to call because api will internally cache
- // the package information in memory.
- api.package_info(&package_name).await
- }));
- };
- }
-
- for result in futures::future::join_all(unresolved_tasks).await {
- result??; // surface the first error
- }
-
- let mut resolver = GraphDependencyResolver::new(&mut graph, &self.api);
-
- // These package_reqs should already be sorted in the order they should
- // be resolved in.
- for package_req in package_reqs {
- // avoid loading the info if this is already in the graph
- if !resolver.has_package_req(&package_req) {
- let info = self.api.package_info(&package_req.name).await?;
- resolver.add_package_req(&package_req, &info)?;
- }
- }
-
- resolver.resolve_pending().await?;
-
- let result = graph.into_snapshot(&self.api).await;
- self.api.clear_memory_cache();
- result
- }
-
pub fn resolve_package_from_id(
&self,
- id: &NpmPackageNodeId,
+ id: &NpmPackageId,
) -> Option<NpmResolutionPackage> {
self.snapshot.read().package_from_id(id).cloned()
}
pub fn resolve_package_cache_folder_id_from_id(
&self,
- id: &NpmPackageNodeId,
+ id: &NpmPackageId,
) -> Option<NpmPackageCacheFolderId> {
self
.snapshot
@@ -400,7 +342,8 @@ impl NpmResolution {
pub fn lock(&self, lockfile: &mut Lockfile) -> Result<(), AnyError> {
let snapshot = self.snapshot.read();
- for (package_req, package_id) in snapshot.package_reqs.iter() {
+ for (package_req, nv) in snapshot.package_reqs.iter() {
+ let package_id = snapshot.root_packages.get(nv).unwrap();
lockfile.insert_npm_specifier(
package_req.to_string(),
package_id.as_serialized(),
@@ -413,40 +356,106 @@ impl NpmResolution {
}
}
+async fn add_package_reqs_to_snapshot(
+ api: &NpmRegistryApi,
+ package_reqs: Vec<NpmPackageReq>,
+ snapshot: NpmResolutionSnapshot,
+) -> Result<NpmResolutionSnapshot, AnyError> {
+ if package_reqs
+ .iter()
+ .all(|req| snapshot.package_reqs.contains_key(req))
+ {
+ return Ok(snapshot); // already up to date
+ }
+
+ // convert the snapshot to a traversable graph
+ let mut graph = Graph::from_snapshot(snapshot).with_context(|| {
+ deno_core::anyhow::anyhow!(
+ "Failed creating npm state. Try recreating your lockfile."
+ )
+ })?;
+
+ // avoid loading the info if this is already in the graph
+ let package_reqs = package_reqs
+ .into_iter()
+ .filter(|r| !graph.has_package_req(r))
+ .collect::<Vec<_>>();
+
+ // go over the top level package names first, then down the tree
+ // one level at a time through all the branches
+ api
+ .cache_in_parallel(
+ package_reqs
+ .iter()
+ .map(|r| r.name.clone())
+ .into_iter()
+ .collect::<Vec<_>>(),
+ )
+ .await?;
+
+ let mut resolver = GraphDependencyResolver::new(&mut graph, api);
+
+ // The package reqs should already be sorted
+ // in the order they should be resolved in.
+ for package_req in package_reqs {
+ let info = api.package_info(&package_req.name).await?;
+ resolver.add_package_req(&package_req, &info)?;
+ }
+
+ resolver.resolve_pending().await?;
+
+ let result = graph.into_snapshot(api).await;
+ api.clear_memory_cache();
+ result
+}
+
#[cfg(test)]
mod test {
+ use deno_graph::npm::NpmPackageNv;
use deno_graph::semver::Version;
- use super::NpmPackageNodeId;
+ use super::NpmPackageId;
#[test]
fn serialize_npm_package_id() {
- let id = NpmPackageNodeId {
- name: "pkg-a".to_string(),
- version: Version::parse_from_npm("1.2.3").unwrap(),
+ let id = NpmPackageId {
+ nv: NpmPackageNv {
+ name: "pkg-a".to_string(),
+ version: Version::parse_from_npm("1.2.3").unwrap(),
+ },
peer_dependencies: vec![
- NpmPackageNodeId {
- name: "pkg-b".to_string(),
- version: Version::parse_from_npm("3.2.1").unwrap(),
+ NpmPackageId {
+ nv: NpmPackageNv {
+ name: "pkg-b".to_string(),
+ version: Version::parse_from_npm("3.2.1").unwrap(),
+ },
peer_dependencies: vec![
- NpmPackageNodeId {
- name: "pkg-c".to_string(),
- version: Version::parse_from_npm("1.3.2").unwrap(),
+ NpmPackageId {
+ nv: NpmPackageNv {
+ name: "pkg-c".to_string(),
+ version: Version::parse_from_npm("1.3.2").unwrap(),
+ },
peer_dependencies: vec![],
},
- NpmPackageNodeId {
- name: "pkg-d".to_string(),
- version: Version::parse_from_npm("2.3.4").unwrap(),
+ NpmPackageId {
+ nv: NpmPackageNv {
+ name: "pkg-d".to_string(),
+ version: Version::parse_from_npm("2.3.4").unwrap(),
+ },
peer_dependencies: vec![],
},
],
},
- NpmPackageNodeId {
- name: "pkg-e".to_string(),
- version: Version::parse_from_npm("2.3.1").unwrap(),
- peer_dependencies: vec![NpmPackageNodeId {
- name: "pkg-f".to_string(),
+ NpmPackageId {
+ nv: NpmPackageNv {
+ name: "pkg-e".to_string(),
version: Version::parse_from_npm("2.3.1").unwrap(),
+ },
+ peer_dependencies: vec![NpmPackageId {
+ nv: NpmPackageNv {
+ name: "pkg-f".to_string(),
+ version: Version::parse_from_npm("2.3.1").unwrap(),
+ },
peer_dependencies: vec![],
}],
},
@@ -456,6 +465,6 @@ mod test {
// this shouldn't change because it's used in the lockfile
let serialized = id.as_serialized();
assert_eq!(serialized, "pkg-a@1.2.3_pkg-b@3.2.1__pkg-c@1.3.2__pkg-d@2.3.4_pkg-e@2.3.1__pkg-f@2.3.1");
- assert_eq!(NpmPackageNodeId::from_serialized(&serialized).unwrap(), id);
+ assert_eq!(NpmPackageId::from_serialized(&serialized).unwrap(), id);
}
}