diff --git a/crates/puffin-resolver/src/resolution.rs b/crates/puffin-resolver/src/resolution.rs index 67e2befc2..67c4d1a32 100644 --- a/crates/puffin-resolver/src/resolution.rs +++ b/crates/puffin-resolver/src/resolution.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::hash::BuildHasherDefault; use anyhow::Result; @@ -255,22 +256,70 @@ impl<'a> DisplayResolutionGraph<'a> { /// Write the graph in the `{name}=={version}` format of requirements.txt that pip uses. impl std::fmt::Display for DisplayResolutionGraph<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // Collect and sort all packages. + #[derive(Debug)] + enum Node<'a> { + /// A node linked to an editable distribution. + Editable(&'a PackageName, &'a LocalEditable), + /// A node linked to a non-editable distribution. + Distribution(&'a PackageName, &'a Dist), + } + + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] + enum NodeKey<'a> { + /// A node linked to an editable distribution, sorted by verbatim representation. + Editable(Cow<'a, str>), + /// A node linked to a non-editable distribution, sorted by package name. + Distribution(&'a PackageName), + } + + impl<'a> Node<'a> { + /// Return the name of the package. + fn name(&self) -> &'a PackageName { + match self { + Node::Editable(name, _) => name, + Node::Distribution(name, _) => name, + } + } + + /// Return a comparable key for the node. + fn key(&self) -> NodeKey<'a> { + match self { + Node::Editable(_, editable) => NodeKey::Editable(editable.verbatim()), + Node::Distribution(name, _) => NodeKey::Distribution(name), + } + } + } + + // Collect all packages. let mut nodes = self .resolution .petgraph .node_indices() - .map(|node| (node, &self.resolution.petgraph[node])) + .map(|index| { + let dist = &self.resolution.petgraph[index]; + let name = dist.name(); + let node = if let Some((editable, _)) = self.resolution.editables.get(name) { + Node::Editable(name, editable) + } else { + Node::Distribution(name, dist) + }; + (index, node) + }) .collect::>(); - nodes.sort_unstable_by_key(|(_, package)| package.name()); + + // Sort the nodes by name, but with editable packages first. + nodes.sort_unstable_by_key(|(index, node)| (node.key(), *index)); // Print out the dependency graph. - for (index, dist) in nodes { + for (index, node) in nodes { // Display the node itself. - if let Some((editable, _)) = self.resolution.editables.get(dist.name()) { - write!(f, "-e {}", editable.verbatim())?; - } else { - write!(f, "{}", dist.verbatim())?; + match node { + Node::Distribution(_, dist) => { + write!(f, "{}", dist.verbatim())?; + } + Node::Editable(_, editable) => { + write!(f, "-e {}", editable.verbatim())?; + } } // Display the distribution hashes, if any. @@ -278,7 +327,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { if let Some(hashes) = self .resolution .hashes - .get(dist.name()) + .get(node.name()) .filter(|hashes| !hashes.is_empty()) { for hash in hashes { diff --git a/crates/puffin/tests/pip_compile.rs b/crates/puffin/tests/pip_compile.rs index f12f5355b..7da671676 100644 --- a/crates/puffin/tests/pip_compile.rs +++ b/crates/puffin/tests/pip_compile.rs @@ -2034,40 +2034,40 @@ fn compile_editable() -> Result<()> { .arg("--exclude-newer") .arg(EXCLUDE_NEWER) .env("VIRTUAL_ENV", context.venv.as_os_str()), @r###" - success: true - exit_code: 0 - ----- stdout ----- - # This file was autogenerated by Puffin v[VERSION] via the following command: - # puffin pip compile requirements.in --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z - aiohttp==3.9.0 - # via black - aiosignal==1.3.1 - # via aiohttp - attrs==23.1.0 - # via aiohttp - -e file://../../scripts/editable-installs/black_editable - boltons==23.1.1 - frozenlist==1.4.0 - # via - # aiohttp - # aiosignal - idna==3.4 - # via yarl - -e ${PROJECT_ROOT}/../../scripts/editable-installs/maturin_editable - multidict==6.0.4 - # via - # aiohttp - # yarl - numpy==1.26.2 - # via poetry-editable - -e ../../scripts/editable-installs/poetry_editable - yarl==1.9.2 - # via aiohttp + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by Puffin v[VERSION] via the following command: + # puffin pip compile requirements.in --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z + -e ${PROJECT_ROOT}/../../scripts/editable-installs/maturin_editable + -e ../../scripts/editable-installs/poetry_editable + -e file://../../scripts/editable-installs/black_editable + aiohttp==3.9.0 + # via black + aiosignal==1.3.1 + # via aiohttp + attrs==23.1.0 + # via aiohttp + boltons==23.1.1 + frozenlist==1.4.0 + # via + # aiohttp + # aiosignal + idna==3.4 + # via yarl + multidict==6.0.4 + # via + # aiohttp + # yarl + numpy==1.26.2 + # via poetry-editable + yarl==1.9.2 + # via aiohttp - ----- stderr ----- - Built 3 editables in [TIME] - Resolved 12 packages in [TIME] - "###); + ----- stderr ----- + Built 3 editables in [TIME] + Resolved 12 packages in [TIME] + "###); Ok(()) }