From 1125934e6ad611f47058840ba7ab4ce6e1d2e6ed Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Sun, 10 Nov 2024 19:17:54 -0800 Subject: [PATCH] new: Add new task/workspace graphs. (#1713) * Start on graph. * Split up expanders. * Add tg command. * Update commands. * Updated affected and ag. * Update docker. * Update more commands. * Update actions. * Update some tests. * More tests. * Polish. * Fixes. * Fixes. * Update types. * Fixes. --- .yarn/versions/3fbef0f1.yml | 23 +- Cargo.lock | 59 +- crates/action-graph/Cargo.toml | 2 +- .../action-graph/src/action_graph_builder.rs | 75 +- .../action-graph/tests/action_graph_test.rs | 259 ++- ...n_graph__run_task_by_target__runs_all.snap | 8 +- crates/action-graph/tests/utils.rs | 10 +- crates/action-pipeline/Cargo.toml | 2 +- crates/action-pipeline/src/action_pipeline.rs | 10 +- crates/action-pipeline/src/action_runner.rs | 33 +- crates/action-pipeline/src/job.rs | 2 +- crates/action-pipeline/src/job_context.rs | 8 +- crates/actions/Cargo.toml | 2 +- crates/actions/src/actions/install_deps.rs | 10 +- crates/actions/src/actions/run_task.rs | 12 +- crates/actions/src/actions/sync_project.rs | 14 +- crates/actions/src/actions/sync_workspace.rs | 7 +- .../actions/src/operations/sync_codeowners.rs | 6 +- crates/affected/Cargo.toml | 2 +- crates/affected/src/affected_tracker.rs | 53 +- .../affected/tests/affected_tracker_test.rs | 38 +- crates/app/Cargo.toml | 1 + crates/app/src/app.rs | 18 +- crates/app/src/commands/check.rs | 12 +- crates/app/src/commands/ci.rs | 26 +- crates/app/src/commands/docker/file.rs | 19 +- crates/app/src/commands/docker/prune.rs | 15 +- crates/app/src/commands/docker/setup.rs | 6 +- crates/app/src/commands/graph/action.rs | 10 +- crates/app/src/commands/graph/dep.rs | 14 - crates/app/src/commands/graph/mod.rs | 2 +- crates/app/src/commands/graph/task.rs | 61 + crates/app/src/commands/graph/utils.rs | 9 +- crates/app/src/commands/project.rs | 13 +- crates/app/src/commands/query.rs | 18 +- crates/app/src/commands/run.rs | 4 +- crates/app/src/commands/syncs/codeowners.rs | 4 +- crates/app/src/commands/syncs/projects.rs | 6 +- crates/app/src/commands/task.rs | 6 +- crates/app/src/components.rs | 4 +- crates/app/src/queries/projects.rs | 25 +- crates/app/src/session.rs | 24 +- crates/cli/src/main.rs | 2 +- .../run_test__errors_for_internal_task.snap | 2 +- ...t__errors_for_unknown_task_in_project.snap | 5 +- .../snapshots/task_test__unknown_task.snap | 5 +- crates/config-schema/src/typescript_types.rs | 1 + crates/plugin/Cargo.toml | 2 +- crates/plugin/src/host.rs | 15 +- crates/plugin/tests/plugin_registry_test.rs | 4 +- crates/project-builder/src/project_builder.rs | 6 +- crates/project-expander/Cargo.toml | 9 +- .../project-expander/src/expander_context.rs | 119 +- crates/project-expander/src/lib.rs | 8 - .../project-expander/src/project_expander.rs | 55 +- crates/project-expander/src/tasks_expander.rs | 353 ---- .../tests/tasks_expander_test.rs | 1473 ----------------- crates/project-graph/Cargo.toml | 2 +- crates/project-graph/src/lib.rs | 3 +- crates/project-graph/src/project_graph.rs | 104 +- crates/project-graph/src/project_matcher.rs | 84 - .../project-graph/tests/project_graph_test.rs | 467 +++--- crates/project/src/lib.rs | 2 - crates/project/src/project.rs | 56 +- crates/project/src/project_error.rs | 22 - crates/task-expander/Cargo.toml | 32 + crates/task-expander/src/expander_context.rs | 121 ++ crates/task-expander/src/lib.rs | 11 + .../src/task_expander.rs} | 49 +- .../src/task_expander_error.rs} | 0 .../src/token_expander.rs | 10 +- .../src/token_expander_error.rs | 0 .../tests/__fixtures__/env-file/.env-shared | 0 .../__fixtures__/env-file/project/source/.env | 0 .../env-file/project/source/.env-invalid | 0 .../file-group/project/source/config.yml | 0 .../project/source/dir/subdir/nested.json | 0 .../project/source/dir/subdir/not-used.md | 0 .../file-group/project/source/docs.md | 0 .../file-group/project/source/other/file.json | 0 .../task-expander/tests/task_expander_test.rs | 822 +++++++++ .../tests/token_expander_test.rs | 134 +- .../tests/utils.rs | 24 +- crates/task-graph/Cargo.toml | 2 + crates/task-graph/src/lib.rs | 2 + crates/task-graph/src/task_graph.rs | 171 +- crates/task-graph/src/task_graph_error.rs | 11 +- crates/task-hasher/tests/task_hasher_test.rs | 226 +-- .../task-runner/tests/command_builder_test.rs | 48 +- .../tests/command_executor_test.rs | 12 +- .../task-runner/tests/output_archiver_test.rs | 143 +- .../task-runner/tests/output_hydrater_test.rs | 28 +- crates/task-runner/tests/task_runner_test.rs | 315 ++-- crates/task-runner/tests/utils.rs | 78 +- crates/task/src/task.rs | 4 +- crates/test-utils/Cargo.toml | 2 + crates/test-utils/src/lib.rs | 4 +- crates/test-utils/src/project_graph.rs | 26 - crates/test-utils/src/workspace_graph.rs | 27 + crates/test-utils/src/workspace_mocker.rs | 26 +- crates/workspace-graph/Cargo.toml | 22 + crates/workspace-graph/src/lib.rs | 252 +++ crates/workspace/Cargo.toml | 1 + crates/workspace/src/lib.rs | 9 - crates/workspace/src/workspace_builder.rs | 30 +- packages/nx-compat/src/moon.ts | 83 +- packages/types/src/project-config.ts | 5 +- packages/types/src/project.ts | 31 +- 108 files changed, 2886 insertions(+), 3606 deletions(-) delete mode 100644 crates/app/src/commands/graph/dep.rs create mode 100644 crates/app/src/commands/graph/task.rs delete mode 100644 crates/project-expander/src/tasks_expander.rs delete mode 100644 crates/project-expander/tests/tasks_expander_test.rs delete mode 100644 crates/project-graph/src/project_matcher.rs delete mode 100644 crates/project/src/project_error.rs create mode 100644 crates/task-expander/Cargo.toml create mode 100644 crates/task-expander/src/expander_context.rs create mode 100644 crates/task-expander/src/lib.rs rename crates/{project-expander/src/tasks_expander_new.rs => task-expander/src/task_expander.rs} (82%) rename crates/{project-expander/src/tasks_expander_error.rs => task-expander/src/task_expander_error.rs} (100%) rename crates/{project-expander => task-expander}/src/token_expander.rs (98%) rename crates/{project-expander => task-expander}/src/token_expander_error.rs (100%) rename crates/{project-expander => task-expander}/tests/__fixtures__/env-file/.env-shared (100%) rename crates/{project-expander => task-expander}/tests/__fixtures__/env-file/project/source/.env (100%) rename crates/{project-expander => task-expander}/tests/__fixtures__/env-file/project/source/.env-invalid (100%) rename crates/{project-expander => task-expander}/tests/__fixtures__/file-group/project/source/config.yml (100%) rename crates/{project-expander => task-expander}/tests/__fixtures__/file-group/project/source/dir/subdir/nested.json (100%) rename crates/{project-expander => task-expander}/tests/__fixtures__/file-group/project/source/dir/subdir/not-used.md (100%) rename crates/{project-expander => task-expander}/tests/__fixtures__/file-group/project/source/docs.md (100%) rename crates/{project-expander => task-expander}/tests/__fixtures__/file-group/project/source/other/file.json (100%) create mode 100644 crates/task-expander/tests/task_expander_test.rs rename crates/{project-expander => task-expander}/tests/token_expander_test.rs (92%) rename crates/{project-expander => task-expander}/tests/utils.rs (82%) delete mode 100644 crates/test-utils/src/project_graph.rs create mode 100644 crates/test-utils/src/workspace_graph.rs create mode 100644 crates/workspace-graph/Cargo.toml create mode 100644 crates/workspace-graph/src/lib.rs diff --git a/.yarn/versions/3fbef0f1.yml b/.yarn/versions/3fbef0f1.yml index 28fac241db0..0e53606a2e7 100644 --- a/.yarn/versions/3fbef0f1.yml +++ b/.yarn/versions/3fbef0f1.yml @@ -1,9 +1,16 @@ releases: - "@moonrepo/cli": minor - "@moonrepo/core-linux-arm64-gnu": minor - "@moonrepo/core-linux-arm64-musl": minor - "@moonrepo/core-linux-x64-gnu": minor - "@moonrepo/core-linux-x64-musl": minor - "@moonrepo/core-macos-arm64": minor - "@moonrepo/core-macos-x64": minor - "@moonrepo/core-windows-x64-msvc": minor + '@moonrepo/cli': minor + '@moonrepo/core-linux-arm64-gnu': minor + '@moonrepo/core-linux-arm64-musl': minor + '@moonrepo/core-linux-x64-gnu': minor + '@moonrepo/core-linux-x64-musl': minor + '@moonrepo/core-macos-arm64': minor + '@moonrepo/core-macos-x64': minor + '@moonrepo/core-windows-x64-msvc': minor + '@moonrepo/types': minor + +declined: + - '@moonrepo/nx-compat' + - '@moonrepo/report' + - '@moonrepo/runtime' + - website diff --git a/Cargo.lock b/Cargo.lock index 845c04c9ee1..161d95d5fab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2852,11 +2852,11 @@ dependencies = [ "moon_config", "moon_platform", "moon_project", - "moon_project_graph", "moon_query", "moon_task", "moon_task_args", "moon_test_utils2", + "moon_workspace_graph", "petgraph", "rustc-hash 2.0.0", "starbase_sandbox", @@ -2882,10 +2882,10 @@ dependencies = [ "moon_console", "moon_notifier", "moon_project", - "moon_project_graph", "moon_task", "moon_toolchain", "moon_toolchain_plugin", + "moon_workspace_graph", "num_cpus", "petgraph", "rustc-hash 2.0.0", @@ -2913,11 +2913,11 @@ dependencies = [ "moon_platform", "moon_process", "moon_project", - "moon_project_graph", "moon_task_runner", "moon_time", "moon_toolchain_plugin", "moon_vcs_hooks", + "moon_workspace_graph", "proto_core", "rustc-hash 2.0.0", "scc", @@ -2935,10 +2935,10 @@ dependencies = [ "miette", "moon_common", "moon_project", - "moon_project_graph", "moon_target", "moon_task", "moon_test_utils2", + "moon_workspace_graph", "rustc-hash 2.0.0", "serde", "tokio", @@ -3029,6 +3029,7 @@ dependencies = [ "moon_typescript_lang", "moon_vcs", "moon_workspace", + "moon_workspace_graph", "once_cell", "open", "petgraph", @@ -3643,8 +3644,8 @@ dependencies = [ "moon_common", "moon_env", "moon_pdk_api", - "moon_project_graph", "moon_target", + "moon_workspace_graph", "proto_core", "scc", "serde_json", @@ -3721,17 +3722,10 @@ dependencies = [ name = "moon_project_expander" version = "0.0.1" dependencies = [ - "dotenvy", "miette", - "moon_args", "moon_common", "moon_config", "moon_project", - "moon_task", - "moon_task_args", - "moon_time", - "pathdiff", - "regex", "rustc-hash 2.0.0", "starbase_sandbox", "starbase_utils", @@ -3922,6 +3916,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "moon_task_expander" +version = "0.0.1" +dependencies = [ + "dotenvy", + "miette", + "moon_args", + "moon_common", + "moon_config", + "moon_project", + "moon_task", + "moon_task_args", + "moon_time", + "pathdiff", + "regex", + "rustc-hash 2.0.0", + "starbase_sandbox", + "starbase_utils", + "thiserror", + "tracing", +] + [[package]] name = "moon_task_graph" version = "0.0.1" @@ -3930,8 +3946,10 @@ dependencies = [ "moon_common", "moon_config", "moon_graph_utils", + "moon_project_graph", "moon_target", "moon_task", + "moon_task_expander", "petgraph", "rustc-hash 2.0.0", "serde", @@ -4024,8 +4042,10 @@ dependencies = [ "moon_project_graph", "moon_rust_platform", "moon_system_platform", + "moon_task_graph", "moon_vcs", "moon_workspace", + "moon_workspace_graph", "proto_core", "starbase_events", "starbase_sandbox", @@ -4196,6 +4216,7 @@ dependencies = [ "moon_task_builder", "moon_task_graph", "moon_vcs", + "moon_workspace_graph", "petgraph", "rustc-hash 2.0.0", "serde", @@ -4205,6 +4226,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "moon_workspace_graph" +version = "0.0.1" +dependencies = [ + "miette", + "moon_common", + "moon_graph_utils", + "moon_project_graph", + "moon_query", + "moon_task_graph", + "scc", + "tracing", +] + [[package]] name = "native-tls" version = "0.2.12" diff --git a/crates/action-graph/Cargo.toml b/crates/action-graph/Cargo.toml index 80b4d59ae98..370b55c6769 100644 --- a/crates/action-graph/Cargo.toml +++ b/crates/action-graph/Cargo.toml @@ -17,10 +17,10 @@ moon_config = { path = "../config" } # TODO remove moon_platform = { path = "../../legacy/core/platform" } moon_project = { path = "../project" } -moon_project_graph = { path = "../project-graph" } moon_task = { path = "../task" } moon_task_args = { path = "../task-args" } moon_query = { path = "../query" } +moon_workspace_graph = { path = "../workspace-graph" } graph-cycles = "0.1.0" miette = { workspace = true } petgraph = { workspace = true } diff --git a/crates/action-graph/src/action_graph_builder.rs b/crates/action-graph/src/action_graph_builder.rs index b21e42b65db..de6f82c40e8 100644 --- a/crates/action-graph/src/action_graph_builder.rs +++ b/crates/action-graph/src/action_graph_builder.rs @@ -9,11 +9,11 @@ use moon_common::path::WorkspaceRelativePathBuf; use moon_common::{color, Id}; use moon_config::{PlatformType, TaskDependencyConfig}; use moon_platform::{PlatformManager, Runtime}; -use moon_project::{Project, ProjectError}; -use moon_project_graph::{GraphConnections, ProjectGraph}; +use moon_project::Project; use moon_query::{build_query, Criteria}; use moon_task::{Target, TargetError, TargetLocator, TargetScope, Task}; use moon_task_args::parse_task_args; +use moon_workspace_graph::{tasks::TaskGraphError, GraphConnections, WorkspaceGraph}; use petgraph::prelude::*; use rustc_hash::{FxHashMap, FxHashSet}; use std::mem; @@ -44,7 +44,7 @@ pub struct ActionGraphBuilder<'app> { graph: DiGraph, indices: FxHashMap, platform_manager: &'app PlatformManager, - project_graph: &'app ProjectGraph, + workspace_graph: &'app WorkspaceGraph, // Affected states affected: Option>, @@ -57,13 +57,13 @@ pub struct ActionGraphBuilder<'app> { } impl<'app> ActionGraphBuilder<'app> { - pub fn new(project_graph: &'app ProjectGraph) -> miette::Result { - ActionGraphBuilder::with_platforms(PlatformManager::read(), project_graph) + pub fn new(workspace_graph: &'app WorkspaceGraph) -> miette::Result { + ActionGraphBuilder::with_platforms(PlatformManager::read(), workspace_graph) } pub fn with_platforms( platform_manager: &'app PlatformManager, - project_graph: &'app ProjectGraph, + workspace_graph: &'app WorkspaceGraph, ) -> miette::Result { debug!("Building action graph"); @@ -76,7 +76,7 @@ impl<'app> ActionGraphBuilder<'app> { passthrough_targets: FxHashSet::default(), platform_manager, primary_targets: FxHashSet::default(), - project_graph, + workspace_graph, touched_files: None, }) } @@ -146,7 +146,7 @@ impl<'app> ActionGraphBuilder<'app> { if downstream != DownstreamScope::None { debug!("Force loading all projects to determine downstream relationships"); - self.project_graph.get_all()?; + self.workspace_graph.get_all_projects()?; } self.affected @@ -168,7 +168,7 @@ impl<'app> ActionGraphBuilder<'app> { touched_files: &'app FxHashSet, ) -> miette::Result<()> { self.touched_files = Some(touched_files); - self.affected = Some(AffectedTracker::new(self.project_graph, touched_files)); + self.affected = Some(AffectedTracker::new(self.workspace_graph, touched_files)); Ok(()) } @@ -453,19 +453,19 @@ impl<'app> ActionGraphBuilder<'app> { let mut projects_to_build = vec![]; // From self project - let self_project = self.project_graph.get(project_locator)?; + let self_project = self.workspace_graph.get_project(project_locator)?; projects_to_build.push(self_project.clone()); // From other projects - for dependent_id in self.project_graph.dependents_of(&self_project) { - projects_to_build.push(self.project_graph.get(&dependent_id)?); + for dependent_id in self.workspace_graph.projects.dependents_of(&self_project) { + projects_to_build.push(self.workspace_graph.get_project(&dependent_id)?); } for project in projects_to_build { // Don't skip internal tasks, since they are a dependency of the parent // task, and must still run! They just can't be ran manually. - for dep_task in project.tasks.values() { + for dep_task in self.workspace_graph.get_tasks_from_project(&project.id)? { // But do skip persistent tasks! if dep_task.is_persistent() { continue; @@ -480,7 +480,7 @@ impl<'app> ActionGraphBuilder<'app> { } if dep_task.deps.iter().any(|dep| dep.target == task.target) { - if let Some(index) = self.run_task(&project, dep_task, &reqs)? { + if let Some(index) = self.run_task(&project, &dep_task, &reqs)? { indices.push(index); } } @@ -515,20 +515,23 @@ impl<'app> ActionGraphBuilder<'app> { let mut projects = vec![]; if let Some(all_query) = &self.all_query { - projects.extend(self.project_graph.query(all_query)?); + projects.extend(self.workspace_graph.query_projects(all_query)?); } else { - projects.extend(self.project_graph.get_all()?); + projects.extend(self.workspace_graph.get_all_projects()?); }; for project in projects { // Don't error if the task does not exist - if let Ok(task) = project.get_task(&target.task_id) { + if let Ok(task) = self + .workspace_graph + .get_task_from_project(&project.id, &target.task_id) + { if task.is_internal() { continue; } if let Some(index) = - self.run_task_with_config(&project, task, reqs, config)? + self.run_task_with_config(&project, &task, reqs, config)? { inserted_targets.insert(task.target.clone()); inserted_indices.insert(index); @@ -542,19 +545,17 @@ impl<'app> ActionGraphBuilder<'app> { } // project:task TargetScope::Project(project_locator) => { - let project = self.project_graph.get(project_locator.as_str())?; - let task = project.get_task(&target.task_id)?; + let project = self.workspace_graph.get_project(project_locator)?; + let task = self + .workspace_graph + .get_task_from_project(&project.id, &target.task_id)?; // Don't allow internal tasks to be ran if task.is_internal() && reqs.has_target(&task.target) { - return Err(ProjectError::UnknownTask { - task_id: task.target.task_id.clone(), - project_id: project.id.clone(), - } - .into()); + return Err(TaskGraphError::UnconfiguredTarget(task.target.clone()).into()); } - if let Some(index) = self.run_task_with_config(&project, task, reqs, config)? { + if let Some(index) = self.run_task_with_config(&project, &task, reqs, config)? { inserted_targets.insert(task.target.to_owned()); inserted_indices.insert(index); } @@ -562,18 +563,21 @@ impl<'app> ActionGraphBuilder<'app> { // #tag:task TargetScope::Tag(tag) => { let projects = self - .project_graph - .query(build_query(format!("tag={}", tag).as_str())?)?; + .workspace_graph + .query_projects(build_query(format!("tag={}", tag).as_str())?)?; for project in projects { // Don't error if the task does not exist - if let Ok(task) = project.get_task(&target.task_id) { + if let Ok(task) = self + .workspace_graph + .get_task_from_project(&project.id, &target.task_id) + { if task.is_internal() { continue; } if let Some(index) = - self.run_task_with_config(&project, task, reqs, config)? + self.run_task_with_config(&project, &task, reqs, config)? { inserted_targets.insert(task.target.clone()); inserted_indices.insert(index); @@ -600,9 +604,10 @@ impl<'app> ActionGraphBuilder<'app> { for locator in reqs.target_locators.clone() { let target = match locator { TargetLocator::Qualified(target) => target, - TargetLocator::TaskFromWorkingDir(task_id) => { - Target::new(&self.project_graph.get_from_path(None)?.id, task_id)? - } + TargetLocator::TaskFromWorkingDir(task_id) => Target::new( + &self.workspace_graph.get_project_from_path(None)?.id, + task_id, + )?, }; // Track the qualified as an initial target @@ -672,12 +677,12 @@ impl<'app> ActionGraphBuilder<'app> { let mut edges = vec![setup_tool_index]; // And we should also depend on other projects - for dep_project_id in self.project_graph.dependencies_of(project) { + for dep_project_id in self.workspace_graph.projects.dependencies_of(project) { if cycle.contains(&dep_project_id) { continue; } - let dep_project = self.project_graph.get(&dep_project_id)?; + let dep_project = self.workspace_graph.get_project(&dep_project_id)?; let dep_project_index = self.internal_sync_project(&dep_project, cycle)?; if index != dep_project_index { diff --git a/crates/action-graph/tests/action_graph_test.rs b/crates/action-graph/tests/action_graph_test.rs index 76e72fa379d..33fb048d0e8 100644 --- a/crates/action-graph/tests/action_graph_test.rs +++ b/crates/action-graph/tests/action_graph_test.rs @@ -9,9 +9,9 @@ use moon_common::path::WorkspaceRelativePathBuf; use moon_common::Id; use moon_config::{PlatformType, TaskArgs, TaskDependencyConfig}; use moon_platform::*; -use moon_project_graph::ProjectGraph; use moon_task::{Target, TargetLocator, Task}; -use moon_test_utils2::generate_project_graph; +use moon_test_utils2::generate_workspace_graph; +use moon_workspace_graph::WorkspaceGraph; use rustc_hash::{FxHashMap, FxHashSet}; use starbase_sandbox::{assert_snapshot, create_sandbox}; use std::env; @@ -25,8 +25,8 @@ fn create_task(id: &str, project: &str) -> Task { } } -async fn create_project_graph() -> ProjectGraph { - generate_project_graph("projects").await +async fn create_project_graph() -> WorkspaceGraph { + generate_workspace_graph("projects").await } // fn create_bun_runtime() -> Runtime { @@ -78,19 +78,25 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("deps").unwrap(); + let project = container.workspace_graph.get_project("deps").unwrap(); builder .run_task( &project, - project.get_task("cycle1").unwrap(), + &container + .workspace_graph + .get_task_from_project(&project.id, "cycle1") + .unwrap(), &RunRequirements::default(), ) .unwrap(); builder .run_task( &project, - project.get_task("cycle2").unwrap(), + &container + .workspace_graph + .get_task_from_project(&project.id, "cycle2") + .unwrap(), &RunRequirements::default(), ) .unwrap(); @@ -107,7 +113,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let bar = container.project_graph.get("bar").unwrap(); + let bar = container.workspace_graph.get_project("bar").unwrap(); builder.install_deps(&bar, None).unwrap(); let graph = builder.build(); @@ -133,7 +139,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let bar = container.project_graph.get("bar").unwrap(); + let bar = container.workspace_graph.get_project("bar").unwrap(); builder.install_deps(&bar, None).unwrap(); builder.install_deps(&bar, None).unwrap(); builder.install_deps(&bar, None).unwrap(); @@ -160,10 +166,10 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let inside = container.project_graph.get("in").unwrap(); + let inside = container.workspace_graph.get_project("in").unwrap(); builder.install_deps(&inside, None).unwrap(); - let outside = container.project_graph.get("out").unwrap(); + let outside = container.workspace_graph.get_project("out").unwrap(); builder.install_deps(&outside, None).unwrap(); let graph = builder.build(); @@ -201,7 +207,7 @@ mod action_graph { node.platform = PlatformType::Node; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); builder.install_deps(&project, Some(&bun)).unwrap(); builder.install_deps(&project, Some(&node)).unwrap(); @@ -223,7 +229,7 @@ mod action_graph { // Reverse order let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); builder.install_deps(&project, Some(&node)).unwrap(); builder.install_deps(&project, Some(&bun)).unwrap(); @@ -254,7 +260,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Node; @@ -291,7 +297,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Node; @@ -333,7 +339,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Node; @@ -359,7 +365,7 @@ mod action_graph { let file = WorkspaceRelativePathBuf::from("bar/file.js"); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Node; @@ -384,7 +390,7 @@ mod action_graph { let mut builder = container.create_builder(); // node - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Rust; @@ -425,7 +431,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.options.interactive = true; @@ -452,7 +458,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let task = create_task("build", "bar"); builder @@ -484,7 +490,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.options.persistent = true; @@ -511,7 +517,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Node; @@ -587,7 +593,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Node; @@ -645,7 +651,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Node; @@ -703,7 +709,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Node; @@ -779,7 +785,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Node; @@ -837,7 +843,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.platform = PlatformType::Node; @@ -938,7 +944,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.options.run_in_ci = true; @@ -968,13 +974,16 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("ci").unwrap(); - let task = project.get_task("ci3-dependency").unwrap(); + let project = container.workspace_graph.get_project("ci").unwrap(); + let task = container + .workspace_graph + .get_task_from_project(&project.id, "ci3-dependency") + .unwrap(); builder .run_task( &project, - task, + &task, &RunRequirements { ci: true, ci_check: true, @@ -995,13 +1004,16 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("ci").unwrap(); - let task = project.get_task("ci4-dependency").unwrap(); + let project = container.workspace_graph.get_project("ci").unwrap(); + let task = container + .workspace_graph + .get_task_from_project(&project.id, "ci4-dependency") + .unwrap(); builder .run_task( &project, - task, + &task, &RunRequirements { ci: true, ci_check: true, @@ -1026,7 +1038,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.options.run_in_ci = false; @@ -1063,7 +1075,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.options.run_in_ci = false; @@ -1093,7 +1105,7 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("bar").unwrap(); + let project = container.workspace_graph.get_project("bar").unwrap(); let mut task = create_task("build", "bar"); task.options.run_in_ci = false; @@ -1124,7 +1136,7 @@ mod action_graph { // let container = ActionGraphContainer::new(sandbox.path()).await; // let mut builder = container.create_builder(); - // let project = container.project_graph.get("ci").unwrap(); + // let project = container.workspace_graph.get_project("ci").unwrap(); // let task = project.get_task("ci2-dependency").unwrap(); // // Must be affected to run the dependent @@ -1157,13 +1169,16 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("ci").unwrap(); - let task = project.get_task("ci2-dependency").unwrap(); + let project = container.workspace_graph.get_project("ci").unwrap(); + let task = container + .workspace_graph + .get_task_from_project(&project.id, "ci2-dependency") + .unwrap(); builder .run_task( &project, - task, + &task, &RunRequirements { ci: true, ci_check: true, @@ -1184,13 +1199,16 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("ci").unwrap(); - let task = project.get_task("ci2-dependency").unwrap(); + let project = container.workspace_graph.get_project("ci").unwrap(); + let task = container + .workspace_graph + .get_task_from_project(&project.id, "ci2-dependency") + .unwrap(); builder .run_task( &project, - task, + &task, &RunRequirements { ci: true, ci_check: true, @@ -1225,11 +1243,14 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("deps").unwrap(); - let task = project.get_task("parallel").unwrap(); + let project = container.workspace_graph.get_project("deps").unwrap(); + let task = container + .workspace_graph + .get_task_from_project(&project.id, "parallel") + .unwrap(); builder - .run_task(&project, task, &RunRequirements::default()) + .run_task(&project, &task, &RunRequirements::default()) .unwrap(); let graph = builder.build(); @@ -1243,11 +1264,14 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("deps").unwrap(); - let task = project.get_task("serial").unwrap(); + let project = container.workspace_graph.get_project("deps").unwrap(); + let task = container + .workspace_graph + .get_task_from_project(&project.id, "serial") + .unwrap(); builder - .run_task(&project, task, &RunRequirements::default()) + .run_task(&project, &task, &RunRequirements::default()) .unwrap(); let graph = builder.build(); @@ -1261,11 +1285,14 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("deps").unwrap(); - let task = project.get_task("chain1").unwrap(); + let project = container.workspace_graph.get_project("deps").unwrap(); + let task = container + .workspace_graph + .get_task_from_project(&project.id, "chain1") + .unwrap(); builder - .run_task(&project, task, &RunRequirements::default()) + .run_task(&project, &task, &RunRequirements::default()) .unwrap(); let graph = builder.build(); @@ -1279,11 +1306,14 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("deps").unwrap(); - let task = project.get_task("base").unwrap(); + let project = container.workspace_graph.get_project("deps").unwrap(); + let task = container + .workspace_graph + .get_task_from_project(&project.id, "base") + .unwrap(); builder - .run_task(&project, task, &RunRequirements::default()) + .run_task(&project, &task, &RunRequirements::default()) .unwrap(); let graph = builder.build(); @@ -1297,13 +1327,16 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("deps").unwrap(); - let task = project.get_task("base").unwrap(); + let project = container.workspace_graph.get_project("deps").unwrap(); + let task = container + .workspace_graph + .get_task_from_project(&project.id, "base") + .unwrap(); builder .run_task( &project, - task, + &task, &RunRequirements { dependents: true, ..RunRequirements::default() @@ -1322,13 +1355,16 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let project = container.project_graph.get("deps").unwrap(); - let task = project.get_task("base").unwrap(); + let project = container.workspace_graph.get_project("deps").unwrap(); + let task = container + .workspace_graph + .get_task_from_project(&project.id, "base") + .unwrap(); builder .run_task( &project, - task, + &task, &RunRequirements { ci: true, dependents: true, @@ -1639,49 +1675,6 @@ mod action_graph { assert_snapshot!(graph.to_dot()); } - #[tokio::test] - async fn runs_by_file_path() { - let sandbox = create_sandbox("tasks"); - let mut container = ActionGraphContainer::new(sandbox.path()).await; - - container.project_graph.working_dir = sandbox.path().join("server/nested"); - - let mut builder = container.create_builder(); - - builder - .run_from_requirements(RunRequirements { - target_locators: FxHashSet::from_iter([TargetLocator::TaskFromWorkingDir( - Id::raw("lint"), - )]), - ..Default::default() - }) - .unwrap(); - - let graph = builder.build(); - - assert_snapshot!(graph.to_dot()); - } - - #[tokio::test] - #[should_panic(expected = "No project could be located starting from path unknown/path.")] - async fn errors_if_no_project_by_path() { - let sandbox = create_sandbox("tasks"); - let mut container = ActionGraphContainer::new(sandbox.path()).await; - - container.project_graph.working_dir = sandbox.path().join("unknown/path"); - - let mut builder = container.create_builder(); - - builder - .run_from_requirements(RunRequirements { - target_locators: FxHashSet::from_iter([TargetLocator::TaskFromWorkingDir( - Id::raw("lint"), - )]), - ..Default::default() - }) - .unwrap(); - } - #[tokio::test] async fn computes_context() { let sandbox = create_sandbox("tasks"); @@ -1748,8 +1741,8 @@ mod action_graph { #[tokio::test] async fn graphs() { - let pg = ProjectGraph::default(); - let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let wg = WorkspaceGraph::default(); + let mut builder = ActionGraphBuilder::new(&wg).unwrap(); let system = Runtime::system(); let node = Runtime::new( PlatformType::Node, @@ -1774,8 +1767,8 @@ mod action_graph { #[tokio::test] async fn graphs_same_platform() { - let pg = ProjectGraph::default(); - let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let wg = WorkspaceGraph::default(); + let mut builder = ActionGraphBuilder::new(&wg).unwrap(); let node1 = Runtime::new( PlatformType::Node, @@ -1807,8 +1800,8 @@ mod action_graph { #[tokio::test] async fn ignores_dupes() { - let pg = ProjectGraph::default(); - let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let wg = WorkspaceGraph::default(); + let mut builder = ActionGraphBuilder::new(&wg).unwrap(); let system = Runtime::system(); builder.setup_toolchain(&system); @@ -1831,10 +1824,10 @@ mod action_graph { #[tokio::test] async fn graphs_single() { - let pg = create_project_graph().await; - let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let wg = create_project_graph().await; + let mut builder = ActionGraphBuilder::new(&wg).unwrap(); - let bar = pg.get("bar").unwrap(); + let bar = wg.get_project("bar").unwrap(); builder.sync_project(&bar).unwrap(); let graph = builder.build(); @@ -1857,10 +1850,10 @@ mod action_graph { #[tokio::test] async fn graphs_single_with_dep() { - let pg = create_project_graph().await; - let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let wg = create_project_graph().await; + let mut builder = ActionGraphBuilder::new(&wg).unwrap(); - let foo = pg.get("foo").unwrap(); + let foo = wg.get_project("foo").unwrap(); builder.sync_project(&foo).unwrap(); let graph = builder.build(); @@ -1887,16 +1880,16 @@ mod action_graph { #[tokio::test] async fn graphs_multiple() { - let pg = create_project_graph().await; - let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let wg = create_project_graph().await; + let mut builder = ActionGraphBuilder::new(&wg).unwrap(); - let foo = pg.get("foo").unwrap(); + let foo = wg.get_project("foo").unwrap(); builder.sync_project(&foo).unwrap(); - let bar = pg.get("bar").unwrap(); + let bar = wg.get_project("bar").unwrap(); builder.sync_project(&bar).unwrap(); - let qux = pg.get("qux").unwrap(); + let qux = wg.get_project("qux").unwrap(); builder.sync_project(&qux).unwrap(); let graph = builder.build(); @@ -1927,10 +1920,10 @@ mod action_graph { #[tokio::test] async fn ignores_dupes() { - let pg = create_project_graph().await; - let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let wg = create_project_graph().await; + let mut builder = ActionGraphBuilder::new(&wg).unwrap(); - let foo = pg.get("foo").unwrap(); + let foo = wg.get_project("foo").unwrap(); builder.sync_project(&foo).unwrap(); builder.sync_project(&foo).unwrap(); @@ -1963,10 +1956,10 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let bar = container.project_graph.get("bar").unwrap(); + let bar = container.workspace_graph.get_project("bar").unwrap(); builder.sync_project(&bar).unwrap(); - let qux = container.project_graph.get("qux").unwrap(); + let qux = container.workspace_graph.get_project("qux").unwrap(); builder.sync_project(&qux).unwrap(); let graph = builder.build(); @@ -2000,10 +1993,10 @@ mod action_graph { let container = ActionGraphContainer::new(sandbox.path()).await; let mut builder = container.create_builder(); - let bar = container.project_graph.get("bar").unwrap(); + let bar = container.workspace_graph.get_project("bar").unwrap(); builder.sync_project(&bar).unwrap(); - let baz = container.project_graph.get("baz").unwrap(); + let baz = container.workspace_graph.get_project("baz").unwrap(); builder.sync_project(&baz).unwrap(); let graph = builder.build(); @@ -2043,9 +2036,9 @@ mod action_graph { #[tokio::test] async fn graphs() { - let pg = ProjectGraph::default(); + let wg = WorkspaceGraph::default(); - let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let mut builder = ActionGraphBuilder::new(&wg).unwrap(); builder.sync_workspace(); let graph = builder.build(); @@ -2056,9 +2049,9 @@ mod action_graph { #[tokio::test] async fn ignores_dupes() { - let pg = ProjectGraph::default(); + let wg = WorkspaceGraph::default(); - let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let mut builder = ActionGraphBuilder::new(&wg).unwrap(); builder.sync_workspace(); builder.sync_workspace(); builder.sync_workspace(); diff --git a/crates/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_all.snap b/crates/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_all.snap index 36cd2a7a823..2ff2ee51180 100644 --- a/crates/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_all.snap +++ b/crates/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_all.snap @@ -11,8 +11,8 @@ digraph { 5 [ label="SyncProject(system, server)" ] 6 [ label="SyncProject(system, common)" ] 7 [ label="RunTask(client:build)" ] - 8 [ label="RunTask(common:build)" ] - 9 [ label="RunTask(server:build)" ] + 8 [ label="RunTask(server:build)" ] + 9 [ label="RunTask(common:build)" ] 1 -> 0 [ ] 2 -> 1 [ ] 3 -> 2 [ ] @@ -22,8 +22,8 @@ digraph { 4 -> 1 [ ] 4 -> 5 [ ] 4 -> 6 [ ] - 8 -> 6 [ ] - 9 -> 5 [ ] + 8 -> 5 [ ] + 9 -> 6 [ ] 7 -> 4 [ ] 7 -> 8 [ ] 7 -> 9 [ ] diff --git a/crates/action-graph/tests/utils.rs b/crates/action-graph/tests/utils.rs index 76498f13202..2afeac604e4 100644 --- a/crates/action-graph/tests/utils.rs +++ b/crates/action-graph/tests/utils.rs @@ -1,25 +1,25 @@ use moon_action_graph::ActionGraphBuilder; use moon_platform::PlatformManager; -use moon_project_graph::ProjectGraph; use moon_test_utils2::{ - generate_platform_manager_from_sandbox, generate_project_graph_from_sandbox, + generate_platform_manager_from_sandbox, generate_workspace_graph_from_sandbox, }; +use moon_workspace_graph::WorkspaceGraph; use std::path::Path; pub struct ActionGraphContainer { pub platform_manager: PlatformManager, - pub project_graph: ProjectGraph, + pub workspace_graph: WorkspaceGraph, } impl ActionGraphContainer { pub async fn new(root: &Path) -> Self { Self { platform_manager: generate_platform_manager_from_sandbox(root).await, - project_graph: generate_project_graph_from_sandbox(root).await, + workspace_graph: generate_workspace_graph_from_sandbox(root).await, } } pub fn create_builder(&self) -> ActionGraphBuilder { - ActionGraphBuilder::with_platforms(&self.platform_manager, &self.project_graph).unwrap() + ActionGraphBuilder::with_platforms(&self.platform_manager, &self.workspace_graph).unwrap() } } diff --git a/crates/action-pipeline/Cargo.toml b/crates/action-pipeline/Cargo.toml index b276bb5274a..249782f1738 100644 --- a/crates/action-pipeline/Cargo.toml +++ b/crates/action-pipeline/Cargo.toml @@ -17,10 +17,10 @@ moon_common = { path = "../common" } moon_console = { path = "../console" } moon_notifier = { path = "../notifier" } moon_project = { path = "../project" } -moon_project_graph = { path = "../project-graph" } moon_task = { path = "../task" } moon_toolchain = { path = "../toolchain" } moon_toolchain_plugin = { path = "../toolchain-plugin" } +moon_workspace_graph = { path = "../workspace-graph" } async-trait = { workspace = true } miette = { workspace = true } num_cpus = "1.16.0" diff --git a/crates/action-pipeline/src/action_pipeline.rs b/crates/action-pipeline/src/action_pipeline.rs index 4e9a6a1e1d2..31b66152384 100644 --- a/crates/action-pipeline/src/action_pipeline.rs +++ b/crates/action-pipeline/src/action_pipeline.rs @@ -13,8 +13,8 @@ use moon_action_graph::ActionGraph; use moon_api::Moonbase; use moon_app_context::AppContext; use moon_common::{color, is_ci, is_test_env}; -use moon_project_graph::ProjectGraph; use moon_toolchain_plugin::ToolchainRegistry; +use moon_workspace_graph::WorkspaceGraph; use rustc_hash::{FxHashMap, FxHashSet}; use std::mem; use std::sync::Arc; @@ -39,15 +39,15 @@ pub struct ActionPipeline { app_context: Arc, action_context: Arc, emitter: Arc, - project_graph: Arc, toolchain_registry: Arc, + workspace_graph: WorkspaceGraph, } impl ActionPipeline { pub fn new( app_context: Arc, - project_graph: Arc, toolchain_registry: Arc, + workspace_graph: WorkspaceGraph, ) -> Self { debug!("Creating pipeline to run actions"); @@ -60,10 +60,10 @@ impl ActionPipeline { concurrency: num_cpus::get(), duration: None, emitter: Arc::new(EventEmitter::default()), - project_graph, report_name: "runReport.json".into(), summarize: false, toolchain_registry, + workspace_graph, } } @@ -148,11 +148,11 @@ impl ActionPipeline { cancel_token: cancel_token.clone(), completed_jobs: Arc::new(RwLock::new(FxHashSet::default())), emitter: Arc::clone(&self.emitter), - project_graph: Arc::clone(&self.project_graph), result_sender: sender, semaphore: Arc::new(Semaphore::new(self.concurrency)), running_jobs: Arc::new(RwLock::new(FxHashMap::default())), toolchain_registry: Arc::clone(&self.toolchain_registry), + workspace_graph: self.workspace_graph.clone(), }; // Monitor signals and ctrl+c diff --git a/crates/action-pipeline/src/action_runner.rs b/crates/action-pipeline/src/action_runner.rs index fb6bfcc681b..7c679b2b2f0 100644 --- a/crates/action-pipeline/src/action_runner.rs +++ b/crates/action-pipeline/src/action_runner.rs @@ -4,8 +4,8 @@ use moon_action_context::ActionContext; use moon_actions::actions::*; use moon_app_context::AppContext; use moon_common::color; -use moon_project_graph::ProjectGraph; use moon_toolchain_plugin::ToolchainRegistry; +use moon_workspace_graph::WorkspaceGraph; use std::sync::Arc; use tracing::{instrument, trace}; @@ -14,7 +14,7 @@ pub async fn run_action( action: &mut Action, action_context: Arc, app_context: Arc, - project_graph: Arc, + workspace_graph: WorkspaceGraph, toolchain_registry: Arc, emitter: Arc, ) -> miette::Result<()> { @@ -42,7 +42,7 @@ pub async fn run_action( action, action_context, app_context, - project_graph, + workspace_graph.clone(), toolchain_registry, ) .await; @@ -57,7 +57,7 @@ pub async fn run_action( } ActionNode::SyncProject(inner) => { - let project = project_graph.get(&inner.project)?; + let project = workspace_graph.get_project(&inner.project)?; emitter .emit(Event::ProjectSyncing { @@ -66,8 +66,14 @@ pub async fn run_action( }) .await?; - let result = - sync_project(action, action_context, app_context, project_graph, inner).await; + let result = sync_project( + action, + action_context, + app_context, + workspace_graph.clone(), + inner, + ) + .await; emitter .emit(Event::ProjectSynced { @@ -111,7 +117,7 @@ pub async fn run_action( action, action_context, app_context, - project_graph, + workspace_graph.clone(), &inner.runtime, None, ) @@ -129,7 +135,7 @@ pub async fn run_action( } ActionNode::InstallProjectDeps(inner) => { - let project = project_graph.get(&inner.project)?; + let project = workspace_graph.get_project(&inner.project)?; emitter .emit(Event::DependenciesInstalling { @@ -142,7 +148,7 @@ pub async fn run_action( action, action_context, app_context, - project_graph, + workspace_graph.clone(), &inner.runtime, Some(&project), ) @@ -167,7 +173,14 @@ pub async fn run_action( }) .await?; - let result = run_task(action, action_context, app_context, project_graph, inner).await; + let result = run_task( + action, + action_context, + app_context, + workspace_graph.clone(), + inner, + ) + .await; emitter .emit(Event::TaskRan { diff --git a/crates/action-pipeline/src/job.rs b/crates/action-pipeline/src/job.rs index ed631bf525f..20536dd7e14 100644 --- a/crates/action-pipeline/src/job.rs +++ b/crates/action-pipeline/src/job.rs @@ -51,7 +51,7 @@ impl Job { &mut action, self.action_context, self.app_context, - self.context.project_graph.clone(), + self.context.workspace_graph.clone(), self.context.toolchain_registry.clone(), self.context.emitter.clone(), ) => {}, diff --git a/crates/action-pipeline/src/job_context.rs b/crates/action-pipeline/src/job_context.rs index f4b983f1759..d78065303b2 100644 --- a/crates/action-pipeline/src/job_context.rs +++ b/crates/action-pipeline/src/job_context.rs @@ -1,7 +1,7 @@ use crate::event_emitter::EventEmitter; use moon_action::Action; -use moon_project_graph::ProjectGraph; use moon_toolchain_plugin::ToolchainRegistry; +use moon_workspace_graph::WorkspaceGraph; use petgraph::graph::NodeIndex; use rustc_hash::{FxHashMap, FxHashSet}; use std::sync::Arc; @@ -22,9 +22,6 @@ pub struct JobContext { /// Internal pipeline event emitter pub emitter: Arc, - /// The project graph, for use within actions - pub project_graph: Arc, - /// Sends results to the parent pipeline pub result_sender: Sender, @@ -36,6 +33,9 @@ pub struct JobContext { /// The registry of all toolchain plugins pub toolchain_registry: Arc, + + /// The project and task graphs, for use within actions + pub workspace_graph: WorkspaceGraph, } impl JobContext { diff --git a/crates/actions/Cargo.toml b/crates/actions/Cargo.toml index 80fc15e59fd..9926a0a6304 100644 --- a/crates/actions/Cargo.toml +++ b/crates/actions/Cargo.toml @@ -17,11 +17,11 @@ moon_hash = { path = "../hash" } moon_pdk_api = { path = "../pdk-api" } moon_process = { path = "../process" } moon_project = { path = "../project" } -moon_project_graph = { path = "../project-graph" } moon_task_runner = { path = "../task-runner" } moon_time = { path = "../time" } moon_toolchain_plugin = { path = "../toolchain-plugin" } moon_vcs_hooks = { path = "../vcs-hooks" } +moon_workspace_graph = { path = "../workspace-graph" } miette = { workspace = true } proto_core = { workspace = true } rustc-hash = { workspace = true } diff --git a/crates/actions/src/actions/install_deps.rs b/crates/actions/src/actions/install_deps.rs index 812c2a53fb1..603f4f8794a 100644 --- a/crates/actions/src/actions/install_deps.rs +++ b/crates/actions/src/actions/install_deps.rs @@ -8,8 +8,8 @@ use moon_common::{color, is_ci}; use moon_config::PlatformType; use moon_platform::{BoxedPlatform, PlatformManager, Runtime}; use moon_project::Project; -use moon_project_graph::ProjectGraph; use moon_time::to_millis; +use moon_workspace_graph::WorkspaceGraph; use starbase_utils::fs; use std::path::PathBuf; use std::sync::Arc; @@ -28,7 +28,7 @@ pub async fn install_deps( action: &mut Action, action_context: Arc, app_context: Arc, - project_graph: Arc, + workspace_graph: WorkspaceGraph, runtime: &Runtime, project: Option<&Project>, ) -> miette::Result { @@ -93,7 +93,7 @@ pub async fn install_deps( action, &action_context, &app_context, - &project_graph, + &workspace_graph, project, platform, &manifest_name, @@ -177,7 +177,7 @@ async fn hash_manifests( action: &mut Action, action_context: &ActionContext, app_context: &AppContext, - project_graph: &ProjectGraph, + workspace_graph: &WorkspaceGraph, project: Option<&Project>, platform: &BoxedPlatform, manifest_name: &str, @@ -216,7 +216,7 @@ async fn hash_manifests( } // When running in the workspace root, include all project manifests else { - for project in project_graph.get_all()? { + for project in workspace_graph.projects.get_all_unexpanded() { let project_manifest = project.root.join(manifest_name); if project_manifest.exists() { diff --git a/crates/actions/src/actions/run_task.rs b/crates/actions/src/actions/run_task.rs index d01b6e415ba..19c2f6af2e5 100644 --- a/crates/actions/src/actions/run_task.rs +++ b/crates/actions/src/actions/run_task.rs @@ -2,31 +2,31 @@ use moon_action::{Action, ActionStatus, RunTaskNode}; use moon_action_context::ActionContext; use moon_app_context::AppContext; use moon_common::color; -use moon_project_graph::ProjectGraph; use moon_task_runner::TaskRunner; +use moon_workspace_graph::WorkspaceGraph; use std::sync::Arc; use tracing::{instrument, warn}; -#[instrument(skip(action, action_context, app_context, project_graph))] +#[instrument(skip(action, action_context, app_context, workspace_graph))] pub async fn run_task( action: &mut Action, action_context: Arc, app_context: Arc, - project_graph: Arc, + workspace_graph: WorkspaceGraph, node: &RunTaskNode, ) -> miette::Result { let project_id = node .target .get_project_id() .expect("Project required for running tasks!"); - let project = project_graph.get(project_id)?; - let task = project.get_task(&node.target.task_id)?; + let project = workspace_graph.get_project(project_id)?; + let task = workspace_graph.get_task(&node.target)?; // Must be set before running the task in case it fails and // and error is bubbled up the stack action.allow_failure = task.options.allow_failure; - let result = TaskRunner::new(&app_context, &project, task)? + let result = TaskRunner::new(&app_context, &project, &task)? .run(&action_context, &action.node) .await?; diff --git a/crates/actions/src/actions/sync_project.rs b/crates/actions/src/actions/sync_project.rs index 8c45141535f..cf220e28fb2 100644 --- a/crates/actions/src/actions/sync_project.rs +++ b/crates/actions/src/actions/sync_project.rs @@ -4,20 +4,21 @@ use moon_action_context::ActionContext; use moon_app_context::AppContext; use moon_common::{color, is_ci}; use moon_platform::PlatformManager; -use moon_project_graph::ProjectGraph; +use moon_workspace_graph::WorkspaceGraph; use rustc_hash::FxHashMap; use std::sync::Arc; use tracing::{debug, instrument, warn}; -#[instrument(skip(_action, action_context, app_context, project_graph))] +#[instrument(skip(_action, action_context, app_context, workspace_graph))] pub async fn sync_project( _action: &mut Action, action_context: Arc, app_context: Arc, - project_graph: Arc, + workspace_graph: WorkspaceGraph, node: &SyncProjectNode, ) -> miette::Result { - let project = project_graph.get(&node.project)?; + // Include tasks for snapshot! + let project = workspace_graph.get_project_with_tasks(&node.project)?; if let Some(value) = should_skip_action_matching("MOON_SKIP_SYNC_PROJECT", &project.id) { debug!( @@ -43,7 +44,10 @@ pub async fn sync_project( let mut dependencies = FxHashMap::default(); for dep_config in &project.dependencies { - dependencies.insert(dep_config.id.to_owned(), project_graph.get(&dep_config.id)?); + dependencies.insert( + dep_config.id.to_owned(), + workspace_graph.get_project(&dep_config.id)?, + ); } // Sync the projects and return true if any files have been mutated diff --git a/crates/actions/src/actions/sync_workspace.rs b/crates/actions/src/actions/sync_workspace.rs index 47d7127431a..dd0dccab794 100644 --- a/crates/actions/src/actions/sync_workspace.rs +++ b/crates/actions/src/actions/sync_workspace.rs @@ -7,8 +7,8 @@ use moon_action::{Action, ActionStatus, Operation}; use moon_action_context::ActionContext; use moon_app_context::AppContext; use moon_common::color; -use moon_project_graph::ProjectGraph; use moon_toolchain_plugin::ToolchainRegistry; +use moon_workspace_graph::WorkspaceGraph; use std::sync::Arc; use tokio::task; use tracing::{debug, instrument}; @@ -18,7 +18,7 @@ pub async fn sync_workspace( action: &mut Action, _action_context: Arc, app_context: Arc, - project_graph: Arc, + workspace_graph: WorkspaceGraph, toolchain_registry: Arc, ) -> miette::Result { if should_skip_action("MOON_SKIP_SYNC_WORKSPACE").is_some() { @@ -59,12 +59,11 @@ pub async fn sync_workspace( ); let app_context = Arc::clone(&app_context); - let project_graph = Arc::clone(&project_graph); operation_futures.push(task::spawn(async move { let op = Operation::sync_operation("Codeowners") .track_async_with_check( - || sync_codeowners(&app_context, &project_graph, false), + || sync_codeowners(&app_context, &workspace_graph, false), |result| result.is_some(), ) .await?; diff --git a/crates/actions/src/operations/sync_codeowners.rs b/crates/actions/src/operations/sync_codeowners.rs index 454d74d1e0e..59fcc8efa8f 100644 --- a/crates/actions/src/operations/sync_codeowners.rs +++ b/crates/actions/src/operations/sync_codeowners.rs @@ -1,14 +1,14 @@ use moon_app_context::AppContext; use moon_codeowners::{CodeownersGenerator, CodeownersHash}; use moon_config::CodeownersOrderBy; -use moon_project_graph::ProjectGraph; +use moon_workspace_graph::WorkspaceGraph; use std::path::PathBuf; use tracing::instrument; #[instrument(skip_all)] pub async fn sync_codeowners( app_context: &AppContext, - project_graph: &ProjectGraph, + workspace_graph: &WorkspaceGraph, force: bool, ) -> miette::Result> { let mut generator = CodeownersGenerator::new( @@ -17,7 +17,7 @@ pub async fn sync_codeowners( )?; // Sort the projects based on config - let mut projects = project_graph.get_all_unexpanded(); + let mut projects = workspace_graph.projects.get_all_unexpanded(); let order_by = app_context.workspace_config.codeowners.order_by; projects.sort_by(|a, d| match order_by { diff --git a/crates/affected/Cargo.toml b/crates/affected/Cargo.toml index 30094be85ee..03358433b14 100644 --- a/crates/affected/Cargo.toml +++ b/crates/affected/Cargo.toml @@ -11,9 +11,9 @@ publish = false [dependencies] moon_common = { path = "../common" } moon_project = { path = "../project" } -moon_project_graph = { path = "../project-graph" } moon_target = { path = "../target" } moon_task = { path = "../task" } +moon_workspace_graph = { path = "../workspace-graph" } clap = { workspace = true } miette = { workspace = true } rustc-hash = { workspace = true } diff --git a/crates/affected/src/affected_tracker.rs b/crates/affected/src/affected_tracker.rs index 3d358af1a60..894cd2a119d 100644 --- a/crates/affected/src/affected_tracker.rs +++ b/crates/affected/src/affected_tracker.rs @@ -2,14 +2,14 @@ use crate::affected::*; use moon_common::path::WorkspaceRelativePathBuf; use moon_common::{color, Id}; use moon_project::Project; -use moon_project_graph::{GraphConnections, ProjectGraph}; -use moon_task::{Target, TargetScope, Task}; +use moon_task::{Target, Task}; +use moon_workspace_graph::{GraphConnections, WorkspaceGraph}; use rustc_hash::{FxHashMap, FxHashSet}; use std::env; use tracing::{debug, trace}; pub struct AffectedTracker<'app> { - project_graph: &'app ProjectGraph, + workspace_graph: &'app WorkspaceGraph, touched_files: &'app FxHashSet, projects: FxHashMap>, @@ -23,13 +23,13 @@ pub struct AffectedTracker<'app> { impl<'app> AffectedTracker<'app> { pub fn new( - project_graph: &'app ProjectGraph, + workspace_graph: &'app WorkspaceGraph, touched_files: &'app FxHashSet, ) -> Self { debug!("Creating affected tracker"); Self { - project_graph, + workspace_graph, touched_files, projects: FxHashMap::default(), project_downstream: DownstreamScope::None, @@ -125,7 +125,7 @@ impl<'app> AffectedTracker<'app> { pub fn track_projects(&mut self) -> miette::Result<&mut Self> { debug!("Tracking projects and marking any affected"); - for project in self.project_graph.get_all()? { + for project in self.workspace_graph.get_all_projects()? { if let Some(affected) = self.is_project_affected(&project) { self.mark_project_affected(&project, affected)?; } @@ -198,7 +198,7 @@ impl<'app> AffectedTracker<'app> { } } - for dep_id in self.project_graph.dependencies_of(project) { + for dep_id in self.workspace_graph.projects.dependencies_of(project) { self.projects .entry(dep_id.clone()) .or_default() @@ -208,7 +208,7 @@ impl<'app> AffectedTracker<'app> { continue; } - let dep_project = self.project_graph.get(&dep_id)?; + let dep_project = self.workspace_graph.get_project(&dep_id)?; self.track_project_dependencies(&dep_project, depth + 1)?; } @@ -240,7 +240,7 @@ impl<'app> AffectedTracker<'app> { } } - for dep_id in self.project_graph.dependents_of(project) { + for dep_id in self.workspace_graph.projects.dependents_of(project) { self.projects .entry(dep_id.clone()) .or_default() @@ -250,7 +250,7 @@ impl<'app> AffectedTracker<'app> { continue; } - let dep_project = self.project_graph.get(&dep_id)?; + let dep_project = self.workspace_graph.get_project(&dep_id)?; self.track_project_dependents(&dep_project, depth + 1)?; } @@ -261,11 +261,9 @@ impl<'app> AffectedTracker<'app> { pub fn track_tasks(&mut self) -> miette::Result<()> { debug!("Tracking tasks and marking any affected"); - for project in self.project_graph.get_all()? { - for task in project.get_tasks()? { - if let Some(affected) = self.is_task_affected(task)? { - self.mark_task_affected(task, affected)?; - } + for task in self.workspace_graph.get_all_tasks()? { + if let Some(affected) = self.is_task_affected(&task)? { + self.mark_task_affected(&task, affected)?; } } @@ -278,23 +276,11 @@ impl<'app> AffectedTracker<'app> { "Tracking tasks by target and marking any affected", ); - let mut lookup = FxHashMap::<&Id, Vec<&Id>>::default(); - for target in targets { - if let TargetScope::Project(project_id) = &target.scope { - lookup.entry(project_id).or_default().push(&target.task_id); - } - } + let task = self.workspace_graph.get_task(target)?; - for (project_id, task_ids) in lookup { - let project = self.project_graph.get(project_id)?; - - for task_id in task_ids { - let task = project.get_task(task_id)?; - - if let Some(affected) = self.is_task_affected(task)? { - self.mark_task_affected(task, affected)?; - } + if let Some(affected) = self.is_task_affected(&task)? { + self.mark_task_affected(&task, affected)?; } } @@ -384,12 +370,9 @@ impl<'app> AffectedTracker<'app> { continue; } - if let TargetScope::Project(project_id) = &dep_config.target.scope { - let dep_project = self.project_graph.get(project_id)?; - let dep_task = dep_project.get_task(&dep_config.target.task_id)?; + let dep_task = self.workspace_graph.get_task(&dep_config.target)?; - self.track_task_dependencies(dep_task, depth + 1)?; - } + self.track_task_dependencies(&dep_task, depth + 1)?; } Ok(()) diff --git a/crates/affected/tests/affected_tracker_test.rs b/crates/affected/tests/affected_tracker_test.rs index 0ffbc2cbf7f..acfcf7de6b4 100644 --- a/crates/affected/tests/affected_tracker_test.rs +++ b/crates/affected/tests/affected_tracker_test.rs @@ -1,7 +1,7 @@ use moon_affected::*; use moon_common::Id; use moon_task::Target; -use moon_test_utils2::generate_project_graph; +use moon_test_utils2::generate_workspace_graph; use rustc_hash::{FxHashMap, FxHashSet}; use std::env; @@ -40,7 +40,7 @@ mod affected_projects { #[tokio::test] async fn empty_if_no_touched_files() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::default(); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -52,7 +52,7 @@ mod affected_projects { #[tokio::test] async fn tracks_projects() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["a/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -73,7 +73,7 @@ mod affected_projects { #[tokio::test] async fn tracks_multiple_projects() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter([ "a/file.txt".into(), "b/file.txt".into(), @@ -101,7 +101,7 @@ mod affected_projects { #[tokio::test] async fn none() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["a/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -120,7 +120,7 @@ mod affected_projects { #[tokio::test] async fn direct() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["a/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -141,7 +141,7 @@ mod affected_projects { #[tokio::test] async fn direct_no_deps() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["e/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -160,7 +160,7 @@ mod affected_projects { #[tokio::test] async fn deep() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["a/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -182,7 +182,7 @@ mod affected_projects { #[tokio::test] async fn deep_no_deps() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["e/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -205,7 +205,7 @@ mod affected_projects { #[tokio::test] async fn none() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["c/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -224,7 +224,7 @@ mod affected_projects { #[tokio::test] async fn direct() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["c/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -245,7 +245,7 @@ mod affected_projects { #[tokio::test] async fn direct_no_deps() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["e/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -264,7 +264,7 @@ mod affected_projects { #[tokio::test] async fn deep() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["c/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -285,7 +285,7 @@ mod affected_projects { #[tokio::test] async fn deep_no_deps() { - let project_graph = generate_project_graph("projects").await; + let project_graph = generate_workspace_graph("projects").await; let touched_files = FxHashSet::from_iter(["e/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -350,7 +350,7 @@ mod affected_tasks { #[tokio::test] async fn empty_if_no_touched_files() { - let project_graph = generate_project_graph("tasks").await; + let project_graph = generate_workspace_graph("tasks").await; let touched_files = FxHashSet::default(); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -362,7 +362,7 @@ mod affected_tasks { #[tokio::test] async fn self_scope() { - let project_graph = generate_project_graph("tasks").await; + let project_graph = generate_workspace_graph("tasks").await; let touched_files = FxHashSet::from_iter(["self/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -392,7 +392,7 @@ mod affected_tasks { #[tokio::test] async fn parent_scope() { - let project_graph = generate_project_graph("tasks").await; + let project_graph = generate_workspace_graph("tasks").await; let touched_files = FxHashSet::from_iter(["parent/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); @@ -426,7 +426,7 @@ mod affected_tasks { #[tokio::test] async fn by_env_var() { - let project_graph = generate_project_graph("tasks").await; + let project_graph = generate_workspace_graph("tasks").await; let touched_files = FxHashSet::default(); env::set_var("ENV", "affected"); @@ -450,7 +450,7 @@ mod affected_tasks { #[tokio::test] async fn marks_dependency_if_dependent_is_touched() { - let project_graph = generate_project_graph("tasks").await; + let project_graph = generate_workspace_graph("tasks").await; let touched_files = FxHashSet::from_iter(["downstream/file.txt".into()]); let mut tracker = AffectedTracker::new(&project_graph, &touched_files); diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index f6328d78c71..39428f9d7b4 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -32,6 +32,7 @@ moon_toolchain = { path = "../toolchain" } moon_toolchain_plugin = { path = "../toolchain-plugin" } moon_vcs = { path = "../vcs" } moon_workspace = { path = "../workspace" } +moon_workspace_graph = { path = "../workspace-graph" } async-recursion = { workspace = true } async-trait = { workspace = true } bytes = "1.7.2" diff --git a/crates/app/src/app.rs b/crates/app/src/app.rs index 481c8f10cda..38b0e84e927 100644 --- a/crates/app/src/app.rs +++ b/crates/app/src/app.rs @@ -8,6 +8,7 @@ use crate::commands::ext::ExtArgs; use crate::commands::generate::GenerateArgs; use crate::commands::graph::action::ActionGraphArgs; use crate::commands::graph::project::ProjectGraphArgs; +use crate::commands::graph::task::TaskGraphArgs; use crate::commands::init::InitArgs; use crate::commands::migrate::MigrateCommands; use crate::commands::node::NodeCommands; @@ -84,15 +85,6 @@ pub enum Commands { )] ActionGraph(ActionGraphArgs), - // moon dep-graph [target] - #[command( - name = "dep-graph", - about = "Display an interactive dependency graph of all tasks and actions.", - alias = "dg", - hide = true - )] - DepGraph(ActionGraphArgs), - // moon project #[command( name = "project", @@ -109,6 +101,14 @@ pub enum Commands { )] ProjectGraph(ProjectGraphArgs), + // moon task-graph [id] + #[command( + name = "task-graph", + about = "Display an interactive graph of tasks.", + alias = "tg" + )] + TaskGraph(TaskGraphArgs), + #[command(name = "sync", about = "Sync the workspace to a healthy state.")] Sync { #[command(subcommand)] diff --git a/crates/app/src/commands/check.rs b/crates/app/src/commands/check.rs index bcf0ff2c72c..e35275d343e 100644 --- a/crates/app/src/commands/check.rs +++ b/crates/app/src/commands/check.rs @@ -35,18 +35,18 @@ pub struct CheckArgs { #[instrument(skip_all)] pub async fn check(session: CliSession, args: CheckArgs) -> AppResult { - let project_graph = session.get_project_graph().await?; + let workspace_graph = session.get_workspace_graph().await?; let mut projects: Vec> = vec![]; // Load projects if args.all { trace!("Running check on all projects"); - projects.extend(project_graph.get_all()?); + projects.extend(workspace_graph.get_all_projects()?); } else if args.ids.is_empty() { trace!("Loading from path"); - projects.push(project_graph.get_from_path(None)?); + projects.push(workspace_graph.get_project_from_path(None)?); } else { trace!( ids = ?args.ids, @@ -54,7 +54,7 @@ pub async fn check(session: CliSession, args: CheckArgs) -> AppResult { ); for id in &args.ids { - projects.push(project_graph.get(id)?); + projects.push(workspace_graph.get_project(id)?); } }; @@ -62,8 +62,8 @@ pub async fn check(session: CliSession, args: CheckArgs) -> AppResult { let mut targets = vec![]; for project in projects { - for task in project.get_tasks()? { - if !task.is_internal() && (task.is_build_type() || task.is_test_type()) { + for task in workspace_graph.get_tasks_from_project(&project.id)? { + if task.is_build_type() || task.is_test_type() { targets.push(TargetLocator::Qualified(task.target.clone())); } } diff --git a/crates/app/src/commands/ci.rs b/crates/app/src/commands/ci.rs index 1440e509e6d..337d8fabffe 100644 --- a/crates/app/src/commands/ci.rs +++ b/crates/app/src/commands/ci.rs @@ -9,8 +9,8 @@ use moon_action_graph::{ActionGraph, RunRequirements}; use moon_affected::{DownstreamScope, UpstreamScope}; use moon_common::path::WorkspaceRelativePathBuf; use moon_console::Console; -use moon_project_graph::ProjectGraph; use moon_task::{Target, TargetLocator}; +use moon_workspace_graph::WorkspaceGraph; use rustc_hash::FxHashSet; use starbase::AppResult; use starbase_styles::color; @@ -83,10 +83,6 @@ impl CiConsole { } } -async fn generate_project_graph(session: &CliSession) -> AppResult> { - session.get_project_graph().await -} - /// Gather a list of files that have been modified between branches. async fn gather_touched_files( console: &mut CiConsole, @@ -146,7 +142,7 @@ async fn gather_touched_files( /// Gather potential runnable targets. async fn gather_potential_targets( console: &mut CiConsole, - project_graph: &ProjectGraph, + workspace_graph: &WorkspaceGraph, args: &CiArgs, ) -> AppResult { console.print_header("Gathering potential targets")?; @@ -154,13 +150,11 @@ async fn gather_potential_targets( let mut targets = vec![]; // Required for dependents - let projects = project_graph.get_all()?; + workspace_graph.get_all_projects()?; if args.targets.is_empty() { - for project in projects { - for task in project.get_tasks()? { - targets.push(task.target.clone()); - } + for task in workspace_graph.get_all_tasks()? { + targets.push(task.target.clone()); } } else { targets.extend(args.targets.clone()); @@ -212,13 +206,13 @@ fn distribute_targets_across_jobs( async fn generate_action_graph( console: &mut CiConsole, session: &CliSession, - project_graph: &ProjectGraph, + workspace_graph: &WorkspaceGraph, targets: &TargetList, touched_files: &FxHashSet, ) -> AppResult<(ActionGraph, ActionContext)> { console.print_header("Generating action graph")?; - let mut action_graph_builder = session.build_action_graph(project_graph).await?; + let mut action_graph_builder = session.build_action_graph(workspace_graph).await?; action_graph_builder.set_touched_files(touched_files)?; action_graph_builder.set_affected_scopes(UpstreamScope::Direct, DownstreamScope::Deep)?; @@ -256,9 +250,9 @@ pub async fn ci(session: CliSession, args: CiArgs) -> AppResult { last_title: String::new(), }; - let project_graph = generate_project_graph(&session).await?; + let workspace_graph = session.get_workspace_graph().await?; let touched_files = gather_touched_files(&mut console, &session, &args).await?; - let targets = gather_potential_targets(&mut console, &project_graph, &args).await?; + let targets = gather_potential_targets(&mut console, &workspace_graph, &args).await?; if targets.is_empty() { console.write_line(color::invalid("No tasks to run"))?; @@ -270,7 +264,7 @@ pub async fn ci(session: CliSession, args: CiArgs) -> AppResult { let (action_graph, action_context) = generate_action_graph( &mut console, &session, - &project_graph, + &workspace_graph, &targets, &touched_files, ) diff --git a/crates/app/src/commands/docker/file.rs b/crates/app/src/commands/docker/file.rs index fa1ad79c4fd..478d9f85eb7 100644 --- a/crates/app/src/commands/docker/file.rs +++ b/crates/app/src/commands/docker/file.rs @@ -41,10 +41,11 @@ pub struct DockerFileArgs { #[instrument(skip_all)] pub async fn file(session: CliSession, args: DockerFileArgs) -> AppResult { let console = &session.console; - let project_graph = session.get_project_graph().await?; + let workspace_graph = session.get_workspace_graph().await?; // Ensure the project exists - let project = project_graph.get(&args.id)?; + let project = workspace_graph.get_project(&args.id)?; + let tasks = workspace_graph.get_tasks_from_project(&project.id)?; // Build the options let mut options = GenerateDockerfileOptions { @@ -87,7 +88,7 @@ pub async fn file(session: CliSession, args: DockerFileArgs) -> AppResult { } else if args.defaults { project.config.docker.file.build_task.as_ref() } else { - let mut ids = project.get_task_ids()?; + let mut ids = tasks.iter().map(|task| &task.id).collect::>(); ids.sort(); let starting_cursor = project @@ -106,7 +107,10 @@ pub async fn file(session: CliSession, args: DockerFileArgs) -> AppResult { }; if let Some(task_id) = build_task_id { - let target = project.get_task(task_id)?.target.to_owned(); + let target = workspace_graph + .get_task_from_project(&project.id, task_id)? + .target + .to_owned(); debug!(task = target.as_str(), "Using build task"); @@ -120,7 +124,7 @@ pub async fn file(session: CliSession, args: DockerFileArgs) -> AppResult { } else if args.defaults { project.config.docker.file.start_task.as_ref() } else { - let mut ids = project.get_task_ids()?; + let mut ids = tasks.iter().map(|task| &task.id).collect::>(); ids.sort(); let starting_cursor = project @@ -139,7 +143,10 @@ pub async fn file(session: CliSession, args: DockerFileArgs) -> AppResult { }; if let Some(task_id) = start_task_id { - let target = project.get_task(task_id)?.target.to_owned(); + let target = workspace_graph + .get_task_from_project(&project.id, task_id)? + .target + .to_owned(); debug!(task = target.as_str(), "Using start task"); diff --git a/crates/app/src/commands/docker/prune.rs b/crates/app/src/commands/docker/prune.rs index a8925679fcc..ec6839e8e3c 100644 --- a/crates/app/src/commands/docker/prune.rs +++ b/crates/app/src/commands/docker/prune.rs @@ -6,7 +6,6 @@ use moon_deno_tool::DenoTool; use moon_node_lang::PackageJsonCache; use moon_node_tool::NodeTool; use moon_platform::PlatformManager; -use moon_project_graph::ProjectGraph; use moon_rust_tool::RustTool; use moon_tool::DependencyManager; use rustc_hash::FxHashSet; @@ -18,9 +17,10 @@ use tracing::{debug, instrument}; pub async fn prune_bun( bun: &BunTool, session: &CliSession, - project_graph: &ProjectGraph, manifest: &DockerManifest, ) -> AppResult { + let project_graph = session.get_project_graph().await?; + // Some package managers do not delete stale node modules if session .workspace_config @@ -69,7 +69,6 @@ pub async fn prune_bun( pub async fn prune_deno( deno: &DenoTool, session: &CliSession, - _project_graph: &ProjectGraph, _manifest: &DockerManifest, ) -> AppResult { // noop @@ -84,9 +83,10 @@ pub async fn prune_deno( pub async fn prune_node( node: &NodeTool, session: &CliSession, - project_graph: &ProjectGraph, manifest: &DockerManifest, ) -> AppResult { + let project_graph = session.get_project_graph().await?; + // Some package managers do not delete stale node modules if session .workspace_config @@ -166,7 +166,7 @@ pub async fn prune(session: CliSession) -> AppResult { return Err(AppDockerError::MissingManifest.into()); } - let project_graph = session.get_project_graph().await?; + let workspace_graph = session.get_workspace_graph().await?; let manifest: DockerManifest = json::read_file(manifest_path)?; let mut platforms = FxHashSet::::default(); @@ -176,7 +176,7 @@ pub async fn prune(session: CliSession) -> AppResult { ); for project_id in &manifest.focused_projects { - platforms.insert(project_graph.get(project_id)?.platform); + platforms.insert(workspace_graph.get_project(project_id)?.platform); } // Do this later so we only run once for each platform instead of per project @@ -197,7 +197,6 @@ pub async fn prune(session: CliSession) -> AppResult { .downcast_ref::() .unwrap(), &session, - &project_graph, &manifest, ) .await?; @@ -210,7 +209,6 @@ pub async fn prune(session: CliSession) -> AppResult { .downcast_ref::() .unwrap(), &session, - &project_graph, &manifest, ) .await?; @@ -223,7 +221,6 @@ pub async fn prune(session: CliSession) -> AppResult { .downcast_ref::() .unwrap(), &session, - &project_graph, &manifest, ) .await?; diff --git a/crates/app/src/commands/docker/setup.rs b/crates/app/src/commands/docker/setup.rs index 003fc4a7988..3a55f8291a9 100644 --- a/crates/app/src/commands/docker/setup.rs +++ b/crates/app/src/commands/docker/setup.rs @@ -14,8 +14,8 @@ pub async fn setup(session: CliSession) -> AppResult { } let manifest: DockerManifest = json::read_file(manifest_path)?; - let project_graph = session.get_project_graph().await?; - let mut action_graph_builder = session.build_action_graph(&project_graph).await?; + let workspace_graph = session.get_workspace_graph().await?; + let mut action_graph_builder = session.build_action_graph(&workspace_graph).await?; debug!( projects = ?manifest.focused_projects.iter().map(|id| id.as_str()).collect::>(), @@ -23,7 +23,7 @@ pub async fn setup(session: CliSession) -> AppResult { ); for project_id in &manifest.focused_projects { - let project = project_graph.get(project_id)?; + let project = workspace_graph.get_project(project_id)?; action_graph_builder.install_deps(&project, None)?; } diff --git a/crates/app/src/commands/graph/action.rs b/crates/app/src/commands/graph/action.rs index 3b3a8229ab3..cb605bbed85 100644 --- a/crates/app/src/commands/graph/action.rs +++ b/crates/app/src/commands/graph/action.rs @@ -25,8 +25,8 @@ pub struct ActionGraphArgs { #[instrument] pub async fn action_graph(session: CliSession, args: ActionGraphArgs) -> AppResult { - let project_graph = session.get_project_graph().await?; - let mut action_graph_builder = session.build_action_graph(&project_graph).await?; + let workspace_graph = session.get_workspace_graph().await?; + let mut action_graph_builder = session.build_action_graph(&workspace_graph).await?; let requirements = RunRequirements { dependents: args.dependents, @@ -41,9 +41,9 @@ pub async fn action_graph(session: CliSession, args: ActionGraphArgs) -> AppResu // Show all targets and actions } else { - for project in project_graph.get_all()? { - for task in project.get_tasks()? { - action_graph_builder.run_task(&project, task, &requirements)?; + for project in workspace_graph.get_all_projects()? { + for task in workspace_graph.get_tasks_from_project(&project.id)? { + action_graph_builder.run_task(&project, &task, &requirements)?; } } } diff --git a/crates/app/src/commands/graph/dep.rs b/crates/app/src/commands/graph/dep.rs deleted file mode 100644 index 39ee08d7253..00000000000 --- a/crates/app/src/commands/graph/dep.rs +++ /dev/null @@ -1,14 +0,0 @@ -use super::action::*; -use crate::session::CliSession; -use moon_common::color; -use starbase::AppResult; -use tracing::warn; - -pub async fn dep_graph(session: CliSession, args: ActionGraphArgs) -> AppResult { - warn!( - "This command is deprecated. Use {} instead.", - color::shell("moon action-graph") - ); - - action_graph(session, args).await -} diff --git a/crates/app/src/commands/graph/mod.rs b/crates/app/src/commands/graph/mod.rs index b441f32da5e..f0628969345 100644 --- a/crates/app/src/commands/graph/mod.rs +++ b/crates/app/src/commands/graph/mod.rs @@ -1,5 +1,5 @@ pub mod action; -pub mod dep; mod dto; pub mod project; +pub mod task; mod utils; diff --git a/crates/app/src/commands/graph/task.rs b/crates/app/src/commands/graph/task.rs new file mode 100644 index 00000000000..f91f99ad6b5 --- /dev/null +++ b/crates/app/src/commands/graph/task.rs @@ -0,0 +1,61 @@ +use super::utils::{respond_to_request, setup_server, task_graph_repr}; +use crate::session::CliSession; +use clap::Args; +use moon_task::Target; +use moon_task_graph::{GraphToDot, GraphToJson}; +use starbase::AppResult; +use starbase_styles::color; +use std::sync::Arc; +use tracing::instrument; + +#[derive(Args, Clone, Debug)] +pub struct TaskGraphArgs { + #[arg(help = "Target of task to *only* graph")] + target: Option, + + #[arg(long, help = "Include direct dependents of the focused target")] + dependents: bool, + + #[arg(long, help = "Print the graph in DOT format")] + dot: bool, + + #[arg(long, help = "Print the graph in JSON format")] + json: bool, +} + +#[instrument(skip_all)] +pub async fn task_graph(session: CliSession, args: TaskGraphArgs) -> AppResult { + let mut task_graph = session.get_task_graph().await?; + + if let Some(target) = &args.target { + task_graph = Arc::new(task_graph.focus_for(target, args.dependents)?); + } + + // Force expand all tasks + task_graph.get_all()?; + + if args.dot { + println!("{}", task_graph.to_dot()); + + return Ok(()); + } + + if args.json { + println!("{}", task_graph.to_json()?); + + return Ok(()); + } + + let graph_info = task_graph_repr(&task_graph).await; + let (server, mut tera) = setup_server().await?; + let url = format!("http://{}", server.server_addr()); + let _ = open::that(&url); + + println!("Started server on {}", color::url(url)); + + for req in server.incoming_requests() { + respond_to_request(req, &mut tera, &graph_info, "Task graph".to_owned())?; + } + + Ok(()) +} diff --git a/crates/app/src/commands/graph/utils.rs b/crates/app/src/commands/graph/utils.rs index 25ddece3e02..13cbc73bc33 100644 --- a/crates/app/src/commands/graph/utils.rs +++ b/crates/app/src/commands/graph/utils.rs @@ -2,6 +2,7 @@ use super::dto::{GraphEdgeDto, GraphInfoDto, GraphNodeDto}; use miette::IntoDiagnostic; use moon_action_graph::ActionGraph; use moon_project_graph::{GraphConversions, ProjectGraph}; +use moon_task_graph::TaskGraph; use petgraph::{graph::NodeIndex, Graph}; use rustc_hash::FxHashMap; use serde::Serialize; @@ -17,9 +18,7 @@ const INDEX_HTML: &str = include_str!("graph.html.tera"); #[derive(Debug, Serialize)] pub struct RenderContext { pub page_title: String, - pub graph_data: String, - pub js_url: String, } @@ -84,6 +83,12 @@ pub async fn project_graph_repr(project_graph: &ProjectGraph) -> GraphInfoDto { extract_nodes_and_edges_from_graph(&labeled_graph, true) } +/// Get a serialized representation of the task graph. +pub async fn task_graph_repr(task_graph: &TaskGraph) -> GraphInfoDto { + let labeled_graph = task_graph.to_labeled_graph(); + extract_nodes_and_edges_from_graph(&labeled_graph, true) +} + /// Get a serialized representation of the dependency graph. pub async fn action_graph_repr(action_graph: &ActionGraph) -> GraphInfoDto { let labeled_graph = action_graph.labeled_graph(); diff --git a/crates/app/src/commands/project.rs b/crates/app/src/commands/project.rs index bb8e2aa3d1a..3b216148b67 100644 --- a/crates/app/src/commands/project.rs +++ b/crates/app/src/commands/project.rs @@ -17,11 +17,8 @@ pub struct ProjectArgs { #[instrument(skip_all)] pub async fn project(session: CliSession, args: ProjectArgs) -> AppResult { - let project_graph = session - .get_project_graph() - .await? - .focus_for(&args.id, false)?; - let project = project_graph.get(&args.id)?; + let workspace_graph = session.get_workspace_graph().await?; + let project = workspace_graph.get_project_with_tasks(&args.id)?; let config = &project.config; let console = session.console.stdout(); @@ -128,12 +125,10 @@ pub async fn project(session: CliSession, args: ProjectArgs) -> AppResult { } } - let tasks = project.get_tasks()?; - - if !tasks.is_empty() { + if !project.tasks.is_empty() { console.print_entry_header("Tasks")?; - for task in tasks { + for task in project.tasks.values() { console.print_entry(&task.id, "")?; console.write_line(format!( diff --git a/crates/app/src/commands/query.rs b/crates/app/src/commands/query.rs index 339d14e2f09..b1e0699e540 100644 --- a/crates/app/src/commands/query.rs +++ b/crates/app/src/commands/query.rs @@ -223,7 +223,7 @@ pub struct QueryProjectsArgs { #[instrument(skip_all)] pub async fn projects(session: CliSession, args: QueryProjectsArgs) -> AppResult { let console = &session.console; - let project_graph = session.get_project_graph().await?; + let workspace_graph = session.get_workspace_graph().await?; let mut options = QueryProjectsOptions { alias: args.alias, @@ -243,7 +243,7 @@ pub async fn projects(session: CliSession, args: QueryProjectsArgs) -> AppResult if args.affected { let vcs = session.get_vcs_adapter()?; let touched_files = load_touched_files(&vcs).await?; - let mut affected_tracker = AffectedTracker::new(&project_graph, &touched_files); + let mut affected_tracker = AffectedTracker::new(&workspace_graph, &touched_files); #[allow(deprecated)] if args.dependents { @@ -261,7 +261,7 @@ pub async fn projects(session: CliSession, args: QueryProjectsArgs) -> AppResult options.affected = Some(affected_tracker.build()); } - let mut projects = query_projects(&project_graph, &options).await?; + let mut projects = query_projects(&workspace_graph, &options).await?; projects.sort_by(|a, d| a.id.cmp(&d.id)); // Write to stdout directly to avoid broken pipe panics @@ -334,7 +334,7 @@ pub struct QueryTasksArgs { #[instrument(skip_all)] pub async fn tasks(session: CliSession, args: QueryTasksArgs) -> AppResult { let console = &session.console; - let project_graph = session.get_project_graph().await?; + let workspace_graph = session.get_workspace_graph().await?; // Query for projects that match the filters let options = QueryProjectsOptions { @@ -348,20 +348,20 @@ pub async fn tasks(session: CliSession, args: QueryTasksArgs) -> AppResult { type_of: args.type_of, ..QueryProjectsOptions::default() }; - let projects = query_projects(&project_graph, &options).await?; + let projects = query_projects(&workspace_graph, &options).await?; // Group tasks by project let mut grouped_tasks = BTreeMap::default(); let mut targets_list = vec![]; for project in projects { - let filtered_tasks = project - .get_tasks()? + let filtered_tasks = workspace_graph + .get_tasks_from_project(&project.id)? .into_iter() .map(|task| { targets_list.push(task.target.clone()); - (task.id.to_owned(), task.to_owned()) + (task.id.to_owned(), task) }) .collect::>(); @@ -377,7 +377,7 @@ pub async fn tasks(session: CliSession, args: QueryTasksArgs) -> AppResult { let vcs = session.get_vcs_adapter()?; let touched_files = load_touched_files(&vcs).await?; - let mut affected_tracker = AffectedTracker::new(&project_graph, &touched_files); + let mut affected_tracker = AffectedTracker::new(&workspace_graph, &touched_files); affected_tracker.with_task_scopes(UpstreamScope::Deep, DownstreamScope::None); affected_tracker.track_tasks_by_target(&targets_list)?; diff --git a/crates/app/src/commands/run.rs b/crates/app/src/commands/run.rs index d1bdcb4001f..5444665164e 100644 --- a/crates/app/src/commands/run.rs +++ b/crates/app/src/commands/run.rs @@ -110,7 +110,7 @@ pub async fn run_target( ) -> AppResult { let console = &session.console; let cache_engine = session.get_cache_engine()?; - let project_graph = session.get_project_graph().await?; + let workspace_graph = session.get_workspace_graph().await?; let vcs = session.get_vcs_adapter()?; // Force cache to update using write-only mode @@ -144,7 +144,7 @@ pub async fn run_target( }; // Generate a dependency graph for all the targets that need to be ran - let mut action_graph_builder = session.build_action_graph(&project_graph).await?; + let mut action_graph_builder = session.build_action_graph(&workspace_graph).await?; if let Some(query_input) = &args.query { action_graph_builder.set_query(query_input)?; diff --git a/crates/app/src/commands/syncs/codeowners.rs b/crates/app/src/commands/syncs/codeowners.rs index f350497703e..0963006b475 100644 --- a/crates/app/src/commands/syncs/codeowners.rs +++ b/crates/app/src/commands/syncs/codeowners.rs @@ -35,8 +35,8 @@ pub async fn sync(session: CliSession, args: SyncCodeownersArgs) -> AppResult { true, ); } else { - let project_graph = session.get_project_graph().await?; - let codeowners_path = sync_codeowners(&context, &project_graph, args.force).await?; + let workspace_graph = session.get_workspace_graph().await?; + let codeowners_path = sync_codeowners(&context, &workspace_graph, args.force).await?; done( format!( diff --git a/crates/app/src/commands/syncs/projects.rs b/crates/app/src/commands/syncs/projects.rs index 23095ede833..ca3ea9ee6ee 100644 --- a/crates/app/src/commands/syncs/projects.rs +++ b/crates/app/src/commands/syncs/projects.rs @@ -8,11 +8,11 @@ use tracing::instrument; pub async fn sync(session: CliSession) -> AppResult { let done = create_progress_bar("Syncing projects..."); - let project_graph = session.get_project_graph().await?; + let workspace_graph = session.get_workspace_graph().await?; let mut project_count = 0; - let mut action_graph_builder = session.build_action_graph(&project_graph).await?; + let mut action_graph_builder = session.build_action_graph(&workspace_graph).await?; - for project in project_graph.get_all_unexpanded() { + for project in workspace_graph.projects.get_all_unexpanded() { action_graph_builder.sync_project(project)?; project_count += 1; } diff --git a/crates/app/src/commands/task.rs b/crates/app/src/commands/task.rs index bb0be3f8305..08b9bf8d13e 100644 --- a/crates/app/src/commands/task.rs +++ b/crates/app/src/commands/task.rs @@ -22,9 +22,9 @@ pub async fn task(session: CliSession, args: TaskArgs) -> AppResult { return Err(AppError::ProjectIdRequired.into()); }; - let project_graph = session.get_project_graph().await?; - let project = project_graph.get(project_locator)?; - let task = project.get_task(&args.target.task_id)?; + let workspace_graph = session.get_workspace_graph().await?; + let project = workspace_graph.get_project(project_locator)?; + let task = workspace_graph.get_task(&args.target)?; let console = session.console.stdout(); diff --git a/crates/app/src/components.rs b/crates/app/src/components.rs index dc1bfb94800..26ab614b571 100644 --- a/crates/app/src/components.rs +++ b/crates/app/src/components.rs @@ -18,12 +18,12 @@ pub async fn run_action_pipeline( action_context: ActionContext, action_graph: ActionGraph, ) -> miette::Result> { - let project_graph = session.get_project_graph().await?; + let workspace_graph = session.get_workspace_graph().await?; let toolchain_registry = session.get_toolchain_registry().await?; let mut pipeline = ActionPipeline::new( session.get_app_context()?, - project_graph, toolchain_registry, + workspace_graph, ); if let Some(concurrency) = &session.cli.concurrency { diff --git a/crates/app/src/queries/projects.rs b/crates/app/src/queries/projects.rs index 91a1c628b5e..5be4f60f0de 100644 --- a/crates/app/src/queries/projects.rs +++ b/crates/app/src/queries/projects.rs @@ -5,9 +5,9 @@ use miette::IntoDiagnostic; use moon_affected::Affected; use moon_common::{is_ci, path::WorkspaceRelativePathBuf, Id}; use moon_project::Project; -use moon_project_graph::ProjectGraph; use moon_task::Task; use moon_vcs::BoxedVcs; +use moon_workspace_graph::WorkspaceGraph; use rustc_hash::FxHashSet; use serde::{Deserialize, Serialize}; use starbase::AppResult; @@ -43,7 +43,7 @@ pub struct QueryProjectsResult { #[derive(Deserialize, Serialize)] pub struct QueryTasksResult { - pub tasks: BTreeMap>, + pub tasks: BTreeMap>>, pub options: QueryProjectsOptions, } @@ -103,12 +103,15 @@ pub async fn load_touched_files(vcs: &BoxedVcs) -> AppResult miette::Result>> { - project_graph.query(moon_query::build_query(query)?) +fn load_with_query( + workspace_graph: &WorkspaceGraph, + query: &str, +) -> miette::Result>> { + workspace_graph.query_projects(moon_query::build_query(query)?) } fn load_with_regex( - project_graph: &ProjectGraph, + workspace_graph: &WorkspaceGraph, options: &QueryProjectsOptions, ) -> miette::Result>> { let alias_regex = convert_to_regex("alias", &options.alias)?; @@ -121,7 +124,7 @@ fn load_with_regex( let type_regex = convert_to_regex("type", &options.type_of)?; let mut filtered = vec![]; - for project in project_graph.get_all()? { + for project in workspace_graph.get_all_projects()? { if let Some(regex) = &id_regex { if !regex.is_match(&project.id) { continue; @@ -152,9 +155,9 @@ fn load_with_regex( if let Some(regex) = &tasks_regex { let has_task = project - .get_task_ids()? + .task_targets .iter() - .any(|task_id| regex.is_match(task_id)); + .any(|target| regex.is_match(&target.task_id)); if !has_task { continue; @@ -186,15 +189,15 @@ fn load_with_regex( } pub async fn query_projects( - project_graph: &ProjectGraph, + workspace_graph: &WorkspaceGraph, options: &QueryProjectsOptions, ) -> AppResult>> { debug!("Querying for projects"); let mut projects = if let Some(query) = &options.query { - load_with_query(project_graph, query)? + load_with_query(workspace_graph, query)? } else { - load_with_regex(project_graph, options)? + load_with_regex(workspace_graph, options)? }; if let Some(affected) = &options.affected { diff --git a/crates/app/src/session.rs b/crates/app/src/session.rs index 9d294cb69d5..44df7a8ddf9 100644 --- a/crates/app/src/session.rs +++ b/crates/app/src/session.rs @@ -19,6 +19,7 @@ use moon_task_graph::TaskGraph; use moon_toolchain_plugin::*; use moon_vcs::{BoxedVcs, Git}; use moon_workspace::WorkspaceBuilder; +use moon_workspace_graph::WorkspaceGraph; use once_cell::sync::OnceCell; use proto_core::ProtoEnvironment; use semver::Version; @@ -88,9 +89,9 @@ impl CliSession { pub async fn build_action_graph<'graph>( &self, - project_graph: &'graph ProjectGraph, + workspace_graph: &'graph WorkspaceGraph, ) -> AppResult> { - ActionGraphBuilder::new(project_graph) + ActionGraphBuilder::new(workspace_graph) } pub fn get_app_context(&self) -> AppResult> { @@ -119,13 +120,13 @@ impl CliSession { } pub async fn get_extension_registry(&self) -> AppResult> { - let project_graph = self.get_project_graph().await?; + let workspace_graph = self.get_workspace_graph().await?; let item = self.extension_registry.get_or_init(|| { let mut registry = ExtensionRegistry::new(PluginHostData { moon_env: Arc::clone(&self.moon_env), - project_graph, proto_env: Arc::clone(&self.proto_env), + workspace_graph, }); // Convert moon IDs to plugin IDs @@ -156,13 +157,13 @@ impl CliSession { } pub async fn get_toolchain_registry(&self) -> AppResult> { - let project_graph = self.get_project_graph().await?; + let workspace_graph = self.get_workspace_graph().await?; let item = self.toolchain_registry.get_or_init(|| { let mut registry = ToolchainRegistry::new(PluginHostData { moon_env: Arc::clone(&self.moon_env), - project_graph, proto_env: Arc::clone(&self.proto_env), + workspace_graph, }); // Convert moon IDs to plugin IDs @@ -191,6 +192,13 @@ impl CliSession { Ok(Arc::clone(item)) } + pub async fn get_workspace_graph(&self) -> AppResult { + let projects = self.get_project_graph().await?; + let tasks = self.get_task_graph().await?; + + Ok(WorkspaceGraph::new(projects, tasks)) + } + pub fn is_telemetry_enabled(&self) -> bool { self.workspace_config.telemetry } @@ -215,8 +223,8 @@ impl CliSession { let builder = WorkspaceBuilder::new_with_cache(context, &cache_engine).await?; let result = builder.build().await?; - let _ = self.project_graph.set(Arc::new(result.project_graph)); - let _ = self.task_graph.set(Arc::new(result.task_graph)); + let _ = self.project_graph.set(result.projects); + let _ = self.task_graph.set(result.tasks); Ok(()) } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 515b5ff2ce2..266f68f8bef 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -127,7 +127,6 @@ async fn main() -> MainResult { Commands::Completions(args) => { commands::completions::completions(session, args).await } - Commands::DepGraph(args) => commands::graph::dep::dep_graph(session, args).await, Commands::Docker { command } => match command { DockerCommands::File(args) => commands::docker::file(session, args).await, DockerCommands::Prune => commands::docker::prune(session).await, @@ -185,6 +184,7 @@ async fn main() -> MainResult { None => commands::sync::sync(session).await, }, Commands::Task(args) => commands::task::task(session, args).await, + Commands::TaskGraph(args) => commands::graph::task::task_graph(session, args).await, Commands::Teardown => commands::teardown::teardown().await, Commands::Templates => commands::templates::templates(session).await, Commands::Upgrade => commands::upgrade::upgrade(session).await, diff --git a/crates/cli/tests/snapshots/run_test__errors_for_internal_task.snap b/crates/cli/tests/snapshots/run_test__errors_for_internal_task.snap index 21f7a56041c..93b80183698 100644 --- a/crates/cli/tests/snapshots/run_test__errors_for_internal_task.snap +++ b/crates/cli/tests/snapshots/run_test__errors_for_internal_task.snap @@ -2,7 +2,7 @@ source: crates/cli/tests/run_test.rs expression: assert.output() --- -Error: project::task::unknown +Error: task_graph::unknown_target × Unknown task internalOnly for project base. help: Has this task been configured? diff --git a/crates/cli/tests/snapshots/run_test__errors_for_unknown_task_in_project.snap b/crates/cli/tests/snapshots/run_test__errors_for_unknown_task_in_project.snap index c04e8cfc86e..cd0adcb28d5 100644 --- a/crates/cli/tests/snapshots/run_test__errors_for_unknown_task_in_project.snap +++ b/crates/cli/tests/snapshots/run_test__errors_for_unknown_task_in_project.snap @@ -2,10 +2,7 @@ source: crates/cli/tests/run_test.rs expression: assert.output() --- -Error: project::task::unknown +Error: task_graph::unknown_target × Unknown task unknown for project base. help: Has this task been configured? - - - diff --git a/crates/cli/tests/snapshots/task_test__unknown_task.snap b/crates/cli/tests/snapshots/task_test__unknown_task.snap index c5e82131a5a..d30f0b7e950 100644 --- a/crates/cli/tests/snapshots/task_test__unknown_task.snap +++ b/crates/cli/tests/snapshots/task_test__unknown_task.snap @@ -2,10 +2,7 @@ source: crates/cli/tests/task_test.rs expression: get_assert_stderr_output(&assert.inner) --- -Error: project::task::unknown +Error: task_graph::unknown_target × Unknown task unknown for project tasks. help: Has this task been configured? - - - diff --git a/crates/config-schema/src/typescript_types.rs b/crates/config-schema/src/typescript_types.rs index f551d29b3ac..a32d19f5aca 100644 --- a/crates/config-schema/src/typescript_types.rs +++ b/crates/config-schema/src/typescript_types.rs @@ -8,6 +8,7 @@ use std::path::Path; fn generate_project(out_dir: &Path) -> miette::Result<()> { let mut generator = SchemaGenerator::default(); + generator.add::(); generator.add::(); generator.add::(); generator.add::(); diff --git a/crates/plugin/Cargo.toml b/crates/plugin/Cargo.toml index 09483b94f23..046b6b58f5f 100644 --- a/crates/plugin/Cargo.toml +++ b/crates/plugin/Cargo.toml @@ -12,8 +12,8 @@ publish = false moon_common = { path = "../common" } moon_env = { path = "../env" } moon_pdk_api = { version = "0.0.10", path = "../pdk-api" } -moon_project_graph = { path = "../project-graph" } moon_target = { path = "../target" } +moon_workspace_graph = { path = "../workspace-graph" } async-trait = { workspace = true } convert_case = "0.6.0" extism = { workspace = true } diff --git a/crates/plugin/src/host.rs b/crates/plugin/src/host.rs index d239301a139..781973f69e5 100644 --- a/crates/plugin/src/host.rs +++ b/crates/plugin/src/host.rs @@ -1,8 +1,8 @@ use extism::{CurrentPlugin, Error, Function, UserData, Val, ValType}; use moon_common::{color, serde::*, Id}; use moon_env::MoonEnvironment; -use moon_project_graph::ProjectGraph; -use moon_target::{Target, TargetScope}; +use moon_target::Target; +use moon_workspace_graph::WorkspaceGraph; use proto_core::ProtoEnvironment; use std::fmt; use std::sync::Arc; @@ -12,8 +12,8 @@ use warpgate::host::{create_host_functions as create_shared_host_functions, Host #[derive(Clone, Default)] pub struct PluginHostData { pub moon_env: Arc, - pub project_graph: Arc, pub proto_env: Arc, + pub workspace_graph: WorkspaceGraph, } impl fmt::Debug for PluginHostData { @@ -71,7 +71,7 @@ fn load_project( let data = user_data.get()?; let data = data.lock().unwrap(); - let project = data.project_graph.get(&id).map_err(map_error)?; + let project = data.workspace_graph.get_project(&id).map_err(map_error)?; trace!( plugin = &uuid, @@ -107,7 +107,7 @@ fn load_task( color::label("load_task"), ); - let TargetScope::Project(project_id) = &target.scope else { + if target.get_project_id().is_none() { return Err(Error::msg( "Unable to load task. Requires a fully-qualified target with a project scope.", )); @@ -115,8 +115,7 @@ fn load_task( let data = user_data.get()?; let data = data.lock().unwrap(); - let project = data.project_graph.get(project_id).map_err(map_error)?; - let task = project.get_task(&target.task_id).map_err(map_error)?; + let task = data.workspace_graph.get_task(&target).map_err(map_error)?; trace!( plugin = &uuid, @@ -127,7 +126,7 @@ fn load_task( enable_wasm_bridge(); - plugin.memory_set_val(&mut outputs[0], serde_json::to_string(task)?)?; + plugin.memory_set_val(&mut outputs[0], serde_json::to_string(&task)?)?; disable_wasm_bridge(); diff --git a/crates/plugin/tests/plugin_registry_test.rs b/crates/plugin/tests/plugin_registry_test.rs index a6b6dd84a40..49803f5c220 100644 --- a/crates/plugin/tests/plugin_registry_test.rs +++ b/crates/plugin/tests/plugin_registry_test.rs @@ -4,7 +4,7 @@ use moon_plugin::{ Plugin, PluginHostData, PluginId as Id, PluginLocator, PluginRegistration, PluginRegistry, PluginType, }; -use moon_project_graph::ProjectGraph; +use moon_workspace_graph::WorkspaceGraph; use proto_core::{warpgate::FileLocator, ProtoEnvironment}; use starbase_sandbox::{create_empty_sandbox, create_sandbox}; use std::fs; @@ -30,8 +30,8 @@ fn create_registry(sandbox: &Path) -> PluginRegistry { PluginType::Extension, PluginHostData { moon_env: Arc::new(MoonEnvironment::new_testing(sandbox)), - project_graph: Arc::new(ProjectGraph::default()), proto_env: Arc::new(ProtoEnvironment::new_testing(sandbox).unwrap()), + workspace_graph: WorkspaceGraph::default(), }, ); diff --git a/crates/project-builder/src/project_builder.rs b/crates/project-builder/src/project_builder.rs index 32baa73b7cd..ebe129c82cb 100644 --- a/crates/project-builder/src/project_builder.rs +++ b/crates/project-builder/src/project_builder.rs @@ -189,12 +189,16 @@ impl<'app> ProjectBuilder<'app> { #[instrument(name = "build_project", skip_all)] pub async fn build(mut self) -> miette::Result { let tasks = self.build_tasks().await?; + let task_targets = tasks + .values() + .map(|task| task.target.clone()) + .collect::>(); let mut project = Project { alias: self.alias.map(|a| a.to_owned()), dependencies: self.build_dependencies(&tasks)?, file_groups: self.build_file_groups()?, - task_ids: tasks.keys().cloned().collect(), + task_targets, tasks, id: self.id.to_owned(), language: self.language, diff --git a/crates/project-expander/Cargo.toml b/crates/project-expander/Cargo.toml index 55ea8b7542a..15d2b0a49cc 100644 --- a/crates/project-expander/Cargo.toml +++ b/crates/project-expander/Cargo.toml @@ -3,23 +3,16 @@ name = "moon_project_expander" version = "0.0.1" edition = "2021" license = "MIT" -description = "Expands projects and tasks with runtime information." +description = "Expands projects with runtime information." homepage = "https://moonrepo.dev/moon" repository = "https://github.com/moonrepo/moon" publish = false [dependencies] -moon_args = { path = "../args" } moon_common = { path = "../common" } moon_config = { path = "../config" } moon_project = { path = "../project" } -moon_task = { path = "../task" } -moon_task_args = { path = "../task-args" } -moon_time = { path = "../time" } -dotenvy = "0.15.7" miette = { workspace = true } -pathdiff = { workspace = true } -regex = { workspace = true } rustc-hash = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } diff --git a/crates/project-expander/src/expander_context.rs b/crates/project-expander/src/expander_context.rs index 52f10886f2f..7118206ee5c 100644 --- a/crates/project-expander/src/expander_context.rs +++ b/crates/project-expander/src/expander_context.rs @@ -1,128 +1,11 @@ use moon_common::Id; -use moon_config::patterns; -use moon_project::Project; use rustc_hash::FxHashMap; -use std::env; use std::path::Path; -use tracing::debug; -pub struct ExpanderContext<'graph, 'query> { +pub struct ProjectExpanderContext<'graph> { /// Mapping of aliases to their project IDs. pub aliases: FxHashMap<&'graph str, &'graph Id>, - /// The base unexpanded project. - pub project: &'graph Project, - - /// Function to query for projects. - pub query: Box miette::Result> + 'graph>, - /// Workspace root, of course. pub workspace_root: &'graph Path, } - -pub fn substitute_env_vars(mut env: FxHashMap) -> FxHashMap { - let cloned_env = env.clone(); - - for (key, value) in env.iter_mut() { - *value = substitute_env_var(key, value, &cloned_env); - } - - env -} - -pub fn substitute_env_var( - base_name: &str, - value: &str, - env_map: &FxHashMap, -) -> String { - if !value.contains('$') { - return value.to_owned(); - } - - patterns::ENV_VAR_SUBSTITUTE.replace_all( - value, - |caps: &patterns::Captures| { - let Some(name) = caps.name("name1") - .or_else(|| caps.name("name2")) - .map(|cap| cap.as_str()) - else { - return String::new(); - }; - - let flag = caps.name("flag1").or_else(|| caps.name("flag2")).map(|cap| cap.as_str()); - - // If the variable is referencing itself, don't pull - // from the local map, and instead only pull from the - // system environment. Otherwise we hit recursion! - let get_replacement_value = || { - if !base_name.is_empty() && base_name == name { - env::var(name).ok() - } else { - env_map.get(name).cloned().or_else(|| env::var(name).ok()) - } - }; - - match flag { - // Don't substitute - Some("!") => { - format!("${name}") - }, - // Substitute with empty string when missing - Some("?") =>{ - debug!( - "Task value `{}` contains the environment variable ${}, but this variable is not set. Replacing with an empty value.", - value, - name - ); - - get_replacement_value().unwrap_or_default() - }, - // Substitute with self when missing - _ => { - debug!( - "Task value `{}` contains the environment variable ${}, but this variable is not set. Not substituting and keeping as-is. Append with ? or ! to change outcome.", - value, - name - ); - - get_replacement_value() - .unwrap_or_else(|| caps.get(0).unwrap().as_str().to_owned()) - } - } - }) - .to_string() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn handles_flags_when_missing() { - let envs = FxHashMap::default(); - - assert_eq!(substitute_env_var("", "$KEY", &envs), "$KEY"); - assert_eq!(substitute_env_var("", "${KEY}", &envs), "${KEY}"); - - assert_eq!(substitute_env_var("", "$KEY!", &envs), "$KEY"); - assert_eq!(substitute_env_var("", "${KEY!}", &envs), "$KEY"); - - assert_eq!(substitute_env_var("", "$KEY?", &envs), ""); - assert_eq!(substitute_env_var("", "${KEY?}", &envs), ""); - } - - #[test] - fn handles_flags_when_not_missing() { - let mut envs = FxHashMap::default(); - envs.insert("KEY".to_owned(), "value".to_owned()); - - assert_eq!(substitute_env_var("", "$KEY", &envs), "value"); - assert_eq!(substitute_env_var("", "${KEY}", &envs), "value"); - - assert_eq!(substitute_env_var("", "$KEY!", &envs), "$KEY"); - assert_eq!(substitute_env_var("", "${KEY!}", &envs), "$KEY"); - - assert_eq!(substitute_env_var("", "$KEY?", &envs), "value"); - assert_eq!(substitute_env_var("", "${KEY?}", &envs), "value"); - } -} diff --git a/crates/project-expander/src/lib.rs b/crates/project-expander/src/lib.rs index de97c5ac7b0..1e6de3b66b0 100644 --- a/crates/project-expander/src/lib.rs +++ b/crates/project-expander/src/lib.rs @@ -1,13 +1,5 @@ mod expander_context; mod project_expander; -mod tasks_expander; -mod tasks_expander_error; -mod token_expander; -mod token_expander_error; pub use expander_context::*; pub use project_expander::*; -pub use tasks_expander::*; -pub use tasks_expander_error::*; -pub use token_expander::*; -pub use token_expander_error::*; diff --git a/crates/project-expander/src/project_expander.rs b/crates/project-expander/src/project_expander.rs index 8c16bce47db..3184bf60369 100644 --- a/crates/project-expander/src/project_expander.rs +++ b/crates/project-expander/src/project_expander.rs @@ -1,26 +1,23 @@ -use crate::expander_context::ExpanderContext; -use crate::tasks_expander::TasksExpander; +use crate::expander_context::ProjectExpanderContext; use moon_common::color; use moon_config::DependencyConfig; use moon_project::Project; use rustc_hash::FxHashMap; -use std::collections::BTreeMap; use std::mem; use tracing::{debug, instrument}; -pub struct ProjectExpander<'graph, 'query> { - context: ExpanderContext<'graph, 'query>, +pub struct ProjectExpander<'graph> { + context: ProjectExpanderContext<'graph>, } -impl<'graph, 'query> ProjectExpander<'graph, 'query> { - pub fn new(context: ExpanderContext<'graph, 'query>) -> Self { +impl<'graph> ProjectExpander<'graph> { + pub fn new(context: ProjectExpanderContext<'graph>) -> Self { Self { context } } #[instrument(name = "expand_project", skip_all)] - pub fn expand(mut self) -> miette::Result { - // Clone before expanding! - let mut project = self.context.project.to_owned(); + pub fn expand(mut self, project: &Project) -> miette::Result { + let mut project = project.to_owned(); debug!( project_id = project.id.as_str(), @@ -29,13 +26,12 @@ impl<'graph, 'query> ProjectExpander<'graph, 'query> { ); self.expand_deps(&mut project)?; - self.expand_tasks(&mut project)?; Ok(project) } #[instrument(skip_all)] - pub fn expand_deps(&mut self, project: &mut Project) -> miette::Result<()> { + fn expand_deps(&mut self, project: &mut Project) -> miette::Result<()> { let mut depends_on = FxHashMap::default(); for dep_config in mem::take(&mut project.dependencies) { @@ -60,39 +56,4 @@ impl<'graph, 'query> ProjectExpander<'graph, 'query> { Ok(()) } - - #[instrument(skip_all)] - pub fn expand_tasks(&mut self, project: &mut Project) -> miette::Result<()> { - let mut tasks = BTreeMap::new(); - let mut expander = TasksExpander::new(&self.context); - - for (task_id, mut task) in mem::take(&mut project.tasks) { - debug!( - target = task.target.as_str(), - "Expanding task {}", - color::label(&task.target) - ); - - // Resolve in this order! - expander.expand_env(&mut task)?; - expander.expand_deps(&mut task)?; - expander.expand_inputs(&mut task)?; - expander.expand_outputs(&mut task)?; - expander.expand_args(&mut task)?; - - if task.script.is_some() { - expander.expand_script(&mut task)?; - } else { - expander.expand_command(&mut task)?; - } - - task.metadata.expanded = true; - - tasks.insert(task_id, task); - } - - project.tasks = tasks; - - Ok(()) - } } diff --git a/crates/project-expander/src/tasks_expander.rs b/crates/project-expander/src/tasks_expander.rs deleted file mode 100644 index 8b89649aae5..00000000000 --- a/crates/project-expander/src/tasks_expander.rs +++ /dev/null @@ -1,353 +0,0 @@ -use crate::expander_context::*; -use crate::tasks_expander_error::TasksExpanderError; -use crate::token_expander::TokenExpander; -use moon_config::{TaskArgs, TaskDependencyConfig}; -use moon_project::Project; -use moon_task::{Target, TargetScope, Task}; -use moon_task_args::parse_task_args; -use rustc_hash::FxHashMap; -use tracing::{instrument, trace, warn}; - -pub struct TasksExpander<'graph, 'query> { - pub context: &'graph ExpanderContext<'graph, 'query>, - pub token: TokenExpander<'graph, 'query>, -} - -impl<'graph, 'query> TasksExpander<'graph, 'query> { - pub fn new(context: &'graph ExpanderContext<'graph, 'query>) -> Self { - Self { - token: TokenExpander::new(context), - context, - } - } - - #[instrument(skip_all)] - pub fn expand_command(&mut self, task: &mut Task) -> miette::Result<()> { - trace!( - target = task.target.as_str(), - command = &task.command, - "Expanding tokens and variables in command" - ); - - task.command = self.token.expand_command(task)?; - - Ok(()) - } - - #[instrument(skip_all)] - pub fn expand_script(&mut self, task: &mut Task) -> miette::Result<()> { - trace!( - target = task.target.as_str(), - script = task.script.as_ref(), - "Expanding tokens and variables in script" - ); - - task.script = Some(self.token.expand_script(task)?); - - Ok(()) - } - - #[instrument(skip_all)] - pub fn expand_args(&mut self, task: &mut Task) -> miette::Result<()> { - if task.args.is_empty() { - return Ok(()); - } - - trace!( - target = task.target.as_str(), - args = ?task.args, - "Expanding tokens and variables in args", - ); - - task.args = self.token.expand_args(task)?; - - Ok(()) - } - - #[instrument(skip_all)] - pub fn expand_deps(&mut self, task: &mut Task) -> miette::Result<()> { - if task.deps.is_empty() { - return Ok(()); - } - - trace!( - target = task.target.as_str(), - deps = ?task.deps.iter().map(|d| d.target.as_str()).collect::>(), - "Expanding target scopes for deps", - ); - - let project = &self.context.project; - - // Dont use a `HashSet` as we want to preserve order - let mut deps: Vec = vec![]; - - let mut check_and_push_dep = |dep_project: &Project, - dep: &TaskDependencyConfig, - skip_if_missing: bool| - -> miette::Result<()> { - // Allow internal tasks! - let Some(dep_task) = dep_project.tasks.get(&dep.target.task_id) else { - if skip_if_missing { - return Ok(()); - } - - return Err(TasksExpanderError::UnknownTarget { - dep: Target::new(&dep_project.id, &dep.target.task_id)?, - task: task.target.to_owned(), - } - .into()); - }; - - // Do not depend on tasks that can fail - if dep_task.options.allow_failure { - return Err(TasksExpanderError::AllowFailureDepRequirement { - dep: dep_task.target.to_owned(), - task: task.target.to_owned(), - } - .into()); - } - - // Do not depend on tasks that can't run in CI - if !dep_task.options.run_in_ci && task.options.run_in_ci { - return Err(TasksExpanderError::RunInCiDepRequirement { - dep: dep_task.target.to_owned(), - task: task.target.to_owned(), - } - .into()); - } - - // Enforce persistent constraints - if dep_task.is_persistent() && !task.is_persistent() { - return Err(TasksExpanderError::PersistentDepRequirement { - dep: dep_task.target.to_owned(), - task: task.target.to_owned(), - } - .into()); - } - - // Add the dep if it has not already been - let mut dep_args = parse_task_args(&dep.args)?; - let dep_env = self.token.expand_env_with_task(task, &dep.env)?; - - if !dep_args.is_empty() { - dep_args = self.token.expand_args_with_task(task, &dep_args)?; - } - - let dep = TaskDependencyConfig { - args: if dep_args.is_empty() { - TaskArgs::None - } else { - TaskArgs::List(dep_args) - }, - env: substitute_env_vars(dep_env), - optional: dep.optional, - target: Target::new(&dep_project.id, &dep.target.task_id)?, - }; - - if !deps.contains(&dep) { - deps.push(dep); - } - - Ok(()) - }; - - for dep in &task.deps { - let dep_target = &dep.target; - - match &dep_target.scope { - // :task - TargetScope::All => { - return Err(TasksExpanderError::UnsupportedTargetScopeInDeps { - dep: dep_target.to_owned(), - task: task.target.to_owned(), - } - .into()); - } - // ^:task - TargetScope::Deps => { - let mut dep_ids = project - .get_dependency_ids() - .iter() - .map(|id| id.to_string()) - .collect::>(); - - if !dep_ids.is_empty() { - // Sort so query cache is more deterministic - dep_ids.sort(); - - let input = if dep_ids.len() == 1 { - format!("project={id}", id = dep_ids[0]) - } else { - format!("project=[{ids}]", ids = dep_ids.join(",")) - }; - - for dep_project in (self.context.query)(input)? { - check_and_push_dep(dep_project, dep, dep.optional.unwrap_or(true))?; - } - } - } - // ~:task - TargetScope::OwnSelf => { - if dep_target.task_id == task.id { - // Avoid circular references - } else { - check_and_push_dep(project, dep, dep.optional.unwrap_or(false))?; - } - } - // id:task - TargetScope::Project(project_locator) => { - if project.matches_locator(project_locator) { - if dep_target.task_id == task.id { - // Avoid circular references - } else { - check_and_push_dep(project, dep, false)?; - } - } else { - let results = (self.context.query)(format!("project={}", project_locator))?; - - if results.is_empty() { - return Err(TasksExpanderError::UnknownTarget { - dep: dep_target.to_owned(), - task: task.target.to_owned(), - } - .into()); - } - - for dep_project in results { - check_and_push_dep(dep_project, dep, false)?; - } - } - } - // #tag:task - TargetScope::Tag(tag) => { - for dep_project in (self.context.query)(format!("tag={tag}"))? { - if dep_project.id == project.id { - // Avoid circular references - } else { - check_and_push_dep(dep_project, dep, dep.optional.unwrap_or(true))?; - } - } - } - } - } - - task.deps = deps; - - Ok(()) - } - - #[instrument(skip_all)] - pub fn expand_env(&mut self, task: &mut Task) -> miette::Result<()> { - trace!( - target = task.target.as_str(), - env = ?task.env, - "Expanding environment variables" - ); - - let mut env = self.token.expand_env(task)?; - - // Load variables from an .env file - if let Some(env_files) = &task.options.env_files { - let env_paths = env_files - .iter() - .map(|file| { - file.to_workspace_relative(self.context.project.source.as_str()) - .to_path(self.context.workspace_root) - }) - .collect::>(); - - trace!( - target = task.target.as_str(), - env_files = ?env_paths, - "Loading environment variables from .env files", - ); - - let mut missing_paths = vec![]; - let mut merged_env_vars = FxHashMap::default(); - - // The file may not have been committed, so avoid crashing - for env_path in env_paths { - if env_path.exists() { - let handle_error = |error: dotenvy::Error| TasksExpanderError::InvalidEnvFile { - path: env_path.to_path_buf(), - error: Box::new(error), - }; - - for line in dotenvy::from_path_iter(&env_path).map_err(handle_error)? { - let (key, val) = line.map_err(handle_error)?; - - // Overwrite previous values - merged_env_vars.insert(key, val); - } - } else { - missing_paths.push(env_path); - } - } - - // Don't override task-level variables - for (key, val) in merged_env_vars { - env.entry(key).or_insert(val); - } - } - - task.env = substitute_env_vars(env); - - Ok(()) - } - - #[instrument(skip_all)] - pub fn expand_inputs(&mut self, task: &mut Task) -> miette::Result<()> { - if task.inputs.is_empty() { - return Ok(()); - } - - trace!( - target = task.target.as_str(), - inputs = ?task.inputs.iter().map(|d| d.as_str()).collect::>(), - "Expanding inputs into file system paths" - ); - - // Expand inputs to file system paths and environment variables - let result = self.token.expand_inputs(task)?; - - task.input_env.extend(result.env); - task.input_files.extend(result.files); - task.input_globs.extend(result.globs); - - Ok(()) - } - - #[instrument(skip_all)] - pub fn expand_outputs(&mut self, task: &mut Task) -> miette::Result<()> { - if task.outputs.is_empty() { - return Ok(()); - } - - trace!( - target = task.target.as_str(), - outputs = ?task.outputs.iter().map(|d| d.as_str()).collect::>(), - "Expanding outputs into file system paths" - ); - - // Expand outputs to file system paths - let result = self.token.expand_outputs(task)?; - - // Aggregate paths first before globbing, as they are literal - for file in result.files { - // Outputs must *not* be considered an input, - // so if there's an input that matches an output, - // remove it! Is there a better way to do this? - task.input_files.remove(&file); - task.output_files.insert(file); - } - - // Aggregate globs second so we can match against the paths - for glob in result.globs { - // Same treatment here! - task.input_globs.remove(&glob); - task.output_globs.insert(glob); - } - - Ok(()) - } -} diff --git a/crates/project-expander/tests/tasks_expander_test.rs b/crates/project-expander/tests/tasks_expander_test.rs deleted file mode 100644 index f6387a052ef..00000000000 --- a/crates/project-expander/tests/tasks_expander_test.rs +++ /dev/null @@ -1,1473 +0,0 @@ -mod utils; - -use moon_common::path::WorkspaceRelativePathBuf; -use moon_common::Id; -use moon_config::{DependencyConfig, InputPath, OutputPath, TaskArgs, TaskDependencyConfig}; -use moon_project::Project; -use moon_project_expander::TasksExpander; -use moon_task::Target; -use rustc_hash::{FxHashMap, FxHashSet}; -use starbase_sandbox::{create_empty_sandbox, create_sandbox}; -use std::env; -use std::path::Path; -use utils::{ - create_context, create_context_with_query, create_project, create_project_with_tasks, - create_task, -}; - -fn create_path_set(inputs: Vec<&str>) -> FxHashSet { - FxHashSet::from_iter(inputs.into_iter().map(|s| s.into())) -} - -mod tasks_expander { - use super::*; - - mod expand_command { - use super::*; - - #[test] - #[should_panic(expected = "Token @dirs(group) in task project:task cannot be used")] - fn errors_on_token_funcs() { - let sandbox = create_empty_sandbox(); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.command = "@dirs(group)".into(); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_command(&mut task) - .unwrap(); - } - - #[test] - fn replaces_token_vars() { - let sandbox = create_empty_sandbox(); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.command = "./$project/bin".into(); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_command(&mut task) - .unwrap(); - - assert_eq!(task.command, "./project/bin"); - } - - #[test] - fn replaces_env_vars() { - let sandbox = create_empty_sandbox(); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.command = "./$FOO/${BAR}/$BAZ_QUX".into(); - - env::set_var("FOO", "foo"); - env::set_var("BAZ_QUX", "baz-qux"); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_command(&mut task) - .unwrap(); - - env::remove_var("FOO"); - env::remove_var("BAZ_QUX"); - - assert_eq!(task.command, "./foo/${BAR}/baz-qux"); - } - - #[test] - fn replaces_env_var_from_self() { - let sandbox = create_empty_sandbox(); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.command = "./$FOO".into(); - task.env.insert("FOO".into(), "foo-self".into()); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_command(&mut task) - .unwrap(); - - assert_eq!(task.command, "./foo-self"); - } - } - - mod expand_args { - use super::*; - - #[test] - fn replaces_token_funcs() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.args = vec!["a".into(), "@files(all)".into(), "b".into()]; - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_args(&mut task).unwrap(); - - assert_eq!( - task.args, - [ - "a", - "./config.yml", - "./dir/subdir/nested.json", - "./docs.md", - "./other/file.json", - "b" - ] - ); - } - - #[test] - fn replaces_token_funcs_from_workspace_root() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.args = vec!["a".into(), "@files(all)".into(), "b".into()]; - task.options.run_from_workspace_root = true; - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_args(&mut task).unwrap(); - - assert_eq!( - task.args, - [ - "a", - "./project/source/config.yml", - "./project/source/dir/subdir/nested.json", - "./project/source/docs.md", - "./project/source/other/file.json", - "b" - ] - ); - } - - #[test] - fn replaces_token_vars() { - let sandbox = create_empty_sandbox(); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.args = vec![ - "a".into(), - "$project/dir".into(), - "b".into(), - "some/$task".into(), - ]; - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_args(&mut task).unwrap(); - - assert_eq!(task.args, ["a", "project/dir", "b", "some/task"]); - } - - #[test] - fn replaces_env_vars() { - let sandbox = create_empty_sandbox(); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.args = vec![ - "a".into(), - "$FOO_BAR".into(), - "b".into(), - "c/${BAR_BAZ}/d".into(), - ]; - - env::set_var("BAR_BAZ", "bar-baz"); - env::set_var("FOO_BAR", "foo-bar"); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_args(&mut task).unwrap(); - - env::remove_var("FOO_BAR"); - env::remove_var("BAR_BAZ"); - - assert_eq!(task.args, ["a", "foo-bar", "b", "c/bar-baz/d"]); - } - - #[test] - fn replaces_env_var_from_self() { - let sandbox = create_empty_sandbox(); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.args = vec!["a".into(), "${FOO_BAR}".into(), "b".into()]; - task.env.insert("FOO_BAR".into(), "foo-bar-self".into()); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_args(&mut task).unwrap(); - - assert_eq!(task.args, ["a", "foo-bar-self", "b"]); - } - } - - mod expand_deps { - use super::*; - - struct QueryContainer { - projects: Vec, - } - - impl QueryContainer { - pub fn new(root: &Path) -> Self { - Self { - projects: vec![ - create_project_with_tasks(root, "foo"), - create_project_with_tasks(root, "bar"), - create_project_with_tasks(root, "baz"), - ], - } - } - - pub fn all(&self, _: String) -> miette::Result> { - Ok(vec![ - &self.projects[0], - &self.projects[1], - &self.projects[2], - ]) - } - - pub fn filtered(&self, input: String) -> miette::Result> { - Ok(vec![if input.contains("foo") { - &self.projects[0] - } else if input.contains("bar") { - &self.projects[1] - } else { - &self.projects[2] - }]) - } - - pub fn none(&self, _: String) -> miette::Result> { - Ok(vec![]) - } - } - - mod all { - use super::*; - - #[test] - #[should_panic( - expected = "Invalid dependency :build for project:task. All (:) scope is not" - )] - fn errors() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps - .push(TaskDependencyConfig::new(Target::parse(":build").unwrap())); - - let context = create_context_with_query(&project, sandbox.path(), |i| query.all(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - } - } - - mod deps { - use super::*; - - #[test] - fn no_depends_on() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps - .push(TaskDependencyConfig::new(Target::parse("^:build").unwrap())); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!(task.deps, vec![]); - } - - #[test] - fn returns_tasks_of_same_name() { - let sandbox = create_empty_sandbox(); - let mut project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - // The valid list comes from `query` but we need a - // non-empty set for the expansion to work. - project - .dependencies - .push(DependencyConfig::new(Id::raw("foo"))); - - let mut task = create_task(); - task.deps - .push(TaskDependencyConfig::new(Target::parse("^:build").unwrap())); - - let context = create_context_with_query(&project, sandbox.path(), |i| query.all(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![ - TaskDependencyConfig::new(Target::parse("foo:build").unwrap()), - TaskDependencyConfig::new(Target::parse("bar:build").unwrap()), - TaskDependencyConfig::new(Target::parse("baz:build").unwrap()), - ] - ); - } - - #[test] - #[should_panic( - expected = "Non-persistent task project:task cannot depend on persistent task foo:dev." - )] - fn errors_for_persistent_chain() { - let sandbox = create_empty_sandbox(); - let mut project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - // The valid list comes from `query` but we need a - // non-empty set for the expansion to work. - project - .dependencies - .push(DependencyConfig::new(Id::raw("foo"))); - - let mut task = create_task(); - task.options.persistent = false; - task.deps - .push(TaskDependencyConfig::new(Target::parse("^:dev").unwrap())); - - let context = create_context_with_query(&project, sandbox.path(), |i| query.all(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - } - - #[test] - #[should_panic( - expected = "Task project:task cannot depend on task foo:test-fail, as it is allowed to" - )] - fn errors_for_allow_failure_chain() { - let sandbox = create_empty_sandbox(); - let mut project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - // The valid list comes from `query` but we need a - // non-empty set for the expansion to work. - project - .dependencies - .push(DependencyConfig::new(Id::raw("foo"))); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig::new( - Target::parse("^:test-fail").unwrap(), - )); - - let context = create_context_with_query(&project, sandbox.path(), |i| query.all(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - } - } - - mod own_self { - use super::*; - - #[test] - fn refs_sibling_task() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps - .push(TaskDependencyConfig::new(Target::parse("~:build").unwrap())); - task.deps - .push(TaskDependencyConfig::new(Target::parse("lint").unwrap())); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![ - TaskDependencyConfig::new(Target::parse("project:build").unwrap()), - TaskDependencyConfig::new(Target::parse("project:lint").unwrap()), - ] - ); - } - - #[test] - fn ignores_self_ref_cycle() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps - .push(TaskDependencyConfig::new(Target::parse("task").unwrap())); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!(task.deps, vec![]); - } - - #[test] - fn ignores_dupes() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps - .push(TaskDependencyConfig::new(Target::parse("~:test").unwrap())); - task.deps - .push(TaskDependencyConfig::new(Target::parse("test").unwrap())); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![TaskDependencyConfig::new( - Target::parse("project:test").unwrap() - )] - ); - } - - #[test] - #[should_panic( - expected = "Invalid dependency project:unknown for project:task, target does not" - )] - fn errors_unknown_sibling_task() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig::new( - Target::parse("~:unknown").unwrap(), - )); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - } - - #[test] - #[should_panic( - expected = "Non-persistent task project:task cannot depend on persistent task" - )] - fn errors_for_persistent_chain() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.options.persistent = false; - task.deps - .push(TaskDependencyConfig::new(Target::parse("~:dev").unwrap())); - - let context = create_context_with_query(&project, sandbox.path(), |i| query.all(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - } - } - - mod project { - use super::*; - - #[test] - fn refs_sibling_task() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig::new( - Target::parse("project:build").unwrap(), - )); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![TaskDependencyConfig::new( - Target::parse("project:build").unwrap() - )] - ); - } - - #[test] - fn ignores_self_ref_cycle() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig::new( - Target::parse("project:task").unwrap(), - )); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!(task.deps, vec![]); - } - - #[test] - fn refs_other_project_tasks() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig::new( - Target::parse("foo:build").unwrap(), - )); - task.deps.push(TaskDependencyConfig::new( - Target::parse("bar:lint").unwrap(), - )); - task.deps.push(TaskDependencyConfig::new( - Target::parse("baz:test").unwrap(), - )); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.filtered(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![ - TaskDependencyConfig::new(Target::parse("foo:build").unwrap()), - TaskDependencyConfig::new(Target::parse("bar:lint").unwrap()), - TaskDependencyConfig::new(Target::parse("baz:test").unwrap()), - ] - ); - } - - #[test] - #[should_panic( - expected = "Invalid dependency foo:unknown for project:task, target does not exist." - )] - fn errors_unknown_task() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig::new( - Target::parse("foo:unknown").unwrap(), - )); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - } - - #[test] - #[should_panic( - expected = "Non-persistent task project:task cannot depend on persistent task" - )] - fn errors_for_persistent_chain() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.options.persistent = false; - task.deps - .push(TaskDependencyConfig::new(Target::parse("foo:dev").unwrap())); - - let context = create_context_with_query(&project, sandbox.path(), |i| query.all(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - } - } - - mod tag { - use super::*; - - #[test] - fn no_tags() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig::new( - Target::parse("#tag:build").unwrap(), - )); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!(task.deps, vec![]); - } - - #[test] - fn returns_tasks_of_same_name() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig::new( - Target::parse("#tag:build").unwrap(), - )); - - let context = create_context_with_query(&project, sandbox.path(), |i| query.all(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![ - TaskDependencyConfig::new(Target::parse("foo:build").unwrap()), - TaskDependencyConfig::new(Target::parse("bar:build").unwrap()), - TaskDependencyConfig::new(Target::parse("baz:build").unwrap()), - ] - ); - } - - #[test] - fn ignores_self_ref_cycle() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let cloned_project = project.clone(); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig::new( - Target::parse("#tag:task").unwrap(), - )); - - let context = create_context_with_query(&project, sandbox.path(), |_| { - Ok(vec![&cloned_project]) - }); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!(task.deps, vec![]); - } - - #[test] - #[should_panic( - expected = "Non-persistent task project:task cannot depend on persistent task" - )] - fn errors_for_persistent_chain() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.options.persistent = false; - task.deps.push(TaskDependencyConfig::new( - Target::parse("#tag:dev").unwrap(), - )); - - let context = create_context_with_query(&project, sandbox.path(), |i| query.all(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - } - } - - mod config { - use super::*; - - #[test] - fn passes_args_through() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - - task.deps.push(TaskDependencyConfig { - args: TaskArgs::String("a b c".into()), - target: Target::parse("test").unwrap(), - ..TaskDependencyConfig::default() - }); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![TaskDependencyConfig { - args: TaskArgs::List(vec!["a".into(), "b".into(), "c".into()]), - target: Target::parse("project:test").unwrap(), - ..TaskDependencyConfig::default() - }] - ); - } - - #[test] - fn supports_tokens_in_args() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - - task.deps.push(TaskDependencyConfig { - args: TaskArgs::String("$project $language".into()), - target: Target::parse("test").unwrap(), - ..TaskDependencyConfig::default() - }); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![TaskDependencyConfig { - args: TaskArgs::List(vec!["project".into(), "unknown".into()]), - target: Target::parse("project:test").unwrap(), - ..TaskDependencyConfig::default() - }] - ); - } - - #[test] - fn passes_env_through() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - - task.deps.push(TaskDependencyConfig { - env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), - target: Target::parse("test").unwrap(), - ..TaskDependencyConfig::default() - }); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![TaskDependencyConfig { - args: TaskArgs::None, - env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), - target: Target::parse("project:test").unwrap(), - optional: None, - }] - ); - } - - #[test] - fn supports_token_in_env() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - - task.deps.push(TaskDependencyConfig { - env: FxHashMap::from_iter([("FOO".into(), "$project-$language".into())]), - target: Target::parse("test").unwrap(), - ..TaskDependencyConfig::default() - }); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![TaskDependencyConfig { - args: TaskArgs::None, - env: FxHashMap::from_iter([("FOO".into(), "project-unknown".into())]), - target: Target::parse("project:test").unwrap(), - optional: None, - }] - ); - } - - #[test] - fn passes_args_and_env_through() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - - task.deps.push(TaskDependencyConfig { - args: TaskArgs::String("a b c".into()), - env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), - target: Target::parse("test").unwrap(), - optional: None, - }); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![TaskDependencyConfig { - args: TaskArgs::List(vec!["a".into(), "b".into(), "c".into()]), - env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), - target: Target::parse("project:test").unwrap(), - optional: None, - }] - ); - } - - #[test] - fn expands_parent_scope() { - let sandbox = create_empty_sandbox(); - let mut project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - // The valid list comes from `query` but we need a - // non-empty set for the expansion to work. - project - .dependencies - .push(DependencyConfig::new(Id::raw("foo"))); - - let mut task = create_task(); - - task.deps.push(TaskDependencyConfig { - args: TaskArgs::String("a b c".into()), - env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), - target: Target::parse("^:build").unwrap(), - optional: None, - }); - - let context = create_context_with_query(&project, sandbox.path(), |i| query.all(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![ - TaskDependencyConfig { - args: TaskArgs::List(vec!["a".into(), "b".into(), "c".into()]), - env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), - target: Target::parse("foo:build").unwrap(), - optional: None, - }, - TaskDependencyConfig { - args: TaskArgs::List(vec!["a".into(), "b".into(), "c".into()]), - env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), - target: Target::parse("bar:build").unwrap(), - optional: None, - }, - TaskDependencyConfig { - args: TaskArgs::List(vec!["a".into(), "b".into(), "c".into()]), - env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), - target: Target::parse("baz:build").unwrap(), - optional: None, - } - ] - ); - } - - #[test] - fn skip_missing_self_targets() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - - task.deps.push(TaskDependencyConfig { - args: TaskArgs::None, - env: FxHashMap::from_iter([]), - target: Target::parse("do-not-exist").unwrap(), - optional: Some(true), - }); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.filtered(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!(task.deps, vec![]); - } - - #[test] - fn resolve_self_targets_when_optional() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig { - args: TaskArgs::None, - env: FxHashMap::from_iter([]), - target: Target::parse("build").unwrap(), - optional: Some(true), - }); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.filtered(i)); - TasksExpander::new(&context).expand_deps(&mut task).unwrap(); - - assert_eq!( - task.deps, - vec![TaskDependencyConfig { - args: TaskArgs::None, - env: FxHashMap::from_iter([]), - target: Target::parse("project:build").unwrap(), - optional: Some(true), - },] - ); - } - - #[test] - fn error_on_missing_self_targets() { - let sandbox = create_empty_sandbox(); - let project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig { - args: TaskArgs::None, - env: FxHashMap::from_iter([]), - target: Target::parse("do-not-exist").unwrap(), - optional: Some(false), - }); - - let context = - create_context_with_query(&project, sandbox.path(), |i| query.none(i)); - let error = TasksExpander::new(&context) - .expand_deps(&mut task) - .unwrap_err(); - - assert_eq!( - error.to_string(), - "Invalid dependency project:do-not-exist for project:task, target does not exist." - ); - } - - #[test] - fn error_on_missing_deps_target() { - let sandbox = create_empty_sandbox(); - let mut project = create_project_with_tasks(sandbox.path(), "project"); - let query = QueryContainer::new(sandbox.path()); - - // The valid list comes from `query` but we need a - // non-empty set for the expansion to work. - project - .dependencies - .push(DependencyConfig::new(Id::raw("foo"))); - - let mut task = create_task(); - task.deps.push(TaskDependencyConfig { - args: TaskArgs::None, - env: FxHashMap::from_iter([]), - target: Target::parse("^:do-not-exist").unwrap(), - optional: Some(false), - }); - - let context = create_context_with_query(&project, sandbox.path(), |i| query.all(i)); - let error = TasksExpander::new(&context) - .expand_deps(&mut task) - .unwrap_err(); - - assert_eq!( - error.to_string(), - "Invalid dependency foo:do-not-exist for project:task, target does not exist." - ); - } - } - } - - mod expand_env { - use super::*; - - #[test] - fn replaces_env_vars() { - let sandbox = create_empty_sandbox(); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.env.insert("KEY1".into(), "value1".into()); - task.env.insert("KEY2".into(), "inner-${FOO}".into()); - task.env.insert("KEY3".into(), "$KEY1-self".into()); - - env::set_var("FOO", "foo"); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - env::remove_var("FOO"); - - assert_eq!( - task.env, - FxHashMap::from_iter([ - ("KEY1".into(), "value1".into()), - ("KEY2".into(), "inner-foo".into()), - ("KEY3".into(), "value1-self".into()), - ]) - ); - } - - #[test] - fn replaces_tokens() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.env.insert("KEY1".into(), "@globs(all)".into()); - task.env.insert("KEY2".into(), "$project-$task".into()); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - assert_eq!( - task.env, - FxHashMap::from_iter([ - ("KEY1".into(), "./*.md,./**/*.json".into()), - ("KEY2".into(), "project-task".into()), - ]) - ); - } - - #[test] - fn replaces_tokens_from_workspace_root() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.options.run_from_workspace_root = true; - - task.env.insert("KEY1".into(), "@globs(all)".into()); - task.env.insert("KEY2".into(), "$project-$task".into()); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - assert_eq!( - task.env, - FxHashMap::from_iter([ - ( - "KEY1".into(), - "./project/source/*.md,./project/source/**/*.json".into() - ), - ("KEY2".into(), "project-task".into()), - ]) - ); - } - - #[test] - fn can_use_env_vars_and_token_vars() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.env - .insert("KEY".into(), "$project-$FOO-$unknown".into()); - - env::set_var("FOO", "foo"); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - env::remove_var("FOO"); - - assert_eq!( - task.env, - FxHashMap::from_iter([("KEY".into(), "project-foo-$unknown".into()),]) - ); - } - - #[test] - fn loads_from_env_file() { - let sandbox = create_sandbox("env-file"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.env.insert("KEY1".into(), "value1".into()); - task.env.insert("KEY2".into(), "value2".into()); - task.options.env_files = Some(vec![InputPath::ProjectFile(".env".into())]); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - assert_eq!( - task.env, - FxHashMap::from_iter([ - ("KEY1".into(), "value1".into()), - ("KEY2".into(), "value2".into()), // Not overridden by env file - ("KEY3".into(), "value3".into()), - ]) - ); - } - - #[test] - fn loads_from_root_env_file_and_substitutes() { - let sandbox = create_sandbox("env-file"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.options.env_files = Some(vec![InputPath::WorkspaceFile(".env-shared".into())]); - - env::set_var("EXTERNAL", "external-value"); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - env::remove_var("EXTERNAL"); - - assert_eq!( - task.env, - FxHashMap::from_iter([ - ("ROOT".into(), "true".into()), - ("BASE".into(), "value".into()), - ("FROM_SELF1".into(), "value".into()), - ("FROM_SELF2".into(), "value".into()), - ("FROM_SYSTEM".into(), "external-value".into()), - ]) - ); - } - - #[test] - fn can_substitute_var_from_env_file() { - let sandbox = create_sandbox("env-file"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.options.env_files = Some(vec![InputPath::WorkspaceFile(".env-shared".into())]); - task.env.insert("TOP_LEVEL".into(), "$BASE".into()); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - assert_eq!(task.env.get("TOP_LEVEL").unwrap(), "value"); - } - - #[test] - fn can_substitute_self_from_system() { - let sandbox = create_sandbox("env-file"); - let project = create_project(sandbox.path()); - - env::set_var("MYPATH", "/another/path"); - - let mut task = create_task(); - task.env.insert("MYPATH".into(), "/path:$MYPATH".into()); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - assert_eq!(task.env.get("MYPATH").unwrap(), "/path:/another/path"); - - env::remove_var("MYPATH"); - } - - #[test] - fn doesnt_substitute_self_from_local() { - let sandbox = create_sandbox("env-file"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.env.insert("MYPATH".into(), "/path:$MYPATH".into()); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - assert_eq!(task.env.get("MYPATH").unwrap(), "/path:$MYPATH"); - } - - #[test] - fn loads_from_multiple_env_file() { - let sandbox = create_sandbox("env-file"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.env.insert("KEY1".into(), "value1".into()); - task.env.insert("KEY2".into(), "value2".into()); - task.options.env_files = Some(vec![ - InputPath::ProjectFile(".env".into()), - InputPath::WorkspaceFile(".env-shared".into()), - ]); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - assert_eq!( - task.env, - FxHashMap::from_iter([ - ("KEY1".into(), "value1".into()), - ("KEY2".into(), "value2".into()), // Not overridden by env file - ("KEY3".into(), "value3".into()), - // shared - ("ROOT".into(), "true".into()), - ("BASE".into(), "value".into()), - ("FROM_SELF1".into(), "value".into()), - ("FROM_SELF2".into(), "value".into()), - ("FROM_SYSTEM".into(), "".into()), - ]) - ); - } - - #[test] - fn skips_missing_env_file() { - let sandbox = create_sandbox("env-file"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.env.insert("KEY1".into(), "value1".into()); - task.env.insert("KEY2".into(), "value2".into()); - task.options.env_files = Some(vec![InputPath::ProjectFile(".env-missing".into())]); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - - assert_eq!( - task.env, - FxHashMap::from_iter([ - ("KEY1".into(), "value1".into()), - ("KEY2".into(), "value2".into()), - ]) - ); - } - - #[test] - #[should_panic(expected = "Failed to parse env file")] - fn errors_invalid_env_file() { - let sandbox = create_sandbox("env-file"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.options.env_files = Some(vec![InputPath::ProjectFile(".env-invalid".into())]); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context).expand_env(&mut task).unwrap(); - } - } - - mod expand_inputs { - use super::*; - - #[test] - fn extracts_env_var() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.inputs.push(InputPath::EnvVar("FOO_BAR".into())); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_inputs(&mut task) - .unwrap(); - - assert_eq!(task.input_env, FxHashSet::from_iter(["FOO_BAR".into()])); - assert_eq!(task.input_globs, FxHashSet::default()); - assert_eq!(task.input_files, FxHashSet::default()); - } - - #[test] - fn replaces_token_funcs() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.inputs.push(InputPath::ProjectFile("file.txt".into())); - task.inputs.push(InputPath::TokenFunc("@files(all)".into())); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_inputs(&mut task) - .unwrap(); - - assert_eq!(task.input_globs, FxHashSet::default()); - assert_eq!( - task.input_files, - create_path_set(vec![ - "project/source/dir/subdir/nested.json", - "project/source/file.txt", - "project/source/docs.md", - "project/source/config.yml", - "project/source/other/file.json" - ]) - ); - } - - #[test] - fn splits_token_func_into_files_globs() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.inputs.push(InputPath::ProjectFile("file.txt".into())); - task.inputs.push(InputPath::TokenFunc("@group(all)".into())); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_inputs(&mut task) - .unwrap(); - - assert_eq!( - task.input_globs, - create_path_set(vec!["project/source/*.md", "project/source/**/*.json"]) - ); - assert_eq!( - task.input_files, - create_path_set(vec![ - "project/source/dir/subdir", - "project/source/file.txt", - "project/source/config.yml", - ]) - ); - } - - #[test] - fn replaces_token_vars() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.inputs - .push(InputPath::ProjectGlob("$task/**/*".into())); - task.inputs - .push(InputPath::WorkspaceFile("$project/index.js".into())); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_inputs(&mut task) - .unwrap(); - - assert_eq!( - task.input_globs, - create_path_set(vec!["project/source/task/**/*"]) - ); - assert_eq!(task.input_files, create_path_set(vec!["project/index.js"])); - } - } - - mod expand_outputs { - use super::*; - - #[test] - fn replaces_token_funcs() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.outputs - .push(OutputPath::ProjectFile("file.txt".into())); - task.outputs - .push(OutputPath::TokenFunc("@files(all)".into())); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_outputs(&mut task) - .unwrap(); - - assert_eq!(task.output_globs, FxHashSet::default()); - assert_eq!( - task.output_files, - create_path_set(vec![ - "project/source/dir/subdir/nested.json", - "project/source/file.txt", - "project/source/docs.md", - "project/source/config.yml", - "project/source/other/file.json" - ]) - ); - } - - #[test] - fn splits_token_func_into_files_globs() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.outputs - .push(OutputPath::ProjectFile("file.txt".into())); - task.outputs - .push(OutputPath::TokenFunc("@group(all)".into())); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_outputs(&mut task) - .unwrap(); - - assert_eq!( - task.output_globs, - create_path_set(vec!["project/source/*.md", "project/source/**/*.json"]) - ); - assert_eq!( - task.output_files, - create_path_set(vec![ - "project/source/dir/subdir", - "project/source/file.txt", - "project/source/config.yml", - ]) - ); - } - - #[test] - fn replaces_token_vars() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.outputs - .push(OutputPath::ProjectGlob("$task/**/*".into())); - task.outputs - .push(OutputPath::WorkspaceFile("$project/index.js".into())); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_outputs(&mut task) - .unwrap(); - - assert_eq!( - task.output_globs, - create_path_set(vec!["project/source/task/**/*"]) - ); - assert_eq!(task.output_files, create_path_set(vec!["project/index.js"])); - } - - #[test] - fn doesnt_overlap_input_file() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.outputs.push(OutputPath::ProjectFile("out".into())); - task.input_files.insert("project/source/out".into()); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_outputs(&mut task) - .unwrap(); - - assert!(task.input_files.is_empty()); - assert_eq!( - task.output_files, - create_path_set(vec!["project/source/out"]) - ); - } - - #[test] - fn doesnt_overlap_input_glob() { - let sandbox = create_sandbox("file-group"); - let project = create_project(sandbox.path()); - - let mut task = create_task(); - task.outputs - .push(OutputPath::ProjectGlob("out/**/*".into())); - task.input_globs.insert("project/source/out/**/*".into()); - - let context = create_context(&project, sandbox.path()); - TasksExpander::new(&context) - .expand_outputs(&mut task) - .unwrap(); - - assert!(task.input_globs.is_empty()); - assert_eq!( - task.output_globs, - create_path_set(vec!["project/source/out/**/*"]) - ); - } - } -} diff --git a/crates/project-graph/Cargo.toml b/crates/project-graph/Cargo.toml index d34f3902476..8137c931d67 100644 --- a/crates/project-graph/Cargo.toml +++ b/crates/project-graph/Cargo.toml @@ -14,7 +14,6 @@ moon_config = { path = "../config" } moon_graph_utils = { path = "../graph-utils" } moon_project = { path = "../project" } moon_project_expander = { path = "../project-expander" } -moon_query = { path = "../query" } moon_task = { path = "../task" } miette = { workspace = true } petgraph = { workspace = true } @@ -28,6 +27,7 @@ tracing = { workspace = true } [dev-dependencies] moon_cache = { path = "../cache" } moon_test_utils2 = { path = "../test-utils" } +moon_query = { path = "../query" } moon_workspace = { path = "../workspace" } starbase_events = { workspace = true } starbase_sandbox = { workspace = true } diff --git a/crates/project-graph/src/lib.rs b/crates/project-graph/src/lib.rs index 126ca3c772b..c5b25801960 100644 --- a/crates/project-graph/src/lib.rs +++ b/crates/project-graph/src/lib.rs @@ -1,8 +1,7 @@ mod project_graph; mod project_graph_error; -mod project_matcher; pub use moon_graph_utils::*; +pub use moon_project::*; pub use project_graph::*; pub use project_graph_error::*; -pub use project_matcher::*; diff --git a/crates/project-graph/src/project_graph.rs b/crates/project-graph/src/project_graph.rs index 0271db69ec7..ff1a03e2558 100644 --- a/crates/project-graph/src/project_graph.rs +++ b/crates/project-graph/src/project_graph.rs @@ -1,12 +1,10 @@ use crate::project_graph_error::ProjectGraphError; -use crate::project_matcher::matches_criteria; use moon_common::path::{PathExt, WorkspaceRelativePathBuf}; -use moon_common::{color, Id}; +use moon_common::Id; use moon_config::DependencyScope; use moon_graph_utils::*; use moon_project::Project; -use moon_project_expander::{ExpanderContext, ProjectExpander}; -use moon_query::{build_query, Criteria}; +use moon_project_expander::{ProjectExpander, ProjectExpanderContext}; use petgraph::graph::{DiGraph, NodeIndex}; use rustc_hash::FxHashMap; use scc::HashMap; @@ -49,9 +47,6 @@ pub struct ProjectGraph { /// Expanded projects, mapped by project ID. projects: Arc>, - /// Cache of query results, mapped by query input to project IDs. - query_cache: HashMap>>, - /// The current working directory. pub working_dir: PathBuf, @@ -60,21 +55,16 @@ pub struct ProjectGraph { } impl ProjectGraph { - pub fn new( - graph: ProjectGraphType, - metadata: FxHashMap, - workspace_root: &Path, - ) -> Self { + pub fn new(graph: ProjectGraphType, metadata: FxHashMap) -> Self { debug!("Creating project graph"); Self { graph, metadata, projects: Arc::new(RwLock::new(FxHashMap::default())), - working_dir: workspace_root.to_owned(), - workspace_root: workspace_root.to_owned(), + working_dir: PathBuf::new(), + workspace_root: PathBuf::new(), fs_cache: HashMap::new(), - query_cache: HashMap::new(), } } @@ -145,21 +135,6 @@ impl ProjectGraph { self.get(&id) } - /// Return all expanded projects that match the query criteria. - #[instrument(name = "query_project", skip(self))] - pub fn query<'input, Q: AsRef> + Debug>( - &self, - query: Q, - ) -> miette::Result>> { - let mut projects = vec![]; - - for id in self.internal_query(query)?.iter() { - projects.push(self.get(id)?); - } - - Ok(projects) - } - /// Return a map of project IDs to their file source paths. pub fn sources(&self) -> FxHashMap<&Id, &WorkspaceRelativePathBuf> { self.metadata @@ -192,7 +167,6 @@ impl ProjectGraph { graph, metadata, projects: self.projects.clone(), - query_cache: HashMap::new(), working_dir: self.working_dir.clone(), workspace_root: self.workspace_root.clone(), }) @@ -201,84 +175,24 @@ impl ProjectGraph { fn internal_get(&self, id_or_alias: &str) -> miette::Result> { let id = self.resolve_id(id_or_alias); - // Check if the expanded project has been created, if so return it if let Some(project) = self.read_cache().get(&id) { return Ok(Arc::clone(project)); } - // Otherwise expand the project and cache it with an Arc - let query = |input: String| { - let mut results = vec![]; + let mut cache = self.write_cache(); - // Don't use get() for expanded projects, since it'll overflow the - // stack trying to recursively expand projects! Using unexpanded - // dependent projects works just fine for the this entire process. - for result_id in self.internal_query(build_query(&input)?)?.iter() { - results.push(self.get_unexpanded(result_id)?); - } - - Ok(results) - }; - - let expander = ProjectExpander::new(ExpanderContext { + let expander = ProjectExpander::new(ProjectExpanderContext { aliases: self.aliases(), - project: self.get_unexpanded(&id)?, - query: Box::new(query), workspace_root: &self.workspace_root, }); - let project = Arc::new(expander.expand()?); + let project = Arc::new(expander.expand(self.get_unexpanded(&id)?)?); - self.write_cache().insert(id.clone(), Arc::clone(&project)); + cache.insert(id.clone(), Arc::clone(&project)); Ok(project) } - fn internal_query<'input, Q: AsRef>>( - &self, - query: Q, - ) -> miette::Result>> { - let query = query.as_ref(); - let query_input = query - .input - .as_ref() - .expect("Querying the project graph requires a query input string."); - let cache_key = query_input.to_string(); - - if let Some(cache) = self.query_cache.read(&cache_key, |_, v| v.clone()) { - return Ok(cache); - } - - debug!("Querying projects with {}", color::shell(query_input)); - - let mut project_ids = vec![]; - - // Don't use `get_all` as it recursively calls `query`, - // which runs into a deadlock! This should be faster also... - for project in self.get_all_unexpanded() { - if matches_criteria(project, query)? { - project_ids.push(project.id.clone()); - } - } - - // Sort so that the order is deterministic - project_ids.sort(); - - debug!( - projects = ?project_ids - .iter() - .map(|id| id.as_str()) - .collect::>(), - "Found {} matches", - project_ids.len(), - ); - - let ids = Arc::new(project_ids); - let _ = self.query_cache.insert(cache_key, Arc::clone(&ids)); - - Ok(ids) - } - fn internal_search(&self, search: &Path) -> miette::Result> { let cache_key = search.to_path_buf(); diff --git a/crates/project-graph/src/project_matcher.rs b/crates/project-graph/src/project_matcher.rs deleted file mode 100644 index 9e4a434bf4b..00000000000 --- a/crates/project-graph/src/project_matcher.rs +++ /dev/null @@ -1,84 +0,0 @@ -use moon_project::Project; -use moon_query::{Condition, Criteria, Field, LogicalOperator}; - -pub fn matches_criteria(project: &Project, query: &Criteria) -> miette::Result { - let match_all = matches!(query.op, LogicalOperator::And); - let mut matched_any = false; - - for condition in &query.conditions { - let matches = match condition { - Condition::Field { field, .. } => { - let result = match field { - Field::Language(langs) => condition.matches_enum(langs, &project.language), - Field::Project(ids) => { - if condition.matches(ids, &project.id)? { - Ok(true) - } else if let Some(alias) = &project.alias { - condition.matches(ids, alias) - } else { - Ok(false) - } - } - Field::ProjectAlias(aliases) => { - if let Some(alias) = &project.alias { - condition.matches(aliases, alias) - } else { - Ok(false) - } - } - Field::ProjectName(ids) => condition.matches(ids, &project.id), - Field::ProjectSource(sources) => { - condition.matches(sources, project.source.as_str()) - } - Field::ProjectStack(types) => condition.matches_enum(types, &project.stack), - Field::ProjectType(types) => condition.matches_enum(types, &project.type_of), - Field::Tag(tags) => condition.matches_list( - tags, - &project - .config - .tags - .iter() - .map(|t| t.as_str()) - .collect::>(), - ), - Field::Task(ids) => Ok(project - .tasks - .values() - .any(|task| condition.matches(ids, &task.id).unwrap_or_default())), - Field::TaskPlatform(platforms) => Ok(project.tasks.values().any(|task| { - condition - .matches_enum(platforms, &task.platform) - .unwrap_or_default() - })), - Field::TaskType(types) => Ok(project.tasks.values().any(|task| { - condition - .matches_enum(types, &task.type_of) - .unwrap_or_default() - })), - }; - - result? - } - Condition::Criteria { criteria } => matches_criteria(project, criteria)?, - }; - - if matches { - matched_any = true; - - if match_all { - continue; - } else { - break; - } - } else if match_all { - return Ok(false); - } - } - - // No matches using the OR condition - if !matched_any { - return Ok(false); - } - - Ok(true) -} diff --git a/crates/project-graph/tests/project_graph_test.rs b/crates/project-graph/tests/project_graph_test.rs index 7afad9b360a..b1a9486c002 100644 --- a/crates/project-graph/tests/project_graph_test.rs +++ b/crates/project-graph/tests/project_graph_test.rs @@ -33,6 +33,13 @@ fn map_ids(ids: Vec) -> Vec { ids.into_iter().map(|id| id.to_string()).collect() } +fn map_ids_from_target(targets: Vec) -> Vec { + targets + .into_iter() + .map(|target| target.task_id.to_string()) + .collect() +} + fn get_ids_from_projects(projects: Vec>) -> Vec { let mut ids = projects .iter() @@ -47,27 +54,27 @@ mod project_graph { #[tokio::test] async fn gets_by_id() { - let graph = generate_project_graph("dependencies").await; + let graph = generate_workspace_graph("dependencies").await; - assert!(graph.get("a").is_ok()); + assert!(graph.get_project("a").is_ok()); } #[tokio::test] #[should_panic(expected = "No project has been configured with the identifier or alias z")] async fn errors_unknown_id() { - let graph = generate_project_graph("dependencies").await; + let graph = generate_workspace_graph("dependencies").await; - graph.get("z").unwrap(); + graph.get_project("z").unwrap(); } #[tokio::test] async fn gets_by_path() { let sandbox = create_sandbox("dependencies"); - let graph = generate_project_graph_from_sandbox(sandbox.path()).await; + let graph = generate_workspace_graph_from_sandbox(sandbox.path()).await; assert_eq!( graph - .get_from_path(Some(&sandbox.path().join("c/moon.yml"))) + .get_project_from_path(Some(&sandbox.path().join("c/moon.yml"))) .unwrap() .id, "c" @@ -78,17 +85,17 @@ mod project_graph { #[should_panic(expected = "No project could be located starting from path z/moon.yml")] async fn errors_non_matching_path() { let sandbox = create_sandbox("dependencies"); - let graph = generate_project_graph_from_sandbox(sandbox.path()).await; + let graph = generate_workspace_graph_from_sandbox(sandbox.path()).await; graph - .get_from_path(Some(&sandbox.path().join("z/moon.yml"))) + .get_project_from_path(Some(&sandbox.path().join("z/moon.yml"))) .unwrap(); } #[tokio::test] #[should_panic(expected = "A project already exists with the identifier id")] async fn errors_duplicate_ids() { - generate_project_graph("dupe-folder-conflict").await; + generate_workspace_graph("dupe-folder-conflict").await; } mod sources { @@ -96,10 +103,10 @@ mod project_graph { #[tokio::test] async fn globs() { - let graph = generate_project_graph("dependencies").await; + let graph = generate_workspace_graph("dependencies").await; assert_eq!( - get_ids_from_projects(graph.get_all().unwrap()), + get_ids_from_projects(graph.get_all_projects().unwrap()), ["a", "b", "c", "d"] ); } @@ -112,14 +119,14 @@ mod project_graph { // Move files so that we can infer a compatible root project name fs::copy_dir_all(sandbox.path(), sandbox.path(), &root).unwrap(); - let mut mock = create_project_graph_mocker(&root); + let mut mock = create_workspace_graph_mocker(&root); mock.workspace_config.projects = WorkspaceProjects::Globs(string_vec!["*", "."]); - let graph = mock.build_project_graph().await; + let graph = mock.build_workspace_graph().await; assert_eq!( - get_ids_from_projects(graph.get_all().unwrap()), + get_ids_from_projects(graph.get_all_projects().unwrap()), ["a", "b", "c", "d", "dir"] ); } @@ -127,34 +134,40 @@ mod project_graph { #[tokio::test] async fn globs_with_config() { let sandbox = create_sandbox("locate-configs"); - let mut mock = create_project_graph_mocker(sandbox.path()); + let mut mock = create_workspace_graph_mocker(sandbox.path()); mock.workspace_config.projects = WorkspaceProjects::Globs(string_vec!["*/moon.yml"]); - let graph = mock.build_project_graph().await; + let graph = mock.build_workspace_graph().await; - assert_eq!(get_ids_from_projects(graph.get_all().unwrap()), ["a", "c"]); + assert_eq!( + get_ids_from_projects(graph.get_all_projects().unwrap()), + ["a", "c"] + ); } #[tokio::test] async fn paths() { let sandbox = create_sandbox("dependencies"); - let mut mock = create_project_graph_mocker(sandbox.path()); + let mut mock = create_workspace_graph_mocker(sandbox.path()); mock.workspace_config.projects = WorkspaceProjects::Sources(FxHashMap::from_iter([ (Id::raw("c"), "c".into()), (Id::raw("b"), "b".into()), ])); - let graph = mock.build_project_graph().await; + let graph = mock.build_workspace_graph().await; - assert_eq!(get_ids_from_projects(graph.get_all().unwrap()), ["b", "c"]); + assert_eq!( + get_ids_from_projects(graph.get_all_projects().unwrap()), + ["b", "c"] + ); } #[tokio::test] async fn paths_and_globs() { let sandbox = create_sandbox("dependencies"); - let mut mock = create_project_graph_mocker(sandbox.path()); + let mut mock = create_workspace_graph_mocker(sandbox.path()); mock.workspace_config.projects = WorkspaceProjects::Both(WorkspaceProjectsConfig { globs: string_vec!["{a,c}"], @@ -164,10 +177,10 @@ mod project_graph { ]), }); - let graph = mock.build_project_graph().await; + let graph = mock.build_workspace_graph().await; assert_eq!( - get_ids_from_projects(graph.get_all().unwrap()), + get_ids_from_projects(graph.get_all_projects().unwrap()), ["a", "b", "c", "root"] ); } @@ -179,10 +192,10 @@ mod project_graph { sandbox.enable_git(); sandbox.create_file(".moon/workspace.yml", "projects: ['*']"); - let graph = generate_project_graph_from_sandbox(sandbox.path()).await; + let graph = generate_workspace_graph_from_sandbox(sandbox.path()).await; assert_eq!( - get_ids_from_projects(graph.get_all().unwrap()), + get_ids_from_projects(graph.get_all_projects().unwrap()), ["a", "b", "c", "d"] ); } @@ -192,10 +205,10 @@ mod project_graph { let sandbox = create_sandbox("dependencies"); sandbox.create_file(".foo/moon.yml", ""); - let graph = generate_project_graph_from_sandbox(sandbox.path()).await; + let graph = generate_workspace_graph_from_sandbox(sandbox.path()).await; assert_eq!( - get_ids_from_projects(graph.get_all().unwrap()), + get_ids_from_projects(graph.get_all_projects().unwrap()), ["a", "b", "c", "d"] ); } @@ -207,23 +220,23 @@ mod project_graph { sandbox.enable_git(); sandbox.create_file(".gitignore", "*-other"); - let mut mock = create_project_graph_mocker(sandbox.path()); + let mut mock = create_workspace_graph_mocker(sandbox.path()); mock.with_vcs(); - let graph = mock.build_project_graph().await; + let graph = mock.build_workspace_graph().await; assert_eq!( - get_ids_from_projects(graph.get_all().unwrap()), + get_ids_from_projects(graph.get_all_projects().unwrap()), ["app", "library", "tool", "unknown"] ); } #[tokio::test] async fn supports_id_formats() { - let graph = generate_project_graph("ids").await; + let graph = generate_workspace_graph("ids").await; assert_eq!( - get_ids_from_projects(graph.get_all().unwrap()), + get_ids_from_projects(graph.get_all_projects().unwrap()), [ "Capital", "PascalCase", @@ -244,13 +257,13 @@ mod project_graph { const CACHE_PATH: &str = ".moon/cache/states/workspaceGraph.json"; const STATE_PATH: &str = ".moon/cache/states/projectsBuildData.json"; - async fn do_generate(root: &Path) -> ProjectGraph { + async fn do_generate(root: &Path) -> WorkspaceGraph { let cache_engine = CacheEngine::new(root).unwrap(); - let mut mock = create_project_graph_mocker(root); + let mut mock = create_workspace_graph_mocker(root); mock.with_vcs(); - mock.build_project_graph_with_options(ProjectGraphMockOptions { + mock.build_workspace_graph_with_options(WorkspaceMockOptions { cache: Some(cache_engine), ..Default::default() }) @@ -259,7 +272,7 @@ mod project_graph { async fn generate_cached_project_graph( func: impl FnOnce(&Sandbox), - ) -> (Sandbox, ProjectGraph) { + ) -> (Sandbox, WorkspaceGraph) { let sandbox = create_sandbox("dependencies"); func(&sandbox); @@ -294,7 +307,10 @@ mod project_graph { .await; let cached_graph = do_generate(sandbox.path()).await; - assert_eq!(graph.get_node_keys(), cached_graph.get_node_keys()); + assert_eq!( + graph.projects.get_node_keys(), + cached_graph.projects.get_node_keys() + ); } #[tokio::test] @@ -433,25 +449,37 @@ mod project_graph { #[tokio::test] async fn can_generate_with_cycles() { - let graph = generate_project_graph("cycle").await; + let graph = generate_workspace_graph("cycle").await; assert_eq!( - get_ids_from_projects(graph.get_all().unwrap()), + get_ids_from_projects(graph.get_all_projects().unwrap()), ["a", "b", "c"] ); assert_eq!( - map_ids(graph.dependencies_of(&graph.get("a").unwrap())), + map_ids( + graph + .projects + .dependencies_of(&graph.get_project("a").unwrap()) + ), ["b"] ); assert_eq!( - map_ids(graph.dependencies_of(&graph.get("b").unwrap())), + map_ids( + graph + .projects + .dependencies_of(&graph.get_project("b").unwrap()) + ), ["c"] ); assert_eq!( - map_ids(graph.dependencies_of(&graph.get("c").unwrap())), + map_ids( + graph + .projects + .dependencies_of(&graph.get_project("c").unwrap()) + ), string_vec![] ); } @@ -460,16 +488,16 @@ mod project_graph { mod inheritance { use super::*; - async fn generate_inheritance_project_graph(fixture: &str) -> ProjectGraph { + async fn generate_inheritance_project_graph(fixture: &str) -> WorkspaceGraph { let sandbox = create_sandbox(fixture); - let mut mock = create_project_graph_mocker(sandbox.path()); + let mut mock = create_workspace_graph_mocker(sandbox.path()); mock.inherited_tasks = mock .config_loader .load_tasks_manager_from(sandbox.path(), sandbox.path().join(".moon")) .unwrap(); - mock.build_project_graph().await + mock.build_workspace_graph().await } #[tokio::test] @@ -477,27 +505,17 @@ mod project_graph { let graph = generate_inheritance_project_graph("inheritance/scoped").await; assert_eq!( - map_ids( - graph - .get("node") - .unwrap() - .tasks - .keys() - .cloned() - .collect::>() - ), + map_ids_from_target(graph.get_project("node").unwrap().task_targets.clone()), ["global", "global-node", "node"] ); assert_eq!( - map_ids( + map_ids_from_target( graph - .get("node-library") + .get_project("node-library") .unwrap() - .tasks - .keys() - .cloned() - .collect::>() + .task_targets + .clone() ), [ "global", @@ -508,14 +526,12 @@ mod project_graph { ); assert_eq!( - map_ids( + map_ids_from_target( graph - .get("system-library") + .get_project("system-library") .unwrap() - .tasks - .keys() - .cloned() - .collect::>() + .task_targets + .clone() ), ["global", "system-library"] ); @@ -526,41 +542,17 @@ mod project_graph { let graph = generate_inheritance_project_graph("inheritance/tagged").await; assert_eq!( - map_ids( - graph - .get("mage") - .unwrap() - .tasks - .keys() - .cloned() - .collect::>() - ), + map_ids_from_target(graph.get_project("mage").unwrap().task_targets.clone()), ["mage", "magic"] ); assert_eq!( - map_ids( - graph - .get("warrior") - .unwrap() - .tasks - .keys() - .cloned() - .collect::>() - ), + map_ids_from_target(graph.get_project("warrior").unwrap().task_targets.clone()), ["warrior", "weapons"] ); assert_eq!( - map_ids( - graph - .get("priest") - .unwrap() - .tasks - .keys() - .cloned() - .collect::>() - ), + map_ids_from_target(graph.get_project("priest").unwrap().task_targets.clone()), ["magic", "priest", "weapons"] ); } @@ -568,7 +560,7 @@ mod project_graph { #[tokio::test] async fn inherits_file_groups() { let graph = generate_inheritance_project_graph("inheritance/file-groups").await; - let project = graph.get("project").unwrap(); + let project = graph.get_project("project").unwrap(); assert_eq!( project.file_groups.get("sources").unwrap(), @@ -599,8 +591,7 @@ mod project_graph { #[tokio::test] async fn inherits_implicit_deps_inputs() { let graph = generate_inheritance_project_graph("inheritance/implicits").await; - let project = graph.get("project").unwrap(); - let task = project.get_task("example").unwrap(); + let task = graph.get_task_from_project("project", "example").unwrap(); assert_eq!( task.deps, @@ -630,8 +621,8 @@ mod project_graph { #[tokio::test] async fn expands_project() { - let graph = generate_project_graph("expansion").await; - let project = graph.get("project").unwrap(); + let graph = generate_workspace_graph("expansion").await; + let project = graph.get_project("project").unwrap(); assert_eq!( project.dependencies, @@ -643,14 +634,17 @@ mod project_graph { }] ); - assert!(project.get_task("build").unwrap().deps.is_empty()); + assert!(graph + .get_task_from_project("project", "build") + .unwrap() + .deps + .is_empty()); } #[tokio::test] async fn expands_tasks() { - let graph = generate_project_graph("expansion").await; - let project = graph.get("tasks").unwrap(); - let task = project.get_task("build").unwrap(); + let graph = generate_workspace_graph("expansion").await; + let task = graph.get_task_from_project("tasks", "build").unwrap(); assert_eq!(task.args, string_vec!["a", "../other.yaml", "b"]); @@ -685,9 +679,8 @@ mod project_graph { #[tokio::test] async fn expands_tag_deps_in_task() { - let graph = generate_project_graph("expansion").await; - let project = graph.get("tasks").unwrap(); - let task = project.get_task("test-tags").unwrap(); + let graph = generate_workspace_graph("expansion").await; + let task = graph.get_task_from_project("tasks", "test-tags").unwrap(); assert_eq!( task.deps, @@ -704,44 +697,76 @@ mod project_graph { #[tokio::test] async fn lists_ids_of_dependencies() { - let graph = generate_project_graph("dependencies").await; + let graph = generate_workspace_graph("dependencies").await; assert_eq!( - map_ids(graph.dependencies_of(&graph.get("a").unwrap())), + map_ids( + graph + .projects + .dependencies_of(&graph.get_project("a").unwrap()) + ), ["b"] ); assert_eq!( - map_ids(graph.dependencies_of(&graph.get("b").unwrap())), + map_ids( + graph + .projects + .dependencies_of(&graph.get_project("b").unwrap()) + ), ["c"] ); assert_eq!( - map_ids(graph.dependencies_of(&graph.get("c").unwrap())), + map_ids( + graph + .projects + .dependencies_of(&graph.get_project("c").unwrap()) + ), string_vec![] ); assert_eq!( - map_ids(graph.dependencies_of(&graph.get("d").unwrap())), + map_ids( + graph + .projects + .dependencies_of(&graph.get_project("d").unwrap()) + ), ["c", "b", "a"] ); } #[tokio::test] async fn lists_ids_of_dependents() { - let graph = generate_project_graph("dependencies").await; + let graph = generate_workspace_graph("dependencies").await; assert_eq!( - map_ids(graph.dependents_of(&graph.get("a").unwrap())), + map_ids( + graph + .projects + .dependents_of(&graph.get_project("a").unwrap()) + ), ["d"] ); assert_eq!( - map_ids(graph.dependents_of(&graph.get("b").unwrap())), + map_ids( + graph + .projects + .dependents_of(&graph.get_project("b").unwrap()) + ), ["d", "a"] ); assert_eq!( - map_ids(graph.dependents_of(&graph.get("c").unwrap())), + map_ids( + graph + .projects + .dependents_of(&graph.get_project("c").unwrap()) + ), ["d", "b"] ); assert_eq!( - map_ids(graph.dependents_of(&graph.get("d").unwrap())), + map_ids( + graph + .projects + .dependents_of(&graph.get_project("d").unwrap()) + ), string_vec![] ); } @@ -752,22 +777,22 @@ mod project_graph { #[tokio::test] async fn no_depends_on() { let sandbox = create_sandbox("dependency-types"); - let mock = create_project_graph_mocker(sandbox.path()); + let mock = create_workspace_graph_mocker(sandbox.path()); - let graph = mock.build_project_graph_for(&["no-depends-on"]).await; + let graph = mock.build_workspace_graph_for(&["no-depends-on"]).await; - assert_eq!(map_ids(graph.get_node_keys()), ["no-depends-on"]); + assert_eq!(map_ids(graph.projects.get_node_keys()), ["no-depends-on"]); } #[tokio::test] async fn some_depends_on() { let sandbox = create_sandbox("dependency-types"); - let mock = create_project_graph_mocker(sandbox.path()); + let mock = create_workspace_graph_mocker(sandbox.path()); - let graph = mock.build_project_graph_for(&["some-depends-on"]).await; + let graph = mock.build_workspace_graph_for(&["some-depends-on"]).await; assert_eq!( - map_ids(graph.get_node_keys()), + map_ids(graph.projects.get_node_keys()), ["a", "c", "some-depends-on"] ); } @@ -775,13 +800,16 @@ mod project_graph { #[tokio::test] async fn from_task_deps() { let sandbox = create_sandbox("dependency-types"); - let mock = create_project_graph_mocker(sandbox.path()); + let mock = create_workspace_graph_mocker(sandbox.path()); - let graph = mock.build_project_graph_for(&["from-task-deps"]).await; + let graph = mock.build_workspace_graph_for(&["from-task-deps"]).await; - assert_eq!(map_ids(graph.get_node_keys()), ["b", "c", "from-task-deps"]); + assert_eq!( + map_ids(graph.projects.get_node_keys()), + ["b", "c", "from-task-deps"] + ); - let deps = &graph.get("from-task-deps").unwrap().dependencies; + let deps = &graph.get_project("from-task-deps").unwrap().dependencies; assert_eq!(deps[0].scope, DependencyScope::Build); assert_eq!(deps[1].scope, DependencyScope::Build); @@ -790,16 +818,21 @@ mod project_graph { #[tokio::test] async fn from_root_task_deps() { let sandbox = create_sandbox("dependency-types"); - let mock = create_project_graph_mocker(sandbox.path()); + let mock = create_workspace_graph_mocker(sandbox.path()); - let graph = mock.build_project_graph_for(&["from-root-task-deps"]).await; + let graph = mock + .build_workspace_graph_for(&["from-root-task-deps"]) + .await; assert_eq!( - map_ids(graph.get_node_keys()), + map_ids(graph.projects.get_node_keys()), ["root", "from-root-task-deps"] ); - let deps = &graph.get("from-root-task-deps").unwrap().dependencies; + let deps = &graph + .get_project("from-root-task-deps") + .unwrap() + .dependencies; assert_eq!(deps[0].scope, DependencyScope::Root); } @@ -807,11 +840,11 @@ mod project_graph { #[tokio::test] async fn self_task_deps() { let sandbox = create_sandbox("dependency-types"); - let mock = create_project_graph_mocker(sandbox.path()); + let mock = create_workspace_graph_mocker(sandbox.path()); - let graph = mock.build_project_graph_for(&["self-task-deps"]).await; + let graph = mock.build_workspace_graph_for(&["self-task-deps"]).await; - assert_eq!(map_ids(graph.get_node_keys()), ["self-task-deps"]); + assert_eq!(map_ids(graph.projects.get_node_keys()), ["self-task-deps"]); } } } @@ -819,13 +852,13 @@ mod project_graph { mod aliases { use super::*; - async fn generate_aliases_project_graph() -> ProjectGraph { + async fn generate_aliases_project_graph() -> WorkspaceGraph { generate_aliases_project_graph_for_fixture("aliases").await } - async fn generate_aliases_project_graph_for_fixture(fixture: &str) -> ProjectGraph { + async fn generate_aliases_project_graph_for_fixture(fixture: &str) -> WorkspaceGraph { let sandbox = create_sandbox(fixture); - let mock = create_project_graph_mocker(sandbox.path()); + let mock = create_workspace_graph_mocker(sandbox.path()); let context = mock.create_context(); // Set aliases for projects @@ -881,7 +914,7 @@ mod project_graph { ) .await; - mock.build_project_graph_with_options(ProjectGraphMockOptions { + mock.build_workspace_graph_with_options(WorkspaceMockOptions { context: Some(context), ..Default::default() }) @@ -892,10 +925,10 @@ mod project_graph { async fn loads_aliases() { let graph = generate_aliases_project_graph().await; - assert_snapshot!(graph.to_dot()); + assert_snapshot!(graph.projects.to_dot()); assert_eq!( - graph.aliases(), + graph.projects.aliases(), FxHashMap::from_iter([ ("@one", &Id::raw("alias-one")), ("@two", &Id::raw("alias-two")), @@ -908,30 +941,36 @@ mod project_graph { async fn doesnt_set_alias_if_same_as_id() { let graph = generate_aliases_project_graph().await; - assert_eq!(graph.get("alias-same-id").unwrap().alias, None); + assert_eq!(graph.get_project("alias-same-id").unwrap().alias, None); } #[tokio::test] async fn doesnt_set_alias_if_a_project_has_the_id() { let graph = generate_aliases_project_graph_for_fixture("aliases-conflict-ids").await; - assert_eq!(graph.get("one").unwrap().alias, None); - assert_eq!(graph.get("two").unwrap().alias, None); + assert_eq!(graph.get_project("one").unwrap().alias, None); + assert_eq!(graph.get_project("two").unwrap().alias, None); } #[tokio::test] async fn can_get_projects_by_alias() { let graph = generate_aliases_project_graph().await; - assert!(graph.get("@one").is_ok()); - assert!(graph.get("@two").is_ok()); - assert!(graph.get("@three").is_ok()); + assert!(graph.get_project("@one").is_ok()); + assert!(graph.get_project("@two").is_ok()); + assert!(graph.get_project("@three").is_ok()); - assert_eq!(graph.get("@one").unwrap(), graph.get("alias-one").unwrap()); - assert_eq!(graph.get("@two").unwrap(), graph.get("alias-two").unwrap()); assert_eq!( - graph.get("@three").unwrap(), - graph.get("alias-three").unwrap() + graph.get_project("@one").unwrap(), + graph.get_project("alias-one").unwrap() + ); + assert_eq!( + graph.get_project("@two").unwrap(), + graph.get_project("alias-two").unwrap() + ); + assert_eq!( + graph.get_project("@three").unwrap(), + graph.get_project("alias-three").unwrap() ); } @@ -940,17 +979,29 @@ mod project_graph { let graph = generate_aliases_project_graph().await; assert_eq!( - map_ids(graph.dependencies_of(&graph.get("explicit").unwrap())), + map_ids( + graph + .projects + .dependencies_of(&graph.get_project("explicit").unwrap()) + ), ["alias-two", "alias-one"] ); assert_eq!( - map_ids(graph.dependencies_of(&graph.get("explicit-and-implicit").unwrap())), + map_ids( + graph + .projects + .dependencies_of(&graph.get_project("explicit-and-implicit").unwrap()) + ), ["alias-three", "alias-two"] ); assert_eq!( - map_ids(graph.dependencies_of(&graph.get("implicit").unwrap())), + map_ids( + graph + .projects + .dependencies_of(&graph.get_project("implicit").unwrap()) + ), ["alias-three", "alias-one"] ); } @@ -960,7 +1011,7 @@ mod project_graph { let graph = generate_aliases_project_graph().await; assert_eq!( - graph.get("dupes-depends-on").unwrap().dependencies, + graph.get_project("dupes-depends-on").unwrap().dependencies, vec![DependencyConfig { id: Id::raw("alias-two"), scope: DependencyScope::Build, @@ -971,9 +1022,7 @@ mod project_graph { assert_eq!( graph - .get("dupes-task-deps") - .unwrap() - .get_task("no-dupes") + .get_task_from_project("dupes-task-deps", "no-dupes") .unwrap() .deps, [TaskDependencyConfig::new( @@ -988,9 +1037,7 @@ mod project_graph { assert_eq!( graph - .get("tasks") - .unwrap() - .get_task("with-aliases") + .get_task_from_project("tasks", "with-aliases") .unwrap() .deps, [ @@ -1010,7 +1057,7 @@ mod project_graph { #[tokio::test] async fn ignores_duplicate_aliases_if_ids_match() { let sandbox = create_sandbox("aliases-conflict"); - let mock = create_project_graph_mocker(sandbox.path()); + let mock = create_workspace_graph_mocker(sandbox.path()); let context = mock.create_context(); context @@ -1032,14 +1079,14 @@ mod project_graph { .await; let graph = mock - .build_project_graph_with_options(ProjectGraphMockOptions { + .build_workspace_graph_with_options(WorkspaceMockOptions { context: Some(context), ..Default::default() }) .await; - assert!(graph.get("@one").is_ok()); - assert!(graph.get("@two").is_ok()); + assert!(graph.get_project("@one").is_ok()); + assert!(graph.get_project("@two").is_ok()); } } @@ -1048,18 +1095,18 @@ mod project_graph { async fn generate_type_constraints_project_graph( func: impl FnOnce(&Sandbox), - ) -> ProjectGraph { + ) -> WorkspaceGraph { let sandbox = create_sandbox("type-constraints"); func(&sandbox); - let mut mock = create_project_graph_mocker(sandbox.path()); + let mut mock = create_workspace_graph_mocker(sandbox.path()); mock.workspace_config .constraints .enforce_project_type_relationships = true; - mock.build_project_graph().await + mock.build_workspace_graph().await } #[tokio::test] @@ -1181,12 +1228,12 @@ mod project_graph { async fn generate_tag_constraints_project_graph( func: impl FnOnce(&Sandbox), - ) -> ProjectGraph { + ) -> WorkspaceGraph { let sandbox = create_sandbox("tag-constraints"); func(&sandbox); - let mut mock = create_project_graph_mocker(sandbox.path()); + let mut mock = create_workspace_graph_mocker(sandbox.path()); mock.workspace_config.constraints.tag_relationships.insert( Id::raw("warrior"), @@ -1198,7 +1245,7 @@ mod project_graph { vec![Id::raw("wizard"), Id::raw("sorcerer"), Id::raw("druid")], ); - mock.build_project_graph().await + mock.build_workspace_graph().await } #[tokio::test] @@ -1317,10 +1364,10 @@ mod project_graph { #[tokio::test] async fn by_language() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; let projects = graph - .query(build_query("language!=[typescript,python]").unwrap()) + .query_projects(build_query("language!=[typescript,python]").unwrap()) .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["a", "d"]); @@ -1328,19 +1375,21 @@ mod project_graph { #[tokio::test] async fn by_project() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; - let projects = graph.query(build_query("project~{b,d}").unwrap()).unwrap(); + let projects = graph + .query_projects(build_query("project~{b,d}").unwrap()) + .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["b", "d"]); } #[tokio::test] async fn by_project_type() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; let projects = graph - .query(build_query("projectType!=[library]").unwrap()) + .query_projects(build_query("projectType!=[library]").unwrap()) .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["a", "c"]); @@ -1348,10 +1397,10 @@ mod project_graph { #[tokio::test] async fn by_project_source() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; let projects = graph - .query(build_query("projectSource~a").unwrap()) + .query_projects(build_query("projectSource~a").unwrap()) .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["a"]); @@ -1359,10 +1408,10 @@ mod project_graph { #[tokio::test] async fn by_tag() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; let projects = graph - .query(build_query("tag=[three,five]").unwrap()) + .query_projects(build_query("tag=[three,five]").unwrap()) .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["b", "c"]); @@ -1370,10 +1419,10 @@ mod project_graph { #[tokio::test] async fn by_task() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; let projects = graph - .query(build_query("task=[test,build]").unwrap()) + .query_projects(build_query("task=[test,build]").unwrap()) .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["a", "c", "d"]); @@ -1381,16 +1430,16 @@ mod project_graph { #[tokio::test] async fn by_task_platform() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; let projects = graph - .query(build_query("taskPlatform=[node]").unwrap()) + .query_projects(build_query("taskPlatform=[node]").unwrap()) .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["a", "b"]); let projects = graph - .query(build_query("taskPlatform=system").unwrap()) + .query_projects(build_query("taskPlatform=system").unwrap()) .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["c", "d"]); @@ -1398,19 +1447,21 @@ mod project_graph { #[tokio::test] async fn by_task_type() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; - let projects = graph.query(build_query("taskType=run").unwrap()).unwrap(); + let projects = graph + .query_projects(build_query("taskType=run").unwrap()) + .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["a"]); } #[tokio::test] async fn with_and_conditions() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; let projects = graph - .query(build_query("task=build && taskPlatform=deno").unwrap()) + .query_projects(build_query("task=build && taskPlatform=deno").unwrap()) .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["d"]); @@ -1418,10 +1469,10 @@ mod project_graph { #[tokio::test] async fn with_or_conditions() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; let projects = graph - .query(build_query("language=javascript || language=typescript").unwrap()) + .query_projects(build_query("language=javascript || language=typescript").unwrap()) .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["a", "b"]); @@ -1429,10 +1480,12 @@ mod project_graph { #[tokio::test] async fn with_nested_conditions() { - let graph = generate_project_graph("query").await; + let graph = generate_workspace_graph("query").await; let projects = graph - .query(build_query("projectType=library && (taskType=build || tag=three)").unwrap()) + .query_projects( + build_query("projectType=library && (taskType=build || tag=three)").unwrap(), + ) .unwrap(); assert_eq!(get_ids_from_projects(projects), vec!["b", "d"]); @@ -1444,19 +1497,19 @@ mod project_graph { #[tokio::test] async fn renders_full() { - let graph = generate_project_graph("dependencies").await; + let graph = generate_workspace_graph("dependencies").await; - assert_snapshot!(graph.to_dot()); + assert_snapshot!(graph.projects.to_dot()); } #[tokio::test] async fn renders_partial() { let sandbox = create_sandbox("dependencies"); - let mock = create_project_graph_mocker(sandbox.path()); + let mock = create_workspace_graph_mocker(sandbox.path()); - let graph = mock.build_project_graph_for(&["b"]).await; + let graph = mock.build_workspace_graph_for(&["b"]).await; - assert_snapshot!(graph.to_dot()); + assert_snapshot!(graph.projects.to_dot()); } } @@ -1466,20 +1519,18 @@ mod project_graph { #[tokio::test] async fn can_load_by_new_id() { let sandbox = create_sandbox("custom-id"); - let graph = generate_project_graph_from_sandbox(sandbox.path()).await; + let graph = generate_workspace_graph_from_sandbox(sandbox.path()).await; - assert_eq!(graph.get("foo").unwrap().id, "foo"); - assert_eq!(graph.get("bar-renamed").unwrap().id, "bar-renamed"); - assert_eq!(graph.get("baz-renamed").unwrap().id, "baz-renamed"); + assert_eq!(graph.get_project("foo").unwrap().id, "foo"); + assert_eq!(graph.get_project("bar-renamed").unwrap().id, "bar-renamed"); + assert_eq!(graph.get_project("baz-renamed").unwrap().id, "baz-renamed"); } #[tokio::test] async fn tasks_can_depend_on_new_id() { let sandbox = create_sandbox("custom-id"); - let graph = generate_project_graph_from_sandbox(sandbox.path()).await; - - let project = graph.get("foo").unwrap(); - let task = project.tasks.get("noop").unwrap(); + let graph = generate_workspace_graph_from_sandbox(sandbox.path()).await; + let task = graph.get_task_from_project("foo", "noop").unwrap(); assert_eq!( task.deps, @@ -1492,16 +1543,16 @@ mod project_graph { #[tokio::test] async fn doesnt_error_for_duplicate_folder_names_if_renamed() { - let graph = generate_project_graph("dupe-folder-ids").await; + let graph = generate_workspace_graph("dupe-folder-ids").await; - assert!(graph.get("one").is_ok()); - assert!(graph.get("two").is_ok()); + assert!(graph.get_project("one").is_ok()); + assert!(graph.get_project("two").is_ok()); } #[tokio::test] #[should_panic(expected = "A project already exists with the identifier foo")] async fn errors_duplicate_ids_from_rename() { - generate_project_graph("custom-id-conflict").await; + generate_workspace_graph("custom-id-conflict").await; } } } diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index 532ee8a0a9c..3c3a9603db0 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -1,7 +1,5 @@ mod project; -mod project_error; pub use moon_config::{ProjectConfig, ProjectType, StackType}; pub use moon_file_group::FileGroup; pub use project::*; -pub use project_error::*; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4ad8d4787fa..37529eb60ea 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1,4 +1,3 @@ -use crate::project_error::ProjectError; use moon_common::{ cacheable, path::{is_root_level_source, WorkspaceRelativePathBuf}, @@ -10,7 +9,7 @@ use moon_config::{ StackType, }; use moon_file_group::FileGroup; -use moon_task::Task; +use moon_task::{Target, Task}; use std::collections::BTreeMap; use std::fmt; use std::path::PathBuf; @@ -58,10 +57,12 @@ cacheable!( pub source: WorkspaceRelativePathBuf, /// Tasks specific to the project. Inherits all tasks from the global config. + /// Note: This map is empty when the project is in the project graph! pub tasks: BTreeMap, - /// List of IDs of all tasks configured or inherited for the project. - pub task_ids: Vec, + /// List of targets of all tasks configured or inherited for the project. + /// Includes internal tasks! + pub task_targets: Vec, /// The type of project. #[serde(rename = "type")] @@ -78,52 +79,6 @@ impl Project { .collect::>() } - /// Return a task with the defined ID. - pub fn get_task>(&self, task_id: I) -> miette::Result<&Task> { - let task_id = Id::raw(task_id.as_ref()); - - let task = self - .tasks - .get(&task_id) - .ok_or_else(|| ProjectError::UnknownTask { - task_id: task_id.clone(), - project_id: self.id.clone(), - })?; - - if !task.is_expanded() { - return Err(ProjectError::UnexpandedTask { - task_id, - project_id: self.id.clone(), - })?; - } - - Ok(task) - } - - /// Return a list of all visible task IDs. - pub fn get_task_ids(&self) -> miette::Result> { - Ok(self - .get_tasks()? - .iter() - .map(|task| &task.id) - .collect::>()) - } - - /// Return all visible tasks within the project. Does not include internal tasks! - pub fn get_tasks(&self) -> miette::Result> { - let mut tasks = vec![]; - - for task_id in self.tasks.keys() { - let task = self.get_task(task_id)?; - - if !task.is_internal() { - tasks.push(task); - } - } - - Ok(tasks) - } - /// Return true if the root-level project. pub fn is_root_level(&self) -> bool { is_root_level_source(&self.source) @@ -146,6 +101,7 @@ impl PartialEq for Project { && self.source == other.source && self.stack == other.stack && self.tasks == other.tasks + && self.task_targets == other.task_targets && self.type_of == other.type_of } } diff --git a/crates/project/src/project_error.rs b/crates/project/src/project_error.rs deleted file mode 100644 index 628e46547c8..00000000000 --- a/crates/project/src/project_error.rs +++ /dev/null @@ -1,22 +0,0 @@ -use miette::Diagnostic; -use moon_common::{Id, Style, Stylize}; -use thiserror::Error; - -#[derive(Error, Debug, Diagnostic)] -pub enum ProjectError { - #[diagnostic(code(project::task::unexpanded))] - #[error( - "Task {} for project {} has not been expanded. This is a problem with moon's internals, please report an issue.", - .task_id.style(Style::Id), - .project_id.style(Style::Id), - )] - UnexpandedTask { task_id: Id, project_id: Id }, - - #[diagnostic(code(project::task::unknown), help = "Has this task been configured?")] - #[error( - "Unknown task {} for project {}.", - .task_id.style(Style::Id), - .project_id.style(Style::Id), - )] - UnknownTask { task_id: Id, project_id: Id }, -} diff --git a/crates/task-expander/Cargo.toml b/crates/task-expander/Cargo.toml new file mode 100644 index 00000000000..6bfc1e6a686 --- /dev/null +++ b/crates/task-expander/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "moon_task_expander" +version = "0.0.1" +edition = "2021" +license = "MIT" +description = "Expands tasks with runtime information." +homepage = "https://moonrepo.dev/moon" +repository = "https://github.com/moonrepo/moon" +publish = false + +[dependencies] +moon_args = { path = "../args" } +moon_common = { path = "../common" } +moon_config = { path = "../config" } +moon_project = { path = "../project" } +moon_task = { path = "../task" } +moon_task_args = { path = "../task-args" } +moon_time = { path = "../time" } +dotenvy = "0.15.7" +miette = { workspace = true } +pathdiff = { workspace = true } +regex = { workspace = true } +rustc-hash = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] +starbase_sandbox = { workspace = true } +starbase_utils = { workspace = true, features = ["json"] } + +[lints] +workspace = true diff --git a/crates/task-expander/src/expander_context.rs b/crates/task-expander/src/expander_context.rs new file mode 100644 index 00000000000..44f3b0e5280 --- /dev/null +++ b/crates/task-expander/src/expander_context.rs @@ -0,0 +1,121 @@ +use moon_config::patterns; +use moon_project::Project; +use rustc_hash::FxHashMap; +use std::env; +use std::path::Path; +use tracing::debug; + +pub struct TaskExpanderContext<'graph> { + /// The base unexpanded project. + pub project: &'graph Project, + + /// Workspace root, of course. + pub workspace_root: &'graph Path, +} + +pub fn substitute_env_vars(mut env: FxHashMap) -> FxHashMap { + let cloned_env = env.clone(); + + for (key, value) in env.iter_mut() { + *value = substitute_env_var(key, value, &cloned_env); + } + + env +} + +pub fn substitute_env_var( + base_name: &str, + value: &str, + env_map: &FxHashMap, +) -> String { + if !value.contains('$') { + return value.to_owned(); + } + + patterns::ENV_VAR_SUBSTITUTE.replace_all( + value, + |caps: &patterns::Captures| { + let Some(name) = caps.name("name1") + .or_else(|| caps.name("name2")) + .map(|cap| cap.as_str()) + else { + return String::new(); + }; + + let flag = caps.name("flag1").or_else(|| caps.name("flag2")).map(|cap| cap.as_str()); + + // If the variable is referencing itself, don't pull + // from the local map, and instead only pull from the + // system environment. Otherwise we hit recursion! + let get_replacement_value = || { + if !base_name.is_empty() && base_name == name { + env::var(name).ok() + } else { + env_map.get(name).cloned().or_else(|| env::var(name).ok()) + } + }; + + match flag { + // Don't substitute + Some("!") => { + format!("${name}") + }, + // Substitute with empty string when missing + Some("?") =>{ + debug!( + "Task value `{}` contains the environment variable ${}, but this variable is not set. Replacing with an empty value.", + value, + name + ); + + get_replacement_value().unwrap_or_default() + }, + // Substitute with self when missing + _ => { + debug!( + "Task value `{}` contains the environment variable ${}, but this variable is not set. Not substituting and keeping as-is. Append with ? or ! to change outcome.", + value, + name + ); + + get_replacement_value() + .unwrap_or_else(|| caps.get(0).unwrap().as_str().to_owned()) + } + } + }) + .to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn handles_flags_when_missing() { + let envs = FxHashMap::default(); + + assert_eq!(substitute_env_var("", "$KEY", &envs), "$KEY"); + assert_eq!(substitute_env_var("", "${KEY}", &envs), "${KEY}"); + + assert_eq!(substitute_env_var("", "$KEY!", &envs), "$KEY"); + assert_eq!(substitute_env_var("", "${KEY!}", &envs), "$KEY"); + + assert_eq!(substitute_env_var("", "$KEY?", &envs), ""); + assert_eq!(substitute_env_var("", "${KEY?}", &envs), ""); + } + + #[test] + fn handles_flags_when_not_missing() { + let mut envs = FxHashMap::default(); + envs.insert("KEY".to_owned(), "value".to_owned()); + + assert_eq!(substitute_env_var("", "$KEY", &envs), "value"); + assert_eq!(substitute_env_var("", "${KEY}", &envs), "value"); + + assert_eq!(substitute_env_var("", "$KEY!", &envs), "$KEY"); + assert_eq!(substitute_env_var("", "${KEY!}", &envs), "$KEY"); + + assert_eq!(substitute_env_var("", "$KEY?", &envs), "value"); + assert_eq!(substitute_env_var("", "${KEY?}", &envs), "value"); + } +} diff --git a/crates/task-expander/src/lib.rs b/crates/task-expander/src/lib.rs new file mode 100644 index 00000000000..30bb73a269d --- /dev/null +++ b/crates/task-expander/src/lib.rs @@ -0,0 +1,11 @@ +mod expander_context; +mod task_expander; +mod task_expander_error; +mod token_expander; +mod token_expander_error; + +pub use expander_context::*; +pub use task_expander::*; +pub use task_expander_error::*; +pub use token_expander::*; +pub use token_expander_error::*; diff --git a/crates/project-expander/src/tasks_expander_new.rs b/crates/task-expander/src/task_expander.rs similarity index 82% rename from crates/project-expander/src/tasks_expander_new.rs rename to crates/task-expander/src/task_expander.rs index de6f5b276ec..c492355b6de 100644 --- a/crates/project-expander/src/tasks_expander_new.rs +++ b/crates/task-expander/src/task_expander.rs @@ -1,26 +1,55 @@ use crate::expander_context::*; -use crate::tasks_expander_error::TasksExpanderError; +use crate::task_expander_error::TasksExpanderError; use crate::token_expander::TokenExpander; +use moon_common::color; use moon_config::TaskArgs; use moon_task::Task; use moon_task_args::parse_task_args; use rustc_hash::FxHashMap; use std::mem; -use tracing::{instrument, trace, warn}; +use tracing::{debug, instrument, trace, warn}; -pub struct TasksExpander<'graph, 'query> { - pub context: &'graph ExpanderContext<'graph, 'query>, - pub token: TokenExpander<'graph, 'query>, +pub struct TaskExpander<'graph> { + // pub context: TaskExpanderContext<'graph>, + pub token: TokenExpander<'graph>, } -impl<'graph, 'query> TasksExpander<'graph, 'query> { - pub fn new(context: &'graph ExpanderContext<'graph, 'query>) -> Self { +impl<'graph> TaskExpander<'graph> { + pub fn new(context: TaskExpanderContext<'graph>) -> Self { Self { token: TokenExpander::new(context), - context, + // context, } } + #[instrument(name = "expand_task", skip_all)] + pub fn expand(mut self, task: &Task) -> miette::Result { + let mut task = task.to_owned(); + + debug!( + target = task.target.as_str(), + "Expanding task {}", + color::label(&task.target) + ); + + // Resolve in this order! + self.expand_env(&mut task)?; + self.expand_deps(&mut task)?; + self.expand_inputs(&mut task)?; + self.expand_outputs(&mut task)?; + self.expand_args(&mut task)?; + + if task.script.is_some() { + self.expand_script(&mut task)?; + } else { + self.expand_command(&mut task)?; + } + + task.metadata.expanded = true; + + Ok(task) + } + #[instrument(skip_all)] pub fn expand_command(&mut self, task: &mut Task) -> miette::Result<()> { trace!( @@ -112,8 +141,8 @@ impl<'graph, 'query> TasksExpander<'graph, 'query> { let env_paths = env_files .iter() .map(|file| { - file.to_workspace_relative(self.context.project.source.as_str()) - .to_path(self.context.workspace_root) + file.to_workspace_relative(self.token.context.project.source.as_str()) + .to_path(self.token.context.workspace_root) }) .collect::>(); diff --git a/crates/project-expander/src/tasks_expander_error.rs b/crates/task-expander/src/task_expander_error.rs similarity index 100% rename from crates/project-expander/src/tasks_expander_error.rs rename to crates/task-expander/src/task_expander_error.rs diff --git a/crates/project-expander/src/token_expander.rs b/crates/task-expander/src/token_expander.rs similarity index 98% rename from crates/project-expander/src/token_expander.rs rename to crates/task-expander/src/token_expander.rs index 3aa88a50787..11fb791a27a 100644 --- a/crates/project-expander/src/token_expander.rs +++ b/crates/task-expander/src/token_expander.rs @@ -1,4 +1,4 @@ -use crate::expander_context::{substitute_env_var, ExpanderContext}; +use crate::expander_context::{substitute_env_var, TaskExpanderContext}; use crate::token_expander_error::TokenExpanderError; use moon_args::join_args; use moon_common::path::{self, WorkspaceRelativePathBuf}; @@ -46,17 +46,17 @@ impl TokenScope { } } -pub struct TokenExpander<'graph, 'query> { +pub struct TokenExpander<'graph> { pub scope: TokenScope, - context: &'graph ExpanderContext<'graph, 'query>, + pub context: TaskExpanderContext<'graph>, // In the current process env_vars: Vec, } -impl<'graph, 'query> TokenExpander<'graph, 'query> { - pub fn new(context: &'graph ExpanderContext<'graph, 'query>) -> Self { +impl<'graph> TokenExpander<'graph> { + pub fn new(context: TaskExpanderContext<'graph>) -> Self { Self { scope: TokenScope::Args, context, diff --git a/crates/project-expander/src/token_expander_error.rs b/crates/task-expander/src/token_expander_error.rs similarity index 100% rename from crates/project-expander/src/token_expander_error.rs rename to crates/task-expander/src/token_expander_error.rs diff --git a/crates/project-expander/tests/__fixtures__/env-file/.env-shared b/crates/task-expander/tests/__fixtures__/env-file/.env-shared similarity index 100% rename from crates/project-expander/tests/__fixtures__/env-file/.env-shared rename to crates/task-expander/tests/__fixtures__/env-file/.env-shared diff --git a/crates/project-expander/tests/__fixtures__/env-file/project/source/.env b/crates/task-expander/tests/__fixtures__/env-file/project/source/.env similarity index 100% rename from crates/project-expander/tests/__fixtures__/env-file/project/source/.env rename to crates/task-expander/tests/__fixtures__/env-file/project/source/.env diff --git a/crates/project-expander/tests/__fixtures__/env-file/project/source/.env-invalid b/crates/task-expander/tests/__fixtures__/env-file/project/source/.env-invalid similarity index 100% rename from crates/project-expander/tests/__fixtures__/env-file/project/source/.env-invalid rename to crates/task-expander/tests/__fixtures__/env-file/project/source/.env-invalid diff --git a/crates/project-expander/tests/__fixtures__/file-group/project/source/config.yml b/crates/task-expander/tests/__fixtures__/file-group/project/source/config.yml similarity index 100% rename from crates/project-expander/tests/__fixtures__/file-group/project/source/config.yml rename to crates/task-expander/tests/__fixtures__/file-group/project/source/config.yml diff --git a/crates/project-expander/tests/__fixtures__/file-group/project/source/dir/subdir/nested.json b/crates/task-expander/tests/__fixtures__/file-group/project/source/dir/subdir/nested.json similarity index 100% rename from crates/project-expander/tests/__fixtures__/file-group/project/source/dir/subdir/nested.json rename to crates/task-expander/tests/__fixtures__/file-group/project/source/dir/subdir/nested.json diff --git a/crates/project-expander/tests/__fixtures__/file-group/project/source/dir/subdir/not-used.md b/crates/task-expander/tests/__fixtures__/file-group/project/source/dir/subdir/not-used.md similarity index 100% rename from crates/project-expander/tests/__fixtures__/file-group/project/source/dir/subdir/not-used.md rename to crates/task-expander/tests/__fixtures__/file-group/project/source/dir/subdir/not-used.md diff --git a/crates/project-expander/tests/__fixtures__/file-group/project/source/docs.md b/crates/task-expander/tests/__fixtures__/file-group/project/source/docs.md similarity index 100% rename from crates/project-expander/tests/__fixtures__/file-group/project/source/docs.md rename to crates/task-expander/tests/__fixtures__/file-group/project/source/docs.md diff --git a/crates/project-expander/tests/__fixtures__/file-group/project/source/other/file.json b/crates/task-expander/tests/__fixtures__/file-group/project/source/other/file.json similarity index 100% rename from crates/project-expander/tests/__fixtures__/file-group/project/source/other/file.json rename to crates/task-expander/tests/__fixtures__/file-group/project/source/other/file.json diff --git a/crates/task-expander/tests/task_expander_test.rs b/crates/task-expander/tests/task_expander_test.rs new file mode 100644 index 00000000000..d50e770854d --- /dev/null +++ b/crates/task-expander/tests/task_expander_test.rs @@ -0,0 +1,822 @@ +mod utils; + +use moon_common::path::WorkspaceRelativePathBuf; +use moon_config::{InputPath, OutputPath, TaskArgs, TaskDependencyConfig}; +use moon_task::Target; +use moon_task_expander::TaskExpander; +use rustc_hash::{FxHashMap, FxHashSet}; +use starbase_sandbox::{create_empty_sandbox, create_sandbox}; +use std::env; +use utils::{create_context, create_project, create_project_with_tasks, create_task}; + +fn create_path_set(inputs: Vec<&str>) -> FxHashSet { + FxHashSet::from_iter(inputs.into_iter().map(|s| s.into())) +} + +mod task_expander { + use super::*; + + mod expand_command { + use super::*; + + #[test] + #[should_panic(expected = "Token @dirs(group) in task project:task cannot be used")] + fn errors_on_token_funcs() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.command = "@dirs(group)".into(); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context) + .expand_command(&mut task) + .unwrap(); + } + + #[test] + fn replaces_token_vars() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.command = "./$project/bin".into(); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context) + .expand_command(&mut task) + .unwrap(); + + assert_eq!(task.command, "./project/bin"); + } + + #[test] + fn replaces_env_vars() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.command = "./$FOO/${BAR}/$BAZ_QUX".into(); + + env::set_var("FOO", "foo"); + env::set_var("BAZ_QUX", "baz-qux"); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context) + .expand_command(&mut task) + .unwrap(); + + env::remove_var("FOO"); + env::remove_var("BAZ_QUX"); + + assert_eq!(task.command, "./foo/${BAR}/baz-qux"); + } + + #[test] + fn replaces_env_var_from_self() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.command = "./$FOO".into(); + task.env.insert("FOO".into(), "foo-self".into()); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context) + .expand_command(&mut task) + .unwrap(); + + assert_eq!(task.command, "./foo-self"); + } + } + + mod expand_args { + use super::*; + + #[test] + fn replaces_token_funcs() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.args = vec!["a".into(), "@files(all)".into(), "b".into()]; + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_args(&mut task).unwrap(); + + assert_eq!( + task.args, + [ + "a", + "./config.yml", + "./dir/subdir/nested.json", + "./docs.md", + "./other/file.json", + "b" + ] + ); + } + + #[test] + fn replaces_token_funcs_from_workspace_root() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.args = vec!["a".into(), "@files(all)".into(), "b".into()]; + task.options.run_from_workspace_root = true; + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_args(&mut task).unwrap(); + + assert_eq!( + task.args, + [ + "a", + "./project/source/config.yml", + "./project/source/dir/subdir/nested.json", + "./project/source/docs.md", + "./project/source/other/file.json", + "b" + ] + ); + } + + #[test] + fn replaces_token_vars() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.args = vec![ + "a".into(), + "$project/dir".into(), + "b".into(), + "some/$task".into(), + ]; + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_args(&mut task).unwrap(); + + assert_eq!(task.args, ["a", "project/dir", "b", "some/task"]); + } + + #[test] + fn replaces_env_vars() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.args = vec![ + "a".into(), + "$FOO_BAR".into(), + "b".into(), + "c/${BAR_BAZ}/d".into(), + ]; + + env::set_var("BAR_BAZ", "bar-baz"); + env::set_var("FOO_BAR", "foo-bar"); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_args(&mut task).unwrap(); + + env::remove_var("FOO_BAR"); + env::remove_var("BAR_BAZ"); + + assert_eq!(task.args, ["a", "foo-bar", "b", "c/bar-baz/d"]); + } + + #[test] + fn replaces_env_var_from_self() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.args = vec!["a".into(), "${FOO_BAR}".into(), "b".into()]; + task.env.insert("FOO_BAR".into(), "foo-bar-self".into()); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_args(&mut task).unwrap(); + + assert_eq!(task.args, ["a", "foo-bar-self", "b"]); + } + } + + mod expand_deps { + use super::*; + + #[test] + fn passes_args_through() { + let sandbox = create_empty_sandbox(); + let project = create_project_with_tasks(sandbox.path(), "project"); + let mut task = create_task(); + + task.deps.push(TaskDependencyConfig { + args: TaskArgs::String("a b c".into()), + target: Target::parse("test").unwrap(), + ..TaskDependencyConfig::default() + }); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_deps(&mut task).unwrap(); + + assert_eq!( + task.deps, + vec![TaskDependencyConfig { + args: TaskArgs::List(vec!["a".into(), "b".into(), "c".into()]), + target: Target::parse("~:test").unwrap(), + ..TaskDependencyConfig::default() + }] + ); + } + + #[test] + fn supports_tokens_in_args() { + let sandbox = create_empty_sandbox(); + let project = create_project_with_tasks(sandbox.path(), "project"); + let mut task = create_task(); + + task.deps.push(TaskDependencyConfig { + args: TaskArgs::String("$project $language".into()), + target: Target::parse("test").unwrap(), + ..TaskDependencyConfig::default() + }); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_deps(&mut task).unwrap(); + + assert_eq!( + task.deps, + vec![TaskDependencyConfig { + args: TaskArgs::List(vec!["project".into(), "unknown".into()]), + target: Target::parse("~:test").unwrap(), + ..TaskDependencyConfig::default() + }] + ); + } + + #[test] + fn passes_env_through() { + let sandbox = create_empty_sandbox(); + let project = create_project_with_tasks(sandbox.path(), "project"); + let mut task = create_task(); + + task.deps.push(TaskDependencyConfig { + env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), + target: Target::parse("test").unwrap(), + ..TaskDependencyConfig::default() + }); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_deps(&mut task).unwrap(); + + assert_eq!( + task.deps, + vec![TaskDependencyConfig { + args: TaskArgs::None, + env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), + target: Target::parse("~:test").unwrap(), + optional: None, + }] + ); + } + + #[test] + fn supports_token_in_env() { + let sandbox = create_empty_sandbox(); + let project = create_project_with_tasks(sandbox.path(), "project"); + let mut task = create_task(); + + task.deps.push(TaskDependencyConfig { + env: FxHashMap::from_iter([("FOO".into(), "$project-$language".into())]), + target: Target::parse("test").unwrap(), + ..TaskDependencyConfig::default() + }); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_deps(&mut task).unwrap(); + + assert_eq!( + task.deps, + vec![TaskDependencyConfig { + args: TaskArgs::None, + env: FxHashMap::from_iter([("FOO".into(), "project-unknown".into())]), + target: Target::parse("~:test").unwrap(), + optional: None, + }] + ); + } + + #[test] + fn passes_args_and_env_through() { + let sandbox = create_empty_sandbox(); + let project = create_project_with_tasks(sandbox.path(), "project"); + let mut task = create_task(); + + task.deps.push(TaskDependencyConfig { + args: TaskArgs::String("a b c".into()), + env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), + target: Target::parse("test").unwrap(), + optional: None, + }); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_deps(&mut task).unwrap(); + + assert_eq!( + task.deps, + vec![TaskDependencyConfig { + args: TaskArgs::List(vec!["a".into(), "b".into(), "c".into()]), + env: FxHashMap::from_iter([("FOO".into(), "bar".into())]), + target: Target::parse("~:test").unwrap(), + optional: None, + }] + ); + } + } + + mod expand_env { + use super::*; + + #[test] + fn replaces_env_vars() { + let sandbox = create_empty_sandbox(); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.env.insert("KEY1".into(), "value1".into()); + task.env.insert("KEY2".into(), "inner-${FOO}".into()); + task.env.insert("KEY3".into(), "$KEY1-self".into()); + + env::set_var("FOO", "foo"); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + env::remove_var("FOO"); + + assert_eq!( + task.env, + FxHashMap::from_iter([ + ("KEY1".into(), "value1".into()), + ("KEY2".into(), "inner-foo".into()), + ("KEY3".into(), "value1-self".into()), + ]) + ); + } + + #[test] + fn replaces_tokens() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.env.insert("KEY1".into(), "@globs(all)".into()); + task.env.insert("KEY2".into(), "$project-$task".into()); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + assert_eq!( + task.env, + FxHashMap::from_iter([ + ("KEY1".into(), "./*.md,./**/*.json".into()), + ("KEY2".into(), "project-task".into()), + ]) + ); + } + + #[test] + fn replaces_tokens_from_workspace_root() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.options.run_from_workspace_root = true; + + task.env.insert("KEY1".into(), "@globs(all)".into()); + task.env.insert("KEY2".into(), "$project-$task".into()); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + assert_eq!( + task.env, + FxHashMap::from_iter([ + ( + "KEY1".into(), + "./project/source/*.md,./project/source/**/*.json".into() + ), + ("KEY2".into(), "project-task".into()), + ]) + ); + } + + #[test] + fn can_use_env_vars_and_token_vars() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.env + .insert("KEY".into(), "$project-$FOO-$unknown".into()); + + env::set_var("FOO", "foo"); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + env::remove_var("FOO"); + + assert_eq!( + task.env, + FxHashMap::from_iter([("KEY".into(), "project-foo-$unknown".into()),]) + ); + } + + #[test] + fn loads_from_env_file() { + let sandbox = create_sandbox("env-file"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.env.insert("KEY1".into(), "value1".into()); + task.env.insert("KEY2".into(), "value2".into()); + task.options.env_files = Some(vec![InputPath::ProjectFile(".env".into())]); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + assert_eq!( + task.env, + FxHashMap::from_iter([ + ("KEY1".into(), "value1".into()), + ("KEY2".into(), "value2".into()), // Not overridden by env file + ("KEY3".into(), "value3".into()), + ]) + ); + } + + #[test] + fn loads_from_root_env_file_and_substitutes() { + let sandbox = create_sandbox("env-file"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.options.env_files = Some(vec![InputPath::WorkspaceFile(".env-shared".into())]); + + env::set_var("EXTERNAL", "external-value"); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + env::remove_var("EXTERNAL"); + + assert_eq!( + task.env, + FxHashMap::from_iter([ + ("ROOT".into(), "true".into()), + ("BASE".into(), "value".into()), + ("FROM_SELF1".into(), "value".into()), + ("FROM_SELF2".into(), "value".into()), + ("FROM_SYSTEM".into(), "external-value".into()), + ]) + ); + } + + #[test] + fn can_substitute_var_from_env_file() { + let sandbox = create_sandbox("env-file"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.options.env_files = Some(vec![InputPath::WorkspaceFile(".env-shared".into())]); + task.env.insert("TOP_LEVEL".into(), "$BASE".into()); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + assert_eq!(task.env.get("TOP_LEVEL").unwrap(), "value"); + } + + #[test] + fn can_substitute_self_from_system() { + let sandbox = create_sandbox("env-file"); + let project = create_project(sandbox.path()); + + env::set_var("MYPATH", "/another/path"); + + let mut task = create_task(); + task.env.insert("MYPATH".into(), "/path:$MYPATH".into()); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + assert_eq!(task.env.get("MYPATH").unwrap(), "/path:/another/path"); + + env::remove_var("MYPATH"); + } + + #[test] + fn doesnt_substitute_self_from_local() { + let sandbox = create_sandbox("env-file"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.env.insert("MYPATH".into(), "/path:$MYPATH".into()); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + assert_eq!(task.env.get("MYPATH").unwrap(), "/path:$MYPATH"); + } + + #[test] + fn loads_from_multiple_env_file() { + let sandbox = create_sandbox("env-file"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.env.insert("KEY1".into(), "value1".into()); + task.env.insert("KEY2".into(), "value2".into()); + task.options.env_files = Some(vec![ + InputPath::ProjectFile(".env".into()), + InputPath::WorkspaceFile(".env-shared".into()), + ]); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + assert_eq!( + task.env, + FxHashMap::from_iter([ + ("KEY1".into(), "value1".into()), + ("KEY2".into(), "value2".into()), // Not overridden by env file + ("KEY3".into(), "value3".into()), + // shared + ("ROOT".into(), "true".into()), + ("BASE".into(), "value".into()), + ("FROM_SELF1".into(), "value".into()), + ("FROM_SELF2".into(), "value".into()), + ("FROM_SYSTEM".into(), "".into()), + ]) + ); + } + + #[test] + fn skips_missing_env_file() { + let sandbox = create_sandbox("env-file"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.env.insert("KEY1".into(), "value1".into()); + task.env.insert("KEY2".into(), "value2".into()); + task.options.env_files = Some(vec![InputPath::ProjectFile(".env-missing".into())]); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + + assert_eq!( + task.env, + FxHashMap::from_iter([ + ("KEY1".into(), "value1".into()), + ("KEY2".into(), "value2".into()), + ]) + ); + } + + #[test] + #[should_panic(expected = "Failed to parse env file")] + fn errors_invalid_env_file() { + let sandbox = create_sandbox("env-file"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.options.env_files = Some(vec![InputPath::ProjectFile(".env-invalid".into())]); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_env(&mut task).unwrap(); + } + } + + mod expand_inputs { + use super::*; + + #[test] + fn extracts_env_var() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.inputs.push(InputPath::EnvVar("FOO_BAR".into())); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_inputs(&mut task).unwrap(); + + assert_eq!(task.input_env, FxHashSet::from_iter(["FOO_BAR".into()])); + assert_eq!(task.input_globs, FxHashSet::default()); + assert_eq!(task.input_files, FxHashSet::default()); + } + + #[test] + fn replaces_token_funcs() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.inputs.push(InputPath::ProjectFile("file.txt".into())); + task.inputs.push(InputPath::TokenFunc("@files(all)".into())); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_inputs(&mut task).unwrap(); + + assert_eq!(task.input_globs, FxHashSet::default()); + assert_eq!( + task.input_files, + create_path_set(vec![ + "project/source/dir/subdir/nested.json", + "project/source/file.txt", + "project/source/docs.md", + "project/source/config.yml", + "project/source/other/file.json" + ]) + ); + } + + #[test] + fn splits_token_func_into_files_globs() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.inputs.push(InputPath::ProjectFile("file.txt".into())); + task.inputs.push(InputPath::TokenFunc("@group(all)".into())); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_inputs(&mut task).unwrap(); + + assert_eq!( + task.input_globs, + create_path_set(vec!["project/source/*.md", "project/source/**/*.json"]) + ); + assert_eq!( + task.input_files, + create_path_set(vec![ + "project/source/dir/subdir", + "project/source/file.txt", + "project/source/config.yml", + ]) + ); + } + + #[test] + fn replaces_token_vars() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.inputs + .push(InputPath::ProjectGlob("$task/**/*".into())); + task.inputs + .push(InputPath::WorkspaceFile("$project/index.js".into())); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context).expand_inputs(&mut task).unwrap(); + + assert_eq!( + task.input_globs, + create_path_set(vec!["project/source/task/**/*"]) + ); + assert_eq!(task.input_files, create_path_set(vec!["project/index.js"])); + } + } + + mod expand_outputs { + use super::*; + + #[test] + fn replaces_token_funcs() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.outputs + .push(OutputPath::ProjectFile("file.txt".into())); + task.outputs + .push(OutputPath::TokenFunc("@files(all)".into())); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context) + .expand_outputs(&mut task) + .unwrap(); + + assert_eq!(task.output_globs, FxHashSet::default()); + assert_eq!( + task.output_files, + create_path_set(vec![ + "project/source/dir/subdir/nested.json", + "project/source/file.txt", + "project/source/docs.md", + "project/source/config.yml", + "project/source/other/file.json" + ]) + ); + } + + #[test] + fn splits_token_func_into_files_globs() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.outputs + .push(OutputPath::ProjectFile("file.txt".into())); + task.outputs + .push(OutputPath::TokenFunc("@group(all)".into())); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context) + .expand_outputs(&mut task) + .unwrap(); + + assert_eq!( + task.output_globs, + create_path_set(vec!["project/source/*.md", "project/source/**/*.json"]) + ); + assert_eq!( + task.output_files, + create_path_set(vec![ + "project/source/dir/subdir", + "project/source/file.txt", + "project/source/config.yml", + ]) + ); + } + + #[test] + fn replaces_token_vars() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.outputs + .push(OutputPath::ProjectGlob("$task/**/*".into())); + task.outputs + .push(OutputPath::WorkspaceFile("$project/index.js".into())); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context) + .expand_outputs(&mut task) + .unwrap(); + + assert_eq!( + task.output_globs, + create_path_set(vec!["project/source/task/**/*"]) + ); + assert_eq!(task.output_files, create_path_set(vec!["project/index.js"])); + } + + #[test] + fn doesnt_overlap_input_file() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.outputs.push(OutputPath::ProjectFile("out".into())); + task.input_files.insert("project/source/out".into()); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context) + .expand_outputs(&mut task) + .unwrap(); + + assert!(task.input_files.is_empty()); + assert_eq!( + task.output_files, + create_path_set(vec!["project/source/out"]) + ); + } + + #[test] + fn doesnt_overlap_input_glob() { + let sandbox = create_sandbox("file-group"); + let project = create_project(sandbox.path()); + + let mut task = create_task(); + task.outputs + .push(OutputPath::ProjectGlob("out/**/*".into())); + task.input_globs.insert("project/source/out/**/*".into()); + + let context = create_context(&project, sandbox.path()); + TaskExpander::new(context) + .expand_outputs(&mut task) + .unwrap(); + + assert!(task.input_globs.is_empty()); + assert_eq!( + task.output_globs, + create_path_set(vec!["project/source/out/**/*"]) + ); + } + } +} diff --git a/crates/project-expander/tests/token_expander_test.rs b/crates/task-expander/tests/token_expander_test.rs similarity index 92% rename from crates/project-expander/tests/token_expander_test.rs rename to crates/task-expander/tests/token_expander_test.rs index 89259dc5474..a1c7bc40bd5 100644 --- a/crates/project-expander/tests/token_expander_test.rs +++ b/crates/task-expander/tests/token_expander_test.rs @@ -2,7 +2,7 @@ mod utils; use moon_common::path::WorkspaceRelativePathBuf; use moon_config::{InputPath, LanguageType, OutputPath, ProjectType}; -use moon_project_expander::{ExpandedResult, TokenExpander}; +use moon_task_expander::{ExpandedResult, TokenExpander}; use rustc_hash::FxHashMap; use starbase_sandbox::{create_empty_sandbox, create_sandbox, predicates::prelude::*}; use std::borrow::Cow; @@ -19,7 +19,7 @@ mod token_expander { let project = create_project(sandbox.path()); let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); expander.replace_function(&task, "@unknown(id)").unwrap(); } @@ -31,7 +31,7 @@ mod token_expander { let project = create_project(sandbox.path()); let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); expander.replace_function(&task, "@files(unknown)").unwrap(); } @@ -45,7 +45,7 @@ mod token_expander { let project = create_project(sandbox.path()); let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); expander.replace_function(&task, "@in(str)").unwrap(); } @@ -57,7 +57,7 @@ mod token_expander { let project = create_project(sandbox.path()); let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); expander.replace_function(&task, "@in(10)").unwrap(); } @@ -71,7 +71,7 @@ mod token_expander { let project = create_project(sandbox.path()); let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); expander.replace_function(&task, "@out(str)").unwrap(); } @@ -83,7 +83,7 @@ mod token_expander { let project = create_project(sandbox.path()); let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); expander.replace_function(&task, "@out(10)").unwrap(); } @@ -99,7 +99,7 @@ mod token_expander { task.inputs.push(InputPath::TokenFunc("@globs(all)".into())); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); let result = expander.replace_function(&task, "@in(0)").unwrap(); @@ -119,7 +119,7 @@ mod token_expander { .push(InputPath::TokenFunc("@globs(unknown)".into())); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); expander.replace_function(&task, "@in(0)").unwrap(); } @@ -133,7 +133,7 @@ mod token_expander { .push(OutputPath::TokenFunc("@globs(all)".into())); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); let result = expander.replace_function(&task, "@out(0)").unwrap(); @@ -153,7 +153,7 @@ mod token_expander { .push(OutputPath::TokenFunc("@globs(unknown)".into())); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); expander.replace_function(&task, "@out(0)").unwrap(); } @@ -174,7 +174,7 @@ mod token_expander { let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); let get_value = |token: &str| { expander @@ -217,7 +217,7 @@ mod token_expander { let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); let get_value = |token: &str| { expander @@ -246,7 +246,7 @@ mod token_expander { let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); assert_eq!( expander @@ -356,7 +356,7 @@ mod token_expander { let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); assert_eq!( expander @@ -398,7 +398,7 @@ mod token_expander { let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); assert_eq!( expander @@ -427,7 +427,7 @@ mod token_expander { let task = create_task(); let context = create_context(&project, sandbox.path()); - let expander = TokenExpander::new(&context); + let expander = TokenExpander::new(context); assert_eq!( expander @@ -451,7 +451,7 @@ mod token_expander { task.command = "@files(sources)".into(); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_command(&task).unwrap(); } @@ -465,7 +465,7 @@ mod token_expander { task.command = "bin".into(); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!(expander.expand_command(&task).unwrap(), "bin"); } @@ -479,7 +479,7 @@ mod token_expander { task.command = "$project/bin".into(); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!(expander.expand_command(&task).unwrap(), "project/bin"); } @@ -493,7 +493,7 @@ mod token_expander { task.command = "$project/bin/$task".into(); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!(expander.expand_command(&task).unwrap(), "project/bin/task"); } @@ -514,7 +514,7 @@ mod token_expander { task.command = "@meta(name)".into(); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!(expander.expand_command(&task).unwrap(), "name"); } @@ -539,7 +539,7 @@ mod token_expander { task.args.push("@meta(name)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!(expander.expand_args(&task).unwrap(), vec!["name"]); } @@ -554,7 +554,7 @@ mod token_expander { task.args.push("$FOO/$project".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!(expander.expand_args(&task).unwrap(), vec!["bar/project"]); } @@ -572,7 +572,7 @@ mod token_expander { task.env.insert("KEY".into(), "value".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_env(&task).unwrap(), @@ -589,7 +589,7 @@ mod token_expander { task.env.insert("VAR".into(), "$project-prod".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_env(&task).unwrap(), @@ -607,7 +607,7 @@ mod token_expander { .insert("VARS".into(), "$project-debug-$task".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_env(&task).unwrap(), @@ -624,7 +624,7 @@ mod token_expander { task.env.insert("GROUP".into(), "@group(all)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_env(&task).unwrap(), @@ -644,7 +644,7 @@ mod token_expander { task.env.insert("DIRS".into(), "@dirs(dirs)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_env(&task).unwrap(), @@ -661,7 +661,7 @@ mod token_expander { task.env.insert("FILES".into(), "@files(all)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_env(&task).unwrap(), @@ -681,7 +681,7 @@ mod token_expander { task.env.insert("GLOBS".into(), "@globs(all)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_env(&task).unwrap(), @@ -698,7 +698,7 @@ mod token_expander { task.env.insert("ROOT".into(), "@root(all)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_env(&task).unwrap(), @@ -722,7 +722,7 @@ mod token_expander { task.env.insert("ROOT".into(), "@meta(name)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_env(&task).unwrap(), @@ -742,7 +742,7 @@ mod token_expander { task.env.insert("IN".into(), "@in(0)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_env(&task).unwrap(); } @@ -759,7 +759,7 @@ mod token_expander { task.env.insert("OUT".into(), "@out(0)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_env(&task).unwrap(); } @@ -776,7 +776,7 @@ mod token_expander { task.env.insert("OUT".into(), "@envs(envs)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_env(&task).unwrap(); } @@ -794,7 +794,7 @@ mod token_expander { task.inputs = vec![InputPath::EnvVar("FOO_BAR".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_inputs(&task).unwrap(), @@ -819,7 +819,7 @@ mod token_expander { env::set_var("BAR_ONE", "1"); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); let mut result = expander.expand_inputs(&task).unwrap(); result.env.sort(); @@ -847,7 +847,7 @@ mod token_expander { task.inputs = vec![InputPath::TokenFunc("@group(all)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_inputs(&task).unwrap(), @@ -874,7 +874,7 @@ mod token_expander { task.inputs = vec![InputPath::TokenFunc("@dirs(dirs)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_inputs(&task).unwrap(), @@ -898,7 +898,7 @@ mod token_expander { task.inputs = vec![InputPath::TokenFunc("@files(all)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_inputs(&task).unwrap(), @@ -924,7 +924,7 @@ mod token_expander { task.inputs = vec![InputPath::TokenFunc("@globs(all)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_inputs(&task).unwrap(), @@ -948,7 +948,7 @@ mod token_expander { task.inputs = vec![InputPath::TokenFunc("@root(all)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_inputs(&task).unwrap(), @@ -969,7 +969,7 @@ mod token_expander { task.inputs = vec![InputPath::TokenFunc("@envs(envs)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_inputs(&task).unwrap(), @@ -992,7 +992,7 @@ mod token_expander { task.inputs = vec![InputPath::TokenFunc("@in(0)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_inputs(&task).unwrap(); } @@ -1009,7 +1009,7 @@ mod token_expander { task.inputs = vec![InputPath::TokenFunc("@out(0)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_inputs(&task).unwrap(); } @@ -1026,7 +1026,7 @@ mod token_expander { task.inputs = vec![InputPath::TokenFunc("@meta(name)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_inputs(&task).unwrap(); } @@ -1043,7 +1043,7 @@ mod token_expander { ]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_inputs(&task).unwrap(), @@ -1072,7 +1072,7 @@ mod token_expander { ]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_inputs(&task).unwrap(), @@ -1103,7 +1103,7 @@ mod token_expander { task.inputs = vec![InputPath::ProjectFile("dir".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_inputs(&task).unwrap(), @@ -1128,7 +1128,7 @@ mod token_expander { task.outputs = vec![OutputPath::TokenFunc("@group(all)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_outputs(&task).unwrap(), @@ -1155,7 +1155,7 @@ mod token_expander { task.outputs = vec![OutputPath::TokenFunc("@dirs(dirs)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_outputs(&task).unwrap(), @@ -1179,7 +1179,7 @@ mod token_expander { task.outputs = vec![OutputPath::TokenFunc("@files(all)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_outputs(&task).unwrap(), @@ -1205,7 +1205,7 @@ mod token_expander { task.outputs = vec![OutputPath::TokenFunc("@globs(all)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_outputs(&task).unwrap(), @@ -1229,7 +1229,7 @@ mod token_expander { task.outputs = vec![OutputPath::TokenFunc("@root(all)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_outputs(&task).unwrap(), @@ -1253,7 +1253,7 @@ mod token_expander { task.outputs = vec![OutputPath::TokenFunc("@in(0)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_outputs(&task).unwrap(); } @@ -1270,7 +1270,7 @@ mod token_expander { task.outputs = vec![OutputPath::TokenFunc("@out(0)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_outputs(&task).unwrap(); } @@ -1287,7 +1287,7 @@ mod token_expander { task.outputs = vec![OutputPath::TokenFunc("@meta(name)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_outputs(&task).unwrap(); } @@ -1304,7 +1304,7 @@ mod token_expander { task.outputs = vec![OutputPath::TokenFunc("@envs(envs)".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); expander.expand_outputs(&task).unwrap(); } @@ -1323,7 +1323,7 @@ mod token_expander { ]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_outputs(&task).unwrap(), @@ -1358,7 +1358,7 @@ mod token_expander { ]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_outputs(&task).unwrap(), @@ -1389,7 +1389,7 @@ mod token_expander { task.script = Some("bin --foo -az bar".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!(expander.expand_script(&task).unwrap(), "bin --foo -az bar"); } @@ -1403,7 +1403,7 @@ mod token_expander { task.script = Some("$project/bin --foo -az bar".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_script(&task).unwrap(), @@ -1420,7 +1420,7 @@ mod token_expander { task.script = Some("$project/bin/$task --foo -az bar".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_script(&task).unwrap(), @@ -1438,7 +1438,7 @@ mod token_expander { task.outputs = vec![OutputPath::ProjectGlob("**/*.json".into())]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_script(&task).unwrap(), @@ -1459,7 +1459,7 @@ mod token_expander { ]; let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!( expander.expand_script(&task).unwrap(), @@ -1483,7 +1483,7 @@ mod token_expander { task.script = Some("bin --name @meta(name)".into()); let context = create_context(&project, sandbox.path()); - let mut expander = TokenExpander::new(&context); + let mut expander = TokenExpander::new(context); assert_eq!(expander.expand_script(&task).unwrap(), "bin --name name"); } diff --git a/crates/project-expander/tests/utils.rs b/crates/task-expander/tests/utils.rs similarity index 82% rename from crates/project-expander/tests/utils.rs rename to crates/task-expander/tests/utils.rs index c0ca6af07f2..a28a908f7c8 100644 --- a/crates/project-expander/tests/utils.rs +++ b/crates/task-expander/tests/utils.rs @@ -4,37 +4,21 @@ use moon_common::path::WorkspaceRelativePathBuf; use moon_common::Id; use moon_config::InputPath; use moon_project::{FileGroup, Project}; -use moon_project_expander::ExpanderContext; use moon_task::{Target, Task}; -use rustc_hash::FxHashMap; +use moon_task_expander::TaskExpanderContext; use std::collections::BTreeMap; use std::path::Path; -pub fn create_context<'g, 'q>( +pub fn create_context<'g>( project: &'g Project, workspace_root: &'g Path, -) -> ExpanderContext<'g, 'q> { - ExpanderContext { - aliases: FxHashMap::default(), +) -> TaskExpanderContext<'g> { + TaskExpanderContext { project, - query: Box::new(|_| Ok(vec![])), workspace_root, } } -pub fn create_context_with_query<'g, 'q, Q>( - project: &'g Project, - workspace_root: &'g Path, - query: Q, -) -> ExpanderContext<'g, 'q> -where - Q: Fn(String) -> miette::Result> + 'g, -{ - let mut context = create_context(project, workspace_root); - context.query = Box::new(query); - context -} - pub fn create_project(workspace_root: &Path) -> Project { let source = WorkspaceRelativePathBuf::from("project/source"); diff --git a/crates/task-graph/Cargo.toml b/crates/task-graph/Cargo.toml index 222b0dbf8fa..db58ff2d6e8 100644 --- a/crates/task-graph/Cargo.toml +++ b/crates/task-graph/Cargo.toml @@ -12,8 +12,10 @@ publish = false moon_common = { path = "../common" } moon_config = { path = "../config" } moon_graph_utils = { path = "../graph-utils" } +moon_project_graph = { path = "../project-graph" } moon_target = { path = "../target" } moon_task = { path = "../task" } +moon_task_expander = { path = "../task-expander" } miette = { workspace = true } petgraph = { workspace = true } rustc-hash = { workspace = true } diff --git a/crates/task-graph/src/lib.rs b/crates/task-graph/src/lib.rs index 08dab862663..ec2c400efb3 100644 --- a/crates/task-graph/src/lib.rs +++ b/crates/task-graph/src/lib.rs @@ -1,5 +1,7 @@ mod task_graph; mod task_graph_error; +pub use moon_graph_utils::*; +pub use moon_task::*; pub use task_graph::*; pub use task_graph_error::*; diff --git a/crates/task-graph/src/task_graph.rs b/crates/task-graph/src/task_graph.rs index f60394ab84c..7261a6337be 100644 --- a/crates/task-graph/src/task_graph.rs +++ b/crates/task-graph/src/task_graph.rs @@ -1,12 +1,16 @@ +use crate::task_graph_error::TaskGraphError; +use moon_common::Id; use moon_config::DependencyType; use moon_graph_utils::*; +use moon_project_graph::ProjectGraph; use moon_target::Target; use moon_task::Task; +use moon_task_expander::{TaskExpander, TaskExpanderContext}; use petgraph::graph::{DiGraph, NodeIndex}; use rustc_hash::FxHashMap; -use std::sync::Arc; -// use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use tracing::debug; +use std::path::PathBuf; +use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use tracing::{debug, instrument}; pub type TaskGraphType = DiGraph; pub type TasksCache = FxHashMap>; @@ -23,32 +27,163 @@ pub struct TaskGraph { /// Task metadata, mapped by target. metadata: FxHashMap, - // /// Expanded tasks, mapped by target. - // tasks: Arc>, + + /// Project graph, required for expansion. + project_graph: Arc, + + /// Expanded tasks, mapped by target. + tasks: Arc>, + + /// The current working directory. + pub working_dir: PathBuf, + + /// Workspace root, required for expansion. + pub workspace_root: PathBuf, } impl TaskGraph { - pub fn new(graph: TaskGraphType, metadata: FxHashMap) -> Self { + pub fn new( + graph: TaskGraphType, + metadata: FxHashMap, + project_graph: Arc, + ) -> Self { debug!("Creating task graph"); Self { graph, metadata, - // tasks: Arc::new(RwLock::new(FxHashMap::default())), + project_graph, + tasks: Arc::new(RwLock::new(FxHashMap::default())), + working_dir: PathBuf::new(), + workspace_root: PathBuf::new(), + } + } + + /// Return a task with the provided target from the graph. + /// If the task does not exist or has been misconfigured, return an error. + #[instrument(name = "get_task", skip(self))] + pub fn get(&self, target: &Target) -> miette::Result> { + self.internal_get(target) + } + + /// Return an unexpanded task with the provided target from the graph. + pub fn get_unexpanded(&self, target: &Target) -> miette::Result<&Task> { + let metadata = self + .metadata + .get(target) + .ok_or(TaskGraphError::UnconfiguredTarget(target.to_owned()))?; + + Ok(self.graph.node_weight(metadata.index).unwrap()) + } + + /// Return all tasks from the graph. + #[instrument(name = "get_all_tasks", skip(self))] + pub fn get_all(&self) -> miette::Result>> { + let mut all = vec![]; + + for target in self.metadata.keys() { + all.push(self.internal_get(target)?); + } + + Ok(all) + } + + /// Return all tasks for a specific project from the graph. + #[instrument(name = "get_all_project_tasks", skip(self))] + pub fn get_all_for_project( + &self, + project_id: &Id, + include_internal: bool, + ) -> miette::Result>> { + let mut all = vec![]; + + for target in self.metadata.keys() { + if target.get_project_id().is_some_and(|id| id == project_id) { + let task = self.internal_get(target)?; + + if !include_internal && task.is_internal() { + continue; + } + + all.push(task); + } + } + + Ok(all) + } + + /// Return all unexpanded tasks from the graph. + pub fn get_all_unexpanded(&self) -> Vec<&Task> { + self.graph + .raw_nodes() + .iter() + .map(|node| &node.weight) + .collect() + } + + /// Focus the graph for a specific project by target. + pub fn focus_for(&self, target: &Target, with_dependents: bool) -> miette::Result { + let task = self.get(target)?; + let graph = self.to_focused_graph(&task, with_dependents); + + // Copy over metadata + let mut metadata = FxHashMap::default(); + + for new_index in graph.node_indices() { + let inner_target = &graph[new_index].target; + + if let Some(old_node) = self.metadata.get(inner_target) { + let mut new_node = old_node.to_owned(); + new_node.index = new_index; + + metadata.insert(inner_target.to_owned(), new_node); + } + } + + Ok(Self { + graph, + metadata, + project_graph: self.project_graph.clone(), + tasks: self.tasks.clone(), + working_dir: self.working_dir.clone(), + workspace_root: self.workspace_root.clone(), + }) + } + + fn internal_get(&self, target: &Target) -> miette::Result> { + if let Some(task) = self.read_cache().get(target) { + return Ok(Arc::clone(task)); } + + let mut cache = self.write_cache(); + + let expander = TaskExpander::new(TaskExpanderContext { + project: self.project_graph.get_unexpanded( + target + .get_project_id() + .expect("Project scope required for target."), + )?, + workspace_root: &self.workspace_root, + }); + + let task = Arc::new(expander.expand(self.get_unexpanded(target)?)?); + + cache.insert(target.to_owned(), Arc::clone(&task)); + + Ok(task) } - // fn read_cache(&self) -> RwLockReadGuard { - // self.tasks - // .read() - // .expect("Failed to acquire read access to task graph!") - // } - - // fn write_cache(&self) -> RwLockWriteGuard { - // self.tasks - // .write() - // .expect("Failed to acquire write access to task graph!") - // } + fn read_cache(&self) -> RwLockReadGuard { + self.tasks + .read() + .expect("Failed to acquire read access to task graph!") + } + + fn write_cache(&self) -> RwLockWriteGuard { + self.tasks + .write() + .expect("Failed to acquire write access to task graph!") + } } impl GraphData for TaskGraph { diff --git a/crates/task-graph/src/task_graph_error.rs b/crates/task-graph/src/task_graph_error.rs index 784c9f83c61..776bbceba3a 100644 --- a/crates/task-graph/src/task_graph_error.rs +++ b/crates/task-graph/src/task_graph_error.rs @@ -5,7 +5,14 @@ use thiserror::Error; #[derive(Error, Debug, Diagnostic)] pub enum TaskGraphError { - #[diagnostic(code(task_graph::unknown_target))] - #[error("No task has been configured with the target {}.", .0.style(Style::Id))] + #[diagnostic( + code(task_graph::unknown_target), + help = "Has this task been configured?" + )] + #[error( + "Unknown task {} for project {}.", + .0.task_id.style(Style::Id), + .0.get_project_id().unwrap().style(Style::Id), + )] UnconfiguredTarget(Target), } diff --git a/crates/task-hasher/tests/task_hasher_test.rs b/crates/task-hasher/tests/task_hasher_test.rs index a341fc18f25..bc9e4ad08d1 100644 --- a/crates/task-hasher/tests/task_hasher_test.rs +++ b/crates/task-hasher/tests/task_hasher_test.rs @@ -1,7 +1,8 @@ use moon_config::{GlobPath, HasherConfig, HasherWalkStrategy, PortablePath}; use moon_project::Project; +use moon_task::Task; use moon_task_hasher::{TaskHash, TaskHasher}; -use moon_test_utils2::{create_project_graph_mocker, ProjectGraph}; +use moon_test_utils2::{create_workspace_graph_mocker, WorkspaceGraph}; use moon_vcs::BoxedVcs; use starbase_sandbox::create_sandbox; use std::fs; @@ -31,13 +32,13 @@ fn create_hasher_configs() -> (HasherConfig, HasherConfig) { ) } -async fn generate_project_graph(workspace_root: &Path) -> (ProjectGraph, Arc) { - let mut mock = create_project_graph_mocker(workspace_root); +async fn generate_graph(workspace_root: &Path) -> (WorkspaceGraph, Arc) { + let mut mock = create_workspace_graph_mocker(workspace_root); mock.with_vcs(); create_out_files(workspace_root); - let graph = mock.build_project_graph().await; + let graph = mock.build_workspace_graph().await; let vcs = mock.vcs.take().unwrap(); (graph, vcs) @@ -45,18 +46,12 @@ async fn generate_project_graph(workspace_root: &Path) -> (ProjectGraph, Arc( project: &'a Project, - task_name: &'a str, + task: &'a Task, vcs: &'a BoxedVcs, workspace_root: &'a Path, hasher_config: &'a HasherConfig, ) -> TaskHash<'a> { - let mut hasher = TaskHasher::new( - project, - project.get_task(task_name).unwrap(), - vcs, - workspace_root, - hasher_config, - ); + let mut hasher = TaskHasher::new(project, task, vcs, workspace_root, hasher_config); hasher.hash_inputs().await.unwrap(); hasher.hash() } @@ -69,22 +64,16 @@ mod task_hasher { let sandbox = create_sandbox("ignore-patterns"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; - let project = project_graph.get("root").unwrap(); + let (wg, vcs) = generate_graph(sandbox.path()).await; + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "testPatterns").unwrap(); let hasher_config = HasherConfig { ignore_patterns: vec![GlobPath::from_str("**/out/**").unwrap()], ..HasherConfig::default() }; - let result = generate_hash( - &project, - "testPatterns", - &vcs, - sandbox.path(), - &hasher_config, - ) - .await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &hasher_config).await; assert_eq!( result.inputs.keys().collect::>(), @@ -100,19 +89,20 @@ mod task_hasher { let sandbox = create_sandbox("inputs"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "files").unwrap(); let expected = ["2.txt", "dir/abc.txt"]; // VCS - let result = generate_hash(&project, "files", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = generate_hash(&project, "files", &vcs, sandbox.path(), &glob_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -122,19 +112,20 @@ mod task_hasher { let sandbox = create_sandbox("inputs"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "dirs").unwrap(); let expected = ["dir/abc.txt", "dir/az.txt", "dir/xyz.txt"]; // VCS - let result = generate_hash(&project, "dirs", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = generate_hash(&project, "dirs", &vcs, sandbox.path(), &glob_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -144,21 +135,20 @@ mod task_hasher { let sandbox = create_sandbox("inputs"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "globStar").unwrap(); let expected = ["1.txt", "2.txt", "3.txt"]; // VCS - let result = - generate_hash(&project, "globStar", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = - generate_hash(&project, "globStar", &vcs, sandbox.path(), &glob_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -168,9 +158,10 @@ mod task_hasher { let sandbox = create_sandbox("inputs"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "globNestedStar").unwrap(); let expected = [ "1.txt", @@ -182,26 +173,12 @@ mod task_hasher { ]; // VCS - let result = generate_hash( - &project, - "globNestedStar", - &vcs, - sandbox.path(), - &vcs_config, - ) - .await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = generate_hash( - &project, - "globNestedStar", - &vcs, - sandbox.path(), - &glob_config, - ) - .await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -211,21 +188,20 @@ mod task_hasher { let sandbox = create_sandbox("inputs"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "globGroup").unwrap(); let expected = ["dir/az.txt", "dir/xyz.txt"]; // VCS - let result = - generate_hash(&project, "globGroup", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = - generate_hash(&project, "globGroup", &vcs, sandbox.path(), &glob_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -235,12 +211,13 @@ mod task_hasher { let sandbox = create_sandbox("inputs"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; - let project = project_graph.get("root").unwrap(); + let (wg, vcs) = generate_graph(sandbox.path()).await; + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "none").unwrap(); + let hasher_config = HasherConfig::default(); - let result = - generate_hash(&project, "none", &vcs, sandbox.path(), &hasher_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &hasher_config).await; assert_eq!(result.inputs.keys().collect::>(), Vec::<&str>::new()); } @@ -255,12 +232,13 @@ mod task_hasher { cmd.args(["add", "created.txt", "filtered.txt"]); }); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; - let project = project_graph.get("root").unwrap(); + let (wg, vcs) = generate_graph(sandbox.path()).await; + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "touched").unwrap(); + let hasher_config = HasherConfig::default(); - let result = - generate_hash(&project, "touched", &vcs, sandbox.path(), &hasher_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &hasher_config).await; assert_eq!(result.inputs.keys().collect::>(), ["created.txt"]); } @@ -271,21 +249,20 @@ mod task_hasher { sandbox.enable_git(); sandbox.create_file(".env", ""); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "envFile").unwrap(); let expected = [".env"]; // VCS - let result = - generate_hash(&project, "envFile", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = - generate_hash(&project, "envFile", &vcs, sandbox.path(), &glob_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -297,21 +274,20 @@ mod task_hasher { sandbox.create_file(".env.prod", ""); sandbox.create_file(".env.local", ""); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "envFileList").unwrap(); let expected = [".env.local", ".env.prod"]; // VCS - let result = - generate_hash(&project, "envFileList", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = - generate_hash(&project, "envFileList", &vcs, sandbox.path(), &glob_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -325,9 +301,10 @@ mod task_hasher { let sandbox = create_sandbox("output-filters"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "inFileOutFile").unwrap(); let expected = [ ".moon/toolchain.yml", @@ -337,20 +314,12 @@ mod task_hasher { ]; // VCS - let result = - generate_hash(&project, "inFileOutFile", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = generate_hash( - &project, - "inFileOutFile", - &vcs, - sandbox.path(), - &glob_config, - ) - .await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -360,21 +329,20 @@ mod task_hasher { let sandbox = create_sandbox("output-filters"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "inFileOutDir").unwrap(); let expected = [".moon/toolchain.yml", ".moon/workspace.yml"]; // VCS - let result = - generate_hash(&project, "inFileOutDir", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = - generate_hash(&project, "inFileOutDir", &vcs, sandbox.path(), &glob_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -384,27 +352,20 @@ mod task_hasher { let sandbox = create_sandbox("output-filters"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "inFileOutGlob").unwrap(); let expected = [".moon/toolchain.yml", ".moon/workspace.yml"]; // VCS - let result = - generate_hash(&project, "inFileOutGlob", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = generate_hash( - &project, - "inFileOutGlob", - &vcs, - sandbox.path(), - &glob_config, - ) - .await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -414,9 +375,10 @@ mod task_hasher { let sandbox = create_sandbox("output-filters"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "inGlobOutFile").unwrap(); let expected = [ ".gitignore", @@ -429,20 +391,12 @@ mod task_hasher { ]; // VCS - let result = - generate_hash(&project, "inGlobOutFile", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = generate_hash( - &project, - "inGlobOutFile", - &vcs, - sandbox.path(), - &glob_config, - ) - .await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -452,9 +406,10 @@ mod task_hasher { let sandbox = create_sandbox("output-filters"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "inGlobOutDir").unwrap(); let expected = [ ".gitignore", @@ -464,14 +419,12 @@ mod task_hasher { ]; // VCS - let result = - generate_hash(&project, "inGlobOutDir", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = - generate_hash(&project, "inGlobOutDir", &vcs, sandbox.path(), &glob_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } @@ -481,9 +434,10 @@ mod task_hasher { let sandbox = create_sandbox("output-filters"); sandbox.enable_git(); - let (project_graph, vcs) = generate_project_graph(sandbox.path()).await; + let (wg, vcs) = generate_graph(sandbox.path()).await; let (vcs_config, glob_config) = create_hasher_configs(); - let project = project_graph.get("root").unwrap(); + let project = wg.get_project("root").unwrap(); + let task = wg.get_task_from_project("root", "inGlobOutGlob").unwrap(); let expected = [ ".gitignore", @@ -493,20 +447,12 @@ mod task_hasher { ]; // VCS - let result = - generate_hash(&project, "inGlobOutGlob", &vcs, sandbox.path(), &vcs_config).await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &vcs_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); // Glob - let result = generate_hash( - &project, - "inGlobOutGlob", - &vcs, - sandbox.path(), - &glob_config, - ) - .await; + let result = generate_hash(&project, &task, &vcs, sandbox.path(), &glob_config).await; assert_eq!(result.inputs.keys().collect::>(), expected); } diff --git a/crates/task-runner/tests/command_builder_test.rs b/crates/task-runner/tests/command_builder_test.rs index 5fb2e2bd064..b43c17877d2 100644 --- a/crates/task-runner/tests/command_builder_test.rs +++ b/crates/task-runner/tests/command_builder_test.rs @@ -31,7 +31,7 @@ mod command_builder { #[tokio::test] async fn sets_cwd_to_project_root() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container.create_command(ActionContext::default()).await; assert_eq!(command.cwd, Some(container.sandbox.path().join("project"))); @@ -39,7 +39,7 @@ mod command_builder { #[tokio::test] async fn sets_cwd_to_workspace_root() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container .create_command_with_config(ActionContext::default(), |task, _| { task.options.run_from_workspace_root = true; @@ -54,7 +54,7 @@ mod command_builder { #[tokio::test] async fn inherits_task_args() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container.create_command(ActionContext::default()).await; assert_eq!(get_args(&command), vec!["arg", "--opt"]); @@ -62,7 +62,7 @@ mod command_builder { #[tokio::test] async fn inherits_when_a_task_dep() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container .create_command_with_config(ActionContext::default(), |_, node| { if let ActionNode::RunTask(inner) = node { @@ -76,7 +76,7 @@ mod command_builder { #[tokio::test] async fn inherits_passthrough_args_when_a_primary_target() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let mut context = ActionContext::default(); context.passthrough_args.push("--passthrough".into()); @@ -91,7 +91,7 @@ mod command_builder { #[tokio::test] async fn inherits_passthrough_args_when_an_all_initial_target() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let mut context = ActionContext::default(); context.passthrough_args.push("--passthrough".into()); @@ -106,7 +106,7 @@ mod command_builder { #[tokio::test] async fn doesnt_inherit_passthrough_args_when_not_a_target() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let mut context = ActionContext::default(); context.passthrough_args.push("--passthrough".into()); @@ -121,7 +121,7 @@ mod command_builder { #[tokio::test] async fn passthrough_comes_after_node_deps() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let mut context = ActionContext::default(); context.passthrough_args.push("--passthrough".into()); @@ -149,7 +149,7 @@ mod command_builder { #[tokio::test] async fn sets_pwd() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container.create_command(ActionContext::default()).await; assert_eq!( @@ -160,7 +160,7 @@ mod command_builder { #[tokio::test] async fn inherits_task_env() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container.create_command(ActionContext::default()).await; assert_eq!(get_env(&command, "KEY").unwrap(), "value"); @@ -168,7 +168,7 @@ mod command_builder { #[tokio::test] async fn inherits_when_a_task_dep() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container .create_command_with_config(ActionContext::default(), |_, node| { if let ActionNode::RunTask(inner) = node { @@ -182,7 +182,7 @@ mod command_builder { #[tokio::test] async fn can_overwrite_env_via_task_dep() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container .create_command_with_config(ActionContext::default(), |_, node| { if let ActionNode::RunTask(inner) = node { @@ -196,7 +196,7 @@ mod command_builder { #[tokio::test] async fn cannot_overwrite_built_in_env() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container .create_command_with_config(ActionContext::default(), |_, node| { if let ActionNode::RunTask(inner) = node { @@ -222,7 +222,7 @@ mod command_builder { #[tokio::test] async fn uses_a_shell_by_default_for_system_task() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container.create_command(ActionContext::default()).await; assert!(command.shell.is_some()); @@ -230,7 +230,7 @@ mod command_builder { #[tokio::test] async fn sets_default_shell() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container .create_command_with_config(ActionContext::default(), |task, _| { task.options.shell = Some(true); @@ -243,7 +243,7 @@ mod command_builder { #[cfg(unix)] #[tokio::test] async fn can_set_unix_shell() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container .create_command_with_config(ActionContext::default(), |task, _| { task.options.shell = Some(true); @@ -257,7 +257,7 @@ mod command_builder { #[cfg(windows)] #[tokio::test] async fn can_set_windows_shell() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container .create_command_with_config(ActionContext::default(), |task, _| { task.options.shell = Some(true); @@ -279,7 +279,7 @@ mod command_builder { #[tokio::test] async fn does_nothing_if_option_not_set() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container.create_command(ActionContext::default()).await; assert!(get_env(&command, "MOON_AFFECTED_FILES").is_none()); @@ -287,7 +287,7 @@ mod command_builder { #[tokio::test] async fn includes_touched_in_args() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let mut context = ActionContext::default(); context.affected = Some(Affected::default()); @@ -304,7 +304,7 @@ mod command_builder { #[tokio::test] async fn fallsback_to_dot_in_args_when_no_match() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let mut context = ActionContext::default(); context.affected = Some(Affected::default()); @@ -321,7 +321,7 @@ mod command_builder { #[tokio::test] async fn includes_touched_in_env() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let mut context = ActionContext::default(); context.affected = Some(Affected::default()); @@ -341,7 +341,7 @@ mod command_builder { #[tokio::test] async fn fallsback_to_dot_in_env_when_no_match() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let mut context = ActionContext::default(); context.affected = Some(Affected::default()); @@ -358,7 +358,7 @@ mod command_builder { #[tokio::test] async fn can_use_inputs_directly_when_not_affected() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let command = container .create_command_with_config(ActionContext::default(), |task, _| { task.options.affected_files = Some(TaskOptionAffectedFiles::Args); @@ -371,7 +371,7 @@ mod command_builder { #[tokio::test] async fn quotes_files_with_special_chars() { - let container = TaskRunnerContainer::new("builder").await; + let container = TaskRunnerContainer::new("builder", "base").await; let mut context = ActionContext::default(); context.affected = Some(Affected::default()); diff --git a/crates/task-runner/tests/command_executor_test.rs b/crates/task-runner/tests/command_executor_test.rs index 116e21f06b6..0e4a85999cf 100644 --- a/crates/task-runner/tests/command_executor_test.rs +++ b/crates/task-runner/tests/command_executor_test.rs @@ -10,7 +10,7 @@ mod command_executor { #[tokio::test] async fn returns_attempt_on_success() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "success").await; let context = ActionContext::default(); let mut item = TaskReportItem { hash: Some("hash123".into()), @@ -18,7 +18,7 @@ mod command_executor { }; let result = container - .create_command_executor("success", &context) + .create_command_executor(&context) .await .execute(&context, &mut item) .await @@ -45,7 +45,7 @@ mod command_executor { #[tokio::test] async fn returns_attempt_on_failure() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "failure").await; let context = ActionContext::default(); let mut item = TaskReportItem { hash: Some("hash123".into()), @@ -53,7 +53,7 @@ mod command_executor { }; let result = container - .create_command_executor("failure", &context) + .create_command_executor(&context) .await .execute(&context, &mut item) .await @@ -79,12 +79,12 @@ mod command_executor { #[tokio::test] async fn returns_attempts_for_each_retry() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "retry").await; let context = ActionContext::default(); let mut item = TaskReportItem::default(); let result = container - .create_command_executor("retry", &context) + .create_command_executor(&context) .await .execute(&context, &mut item) .await diff --git a/crates/task-runner/tests/output_archiver_test.rs b/crates/task-runner/tests/output_archiver_test.rs index cc5a05382b5..547dc7c099a 100644 --- a/crates/task-runner/tests/output_archiver_test.rs +++ b/crates/task-runner/tests/output_archiver_test.rs @@ -16,8 +16,8 @@ mod output_archiver { #[tokio::test] async fn does_nothing_if_no_outputs_in_task() { - let container = TaskRunnerContainer::new("archive").await; - let archiver = container.create_archiver("no-outputs"); + let container = TaskRunnerContainer::new("archive", "no-outputs").await; + let archiver = container.create_archiver(); assert!(archiver.archive("hash123").await.unwrap().is_none()); } @@ -25,18 +25,18 @@ mod output_archiver { #[tokio::test] #[should_panic(expected = "Task project:file-outputs defines outputs but after being ran")] async fn errors_if_outputs_not_created() { - let container = TaskRunnerContainer::new("archive").await; - let archiver = container.create_archiver("file-outputs"); + let container = TaskRunnerContainer::new("archive", "file-outputs").await; + let archiver = container.create_archiver(); archiver.archive("hash123").await.unwrap(); } #[tokio::test] async fn creates_an_archive() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "file-outputs").await; container.sandbox.create_file("project/file.txt", ""); - let archiver = container.create_archiver("file-outputs"); + let archiver = container.create_archiver(); assert!(archiver.archive("hash123").await.unwrap().is_some()); assert!(container @@ -48,13 +48,13 @@ mod output_archiver { #[tokio::test] async fn doesnt_create_an_archive_if_it_exists() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "file-outputs").await; container.sandbox.create_file("project/file.txt", ""); container .sandbox .create_file(".moon/cache/outputs/hash123.tar.gz", ""); - let archiver = container.create_archiver("file-outputs"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); assert_eq!(fs::metadata(file).unwrap().len(), 0); @@ -62,10 +62,10 @@ mod output_archiver { #[tokio::test] async fn doesnt_create_an_archive_if_cache_disabled() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "file-outputs").await; container.sandbox.create_file("project/file.txt", ""); - let archiver = container.create_archiver("file-outputs"); + let archiver = container.create_archiver(); container .app_context @@ -79,10 +79,10 @@ mod output_archiver { #[tokio::test] async fn doesnt_create_an_archive_if_cache_read_only() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "file-outputs").await; container.sandbox.create_file("project/file.txt", ""); - let archiver = container.create_archiver("file-outputs"); + let archiver = container.create_archiver(); container .app_context @@ -96,10 +96,10 @@ mod output_archiver { #[tokio::test] async fn includes_input_files_in_archive() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "file-outputs").await; container.sandbox.create_file("project/file.txt", ""); - let archiver = container.create_archiver("file-outputs"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -110,12 +110,12 @@ mod output_archiver { #[tokio::test] async fn includes_input_globs_in_archive() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "glob-outputs").await; container.sandbox.create_file("project/one.txt", ""); container.sandbox.create_file("project/two.txt", ""); container.sandbox.create_file("project/three.txt", ""); - let archiver = container.create_archiver("glob-outputs"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -128,7 +128,7 @@ mod output_archiver { #[tokio::test] async fn includes_std_logs_in_archive() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "file-outputs").await; container .sandbox .create_file(".moon/cache/states/project/file-outputs/stdout.log", "out"); @@ -137,7 +137,7 @@ mod output_archiver { .create_file(".moon/cache/states/project/file-outputs/stderr.log", "err"); container.sandbox.create_file("project/file.txt", ""); - let archiver = container.create_archiver("file-outputs"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -154,12 +154,12 @@ mod output_archiver { #[tokio::test] async fn can_ignore_output_files_with_negation() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "file-outputs-negated").await; container.sandbox.create_file("project/a.txt", ""); container.sandbox.create_file("project/b.txt", ""); container.sandbox.create_file("project/c.txt", ""); - let archiver = container.create_archiver("file-outputs-negated"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -172,12 +172,12 @@ mod output_archiver { #[tokio::test] async fn can_ignore_output_globs_with_negation() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "glob-outputs-negated").await; container.sandbox.create_file("project/a.txt", ""); container.sandbox.create_file("project/b.txt", ""); container.sandbox.create_file("project/c.txt", ""); - let archiver = container.create_archiver("glob-outputs-negated"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -190,10 +190,10 @@ mod output_archiver { #[tokio::test] async fn caches_one_file() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "output-one-file").await; container.sandbox.create_file("project/file.txt", ""); - let archiver = container.create_archiver("output-one-file"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -204,12 +204,12 @@ mod output_archiver { #[tokio::test] async fn caches_many_files() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "output-many-files").await; container.sandbox.create_file("project/a.txt", ""); container.sandbox.create_file("project/b.txt", ""); container.sandbox.create_file("project/c.txt", ""); - let archiver = container.create_archiver("output-many-files"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -222,10 +222,10 @@ mod output_archiver { #[tokio::test] async fn caches_one_directory() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "output-one-dir").await; container.sandbox.create_file("project/dir/file.txt", ""); - let archiver = container.create_archiver("output-one-dir"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -236,12 +236,12 @@ mod output_archiver { #[tokio::test] async fn caches_many_directories() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "output-many-dirs").await; container.sandbox.create_file("project/a/file.txt", ""); container.sandbox.create_file("project/b/file.txt", ""); container.sandbox.create_file("project/c/file.txt", ""); - let archiver = container.create_archiver("output-many-dirs"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -254,11 +254,11 @@ mod output_archiver { #[tokio::test] async fn caches_file_and_directory() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "output-file-and-dir").await; container.sandbox.create_file("project/file.txt", ""); container.sandbox.create_file("project/dir/file.txt", ""); - let archiver = container.create_archiver("output-file-and-dir"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -270,12 +270,12 @@ mod output_archiver { #[tokio::test] async fn caches_files_from_workspace() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "output-workspace").await; container.sandbox.create_file("root.txt", ""); container.sandbox.create_file("shared/a.txt", ""); container.sandbox.create_file("shared/z.txt", ""); - let archiver = container.create_archiver("output-workspace"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -288,11 +288,12 @@ mod output_archiver { #[tokio::test] async fn caches_files_from_workspace_and_project() { - let container = TaskRunnerContainer::new("archive").await; + let container = + TaskRunnerContainer::new("archive", "output-workspace-and-project").await; container.sandbox.create_file("root.txt", ""); container.sandbox.create_file("project/file.txt", ""); - let archiver = container.create_archiver("output-workspace-and-project"); + let archiver = container.create_archiver(); let file = archiver.archive("hash123").await.unwrap().unwrap(); let dir = container.sandbox.path().join("out"); @@ -308,23 +309,25 @@ mod output_archiver { #[tokio::test] async fn returns_based_on_type() { - let container = TaskRunnerContainer::new("archive").await; - let archiver = container.create_archiver("build-type"); + let container = TaskRunnerContainer::new("archive", "build-type").await; + let archiver = container.create_archiver(); assert!(archiver.is_archivable().unwrap()); - let archiver = container.create_archiver("run-type"); + let container = TaskRunnerContainer::new("archive", "run-type").await; + let archiver = container.create_archiver(); assert!(!archiver.is_archivable().unwrap()); - let archiver = container.create_archiver("test-type"); + let container = TaskRunnerContainer::new("archive", "test-type").await; + let archiver = container.create_archiver(); assert!(!archiver.is_archivable().unwrap()); } #[tokio::test] async fn can_return_true_for_run_type_if_workspace_configured() { - let mut container = TaskRunnerContainer::new("archive").await; + let mut container = TaskRunnerContainer::new("archive", "run-type").await; // Project scope if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { @@ -334,14 +337,14 @@ mod output_archiver { .push(Target::new("project", "run-type").unwrap()); } - let archiver = container.create_archiver("run-type"); + let archiver = container.create_archiver(); assert!(archiver.is_archivable().unwrap()); } #[tokio::test] async fn can_return_true_for_test_type_if_workspace_configured() { - let mut container = TaskRunnerContainer::new("archive").await; + let mut container = TaskRunnerContainer::new("archive", "test-type").await; // All scope if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { @@ -351,14 +354,14 @@ mod output_archiver { .push(Target::parse(":test-type").unwrap()); } - let archiver = container.create_archiver("test-type"); + let archiver = container.create_archiver(); assert!(archiver.is_archivable().unwrap()); } #[tokio::test] async fn matches_all_config() { - let mut container = TaskRunnerContainer::new("archive").await; + let mut container = TaskRunnerContainer::new("archive", "no-outputs").await; if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { config @@ -367,14 +370,14 @@ mod output_archiver { .push(Target::parse(":no-outputs").unwrap()); } - let archiver = container.create_archiver("no-outputs"); + let archiver = container.create_archiver(); assert!(archiver.is_archivable().unwrap()); } #[tokio::test] async fn doesnt_match_all_config() { - let mut container = TaskRunnerContainer::new("archive").await; + let mut container = TaskRunnerContainer::new("archive", "no-outputs").await; if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { config @@ -383,14 +386,14 @@ mod output_archiver { .push(Target::parse(":unknown-task").unwrap()); } - let archiver = container.create_archiver("no-outputs"); + let archiver = container.create_archiver(); assert!(!archiver.is_archivable().unwrap()); } #[tokio::test] async fn matches_project_config() { - let mut container = TaskRunnerContainer::new("archive").await; + let mut container = TaskRunnerContainer::new("archive", "no-outputs").await; if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { config @@ -399,14 +402,14 @@ mod output_archiver { .push(Target::new("project", "no-outputs").unwrap()); } - let archiver = container.create_archiver("no-outputs"); + let archiver = container.create_archiver(); assert!(archiver.is_archivable().unwrap()); } #[tokio::test] async fn doesnt_match_project_config() { - let mut container = TaskRunnerContainer::new("archive").await; + let mut container = TaskRunnerContainer::new("archive", "no-outputs").await; if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { config @@ -415,14 +418,14 @@ mod output_archiver { .push(Target::new("other-project", "no-outputs").unwrap()); } - let archiver = container.create_archiver("no-outputs"); + let archiver = container.create_archiver(); assert!(!archiver.is_archivable().unwrap()); } #[tokio::test] async fn matches_tag_config() { - let mut container = TaskRunnerContainer::new("archive").await; + let mut container = TaskRunnerContainer::new("archive", "no-outputs").await; if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { config @@ -431,14 +434,14 @@ mod output_archiver { .push(Target::parse("#cache:no-outputs").unwrap()); } - let archiver = container.create_archiver("no-outputs"); + let archiver = container.create_archiver(); assert!(archiver.is_archivable().unwrap()); } #[tokio::test] async fn doesnt_match_tag_config() { - let mut container = TaskRunnerContainer::new("archive").await; + let mut container = TaskRunnerContainer::new("archive", "no-outputs").await; if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { config @@ -447,7 +450,7 @@ mod output_archiver { .push(Target::parse("#other-tag:no-outputs").unwrap()); } - let archiver = container.create_archiver("no-outputs"); + let archiver = container.create_archiver(); assert!(!archiver.is_archivable().unwrap()); } @@ -455,7 +458,7 @@ mod output_archiver { #[tokio::test] #[should_panic(expected = "Dependencies scope (^:) is not supported in run contexts.")] async fn errors_for_deps_config() { - let mut container = TaskRunnerContainer::new("archive").await; + let mut container = TaskRunnerContainer::new("archive", "no-outputs").await; if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { config @@ -464,7 +467,7 @@ mod output_archiver { .push(Target::parse("^:no-outputs").unwrap()); } - let archiver = container.create_archiver("no-outputs"); + let archiver = container.create_archiver(); assert!(!archiver.is_archivable().unwrap()); } @@ -472,7 +475,7 @@ mod output_archiver { #[tokio::test] #[should_panic(expected = "Self scope (~:) is not supported in run contexts.")] async fn errors_for_self_config() { - let mut container = TaskRunnerContainer::new("archive").await; + let mut container = TaskRunnerContainer::new("archive", "no-outputs").await; if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { config @@ -481,7 +484,7 @@ mod output_archiver { .push(Target::parse("~:no-outputs").unwrap()); } - let archiver = container.create_archiver("no-outputs"); + let archiver = container.create_archiver(); assert!(!archiver.is_archivable().unwrap()); } @@ -492,46 +495,46 @@ mod output_archiver { #[tokio::test] async fn returns_false_if_no_files() { - let container = TaskRunnerContainer::new("archive").await; - let archiver = container.create_archiver("file-outputs"); + let container = TaskRunnerContainer::new("archive", "file-outputs").await; + let archiver = container.create_archiver(); assert!(!archiver.has_outputs_been_created(false).unwrap()); } #[tokio::test] async fn returns_true_if_files() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "file-outputs").await; container.sandbox.create_file("project/file.txt", ""); - let archiver = container.create_archiver("file-outputs"); + let archiver = container.create_archiver(); assert!(archiver.has_outputs_been_created(false).unwrap()); } #[tokio::test] async fn returns_false_if_no_globs() { - let container = TaskRunnerContainer::new("archive").await; - let archiver = container.create_archiver("glob-outputs"); + let container = TaskRunnerContainer::new("archive", "glob-outputs").await; + let archiver = container.create_archiver(); assert!(!archiver.has_outputs_been_created(false).unwrap()); } #[tokio::test] async fn returns_true_if_globs() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "glob-outputs").await; container.sandbox.create_file("project/file.txt", ""); - let archiver = container.create_archiver("glob-outputs"); + let archiver = container.create_archiver(); assert!(archiver.has_outputs_been_created(false).unwrap()); } #[tokio::test] async fn returns_true_if_only_negated_globs() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "negated-outputs-only").await; container.sandbox.create_file("project/file.txt", ""); - let archiver = container.create_archiver("negated-outputs-only"); + let archiver = container.create_archiver(); assert!(archiver.has_outputs_been_created(false).unwrap()); } diff --git a/crates/task-runner/tests/output_hydrater_test.rs b/crates/task-runner/tests/output_hydrater_test.rs index 94bc9645584..ad18c3d1625 100644 --- a/crates/task-runner/tests/output_hydrater_test.rs +++ b/crates/task-runner/tests/output_hydrater_test.rs @@ -13,16 +13,16 @@ mod output_hydrater { #[tokio::test] async fn does_nothing_if_no_hash() { - let container = TaskRunnerContainer::new("archive").await; - let hydrater = container.create_hydrator("file-outputs"); + let container = TaskRunnerContainer::new("archive", "file-outputs").await; + let hydrater = container.create_hydrator(); assert!(!hydrater.hydrate("", HydrateFrom::LocalCache).await.unwrap()); } #[tokio::test] async fn does_nothing_if_from_prev_outputs() { - let container = TaskRunnerContainer::new("archive").await; - let hydrater = container.create_hydrator("file-outputs"); + let container = TaskRunnerContainer::new("archive", "file-outputs").await; + let hydrater = container.create_hydrator(); assert!(hydrater .hydrate("hash123", HydrateFrom::PreviousOutput) @@ -32,12 +32,12 @@ mod output_hydrater { #[tokio::test] async fn doesnt_unpack_if_cache_disabled() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "file-outputs").await; container .sandbox .create_file(".moon/cache/outputs/hash123.tar.gz", ""); - let hydrater = container.create_hydrator("file-outputs"); + let hydrater = container.create_hydrator(); container .app_context @@ -54,12 +54,12 @@ mod output_hydrater { #[tokio::test] async fn doesnt_unpack_if_cache_write_only() { - let container = TaskRunnerContainer::new("archive").await; + let container = TaskRunnerContainer::new("archive", "file-outputs").await; container .sandbox .create_file(".moon/cache/outputs/hash123.tar.gz", ""); - let hydrater = container.create_hydrator("file-outputs"); + let hydrater = container.create_hydrator(); container .app_context @@ -76,12 +76,12 @@ mod output_hydrater { #[tokio::test] async fn unpacks_archive_into_project() { - let container = TaskRunnerContainer::new("archive").await; - container.pack_archive("file-outputs"); + let container = TaskRunnerContainer::new("archive", "file-outputs").await; + container.pack_archive(); assert!(!container.sandbox.path().join("project/file.txt").exists()); - let hydrater = container.create_hydrator("file-outputs"); + let hydrater = container.create_hydrator(); hydrater .hydrate("hash123", HydrateFrom::LocalCache) .await @@ -92,8 +92,8 @@ mod output_hydrater { #[tokio::test] async fn unpacks_logs_from_archive() { - let container = TaskRunnerContainer::new("archive").await; - container.pack_archive("file-outputs"); + let container = TaskRunnerContainer::new("archive", "file-outputs").await; + container.pack_archive(); assert!(!container .sandbox @@ -101,7 +101,7 @@ mod output_hydrater { .join(".moon/cache/states/project/file-outputs/stdout.log") .exists()); - let hydrater = container.create_hydrator("file-outputs"); + let hydrater = container.create_hydrator(); hydrater .hydrate("hash123", HydrateFrom::LocalCache) .await diff --git a/crates/task-runner/tests/task_runner_test.rs b/crates/task-runner/tests/task_runner_test.rs index d6df5b6910e..925ac315d49 100644 --- a/crates/task-runner/tests/task_runner_test.rs +++ b/crates/task-runner/tests/task_runner_test.rs @@ -18,9 +18,9 @@ mod task_runner { #[tokio::test] async fn skips_if_noop() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); - let node = container.create_action_node("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.run_with_panic(&context, &node).await.unwrap(); @@ -41,9 +41,9 @@ mod task_runner { #[tokio::test] #[should_panic(expected = "Encountered a missing hash for task project:dep")] async fn errors_if_dep_hasnt_ran() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("has-deps"); - let node = container.create_action_node("has-deps"); + let container = TaskRunnerContainer::new("runner", "has-deps").await; + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.run_with_panic(&context, &node).await.unwrap(); @@ -51,9 +51,9 @@ mod task_runner { #[tokio::test] async fn skips_if_dep_skipped() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("has-deps"); - let node = container.create_action_node("has-deps"); + let container = TaskRunnerContainer::new("runner", "has-deps").await; + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); context @@ -75,9 +75,9 @@ mod task_runner { #[tokio::test] async fn skips_if_dep_failed() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("has-deps"); - let node = container.create_action_node("has-deps"); + let container = TaskRunnerContainer::new("runner", "has-deps").await; + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); context @@ -103,11 +103,11 @@ mod task_runner { #[tokio::test] async fn creates_cache_state_file() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "create-file").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("create-file"); - let node = container.create_action_node("create-file"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.run_with_panic(&context, &node).await.unwrap(); @@ -123,11 +123,11 @@ mod task_runner { #[tokio::test] async fn generates_a_hash() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "create-file").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("create-file"); - let node = container.create_action_node("create-file"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); let result = runner.run_with_panic(&context, &node).await.unwrap(); @@ -137,11 +137,11 @@ mod task_runner { #[tokio::test] async fn generates_a_hash_for_noop() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "noop").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("noop"); - let node = container.create_action_node("noop"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); let result = runner.run_with_panic(&context, &node).await.unwrap(); @@ -151,11 +151,11 @@ mod task_runner { #[tokio::test] async fn generates_same_hashes_based_on_input() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "hash-inputs").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("hash-inputs"); - let node = container.create_action_node("hash-inputs"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); container @@ -170,11 +170,11 @@ mod task_runner { #[tokio::test] async fn generates_different_hashes_based_on_input() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "hash-inputs").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("hash-inputs"); - let node = container.create_action_node("hash-inputs"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); container @@ -194,11 +194,11 @@ mod task_runner { #[tokio::test] async fn creates_operations_for_each_step() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "create-file").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("create-file"); - let node = container.create_action_node("create-file"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); let result = runner.run_with_panic(&context, &node).await.unwrap(); @@ -216,11 +216,11 @@ mod task_runner { #[tokio::test] async fn running_again_hits_the_output_cache() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "create-file").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("create-file"); - let node = container.create_action_node("create-file"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); let before = runner.run_with_panic(&context, &node).await.unwrap(); @@ -240,11 +240,11 @@ mod task_runner { #[tokio::test] #[should_panic(expected = "defines outputs but after being ran")] async fn errors_if_outputs_missing() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "missing-output").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("missing-output"); - let node = container.create_action_node("missing-output"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.run_with_panic(&context, &node).await.unwrap(); @@ -253,11 +253,11 @@ mod task_runner { #[tokio::test] #[should_panic(expected = "defines outputs but after being ran")] async fn errors_if_outputs_missing_via_glob() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "missing-output-glob").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("missing-output-glob"); - let node = container.create_action_node("missing-output-glob"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.run_with_panic(&context, &node).await.unwrap(); @@ -269,11 +269,11 @@ mod task_runner { #[tokio::test] async fn creates_cache_state_file() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "without-cache").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("without-cache"); - let node = container.create_action_node("without-cache"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.run_with_panic(&context, &node).await.unwrap(); @@ -289,11 +289,11 @@ mod task_runner { #[tokio::test] async fn doesnt_generate_a_hash() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "without-cache").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("without-cache"); - let node = container.create_action_node("without-cache"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); let result = runner.run_with_panic(&context, &node).await.unwrap(); @@ -303,11 +303,11 @@ mod task_runner { #[tokio::test] async fn doesnt_create_non_task_operations() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "without-cache").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("without-cache"); - let node = container.create_action_node("without-cache"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); let result = runner.run_with_panic(&context, &node).await.unwrap(); @@ -320,11 +320,11 @@ mod task_runner { #[tokio::test] async fn running_again_reexecutes_task() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "without-cache").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("without-cache"); - let node = container.create_action_node("without-cache"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); let result = runner.run_with_panic(&context, &node).await.unwrap(); @@ -347,16 +347,16 @@ mod task_runner { #[tokio::test] async fn returns_none_by_default() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); assert_eq!(runner.is_cached("hash123").await.unwrap(), None); } #[tokio::test] async fn sets_the_hash_to_cache() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); runner.is_cached("hash123").await.unwrap(); @@ -368,8 +368,8 @@ mod task_runner { #[tokio::test] async fn returns_if_hashes_match() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); runner.cache.data.exit_code = 0; runner.cache.data.hash = "hash123".into(); @@ -382,8 +382,8 @@ mod task_runner { #[tokio::test] async fn skips_if_hashes_dont_match() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); runner.cache.data.exit_code = 0; runner.cache.data.hash = "otherhash456".into(); @@ -393,8 +393,8 @@ mod task_runner { #[tokio::test] async fn skips_if_codes_dont_match() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); runner.cache.data.exit_code = 2; runner.cache.data.hash = "hash123".into(); @@ -404,8 +404,8 @@ mod task_runner { #[tokio::test] async fn skips_if_outputs_dont_exist() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("outputs"); + let container = TaskRunnerContainer::new("runner", "outputs").await; + let mut runner = container.create_runner(); runner.cache.data.exit_code = 0; runner.cache.data.hash = "hash123".into(); @@ -415,8 +415,8 @@ mod task_runner { #[tokio::test] async fn returns_if_outputs_do_exist() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); runner.cache.data.exit_code = 0; runner.cache.data.hash = "hash123".into(); @@ -430,8 +430,8 @@ mod task_runner { #[tokio::test] async fn returns_none_if_non_zero_exit() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); runner.cache.data.exit_code = 1; @@ -444,8 +444,8 @@ mod task_runner { #[tokio::test] async fn returns_if_archive_exists() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); container .sandbox @@ -459,16 +459,16 @@ mod task_runner { #[tokio::test] async fn skips_if_archive_doesnt_exist() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); assert_eq!(runner.is_cached("hash123").await.unwrap(), None); } #[tokio::test] async fn skips_if_cache_isnt_readable() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); container .sandbox @@ -486,8 +486,8 @@ mod task_runner { #[tokio::test] async fn skips_if_cache_is_writeonly() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); container .sandbox @@ -510,8 +510,8 @@ mod task_runner { #[tokio::test] async fn returns_if_within_the_ttl() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("cache-lifetime"); + let container = TaskRunnerContainer::new("runner", "cache-lifetime").await; + let mut runner = container.create_runner(); runner.cache.data.exit_code = 0; runner.cache.data.hash = "hash123".into(); @@ -525,8 +525,8 @@ mod task_runner { #[tokio::test] async fn misses_if_passed_the_ttl() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("cache-lifetime"); + let container = TaskRunnerContainer::new("runner", "cache-lifetime").await; + let mut runner = container.create_runner(); runner.cache.data.exit_code = 0; runner.cache.data.hash = "hash123".into(); @@ -557,8 +557,8 @@ mod task_runner { #[tokio::test] async fn returns_true_if_no_deps() { - let container = TaskRunnerContainer::new("runner").await; - let runner = container.create_runner("no-deps"); + let container = TaskRunnerContainer::new("runner", "no-deps").await; + let runner = container.create_runner(); let context = ActionContext::default(); assert!(runner.is_dependencies_complete(&context).unwrap()); @@ -566,8 +566,8 @@ mod task_runner { #[tokio::test] async fn returns_false_if_dep_failed() { - let container = TaskRunnerContainer::new("runner").await; - let runner = container.create_runner("has-deps"); + let container = TaskRunnerContainer::new("runner", "has-deps").await; + let runner = container.create_runner(); let context = ActionContext::default(); context @@ -580,8 +580,8 @@ mod task_runner { #[tokio::test] async fn returns_false_if_dep_skipped() { - let container = TaskRunnerContainer::new("runner").await; - let runner = container.create_runner("has-deps"); + let container = TaskRunnerContainer::new("runner", "has-deps").await; + let runner = container.create_runner(); let context = ActionContext::default(); context @@ -594,8 +594,8 @@ mod task_runner { #[tokio::test] async fn returns_true_if_dep_passed() { - let container = TaskRunnerContainer::new("runner").await; - let runner = container.create_runner("no-deps"); + let container = TaskRunnerContainer::new("runner", "no-deps").await; + let runner = container.create_runner(); let context = ActionContext::default(); context @@ -612,8 +612,8 @@ mod task_runner { #[tokio::test] #[should_panic(expected = "Encountered a missing hash for task project:dep")] async fn errors_if_dep_not_ran() { - let container = TaskRunnerContainer::new("runner").await; - let runner = container.create_runner("has-deps"); + let container = TaskRunnerContainer::new("runner", "has-deps").await; + let runner = container.create_runner(); let context = ActionContext::default(); runner.is_dependencies_complete(&context).unwrap(); @@ -625,12 +625,12 @@ mod task_runner { #[tokio::test] async fn generates_a_hash() { - let container = TaskRunnerContainer::new("runner").await; + let container = TaskRunnerContainer::new("runner", "base").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("base"); + let mut runner = container.create_runner(); let context = ActionContext::default(); - let node = container.create_action_node("base"); + let node = container.create_action_node(); let hash = runner.generate_hash(&context, &node).await.unwrap(); @@ -640,12 +640,12 @@ mod task_runner { #[tokio::test] async fn generates_a_different_hash_via_passthrough_args() { - let container = TaskRunnerContainer::new("runner").await; + let container = TaskRunnerContainer::new("runner", "base").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("base"); + let mut runner = container.create_runner(); let mut context = ActionContext::default(); - let node = container.create_action_node("base"); + let node = container.create_action_node(); let before_hash = runner.generate_hash(&context, &node).await.unwrap(); @@ -661,12 +661,12 @@ mod task_runner { #[tokio::test] async fn creates_an_operation() { - let container = TaskRunnerContainer::new("runner").await; + let container = TaskRunnerContainer::new("runner", "base").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("base"); + let mut runner = container.create_runner(); let context = ActionContext::default(); - let node = container.create_action_node("base"); + let node = container.create_action_node(); runner.generate_hash(&context, &node).await.unwrap(); @@ -678,12 +678,12 @@ mod task_runner { #[tokio::test] async fn creates_a_manifest_file() { - let container = TaskRunnerContainer::new("runner").await; + let container = TaskRunnerContainer::new("runner", "base").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("base"); + let mut runner = container.create_runner(); let context = ActionContext::default(); - let node = container.create_action_node("base"); + let node = container.create_action_node(); let hash = runner.generate_hash(&context, &node).await.unwrap(); @@ -701,11 +701,11 @@ mod task_runner { #[tokio::test] async fn executes_and_sets_success_state() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "success").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("success"); - let node = container.create_action_node("success"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.report_item.hash = Some("hash123".into()); @@ -723,11 +723,11 @@ mod task_runner { #[tokio::test] async fn executes_and_sets_success_state_without_hash() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "success").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("success"); - let node = container.create_action_node("success"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.execute(&context, &node).await.unwrap(); @@ -744,11 +744,11 @@ mod task_runner { #[tokio::test] async fn executes_and_sets_failed_state() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "failure").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("failure"); - let node = container.create_action_node("failure"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); // Swallow panic so we can check operations @@ -767,11 +767,11 @@ mod task_runner { #[tokio::test] async fn executes_and_creates_operation_on_success() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "success").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("success"); - let node = container.create_action_node("success"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.report_item.hash = Some("hash123".into()); @@ -790,11 +790,11 @@ mod task_runner { #[tokio::test] async fn executes_and_creates_operation_on_failure() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "failure").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("failure"); - let node = container.create_action_node("failure"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); // Swallow panic so we can check operations @@ -813,11 +813,11 @@ mod task_runner { #[tokio::test] async fn saves_stdlog_file_to_cache() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "success").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("success"); - let node = container.create_action_node("success"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.report_item.hash = Some("hash123".into()); @@ -834,11 +834,11 @@ mod task_runner { #[tokio::test] async fn creates_operation_for_mutex_acquire() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "with-mutex").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("with-mutex"); - let node = container.create_action_node("with-mutex"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); // Swallow panic so we can check operations @@ -857,11 +857,11 @@ mod task_runner { #[tokio::test] #[should_panic(expected = "failed to run")] async fn errors_when_task_exec_fails() { - let container = TaskRunnerContainer::new_os("runner").await; + let container = TaskRunnerContainer::new_os("runner", "failure").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("failure"); - let node = container.create_action_node("failure"); + let mut runner = container.create_runner(); + let node = container.create_action_node(); let context = ActionContext::default(); runner.report_item.hash = Some("hash123".into()); @@ -874,8 +874,8 @@ mod task_runner { #[tokio::test] async fn creates_an_operation() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); let context = ActionContext::default(); runner.skip(&context).unwrap(); @@ -888,8 +888,8 @@ mod task_runner { #[tokio::test] async fn sets_skipped_state() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); let context = ActionContext::default(); runner.skip(&context).unwrap(); @@ -910,8 +910,8 @@ mod task_runner { #[tokio::test] async fn creates_an_operation() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); let context = ActionContext::default(); runner.skip_no_op(&context).unwrap(); @@ -924,8 +924,8 @@ mod task_runner { #[tokio::test] async fn sets_passthrough_state() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); let context = ActionContext::default(); runner.skip_no_op(&context).unwrap(); @@ -941,8 +941,8 @@ mod task_runner { } #[tokio::test] async fn sets_completed_state() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("base"); + let container = TaskRunnerContainer::new("runner", "base").await; + let mut runner = container.create_runner(); let context = ActionContext::default(); runner.report_item.hash = Some("hash123".into()); @@ -965,11 +965,11 @@ mod task_runner { #[tokio::test] async fn creates_a_passed_operation_if_archived() { - let container = TaskRunnerContainer::new("runner").await; + let container = TaskRunnerContainer::new("runner", "outputs").await; container.sandbox.enable_git(); container.sandbox.create_file("project/file.txt", ""); - let mut runner = container.create_runner("outputs"); + let mut runner = container.create_runner(); let result = runner.archive("hash123").await.unwrap(); assert!(result); @@ -982,10 +982,10 @@ mod task_runner { #[tokio::test] async fn creates_a_skipped_operation_if_not_archiveable() { - let container = TaskRunnerContainer::new("runner").await; + let container = TaskRunnerContainer::new("runner", "base").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("base"); + let mut runner = container.create_runner(); let result = runner.archive("hash123").await.unwrap(); assert!(!result); @@ -998,7 +998,7 @@ mod task_runner { #[tokio::test] async fn can_archive_tasks_without_outputs() { - let mut container = TaskRunnerContainer::new("runner").await; + let mut container = TaskRunnerContainer::new("runner", "base").await; container.sandbox.enable_git(); if let Some(config) = Arc::get_mut(&mut container.app_context.workspace_config) { @@ -1008,7 +1008,7 @@ mod task_runner { .push(Target::new("project", "base").unwrap()); } - let mut runner = container.create_runner("base"); + let mut runner = container.create_runner(); assert!(runner.archive("hash123").await.unwrap()); } @@ -1022,10 +1022,10 @@ mod task_runner { #[tokio::test] async fn creates_a_skipped_operation_if_no_cache() { - let container = TaskRunnerContainer::new("runner").await; + let container = TaskRunnerContainer::new("runner", "outputs").await; container.sandbox.enable_git(); - let mut runner = container.create_runner("outputs"); + let mut runner = container.create_runner(); let context = ActionContext::default(); let result = runner.hydrate(&context, "hash123").await.unwrap(); @@ -1052,8 +1052,8 @@ mod task_runner { #[tokio::test] async fn creates_a_cached_operation() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("outputs"); + let container = TaskRunnerContainer::new("runner", "outputs").await; + let mut runner = container.create_runner(); setup_previous_state(&container, &mut runner); @@ -1070,8 +1070,8 @@ mod task_runner { #[tokio::test] async fn sets_passed_state() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("outputs"); + let container = TaskRunnerContainer::new("runner", "outputs").await; + let mut runner = container.create_runner(); setup_previous_state(&container, &mut runner); @@ -1090,19 +1090,18 @@ mod task_runner { } mod local_cache { - use std::fs; - use super::*; + use std::fs; fn setup_local_state(container: &TaskRunnerContainer, _runner: &mut TaskRunner) { container.sandbox.enable_git(); - container.pack_archive("outputs"); + container.pack_archive(); } #[tokio::test] async fn creates_a_cached_operation() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("outputs"); + let container = TaskRunnerContainer::new("runner", "outputs").await; + let mut runner = container.create_runner(); setup_local_state(&container, &mut runner); @@ -1119,8 +1118,8 @@ mod task_runner { #[tokio::test] async fn sets_passed_state() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("outputs"); + let container = TaskRunnerContainer::new("runner", "outputs").await; + let mut runner = container.create_runner(); setup_local_state(&container, &mut runner); @@ -1139,8 +1138,8 @@ mod task_runner { #[tokio::test] async fn unpacks_archive_into_project() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("outputs"); + let container = TaskRunnerContainer::new("runner", "outputs").await; + let mut runner = container.create_runner(); setup_local_state(&container, &mut runner); @@ -1155,8 +1154,8 @@ mod task_runner { #[tokio::test] async fn loads_stdlogs_in_archive_into_operation() { - let container = TaskRunnerContainer::new("runner").await; - let mut runner = container.create_runner("outputs"); + let container = TaskRunnerContainer::new("runner", "outputs").await; + let mut runner = container.create_runner(); setup_local_state(&container, &mut runner); diff --git a/crates/task-runner/tests/utils.rs b/crates/task-runner/tests/utils.rs index 62c36827251..cba8d73da4d 100644 --- a/crates/task-runner/tests/utils.rs +++ b/crates/task-runner/tests/utils.rs @@ -14,7 +14,7 @@ use moon_task_runner::output_hydrater::OutputHydrater; use moon_task_runner::TaskRunner; use moon_test_utils2::{ generate_app_context_from_sandbox, generate_platform_manager_from_sandbox, - generate_project_graph_from_sandbox, ProjectGraph, + generate_workspace_graph_from_sandbox, WorkspaceGraph, }; use starbase_archive::Archiver; use starbase_sandbox::{create_sandbox, Sandbox}; @@ -33,53 +33,61 @@ pub struct TaskRunnerContainer { pub sandbox: Sandbox, pub app_context: AppContext, pub platform_manager: PlatformManager, - pub project_graph: ProjectGraph, pub project: Arc, pub project_id: String, + pub task: Arc, + pub task_id: String, + pub workspace_graph: WorkspaceGraph, } impl TaskRunnerContainer { - pub async fn new_for_project(fixture: &str, project_id: &str) -> Self { + pub async fn new_for_project(fixture: &str, project_id: &str, task_id: &str) -> Self { let sandbox = create_sandbox(fixture); let app_context = generate_app_context_from_sandbox(sandbox.path()); - let project_graph = generate_project_graph_from_sandbox(sandbox.path()).await; - let project = project_graph.get(project_id).unwrap(); + let workspace_graph = generate_workspace_graph_from_sandbox(sandbox.path()).await; let platform_manager = generate_platform_manager_from_sandbox(sandbox.path()).await; + let project = workspace_graph.get_project(project_id).unwrap(); + let task = workspace_graph + .get_task_from_project(project_id, task_id) + .unwrap(); Self { sandbox, app_context, platform_manager, - project_graph, + workspace_graph, project, project_id: project_id.to_owned(), + task, + task_id: task_id.to_owned(), } } - pub async fn new_os(fixture: &str) -> Self { - Self::new_for_project(fixture, if cfg!(windows) { "windows" } else { "unix" }).await + pub async fn new_os(fixture: &str, task_id: &str) -> Self { + Self::new_for_project( + fixture, + if cfg!(windows) { "windows" } else { "unix" }, + task_id, + ) + .await } - pub async fn new(fixture: &str) -> Self { - Self::new_for_project(fixture, "project").await + pub async fn new(fixture: &str, task_id: &str) -> Self { + Self::new_for_project(fixture, "project", task_id).await } - pub fn create_archiver(&self, task_id: &str) -> OutputArchiver { - let task = self.project.get_task(task_id).unwrap(); - + pub fn create_archiver(&self) -> OutputArchiver { OutputArchiver { app: &self.app_context, project_config: &self.project.config, - task, + task: &self.task, } } - pub fn create_hydrator(&self, task_id: &str) -> OutputHydrater { - let task = self.project.get_task(task_id).unwrap(); - + pub fn create_hydrator(&self) -> OutputHydrater { OutputHydrater { app: &self.app_context, - task, + task: &self.task, } } @@ -92,7 +100,7 @@ impl TaskRunnerContainer { context: ActionContext, mut op: impl FnMut(&mut Task, &mut ActionNode), ) -> Command { - let mut task = self.project.get_task("base").unwrap().clone(); + let mut task = self.task.as_ref().to_owned(); let mut node = create_node(&task); op(&mut task, &mut node); @@ -100,49 +108,41 @@ impl TaskRunnerContainer { self.internal_create_command(&context, &task, &node).await } - pub async fn create_command_executor( - &self, - task_id: &str, - context: &ActionContext, - ) -> CommandExecutor { - let task = self.project.get_task(task_id).unwrap(); - let node = create_node(task); + pub async fn create_command_executor(&self, context: &ActionContext) -> CommandExecutor { + let node = create_node(&self.task); CommandExecutor::new( &self.app_context, &self.project, - task, + &self.task, &node, - self.internal_create_command(context, task, &node).await, + self.internal_create_command(context, &self.task, &node) + .await, ) } - pub fn create_runner(&self, task_id: &str) -> TaskRunner { - let task = self.project.get_task(task_id).unwrap(); - - let mut runner = TaskRunner::new(&self.app_context, &self.project, task).unwrap(); + pub fn create_runner(&self) -> TaskRunner { + let mut runner = TaskRunner::new(&self.app_context, &self.project, &self.task).unwrap(); runner.set_platform_manager(&self.platform_manager); runner } - pub fn create_action_node(&self, task_id: &str) -> ActionNode { - let task = self.project.get_task(task_id).unwrap(); - - create_node(task) + pub fn create_action_node(&self) -> ActionNode { + create_node(&self.task) } - pub fn pack_archive(&self, task_id: &str) -> PathBuf { + pub fn pack_archive(&self) -> PathBuf { let sandbox = &self.sandbox; let file = sandbox.path().join(".moon/cache/outputs/hash123.tar.gz"); let out = format!( ".moon/cache/states/{}/{}/stdout.log", - self.project_id, task_id, + self.project_id, self.task_id, ); let err = format!( ".moon/cache/states/{}/{}/stderr.log", - self.project_id, task_id, + self.project_id, self.task_id, ); let txt = format!("{}/file.txt", self.project_id); diff --git a/crates/task/src/task.rs b/crates/task/src/task.rs index 2dedba531e7..32a7500298d 100644 --- a/crates/task/src/task.rs +++ b/crates/task/src/task.rs @@ -16,7 +16,7 @@ use std::path::{Path, PathBuf}; cacheable!( #[derive(Clone, Debug, Default, Eq, PartialEq)] - pub struct TaskMetadata { + pub struct TaskInternalMetadata { // Inputs were configured explicitly as `[]` pub empty_inputs: bool, @@ -59,7 +59,7 @@ cacheable!( #[serde(skip_serializing_if = "FxHashSet::is_empty")] pub input_globs: FxHashSet, - pub metadata: TaskMetadata, + pub metadata: TaskInternalMetadata, pub options: TaskOptions, diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 09b8cce8012..41fb6b7f3ef 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -14,8 +14,10 @@ moon_cache = { path = "../cache" } moon_config = { path = "../config" } moon_console = { path = "../console" } moon_project_graph = { path = "../project-graph" } +moon_task_graph = { path = "../task-graph" } moon_vcs = { path = "../vcs" } moon_workspace = { path = "../workspace" } +moon_workspace_graph = { path = "../workspace-graph" } proto_core = { workspace = true } starbase_events = { workspace = true } starbase_sandbox = { workspace = true } diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 17f392f4482..d3c1bf164bb 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -1,12 +1,12 @@ mod app_context; mod platform_manager; -mod project_graph; mod sandbox; +mod workspace_graph; mod workspace_mocker; pub use app_context::*; pub use platform_manager::*; -pub use project_graph::*; pub use sandbox::*; pub use starbase_sandbox::{predicates, pretty_assertions}; +pub use workspace_graph::*; pub use workspace_mocker::*; diff --git a/crates/test-utils/src/project_graph.rs b/crates/test-utils/src/project_graph.rs deleted file mode 100644 index 94174b76510..00000000000 --- a/crates/test-utils/src/project_graph.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::workspace_mocker::*; -use starbase_sandbox::create_sandbox; -use std::path::Path; - -pub use moon_project_graph::ProjectGraph; - -pub fn create_project_graph_mocker(root: &Path) -> WorkspaceMocker { - let mut mock = WorkspaceMocker::new(root); - - mock.with_default_configs() - .with_default_projects() - .with_default_toolchain() - .with_global_tasks(); - - mock -} - -pub async fn generate_project_graph(fixture: &str) -> ProjectGraph { - generate_project_graph_from_sandbox(create_sandbox(fixture).path()).await -} - -pub async fn generate_project_graph_from_sandbox(root: &Path) -> ProjectGraph { - create_project_graph_mocker(root) - .build_project_graph() - .await -} diff --git a/crates/test-utils/src/workspace_graph.rs b/crates/test-utils/src/workspace_graph.rs new file mode 100644 index 00000000000..f5e6d1614aa --- /dev/null +++ b/crates/test-utils/src/workspace_graph.rs @@ -0,0 +1,27 @@ +use crate::workspace_mocker::*; +pub use moon_project_graph::ProjectGraph; +pub use moon_task_graph::TaskGraph; +pub use moon_workspace_graph::WorkspaceGraph; +use starbase_sandbox::create_sandbox; +use std::path::Path; + +pub fn create_workspace_graph_mocker(root: &Path) -> WorkspaceMocker { + let mut mock = WorkspaceMocker::new(root); + + mock.with_default_configs() + .with_default_projects() + .with_default_toolchain() + .with_global_tasks(); + + mock +} + +pub async fn generate_workspace_graph(fixture: &str) -> WorkspaceGraph { + generate_workspace_graph_from_sandbox(create_sandbox(fixture).path()).await +} + +pub async fn generate_workspace_graph_from_sandbox(root: &Path) -> WorkspaceGraph { + create_workspace_graph_mocker(root) + .build_workspace_graph() + .await +} diff --git a/crates/test-utils/src/workspace_mocker.rs b/crates/test-utils/src/workspace_mocker.rs index 9fb2bad6ddf..b7a4818968c 100644 --- a/crates/test-utils/src/workspace_mocker.rs +++ b/crates/test-utils/src/workspace_mocker.rs @@ -1,8 +1,8 @@ use moon_cache::CacheEngine; use moon_config::*; -use moon_project_graph::ProjectGraph; use moon_vcs::{BoxedVcs, Git}; use moon_workspace::*; +use moon_workspace_graph::WorkspaceGraph; use proto_core::ProtoConfig; use starbase_events::Emitter; use std::collections::BTreeMap; @@ -116,23 +116,23 @@ impl WorkspaceMocker { } } - pub async fn build_project_graph(&self) -> ProjectGraph { - self.build_project_graph_with_options(ProjectGraphMockOptions::default()) + pub async fn build_workspace_graph(&self) -> WorkspaceGraph { + self.build_workspace_graph_with_options(WorkspaceMockOptions::default()) .await } - pub async fn build_project_graph_for(&self, ids: &[&str]) -> ProjectGraph { - self.build_project_graph_with_options(ProjectGraphMockOptions { + pub async fn build_workspace_graph_for(&self, ids: &[&str]) -> WorkspaceGraph { + self.build_workspace_graph_with_options(WorkspaceMockOptions { ids: Vec::from_iter(ids.iter().map(|id| id.to_string())), ..Default::default() }) .await } - pub async fn build_project_graph_with_options<'l>( + pub async fn build_workspace_graph_with_options<'l>( &self, - mut options: ProjectGraphMockOptions<'l>, - ) -> ProjectGraph { + mut options: WorkspaceMockOptions<'l>, + ) -> WorkspaceGraph { let context = options .context .take() @@ -155,22 +155,22 @@ impl WorkspaceMocker { builder.load_tasks().await.unwrap(); - let project_graph = builder.build().await.unwrap().project_graph; + let workspace_graph = builder.build().await.unwrap(); if options.ids.is_empty() { - project_graph.get_all().unwrap(); + workspace_graph.projects.get_all().unwrap(); } else { for id in &options.ids { - project_graph.get(id).unwrap(); + workspace_graph.projects.get(id).unwrap(); } } - project_graph + workspace_graph } } #[derive(Default)] -pub struct ProjectGraphMockOptions<'l> { +pub struct WorkspaceMockOptions<'l> { pub cache: Option, pub context: Option>, pub ids: Vec, diff --git a/crates/workspace-graph/Cargo.toml b/crates/workspace-graph/Cargo.toml new file mode 100644 index 00000000000..75030430d3b --- /dev/null +++ b/crates/workspace-graph/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "moon_workspace_graph" +version = "0.0.1" +edition = "2021" +license = "MIT" +description = "Workspace graph." +homepage = "https://moonrepo.dev/moon" +repository = "https://github.com/moonrepo/moon" +publish = false + +[dependencies] +moon_common = { path = "../common" } +moon_graph_utils = { path = "../graph-utils" } +moon_project_graph = { path = "../project-graph" } +moon_query = { path = "../query" } +moon_task_graph = { path = "../task-graph" } +miette = { workspace = true } +scc = { workspace = true } +tracing = { workspace = true } + +[lints] +workspace = true diff --git a/crates/workspace-graph/src/lib.rs b/crates/workspace-graph/src/lib.rs new file mode 100644 index 00000000000..ef09ab72a54 --- /dev/null +++ b/crates/workspace-graph/src/lib.rs @@ -0,0 +1,252 @@ +use moon_common::{color, Id}; +use moon_project_graph::{Project, ProjectGraph}; +use moon_query::*; +use moon_task_graph::{Target, Task, TaskGraph}; +use scc::HashMap; +use std::{fmt::Debug, path::Path, sync::Arc}; +use tracing::{debug, instrument}; + +pub use moon_graph_utils::*; +pub use moon_project_graph as projects; +pub use moon_task_graph as tasks; + +#[derive(Clone, Default)] +pub struct WorkspaceGraph { + pub projects: Arc, + pub tasks: Arc, + + /// Cache of query results, mapped by query input to project IDs. + query_cache: HashMap>>, +} + +impl WorkspaceGraph { + pub fn new(projects: Arc, tasks: Arc) -> Self { + Self { + projects, + tasks, + query_cache: HashMap::default(), + } + } + + pub fn get_project(&self, id_or_alias: impl AsRef) -> miette::Result> { + self.projects.get(id_or_alias.as_ref()) + } + + pub fn get_project_from_path( + &self, + starting_file: Option<&Path>, + ) -> miette::Result> { + self.projects.get_from_path(starting_file) + } + + pub fn get_project_with_tasks(&self, id_or_alias: impl AsRef) -> miette::Result { + let base_project = self.get_project(id_or_alias)?; + let mut project = base_project.as_ref().to_owned(); + + for base_task in self.get_tasks_from_project(&project.id)? { + project + .tasks + .insert(base_task.id.clone(), base_task.as_ref().to_owned()); + } + + Ok(project) + } + + pub fn get_all_projects(&self) -> miette::Result>> { + self.projects.get_all() + } + + pub fn get_task(&self, target: &Target) -> miette::Result> { + self.tasks.get(target) + } + + pub fn get_task_from_project( + &self, + project_id: impl AsRef, + task_id: impl AsRef, + ) -> miette::Result> { + let target = Target::new(project_id, task_id)?; + + self.tasks.get(&target) + } + + pub fn get_tasks_from_project( + &self, + project_id: impl AsRef, + ) -> miette::Result>> { + let project = self.get_project(project_id)?; + let mut all = vec![]; + + for target in &project.task_targets { + let task = self.get_task(target)?; + + if !task.is_internal() { + all.push(task); + } + } + + Ok(all) + } + + pub fn get_all_tasks(&self) -> miette::Result>> { + Ok(self + .tasks + .get_all()? + .into_iter() + .filter(|task| !task.is_internal()) + .collect()) + } +} + +impl WorkspaceGraph { + /// Return all expanded projects that match the query criteria. + #[instrument(name = "query_projects", skip(self))] + pub fn query_projects<'input, Q: AsRef> + Debug>( + &self, + query: Q, + ) -> miette::Result>> { + let mut projects = vec![]; + + for id in self.internal_query(query)?.iter() { + projects.push(self.get_project(id)?); + } + + Ok(projects) + } + + fn internal_query<'input, Q: AsRef>>( + &self, + query: Q, + ) -> miette::Result>> { + let query = query.as_ref(); + let query_input = query + .input + .as_ref() + .expect("Querying the project graph requires a query input string."); + let cache_key = query_input.to_string(); + + if let Some(cache) = self.query_cache.read(&cache_key, |_, v| v.clone()) { + return Ok(cache); + } + + debug!("Querying projects with {}", color::shell(query_input)); + + let mut project_ids = vec![]; + + // Don't use `get_all` as it recursively calls `query`, + // which runs into a deadlock! This should be faster also... + for project in self.projects.get_all_unexpanded() { + if self.matches_criteria(project, query)? { + project_ids.push(project.id.clone()); + } + } + + // Sort so that the order is deterministic + project_ids.sort(); + + debug!( + projects = ?project_ids + .iter() + .map(|id| id.as_str()) + .collect::>(), + "Found {} matches", + project_ids.len(), + ); + + let ids = Arc::new(project_ids); + let _ = self.query_cache.insert(cache_key, Arc::clone(&ids)); + + Ok(ids) + } + + fn matches_criteria(&self, project: &Project, query: &Criteria) -> miette::Result { + let match_all = matches!(query.op, LogicalOperator::And); + let mut matched_any = false; + + for condition in &query.conditions { + let matches = match condition { + Condition::Field { field, .. } => { + let result = match field { + Field::Language(langs) => condition.matches_enum(langs, &project.language), + Field::Project(ids) => { + if condition.matches(ids, &project.id)? { + Ok(true) + } else if let Some(alias) = &project.alias { + condition.matches(ids, alias) + } else { + Ok(false) + } + } + Field::ProjectAlias(aliases) => { + if let Some(alias) = &project.alias { + condition.matches(aliases, alias) + } else { + Ok(false) + } + } + Field::ProjectName(ids) => condition.matches(ids, &project.id), + Field::ProjectSource(sources) => { + condition.matches(sources, project.source.as_str()) + } + Field::ProjectStack(types) => condition.matches_enum(types, &project.stack), + Field::ProjectType(types) => { + condition.matches_enum(types, &project.type_of) + } + Field::Tag(tags) => condition.matches_list( + tags, + &project + .config + .tags + .iter() + .map(|t| t.as_str()) + .collect::>(), + ), + Field::Task(ids) => Ok(project.task_targets.iter().any(|target| { + condition.matches(ids, &target.task_id).unwrap_or_default() + })), + Field::TaskPlatform(platforms) => Ok(self + .tasks + .get_all_for_project(&project.id, false)? + .iter() + .any(|task| { + condition + .matches_enum(platforms, &task.platform) + .unwrap_or_default() + })), + Field::TaskType(types) => Ok(self + .tasks + .get_all_for_project(&project.id, false)? + .iter() + .any(|task| { + condition + .matches_enum(types, &task.type_of) + .unwrap_or_default() + })), + }; + + result? + } + Condition::Criteria { criteria } => self.matches_criteria(project, criteria)?, + }; + + if matches { + matched_any = true; + + if match_all { + continue; + } else { + break; + } + } else if match_all { + return Ok(false); + } + } + + // No matches using the OR condition + if !matched_any { + return Ok(false); + } + + Ok(true) + } +} diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 7a1a9d740cd..6754a23a5dc 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -21,6 +21,7 @@ moon_task = { path = "../task" } moon_task_builder = { path = "../task-builder" } moon_task_graph = { path = "../task-graph" } moon_vcs = { path = "../vcs" } +moon_workspace_graph = { path = "../workspace-graph" } miette = { workspace = true } petgraph = { workspace = true } rustc-hash = { workspace = true } diff --git a/crates/workspace/src/lib.rs b/crates/workspace/src/lib.rs index ecc0f2c5f29..c18914d7adb 100644 --- a/crates/workspace/src/lib.rs +++ b/crates/workspace/src/lib.rs @@ -12,12 +12,3 @@ pub use tasks_querent::*; pub use workspace_builder::*; pub use workspace_builder_error::*; pub use workspace_cache::*; - -use moon_project_graph::ProjectGraph; -use moon_task_graph::TaskGraph; -use std::sync::Arc; - -pub struct WorkspaceGraph { - pub projects: Arc, - pub tasks: Arc, -} diff --git a/crates/workspace/src/workspace_builder.rs b/crates/workspace/src/workspace_builder.rs index 67b2e0117ba..c7efd865bfb 100644 --- a/crates/workspace/src/workspace_builder.rs +++ b/crates/workspace/src/workspace_builder.rs @@ -22,6 +22,7 @@ use moon_task::Target; use moon_task_builder::TaskDepsBuilder; use moon_task_graph::{TaskGraph, TaskGraphError, TaskGraphType, TaskMetadata}; use moon_vcs::BoxedVcs; +use moon_workspace_graph::WorkspaceGraph; use petgraph::prelude::*; use petgraph::visit::IntoNodeReferences; use rustc_hash::{FxHashMap, FxHashSet}; @@ -44,11 +45,6 @@ pub struct WorkspaceBuilderContext<'app> { pub workspace_root: &'app Path, } -pub struct WorkspaceBuildResult { - pub project_graph: ProjectGraph, - pub task_graph: TaskGraph, -} - #[derive(Deserialize, Serialize)] pub struct WorkspaceBuilder<'app> { #[serde(skip)] @@ -182,7 +178,7 @@ impl<'app> WorkspaceBuilder<'app> { /// Build the project graph and return a new structure. #[instrument(name = "build_workspace_graph", skip_all)] - pub async fn build(mut self) -> miette::Result { + pub async fn build(mut self) -> miette::Result { self.enforce_constraints()?; let context = self.context.take().unwrap(); @@ -203,10 +199,10 @@ impl<'app> WorkspaceBuilder<'app> { }) .collect::>(); - let mut project_graph = - ProjectGraph::new(self.project_graph, project_metadata, context.workspace_root); - + let mut project_graph = ProjectGraph::new(self.project_graph, project_metadata); project_graph.working_dir = context.working_dir.to_owned(); + project_graph.workspace_root = context.workspace_root.to_owned(); + let project_graph = Arc::new(project_graph); let task_metadata = self .task_data @@ -221,12 +217,13 @@ impl<'app> WorkspaceBuilder<'app> { }) .collect::>(); - let task_graph = TaskGraph::new(self.task_graph, task_metadata); + let mut task_graph = + TaskGraph::new(self.task_graph, task_metadata, Arc::clone(&project_graph)); + task_graph.working_dir = context.working_dir.to_owned(); + task_graph.workspace_root = context.workspace_root.to_owned(); + let task_graph = Arc::new(task_graph); - Ok(WorkspaceBuildResult { - project_graph, - task_graph, - }) + Ok(WorkspaceGraph::new(project_graph, task_graph)) } /// Load a single project by ID or alias into the graph. @@ -461,9 +458,8 @@ impl<'app> WorkspaceBuilder<'app> { .internal_load_project(target.get_project_id().unwrap(), &mut FxHashSet::default()) .await?; - // TODO change to a remove in a follow-up PR - let project = self.project_graph.node_weight(project_index).unwrap(); - let mut task = project.tasks.get(&target.task_id).unwrap().clone(); + let project = self.project_graph.node_weight_mut(project_index).unwrap(); + let mut task = project.tasks.remove(&target.task_id).unwrap(); cycle.insert(target.clone()); diff --git a/packages/nx-compat/src/moon.ts b/packages/nx-compat/src/moon.ts index 789542708b5..ac0e3496c4b 100644 --- a/packages/nx-compat/src/moon.ts +++ b/packages/nx-compat/src/moon.ts @@ -1,10 +1,8 @@ -/* eslint-disable no-nested-ternary */ - import path from 'node:path'; import { execa } from 'execa'; import type { ProjectGraph as NxProjectGraph, - ProjectGraphProjectNode, + // ProjectGraphProjectNode, } from 'nx/src/config/project-graph'; import type { TaskGraph as NxTaskGraph } from 'nx/src/config/task-graph'; import type { @@ -94,19 +92,20 @@ export function createNxProjectGraphFromMoonProjectGraph( nodes: {}, }; - Object.values(projectGraph.projects).forEach((project) => { - const node: ProjectGraphProjectNode = { - data: createNxProjectFromMoonProject(project), - name: project.id, - type: project.type === 'application' ? 'app' : project.type === 'automation' ? 'e2e' : 'lib', - }; + // TODO + // Object.values(projectGraph.projects).forEach((project) => { + // const node: ProjectGraphProjectNode = { + // data: createNxProjectFromMoonProject(project), + // name: project.id, + // type: project.type === 'application' ? 'app' : project.type === 'automation' ? 'e2e' : 'lib', + // }; - graph.nodes[project.id] = node; + // graph.nodes[project.id] = node; - if (project.alias) { - graph.nodes[project.alias] = node; - } - }); + // if (project.alias) { + // graph.nodes[project.alias] = node; + // } + // }); projectGraph.graph.edges.forEach((edge) => { const source = projectGraph.graph.nodes[edge[0]]; @@ -146,36 +145,32 @@ export function createNxTaskGraphFromMoonGraphs( }; actionGraph.nodes.forEach((node) => { - if (!node.label.startsWith('Run')) { - return; - } - - const target = node.label - .replace('RunTarget(', '') - .replace('RunTask(', '') - .replace('RunInteractiveTask(', '') - .replace('RunPersistentTask(', '') - .replace(')', ''); - const [projectId, taskId] = target.split(':'); - const project = projectGraph.projects[projectId]; - - if (!project) { - return; - } - - const task = project.tasks[taskId]; - - if (!task) { - return; - } - - graph.tasks[String(node.id)] = { - id: String(node.id), - outputs: [...task.outputFiles, ...task.outputGlobs], - overrides: {}, - projectRoot: project.root, - target: { project: projectId, target: taskId }, - }; + // if (!node.label.startsWith('Run')) { + // return + // } + // const target = node.label + // .replace('RunTarget(', '') + // .replace('RunTask(', '') + // .replace('RunInteractiveTask(', '') + // .replace('RunPersistentTask(', '') + // .replace(')', ''); + // const [projectId, taskId] = target.split(':'); + // TODO + // const project = undefined; // projectGraph.projects[projectId]; + // if (!project) { + // return; + // } + // const task = project.tasks[taskId]; + // if (!task) { + // return; + // } + // graph.tasks[String(node.id)] = { + // id: String(node.id), + // outputs: [...task.outputFiles, ...task.outputGlobs], + // overrides: {}, + // projectRoot: project.root, + // target: { project: projectId, target: taskId }, + // }; }); actionGraph.edges.forEach((edge) => { diff --git a/packages/types/src/project-config.ts b/packages/types/src/project-config.ts index de23f2100c3..f72824ed212 100644 --- a/packages/types/src/project-config.ts +++ b/packages/types/src/project-config.ts @@ -2,8 +2,11 @@ /* eslint-disable */ -import type { UnresolvedVersionSpec } from './toolchain-config'; import type { PartialTaskConfig, PlatformType, TaskConfig } from './tasks-config'; +import type { UnresolvedVersionSpec } from './toolchain-config'; + +/** The task-to-task relationship of the dependency. */ +export type DependencyType = 'cleanup' | 'required' | 'optional'; /** The scope and or relationship of the dependency. */ export type DependencyScope = 'build' | 'development' | 'peer' | 'production' | 'root'; diff --git a/packages/types/src/project.ts b/packages/types/src/project.ts index 3f06d549ca2..41dcae86929 100644 --- a/packages/types/src/project.ts +++ b/packages/types/src/project.ts @@ -1,6 +1,7 @@ import type { DependencyConfig, DependencyScope, + DependencyType, LanguageType, ProjectConfig, ProjectType, @@ -95,6 +96,7 @@ export interface Project { source: string; stack: StackType; tasks: Record; + taskTargets: string[]; type: ProjectType; } @@ -105,15 +107,28 @@ export interface ProjectGraphInner { edges: [number, number, DependencyScope][]; } -export interface PartialProjectGraph { - aliases: Record; +export interface ProjectGraph { graph: ProjectGraphInner; - nodes: Record; - root_id: string | null; - sources: Record; } -export interface ProjectGraph { - graph: ProjectGraphInner; - projects: Record; +export interface TaskGraphInner { + nodes: Task[]; + node_holes: string[]; + edge_property: 'directed'; + edges: [number, number, DependencyType][]; +} + +export interface TaskGraph { + graph: TaskGraphInner; +} + +export interface WorkspaceGraph { + projects_by_tag: Record; + project_data: Record; + project_graph: ProjectGraphInner; + renamed_project_ids: Record; + repo_type: 'monorepo-with-root' | 'monorepo' | 'polyrepo'; + root_project_id: string | null; + task_data: Record; + task_graph: TaskGraphInner; }