Skip to content

Commit bca885a

Browse files
authored
[2/n] [ls-apis] return walked dependencies instead of using a callback (#10458)
We're going to return more data from this function (the set of omitted nodes that were seen) in an upcoming PR. I tried out a few different approaches: * two callbacks * a trait * a hybrid approach where we keep the current callback and return the additional data as a value Based on the prototyping I feel like this ends up being the cleanest approach. There are no functional changes in this PR. Best reviewed in conjunction with its dependent PR: * #10459
1 parent 865a959 commit bca885a

2 files changed

Lines changed: 100 additions & 54 deletions

File tree

dev-tools/ls-apis/src/cargo.rs

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ use camino::Utf8Path;
1111
use camino::Utf8PathBuf;
1212
use cargo_metadata::{CargoOpt, Package};
1313
use cargo_metadata::{DependencyKind, PackageId};
14+
use iddqd::IdOrdItem;
15+
use iddqd::IdOrdMap;
16+
use iddqd::id_upcast;
1417
use std::collections::BTreeSet;
1518
use std::collections::{BTreeMap, VecDeque};
1619

@@ -234,23 +237,16 @@ impl Workspace {
234237
Ok(path)
235238
}
236239

237-
/// Iterate over the required dependencies of package `root`, invoking
238-
/// `func` for each one as:
240+
/// Walks the required (normal and build) dependencies of package `root`.
239241
///
240-
/// ```ignore
241-
/// func(package: &Package, dep_path: &DepPath)
242-
/// ```
243-
///
244-
/// where `package` is the package that is (directly or indirectly) a
245-
/// dependency of `root` and `dep_path` describes the dependency path from
246-
/// `root` to `package`.
247-
pub fn walk_required_deps_recursively(
248-
&self,
242+
/// Returns a [`WalkOutcome`] describing every package reachable from
243+
/// `root`, each paired with every dependency path from `root` to it.
244+
pub fn walk_required_deps_recursively<'a>(
245+
&'a self,
249246
root: &Package,
250-
func: &mut dyn FnMut(&Package, &DepPath),
251-
) -> Result<()> {
252-
struct Remaining<'a> {
253-
node: &'a cargo_metadata::Node,
247+
) -> Result<WalkOutcome<'a>> {
248+
struct Remaining<'n> {
249+
node: &'n cargo_metadata::Node,
254250
path: DepPath,
255251
}
256252

@@ -268,6 +264,7 @@ impl Workspace {
268264
path: DepPath::for_pkg(root.id.clone()),
269265
}];
270266
let mut seen: BTreeSet<PackageId> = BTreeSet::new();
267+
let mut found: IdOrdMap<PackageWalkOutcome<'a>> = IdOrdMap::new();
271268

272269
while let Some(Remaining { node: next, path }) = remaining.pop() {
273270
for d in &next.deps {
@@ -288,7 +285,14 @@ impl Workspace {
288285
// package metadata.
289286
let dep_pkg = self.packages_by_id.get(did).unwrap();
290287
let dep_node = self.nodes_by_id.get(did).unwrap();
291-
func(dep_pkg, &path);
288+
found
289+
.entry(&dep_pkg.id)
290+
.or_insert_with(|| PackageWalkOutcome {
291+
package: dep_pkg,
292+
dep_paths: Vec::new(),
293+
})
294+
.dep_paths
295+
.push(path.clone());
292296
if seen.contains(did) {
293297
continue;
294298
}
@@ -299,7 +303,7 @@ impl Workspace {
299303
}
300304
}
301305

302-
Ok(())
306+
Ok(WalkOutcome { found })
303307
}
304308

305309
/// Return all package ids for the given `pkgname`
@@ -343,6 +347,38 @@ fn cargo_toml_parent(
343347
Ok(path)
344348
}
345349

350+
/// The result of [`Workspace::walk_required_deps_recursively`].
351+
pub struct WalkOutcome<'a> {
352+
/// Every package encountered as a required (normal or build) dependency of
353+
/// the walk's `root`, each paired with every dependency path from `root`
354+
/// to that package.
355+
pub found: IdOrdMap<PackageWalkOutcome<'a>>,
356+
}
357+
358+
/// A single entry in [`WalkOutcome::found`]: one package and every dependency
359+
/// path from the walk's `root` to it.
360+
#[derive(Debug)]
361+
pub struct PackageWalkOutcome<'a> {
362+
/// The package that was found during the walk.
363+
pub package: &'a Package,
364+
365+
/// The list of dependency paths from the walk's `root` to this package.
366+
///
367+
/// A package reachable by more than one path appears once per path.
368+
pub dep_paths: Vec<DepPath>,
369+
}
370+
371+
impl<'a> IdOrdItem for PackageWalkOutcome<'a> {
372+
type Key<'b>
373+
= &'a PackageId
374+
where
375+
Self: 'b;
376+
fn key(&self) -> Self::Key<'_> {
377+
&self.package.id
378+
}
379+
id_upcast!();
380+
}
381+
346382
/// Describes a "dependency path": a path through the Cargo dependency graph
347383
/// from one package to another, which describes how one package depends on
348384
/// another

