Skip to content

Commit

Permalink
feat(query): add task dependencies/dependents (#9190)
Browse files Browse the repository at this point in the history
### Description

Adds `direct_dependencies` and `direct_dependents` fields to `Task`

### Testing Instructions

Added some tests in `tests/query/task.t`
  • Loading branch information
NicholasLYang authored Oct 8, 2024
1 parent 4402e7d commit e8ffdb8
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 25 deletions.
14 changes: 0 additions & 14 deletions crates/turborepo-lib/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,20 +417,6 @@ impl Args {
clap_args
}

pub fn get_tasks(&self) -> &[String] {
match &self.command {
Some(Command::Run {
run_args: _,
execution_args: box ExecutionArgs { tasks, .. },
}) => tasks,
_ => self
.execution_args
.as_ref()
.map(|execution_args| execution_args.tasks.as_slice())
.unwrap_or(&[]),
}
}

pub fn track(&self, tel: &GenericEventBuilder) {
// track usage only
track_usage!(tel, self.skip_infer, |val| val);
Expand Down
2 changes: 1 addition & 1 deletion crates/turborepo-lib/src/commands/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub async fn run(
execution_args: Box::default(),
});

let run_builder = RunBuilder::new(base)?;
let run_builder = RunBuilder::new(base)?.add_all_tasks();
let run = run_builder.build(&handler, telemetry).await?;

if let Some(query) = query {
Expand Down
40 changes: 39 additions & 1 deletion crates/turborepo-lib/src/engine/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ pub struct EngineBuilder<'a> {
tasks: Vec<Spanned<TaskName<'static>>>,
root_enabled_tasks: HashSet<TaskName<'static>>,
tasks_only: bool,
add_all_tasks: bool,
}

impl<'a> EngineBuilder<'a> {
Expand All @@ -118,6 +119,7 @@ impl<'a> EngineBuilder<'a> {
tasks: Vec::new(),
root_enabled_tasks: HashSet::new(),
tasks_only: false,
add_all_tasks: false,
}
}

Expand Down Expand Up @@ -148,6 +150,13 @@ impl<'a> EngineBuilder<'a> {
self
}

/// If set, we will include all tasks in the graph, even if they are not
/// specified
pub fn add_all_tasks(mut self) -> Self {
self.add_all_tasks = true;
self
}

// Returns the set of allowed tasks that can be run if --only is used
// The set is exactly the product of the packages in filter and tasks specified
// by CLI
Expand Down Expand Up @@ -185,7 +194,36 @@ impl<'a> EngineBuilder<'a> {
let mut missing_tasks: HashMap<&TaskName<'_>, Spanned<()>> =
HashMap::from_iter(self.tasks.iter().map(|spanned| spanned.as_ref().split()));
let mut traversal_queue = VecDeque::with_capacity(1);
for (workspace, task) in self.workspaces.iter().cartesian_product(self.tasks.iter()) {
let tasks: Vec<Spanned<TaskName<'static>>> = if self.add_all_tasks {
let mut tasks = Vec::new();
if let Ok(turbo_json) = turbo_json_loader.load(&PackageName::Root) {
tasks.extend(
turbo_json
.tasks
.keys()
.map(|task| Spanned::new(task.clone())),
);
}

for workspace in self.workspaces.iter() {
let Ok(turbo_json) = turbo_json_loader.load(workspace) else {
continue;
};

tasks.extend(
turbo_json
.tasks
.keys()
.map(|task| Spanned::new(task.clone())),
);
}

tasks
} else {
self.tasks.clone()
};

for (workspace, task) in self.workspaces.iter().cartesian_product(tasks.iter()) {
let task_id = task
.task_id()
.unwrap_or_else(|| TaskId::new(workspace.as_ref(), task.task()));
Expand Down
9 changes: 7 additions & 2 deletions crates/turborepo-lib/src/query/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod file;
mod package;
mod server;
mod task;

use std::{io, sync::Arc};

Expand All @@ -18,7 +19,7 @@ use turborepo_repository::package_graph::PackageName;

use crate::{
get_version,
query::file::File,
query::{file::File, task::Task},
run::{builder::RunBuilder, Run},
signal::SignalHandler,
};
Expand All @@ -38,8 +39,10 @@ pub enum Error {
#[error("failed to serialize result: {0}")]
Serde(#[from] serde_json::Error),
#[error(transparent)]
#[diagnostic(transparent)]
Run(#[from] crate::run::Error),
#[error(transparent)]
#[diagnostic(transparent)]
Path(#[from] turbopath::PathError),
#[error(transparent)]
UI(#[from] turborepo_ui::Error),
Expand All @@ -56,6 +59,8 @@ impl RepositoryQuery {
}

#[derive(Debug, SimpleObject)]
#[graphql(concrete(name = "Tasks", params(Task)))]
#[graphql(concrete(name = "Packages", params(Package)))]
pub struct Array<T: OutputType> {
items: Vec<T>,
length: usize,
Expand Down Expand Up @@ -235,7 +240,7 @@ impl PackagePredicate {
fn check_has(pkg: &Package, field: &PackageFields, value: &Any) -> bool {
match (field, &value.0) {
(PackageFields::Name, Value::String(name)) => pkg.name.as_ref() == name,
(PackageFields::TaskName, Value::String(name)) => pkg.task_names().contains(name),
(PackageFields::TaskName, Value::String(name)) => pkg.get_tasks().contains_key(name),
_ => false,
}
}
Expand Down
26 changes: 22 additions & 4 deletions crates/turborepo-lib/src/query/package.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
use std::{collections::HashSet, sync::Arc};
use std::{collections::HashMap, sync::Arc};

use async_graphql::Object;
use itertools::Itertools;
use turborepo_errors::Spanned;
use turborepo_repository::package_graph::{PackageName, PackageNode};

use crate::{
query::{Array, Error},
query::{task::Task, Array, Error},
run::Run,
};

#[derive(Clone)]
pub struct Package {
pub run: Arc<Run>,
pub name: PackageName,
}

impl Package {
pub fn task_names(&self) -> HashSet<String> {
pub fn get_tasks(&self) -> HashMap<String, Spanned<String>> {
self.run
.pkg_dep_graph()
.package_json(&self.name)
.map(|json| json.scripts.keys().cloned().collect())
.map(|json| {
json.scripts
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
})
.unwrap_or_default()
}

Expand Down Expand Up @@ -191,4 +198,15 @@ impl Package {
.sorted_by(|a, b| a.name.cmp(&b.name))
.collect())
}

async fn tasks(&self) -> Array<Task> {
self.get_tasks()
.into_iter()
.map(|(name, script)| Task {
name,
package: self.clone(),
script: Some(script),
})
.collect()
}
}
73 changes: 73 additions & 0 deletions crates/turborepo-lib/src/query/task.rs
Original file line number Diff line number Diff line change
@@ -1 +1,74 @@
use async_graphql::Object;
use turborepo_errors::Spanned;

use crate::{
engine::TaskNode,
query::{package::Package, Array},
run::task_id::TaskId,
};

pub struct Task {
pub name: String,
pub package: Package,
pub script: Option<Spanned<String>>,
}

#[Object]
impl Task {
async fn name(&self) -> String {
self.name.clone()
}

async fn package(&self) -> Package {
self.package.clone()
}

async fn script(&self) -> Option<String> {
self.script.as_ref().map(|script| script.value.to_string())
}

async fn direct_dependents(&self) -> Array<Task> {
let task_id = TaskId::from_static(self.package.name.to_string(), self.name.clone());
self.package
.run
.engine()
.dependents(&task_id)
.into_iter()
.flatten()
.filter_map(|task| match task {
TaskNode::Root => None,
TaskNode::Task(task) => Some(Task {
name: task.task().to_string(),
package: Package {
run: self.package.run.clone(),
name: task.package().to_string().into(),
},
script: self.package.get_tasks().get(task.task()).cloned(),
}),
})
.collect()
}

async fn direct_dependencies(&self) -> Array<Task> {
let task_id = TaskId::new(self.package.name.as_ref(), &self.name);

self.package
.run
.engine()
.dependencies(&task_id)
.into_iter()
.flatten()
.filter_map(|task| match task {
TaskNode::Root => None,
TaskNode::Task(task) => Some(Task {
name: task.task().to_string(),
package: Package {
run: self.package.run.clone(),
name: task.package().to_string().into(),
},
script: self.package.get_tasks().get(task.task()).cloned(),
}),
})
.collect()
}
}
19 changes: 16 additions & 3 deletions crates/turborepo-lib/src/run/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub struct RunBuilder {
should_print_prelude_override: Option<bool>,
allow_missing_package_manager: bool,
allow_no_turbo_json: bool,
// If true, we will add all tasks to the graph, even if they are not specified
add_all_tasks: bool,
}

impl RunBuilder {
Expand Down Expand Up @@ -108,6 +110,7 @@ impl RunBuilder {
allow_missing_package_manager,
root_turbo_json_path,
allow_no_turbo_json,
add_all_tasks: false,
})
}

Expand All @@ -121,6 +124,11 @@ impl RunBuilder {
self
}

pub fn add_all_tasks(mut self) -> Self {
self.add_all_tasks = true;
self
}

fn connect_process_manager(&self, signal_subscriber: SignalSubscriber) {
let manager = self.processes.clone();
tokio::spawn(async move {
Expand Down Expand Up @@ -464,7 +472,7 @@ impl RunBuilder {
filtered_pkgs: &HashSet<PackageName>,
turbo_json_loader: TurboJsonLoader,
) -> Result<Engine, Error> {
let mut engine = EngineBuilder::new(
let mut builder = EngineBuilder::new(
&self.repo_root,
pkg_dep_graph,
turbo_json_loader,
Expand All @@ -476,8 +484,13 @@ impl RunBuilder {
.with_tasks(self.opts.run_opts.tasks.iter().map(|task| {
// TODO: Pull span info from command
Spanned::new(TaskName::from(task.as_str()).into_owned())
}))
.build()?;
}));

if self.add_all_tasks {
builder = builder.add_all_tasks();
}

let mut engine = builder.build()?;

// If we have an initial task, we prune out the engine to only
// tasks that are reachable from that initial task.
Expand Down
4 changes: 4 additions & 0 deletions crates/turborepo-lib/src/run/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ impl Run {
&self.pkg_dep_graph
}

pub fn engine(&self) -> &Engine {
&self.engine
}

pub fn filtered_pkgs(&self) -> &HashSet<PackageName> {
&self.filtered_pkgs
}
Expand Down
2 changes: 2 additions & 0 deletions turborepo-tests/integration/tests/command-query.t
Original file line number Diff line number Diff line change
Expand Up @@ -210,5 +210,7 @@ Run the query
$ ${TURBO} query "query { version }" | jq ".data.version" > QUERY_VERSION
WARNING query command is experimental and may change in the future
$ VERSION=${MONOREPO_ROOT_DIR}/version.txt
$ diff --strip-trailing-cr <(head -n 1 ${VERSION}) <(${TURBO} --version)
Loading

0 comments on commit e8ffdb8

Please sign in to comment.