diff --git a/dev-tools/ls-apis/api-manifest.toml b/dev-tools/ls-apis/api-manifest.toml index b9ca4bc5df..d28acecbc5 100644 --- a/dev-tools/ls-apis/api-manifest.toml +++ b/dev-tools/ls-apis/api-manifest.toml @@ -404,6 +404,17 @@ note = """ nexus-types depends on gateway-client for defining some types. """ +[[dependency_filter_rules]] +ancestor = "internal-dns" +client = "dns-service-client" +evaluation = "bogus" +note = """ +Past versions of internal-dns (which does not exist any more) depended on +dns-service-client for defining some types. We can remove this when other repos +that depend on Omicron have updated past the removal of the "internal-dns" +package. +""" + [[dependency_filter_rules]] ancestor = "nexus-types" client = "dns-service-client" diff --git a/dev-tools/ls-apis/src/bin/ls-apis.rs b/dev-tools/ls-apis/src/bin/ls-apis.rs index 39d1865d5b..9d2a5b5349 100644 --- a/dev-tools/ls-apis/src/bin/ls-apis.rs +++ b/dev-tools/ls-apis/src/bin/ls-apis.rs @@ -136,14 +136,29 @@ fn run_apis(apis: &SystemApis, args: ShowDepsArgs) -> Result<()> { let metadata = apis.api_metadata(); for api in metadata.apis() { println!("{} (client: {})", api.label, api.client_package_name); - for (s, path) in + for (s, dep_paths) in apis.api_consumers(&api.client_package_name, args.filter)? { let (repo_name, package_path) = apis.package_label(s)?; - println!(" consumed by: {} ({}/{})", s, repo_name, package_path); + println!( + " consumed by: {} ({}/{}) via {} path{}", + s, + repo_name, + package_path, + dep_paths.len(), + if dep_paths.len() == 1 { "" } else { "s" }, + ); if args.show_deps { - for p in path.nodes() { - println!(" via {}", p); + for (i, dep_path) in dep_paths.iter().enumerate() { + let label = if dep_paths.len() > 1 { + format!(" path {}", i + 1) + } else { + String::new() + }; + + for p in dep_path.nodes() { + println!(" via{}: {}", label, p); + } } } } diff --git a/dev-tools/ls-apis/src/cargo.rs b/dev-tools/ls-apis/src/cargo.rs index edff43ff12..3ec4922f73 100644 --- a/dev-tools/ls-apis/src/cargo.rs +++ b/dev-tools/ls-apis/src/cargo.rs @@ -9,7 +9,7 @@ use anyhow::bail; use anyhow::{anyhow, ensure, Context, Result}; use camino::Utf8Path; use camino::Utf8PathBuf; -use cargo_metadata::Package; +use cargo_metadata::{CargoOpt, Package}; use cargo_metadata::{DependencyKind, PackageId}; use std::collections::BTreeSet; use std::collections::{BTreeMap, VecDeque}; @@ -50,6 +50,7 @@ impl Workspace { pub fn load( name: &str, manifest_path: Option<&Utf8Path>, + extra_features: Option, ignored_non_clients: &BTreeSet, ) -> Result { eprintln!( @@ -65,6 +66,9 @@ impl Workspace { if let Some(manifest_path) = manifest_path { cmd.manifest_path(manifest_path); } + if let Some(extra_features) = extra_features { + cmd.features(extra_features); + } let metadata = cmd.exec().context("loading metadata")?; let workspace_root = metadata.workspace_root; @@ -261,11 +265,6 @@ impl Workspace { while let Some(Remaining { node: next, path }) = remaining.pop() { for d in &next.deps { let did = &d.pkg; - if seen.contains(did) { - continue; - } - - seen.insert(did.clone()); if !d.dep_kinds.iter().any(|k| { matches!( k.kind, @@ -283,8 +282,13 @@ impl Workspace { let dep_pkg = self.packages_by_id.get(did).unwrap(); let dep_node = self.nodes_by_id.get(did).unwrap(); func(dep_pkg, &path); + if seen.contains(did) { + continue; + } + + seen.insert(did.clone()); let dep_path = path.with_dependency_on(did.clone()); - remaining.push(Remaining { node: dep_node, path: dep_path }) + remaining.push(Remaining { node: dep_node, path: dep_path }); } } diff --git a/dev-tools/ls-apis/src/system_apis.rs b/dev-tools/ls-apis/src/system_apis.rs index ad61f31f33..d0da3818c9 100644 --- a/dev-tools/ls-apis/src/system_apis.rs +++ b/dev-tools/ls-apis/src/system_apis.rs @@ -244,7 +244,7 @@ impl SystemApis { &self, client: &ClientPackageName, filter: ApiDependencyFilter, - ) -> Result + '_> + ) -> Result)> + '_> { let mut rv = Vec::new(); @@ -253,7 +253,7 @@ impl SystemApis { }; for (server_pkgname, dep_paths) in api_consumers { - let mut include = None; + let mut include = Vec::new(); for p in dep_paths { if filter.should_include( &self.api_metadata, @@ -261,13 +261,12 @@ impl SystemApis { &client, p, )? { - include = Some(p); - break; + include.push(p); } } - if let Some(p) = include { - rv.push((server_pkgname, p)) + if !include.is_empty() { + rv.push((server_pkgname, include)) } } diff --git a/dev-tools/ls-apis/src/workspaces.rs b/dev-tools/ls-apis/src/workspaces.rs index ace565e011..54df7a44e3 100644 --- a/dev-tools/ls-apis/src/workspaces.rs +++ b/dev-tools/ls-apis/src/workspaces.rs @@ -9,6 +9,7 @@ use crate::cargo::Workspace; use crate::ClientPackageName; use anyhow::{anyhow, ensure, Context, Result}; use camino::Utf8Path; +use cargo_metadata::CargoOpt; use cargo_metadata::Package; use cargo_metadata::PackageId; use std::collections::BTreeMap; @@ -35,8 +36,12 @@ impl Workspaces { // First, load information about the "omicron" workspace. This is the // current workspace so we don't need to provide the path to it. let ignored_non_clients = api_metadata.ignored_non_clients(); - let omicron = - Arc::new(Workspace::load("omicron", None, ignored_non_clients)?); + let omicron = Arc::new(Workspace::load( + "omicron", + None, + None, + ignored_non_clients, + )?); // In order to assemble this metadata, Cargo already has a clone of most // of the other workspaces that we care about. We'll use those clones @@ -55,17 +60,36 @@ impl Workspaces { // concurrency. let handles: Vec<_> = [ // To find this repo ... look up this package in Omicron - // v v - ("crucible", "crucible-agent-client"), - ("propolis", "propolis-client"), - ("maghemite", "mg-admin-client"), + // | | +---- and enable these extra + // | | | features when loading + // v v v + ("crucible", "crucible-agent-client", None), + ( + "propolis", + "propolis-client", + // The artifacts shipped from the Propolis repo (particularly, + // `propolis-server`) are built with the `omicron-build` + // feature, which is not enabled by default. Enable this + // feature when loading the Propolis repo metadata so that we + // see the dependency tree that a shipping system will have. + Some(CargoOpt::SomeFeatures(vec![String::from( + "omicron-build", + )])), + ), + ("maghemite", "mg-admin-client", None), ] .into_iter() - .map(|(repo, omicron_pkg)| { + .map(|(repo, omicron_pkg, extra_features)| { let mine = omicron.clone(); let my_ignored = ignored_non_clients.clone(); std::thread::spawn(move || { - load_dependent_repo(&mine, repo, omicron_pkg, my_ignored) + load_dependent_repo( + &mine, + repo, + omicron_pkg, + extra_features, + my_ignored, + ) }) }) .collect(); @@ -97,6 +121,7 @@ impl Workspaces { &maghemite, "dendrite", "dpd-client", + None, ignored_non_clients.clone(), )?, ); @@ -231,6 +256,7 @@ fn load_dependent_repo( workspace: &Workspace, repo: &str, pkgname: &str, + extra_features: Option, ignored_non_clients: BTreeSet, ) -> Result { // `Workspace` doesn't let us look up a non-workspace package by name @@ -288,5 +314,10 @@ fn load_dependent_repo( ) })?; let workspace_manifest = Utf8Path::new(output.trim_end()); - Workspace::load(repo, Some(workspace_manifest), &ignored_non_clients) + Workspace::load( + repo, + Some(workspace_manifest), + extra_features, + &ignored_non_clients, + ) } diff --git a/dev-tools/ls-apis/tests/api_dependencies.out b/dev-tools/ls-apis/tests/api_dependencies.out index 62da29db42..cfe273f5f3 100644 --- a/dev-tools/ls-apis/tests/api_dependencies.out +++ b/dev-tools/ls-apis/tests/api_dependencies.out @@ -1,77 +1,77 @@ Bootstrap Agent (client: bootstrap-agent-client) - consumed by: omicron-sled-agent (omicron/sled-agent) - consumed by: wicketd (omicron/wicketd) + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path + consumed by: wicketd (omicron/wicketd) via 2 paths Clickhouse Cluster Admin (client: clickhouse-admin-client) - consumed by: omicron-nexus (omicron/nexus) + consumed by: omicron-nexus (omicron/nexus) via 3 paths CockroachDB Cluster Admin (client: cockroach-admin-client) - consumed by: omicron-nexus (omicron/nexus) + consumed by: omicron-nexus (omicron/nexus) via 2 paths Crucible Agent (client: crucible-agent-client) - consumed by: omicron-nexus (omicron/nexus) + consumed by: omicron-nexus (omicron/nexus) via 1 path Crucible Control (for testing only) (client: crucible-control-client) Crucible Pantry (client: crucible-pantry-client) - consumed by: omicron-nexus (omicron/nexus) + consumed by: omicron-nexus (omicron/nexus) via 1 path Maghemite DDM Admin (client: ddm-admin-client) - consumed by: installinator (omicron/installinator) - consumed by: mgd (maghemite/mgd) - consumed by: omicron-sled-agent (omicron/sled-agent) - consumed by: wicketd (omicron/wicketd) + consumed by: installinator (omicron/installinator) via 1 path + consumed by: mgd (maghemite/mgd) via 1 path + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path + consumed by: wicketd (omicron/wicketd) via 1 path DNS Server (client: dns-service-client) - consumed by: omicron-nexus (omicron/nexus) - consumed by: omicron-sled-agent (omicron/sled-agent) + consumed by: omicron-nexus (omicron/nexus) via 1 path + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path Dendrite DPD (client: dpd-client) - consumed by: ddmd (maghemite/ddmd) - consumed by: mgd (maghemite/mgd) - consumed by: omicron-nexus (omicron/nexus) - consumed by: omicron-sled-agent (omicron/sled-agent) - consumed by: tfportd (dendrite/tfportd) - consumed by: wicketd (omicron/wicketd) + consumed by: ddmd (maghemite/ddmd) via 2 paths + consumed by: mgd (maghemite/mgd) via 1 path + consumed by: omicron-nexus (omicron/nexus) via 1 path + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path + consumed by: tfportd (dendrite/tfportd) via 1 path + consumed by: wicketd (omicron/wicketd) via 2 paths Downstairs Controller (debugging only) (client: dsc-client) Management Gateway Service (client: gateway-client) - consumed by: dpd (dendrite/dpd) - consumed by: omicron-nexus (omicron/nexus) - consumed by: omicron-sled-agent (omicron/sled-agent) - consumed by: wicketd (omicron/wicketd) + consumed by: dpd (dendrite/dpd) via 1 path + consumed by: omicron-nexus (omicron/nexus) via 3 paths + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path + consumed by: wicketd (omicron/wicketd) via 3 paths Wicketd Installinator (client: installinator-client) - consumed by: installinator (omicron/installinator) + consumed by: installinator (omicron/installinator) via 1 path Maghemite MG Admin (client: mg-admin-client) - consumed by: omicron-nexus (omicron/nexus) - consumed by: omicron-sled-agent (omicron/sled-agent) + consumed by: omicron-nexus (omicron/nexus) via 1 path + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path Nexus Internal API (client: nexus-client) - consumed by: dpd (dendrite/dpd) - consumed by: omicron-nexus (omicron/nexus) - consumed by: omicron-sled-agent (omicron/sled-agent) - consumed by: oximeter-collector (omicron/oximeter/collector) - consumed by: propolis-server (propolis/bin/propolis-server) + consumed by: dpd (dendrite/dpd) via 1 path + consumed by: omicron-nexus (omicron/nexus) via 1 path + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path + consumed by: oximeter-collector (omicron/oximeter/collector) via 1 path + consumed by: propolis-server (propolis/bin/propolis-server) via 3 paths External API (client: oxide-client) Oximeter (client: oximeter-client) - consumed by: omicron-nexus (omicron/nexus) + consumed by: omicron-nexus (omicron/nexus) via 2 paths Propolis (client: propolis-client) - consumed by: omicron-nexus (omicron/nexus) - consumed by: omicron-sled-agent (omicron/sled-agent) + consumed by: omicron-nexus (omicron/nexus) via 2 paths + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path Crucible Repair (client: repair-client) - consumed by: crucible-downstairs (crucible/downstairs) + consumed by: crucible-downstairs (crucible/downstairs) via 1 path Sled Agent (client: sled-agent-client) - consumed by: dpd (dendrite/dpd) - consumed by: omicron-nexus (omicron/nexus) - consumed by: omicron-sled-agent (omicron/sled-agent) + consumed by: dpd (dendrite/dpd) via 1 path + consumed by: omicron-nexus (omicron/nexus) via 7 paths + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path Wicketd (client: wicketd-client)