Skip to content

Commit

Permalink
feat: support task id entrypoints (#9344)
Browse files Browse the repository at this point in the history
### Description

Currently if you run `turbo cli#build` you will get the following
failure*:
```
Error:   × could not find task `cli#build` in project
```
*It works if you had `"tasks": {"cli#build": {...}}` in your root level
`turbo.json`

After this PR, passing a fully qualified task id to run will now work,
regardless of how the task definition is defined:
 - In the root `turbo.json` as `build`
 - In the root `turbo.json` as `cli#build`
 - In the `cli` workspace's `turbo.json` as `build`

The usage of `#` is safe here as you already have been unable to use `#`
in a unqualified task name.
- If you attempt to use a `#` in a task name in a workspace level
`turbo.json` you will get an error about using package task syntax in a
workspace file.
- If you attempt to specify a task in the root `turbo.json` of the form
`foo#bar` it will be read as the `bar` task in package `foo`
- If you attempt to use `foo#bar#baz` as a task name in root
`turbo.json` it will work currently with `foo#bar#baz` and after this PR
it will continue to work

### Testing Instructions

Added unit tests!

Manual verification by running `turbo cli#build`.
  • Loading branch information
chris-olszewski authored Oct 29, 2024
1 parent 7bf0228 commit ba769dc
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/turborepo-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ daemon-file-hashing = []
anyhow = { workspace = true, features = ["backtrace"] }
assert_cmd = { workspace = true }
async-stream = "0.3.4"
insta = { workspace = true }
itertools = { workspace = true }
port_scanner = { workspace = true }
pretty_assertions = { workspace = true }
Expand Down
129 changes: 128 additions & 1 deletion crates/turborepo-lib/src/engine/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,16 @@ impl<'a> EngineBuilder<'a> {
};

let task_id_as_name = task_id.as_task_name();
if turbo_json.tasks.contains_key(&task_id_as_name)
if
// See if pkg#task is defined e.g. `docs#build`. This can only happen in root turbo.json
turbo_json.tasks.contains_key(&task_id_as_name)
// See if task is defined e.g. `build`. This can happen in root or workspace turbo.json
// This will fail if the user provided a task id e.g. turbo `docs#build`
|| turbo_json.tasks.contains_key(task_name)
// If user provided a task id, then we see if the task is defined
// e.g. `docs#build` should resolve if there's a `build` in root turbo.json or docs workspace level turbo.json
|| (matches!(workspace, PackageName::Root) && turbo_json.tasks.contains_key(&TaskName::from(task_name.task())))
|| (workspace == &PackageName::from(task_id.package()) && turbo_json.tasks.contains_key(&TaskName::from(task_name.task())))
{
Ok(true)
} else if !matches!(workspace, PackageName::Root) {
Expand Down Expand Up @@ -553,6 +561,7 @@ fn validate_task_name(task: Spanned<&str>) -> Result<(), Error> {
mod test {
use std::assert_matches::assert_matches;

use insta::assert_json_snapshot;
use pretty_assertions::assert_eq;
use serde_json::json;
use tempfile::TempDir;
Expand Down Expand Up @@ -680,6 +689,10 @@ mod test {
#[test_case(PackageName::from("b"), "build", "b#build", true ; "workspace task in workspace")]
#[test_case(PackageName::from("b"), "test", "b#test", true ; "task missing from workspace")]
#[test_case(PackageName::from("c"), "missing", "c#missing", false ; "task missing")]
#[test_case(PackageName::from("c"), "c#curse", "c#curse", true ; "root defined task")]
#[test_case(PackageName::from("b"), "c#curse", "c#curse", true ; "non-workspace root defined task")]
#[test_case(PackageName::from("b"), "b#special", "b#special", true ; "workspace defined task")]
#[test_case(PackageName::from("c"), "b#special", "b#special", false ; "non-workspace defined task")]
fn test_task_definition(
workspace: PackageName,
task_name: &'static str,
Expand All @@ -694,6 +707,7 @@ mod test {
"test": { "inputs": ["testing"] },
"build": { "inputs": ["primary"] },
"a#build": { "inputs": ["special"] },
"c#curse": {},
}
})),
),
Expand All @@ -702,6 +716,7 @@ mod test {
turbo_json(json!({
"tasks": {
"build": { "inputs": ["outer"]},
"special": {},
}
})),
),
Expand Down Expand Up @@ -1245,4 +1260,116 @@ mod test {
.err();
assert_eq!(result.as_deref(), reason);
}

#[test]
fn test_run_package_task_exact() {
let repo_root_dir = TempDir::with_prefix("repo").unwrap();
let repo_root = AbsoluteSystemPathBuf::new(repo_root_dir.path().to_str().unwrap()).unwrap();
let package_graph = mock_package_graph(
&repo_root,
package_jsons! {
repo_root,
"app1" => ["libA"],
"app2" => ["libA"],
"libA" => []
},
);
let turbo_jsons = vec![
(
PackageName::Root,
turbo_json(json!({
"tasks": {
"build": { "dependsOn": ["^build"] },
"special": { "dependsOn": ["^build"] },
}
})),
),
(
PackageName::from("app2"),
turbo_json(json!({
"extends": ["//"],
"tasks": {
"another": { "dependsOn": ["^build"] },
}
})),
),
]
.into_iter()
.collect();
let loader = TurboJsonLoader::noop(turbo_jsons);
let engine = EngineBuilder::new(&repo_root, &package_graph, loader, false)
.with_tasks(vec![
Spanned::new(TaskName::from("app1#special")),
Spanned::new(TaskName::from("app2#another")),
])
.with_workspaces(vec![PackageName::from("app1"), PackageName::from("app2")])
.build()
.unwrap();

let expected = deps! {
"app1#special" => ["libA#build"],
"app2#another" => ["libA#build"],
"libA#build" => ["___ROOT___"]
};
assert_eq!(all_dependencies(&engine), expected);
}

#[test]
fn test_run_package_task_exact_error() {
let repo_root_dir = TempDir::with_prefix("repo").unwrap();
let repo_root = AbsoluteSystemPathBuf::new(repo_root_dir.path().to_str().unwrap()).unwrap();
let package_graph = mock_package_graph(
&repo_root,
package_jsons! {
repo_root,
"app1" => ["libA"],
"libA" => []
},
);
let turbo_jsons = vec![
(
PackageName::Root,
turbo_json(json!({
"tasks": {
"build": { "dependsOn": ["^build"] },
}
})),
),
(
PackageName::from("app1"),
turbo_json(json!({
"extends": ["//"],
"tasks": {
"another": { "dependsOn": ["^build"] },
}
})),
),
]
.into_iter()
.collect();
let loader = TurboJsonLoader::noop(turbo_jsons);
let engine = EngineBuilder::new(&repo_root, &package_graph, loader.clone(), false)
.with_tasks(vec![Spanned::new(TaskName::from("app1#special"))])
.with_workspaces(vec![PackageName::from("app1")])
.build();
assert!(engine.is_err());
let report = miette::Report::new(engine.unwrap_err());
let mut msg = String::new();
miette::JSONReportHandler::new()
.render_report(&mut msg, report.as_ref())
.unwrap();
assert_json_snapshot!(msg);

let engine = EngineBuilder::new(&repo_root, &package_graph, loader, false)
.with_tasks(vec![Spanned::new(TaskName::from("app1#another"))])
.with_workspaces(vec![PackageName::from("libA")])
.build();
assert!(engine.is_err());
let report = miette::Report::new(engine.unwrap_err());
let mut msg = String::new();
miette::JSONReportHandler::new()
.render_report(&mut msg, report.as_ref())
.unwrap();
assert_json_snapshot!(msg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: crates/turborepo-lib/src/engine/builder.rs
expression: msg
---
"{\"message\": \"missing tasks in project\",\"severity\": \"error\",\"causes\": [],\"labels\": [],\"related\": [{\"message\": \"could not find task `app1#another` in project\",\"severity\": \"error\",\"causes\": [],\"filename\": \"\",\"labels\": [],\"related\": []}]}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: crates/turborepo-lib/src/engine/builder.rs
expression: msg
---
"{\"message\": \"missing tasks in project\",\"severity\": \"error\",\"causes\": [],\"labels\": [],\"related\": [{\"message\": \"could not find task `app1#special` in project\",\"severity\": \"error\",\"causes\": [],\"filename\": \"\",\"labels\": [],\"related\": []}]}"
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,24 @@ Setup
Cached: 0 cached, 2 total
Time:\s*[\.0-9]+m?s (re)

$ ${TURBO} run cross-workspace#cross-workspace-task
\xe2\x80\xa2 Packages in scope: add-keys, add-tasks, bad-json, blank-pkg, cached, config-change, cross-workspace, invalid-config, missing-workspace-config, omit-keys, override-values, persistent (esc)
\xe2\x80\xa2 Running cross-workspace#cross-workspace-task in 12 packages (esc)
\xe2\x80\xa2 Remote caching disabled (esc)
blank-pkg:cross-workspace-underlying-task: cache hit, replaying logs 39566f6362976823
blank-pkg:cross-workspace-underlying-task:
blank-pkg:cross-workspace-underlying-task: > cross-workspace-underlying-task
blank-pkg:cross-workspace-underlying-task: > echo cross-workspace-underlying-task from blank-pkg
blank-pkg:cross-workspace-underlying-task:
blank-pkg:cross-workspace-underlying-task: cross-workspace-underlying-task from blank-pkg
cross-workspace:cross-workspace-task: cache hit, replaying logs bce507a110930f07
cross-workspace:cross-workspace-task:
cross-workspace:cross-workspace-task: > cross-workspace-task
cross-workspace:cross-workspace-task: > echo cross-workspace-task
cross-workspace:cross-workspace-task:
cross-workspace:cross-workspace-task: cross-workspace-task

Tasks: 2 successful, 2 total
Cached: 2 cached, 2 total
Time:\s*[\.0-9]+m?s >>> FULL TURBO (re)

0 comments on commit ba769dc

Please sign in to comment.