Skip to content

Commit

Permalink
feat(query): provide query for external dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-olszewski committed Feb 10, 2025
1 parent 4115d5c commit 4c3f1a3
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 0 deletions.
17 changes: 17 additions & 0 deletions crates/turborepo-lib/src/query/external_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::sync::Arc;

use async_graphql::Object;

use super::{package::Package, Array, Error};
use crate::run::Run;

#[derive(Clone)]
Expand Down Expand Up @@ -30,4 +31,20 @@ impl ExternalPackage {
async fn name(&self) -> String {
self.human_name().to_string()
}

async fn internal_dependants(&self) -> Result<Array<Package>, Error> {
let Some(names) = self
.run
.pkg_dep_graph()
.internal_dependencies_for_external_dependency(&self.package)
else {
return Ok(Array::from(Vec::new()));
};
let mut packages = names
.iter()
.map(|name| Package::new(self.run.clone(), name.as_package_name().clone()))
.collect::<Result<Array<_>, Error>>()?;
packages.sort_by(|a, b| a.get_name().cmp(b.get_name()));
Ok(packages)
}
}
12 changes: 12 additions & 0 deletions crates/turborepo-lib/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,18 @@ impl RepositoryQuery {

Ok(packages)
}

async fn external_dependencies(&self) -> Result<Array<ExternalPackage>, Error> {
let mut packages = self
.run
.pkg_dep_graph()
.external_to_internal()
.keys()
.map(|pkg| ExternalPackage::new(self.run.clone(), pkg.clone()))
.collect::<Array<_>>();
packages.sort_by_key(|pkg| pkg.human_name());
Ok(packages)
}
}

pub async fn graphiql() -> impl IntoResponse {
Expand Down
2 changes: 2 additions & 0 deletions crates/turborepo-repository/src/package_graph/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ impl<'a, T: PackageDiscovery> BuildState<'a, ResolvedPackageManager, T> {
lockfile,
package_manager,
repo_root: repo_root.to_owned(),
external_to_internal: std::sync::OnceLock::new(),
})
}
}
Expand Down Expand Up @@ -539,6 +540,7 @@ impl<T: PackageDiscovery> BuildState<'_, ResolvedLockfile, T> {
package_manager,
lockfile,
repo_root: repo_root.to_owned(),
external_to_internal: std::sync::OnceLock::new(),
})
}
}
Expand Down
55 changes: 55 additions & 0 deletions crates/turborepo-repository/src/package_graph/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{
collections::{BTreeMap, HashMap, HashSet},
fmt,
sync::OnceLock,
};

use itertools::Itertools;
Expand Down Expand Up @@ -34,6 +35,7 @@ pub struct PackageGraph {
package_manager: PackageManager,
lockfile: Option<Box<dyn Lockfile>>,
repo_root: AbsoluteSystemPathBuf,
external_to_internal: OnceLock<HashMap<turborepo_lockfiles::Package, HashSet<PackageNode>>>,
}

/// The WorkspacePackage.
Expand Down Expand Up @@ -555,6 +557,59 @@ impl PackageGraph {
}))
}

pub fn internal_dependencies_for_external_dependency(
&self,
external_package: &turborepo_lockfiles::Package,
) -> Option<&HashSet<PackageNode>> {
// In order to answer this once we have to calculate the info for every external
// package so we store the results
let map = self
.external_to_internal
.get_or_init(|| self.external_to_internal());
map.get(external_package)
}

// Gotta come up with a better name
pub fn external_to_internal(
&self,
) -> HashMap<turborepo_lockfiles::Package, HashSet<PackageNode>> {
// TODO: provide size hint from Lockfile trait
let mut map: HashMap<turborepo_lockfiles::Package, HashSet<PackageNode>> = HashMap::new();
// First find which packages directly depend on each external package
for (pkg, info) in self.packages.iter() {
for dep in info.transitive_dependencies.as_ref().into_iter().flatten() {
let rdeps = map.entry(dep.clone()).or_default();
rdeps.insert(PackageNode::Workspace(pkg.clone()));
}
}
// Now trace through all ancestors of the direct dependants
let root_internal_dependencies = self
.root_internal_dependencies()
.into_iter()
.cloned()
.collect::<HashSet<_>>();
for (_external_pkg, rdeps) in map.iter_mut() {
// If one of the reverse dependencies of this external package is a root
// dependency, everything depends on this
if !root_internal_dependencies.is_disjoint(rdeps) {
rdeps.extend(self.graph.node_weights().cloned());
} else {
let transitive_rdeps = turborepo_graph_utils::transitive_closure(
&self.graph,
rdeps.iter().map(|node| {
*self
.node_lookup
.get(node)
.expect("all nodes should have an index")
}),
petgraph::Direction::Incoming,
);
rdeps.extend(transitive_rdeps.into_iter().cloned());
}
}
map
}

// Returns a map of package name and version for external dependencies
#[allow(dead_code)]
fn external_dependencies(
Expand Down

0 comments on commit 4c3f1a3

Please sign in to comment.