dev-tools/ls-apis/src/system_apis.rs

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ use crate::workspaces::Workspaces;
2222
use anyhow::Result;
2323
use anyhow::{Context, anyhow, bail};
2424
use camino::Utf8PathBuf;
25-
use cargo_metadata::Package;
2625
use iddqd::IdOrdItem;
2726
use iddqd::IdOrdMap;
2827
use iddqd::id_upcast;
@@ -173,14 +172,21 @@ impl SystemApis {
173172
let (workspace, server_pkg) =
174173
workspaces.find_package_workspace(dunit_pkg)?;
175174
let dep_path = DepPath::for_pkg(server_pkg.id.clone());
176-
tracker.found_package(dunit_pkg, dunit_pkg, &dep_path);
175+
tracker.found_package(
176+
dunit_pkg,
177+
dunit_pkg,
178+
std::slice::from_ref(&dep_path),
179+
);
177180

178-
workspace.walk_required_deps_recursively(
179-
server_pkg,
180-
&mut |p: &Package, dep_path: &DepPath| {
181-
tracker.found_package(dunit_pkg, &p.name, dep_path);
182-
},
183-
)?;
181+
let outcome =
182+
workspace.walk_required_deps_recursively(server_pkg)?;
183+
for pkg_outcome in &outcome.found {
184+
tracker.found_package(
185+
dunit_pkg,
186+
&pkg_outcome.package.name,
187+
&pkg_outcome.dep_paths,
188+
);
189+
}
184190
}
185191
}
186192

@@ -219,24 +225,22 @@ impl SystemApis {
219225
for server_pkgname in server_component_units.keys() {
220226
let (workspace, pkg) =
221227
workspaces.find_package_workspace(server_pkgname)?;
222-
workspace
223-
.walk_required_deps_recursively(
224-
pkg,
225-
&mut |p: &Package, dep_path: &DepPath| {
226-
deps_tracker.found_dependency(
227-
server_pkgname,
228-
&p.name,
229-
dep_path,
230-
);
231-
},
232-
)
228+
let outcome = workspace
229+
.walk_required_deps_recursively(pkg)
233230
.with_context(|| {
234231
format!(
235232
"iterating dependencies of workspace {:?} package {:?}",
236233
workspace.name(),
237234
server_pkgname
238235
)
239236
})?;
237+
for pkg_outcome in &outcome.found {
238+
deps_tracker.found_dependency(
239+
server_pkgname,
240+
&pkg_outcome.package.name,
241+
&pkg_outcome.dep_paths,
242+
);
243+
}
240244
}
241245

242246
let (apis_consumed, api_consumers) =
@@ -1414,7 +1418,7 @@ impl<'a> ServerComponentsTracker<'a> {
14141418
}
14151419

14161420
/// Record that deployment unit package `dunit_pkgname` depends on package
1417-
/// `pkgname` via dependency chain `dep_path`
1421+
/// `pkgname` via each of the given dependency chains `dep_paths`
14181422
///
14191423
/// This only records anything if `pkgname` turns out to be a known API
14201424
/// client package name, in which case this records that the server
@@ -1423,14 +1427,16 @@ impl<'a> ServerComponentsTracker<'a> {
14231427
&mut self,
14241428
dunit_pkgname: &ServerComponentName,
14251429
pkgname: &str,
1426-
dep_path: &DepPath,
1430+
dep_paths: &[DepPath],
14271431
) {
14281432
let Some(apis) = self.known_server_packages.get(pkgname) else {
14291433
return;
14301434
};
14311435

1432-
for api in apis {
1433-
self.found_api_producer(api, dunit_pkgname, dep_path);
1436+
for dep_path in dep_paths {
1437+
for api in apis {
1438+
self.found_api_producer(api, dunit_pkgname, dep_path);
1439+
}
14341440
}
14351441
}
14361442

@@ -1488,7 +1494,7 @@ impl<'a> ClientDependenciesTracker<'a> {
14881494
}
14891495

14901496
/// Record that comopnent `server_pkgname` consumes package `pkgname` via
1491-
/// dependency chain `dep_path`
1497+
/// each of the given dependency chains `dep_paths`
14921498
///
14931499
/// This only records cases where `pkgname` is a known client package for
14941500
/// one of our APIs, in which case it records that this server component
@@ -1497,7 +1503,7 @@ impl<'a> ClientDependenciesTracker<'a> {
14971503
&mut self,
14981504
server_pkgname: &ServerComponentName,
14991505
pkgname: &str,
1500-
dep_path: &DepPath,
1506+
dep_paths: &[DepPath],
15011507
) {
15021508
let Some(api) = self.api_metadata.client_pkgname_lookup(pkgname) else {
15031509
return;
@@ -1506,18 +1512,22 @@ impl<'a> ClientDependenciesTracker<'a> {
15061512
// This is the name of a known client package. Record it.
15071513
let status = api.restricted_to_consumers.status(server_pkgname);
15081514
let client_pkgname = ClientPackageName::from(pkgname.to_owned());
1509-
self.api_consumers
1510-
.entry(client_pkgname.clone())
1511-
.or_insert_with(IdOrdMap::new)
1512-
.entry(&server_pkgname)
1513-
.or_insert_with(|| ApiConsumer::new(server_pkgname.clone(), status))
1514-
.add_path(dep_path.clone());
1515-
self.apis_consumed
1516-
.entry(server_pkgname.clone())
1517-
.or_insert_with(BTreeMap::new)
1518-
.entry(client_pkgname)
1519-
.or_insert_with(Vec::new)
1520-
.push(dep_path.clone());
1515+
for dep_path in dep_paths {
1516+
self.api_consumers
1517+
.entry(client_pkgname.clone())
1518+
.or_insert_with(IdOrdMap::new)
1519+
.entry(&server_pkgname)
1520+
.or_insert_with(|| {
1521+
ApiConsumer::new(server_pkgname.clone(), status.clone())
1522+
})
1523+
.add_path(dep_path.clone());
1524+
self.apis_consumed
1525+
.entry(server_pkgname.clone())
1526+
.or_insert_with(BTreeMap::new)
1527+
.entry(client_pkgname.clone())
1528+
.or_insert_with(Vec::new)
1529+
.push(dep_path.clone());
1530+
}
15211531
}
15221532
}
15231533

0 commit comments

Comments
 (0)