Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(query): provide query for external dependencies #9929

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
59 changes: 59 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,63 @@ 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<_>>();
let root_external_dependencies =
self.transitive_external_dependencies(Some(&PackageName::Root));
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_external_dependencies.contains(external_pkg)
|| !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
Loading