diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.ymldisabled similarity index 84% rename from .github/workflows/benchmark.yml rename to .github/workflows/benchmark.ymldisabled index 7dee6594a53..0b0f66c4f3f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.ymldisabled @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest # strategy: # matrix: - # bench: [dep_graph_benchmark, emitter_benchmark, pipeline_benchmark] + # bench: [emitter_benchmark, pipeline_benchmark] # fail-fast: false steps: - run: echo ${{ github.event.pull_request.head.sha }} @@ -37,8 +37,8 @@ jobs: - name: Run benchmarks # args: --workspace --bench ${{ matrix.bench }} -- --save-baseline base-sha run: - cargo bench --workspace --bench dep_graph_benchmark --bench emitter_benchmark --bench - pipeline_benchmark --bench tar_benchmark -- --save-baseline head-sha + cargo bench --workspace --bench emitter_benchmark --bench pipeline_benchmark --bench + tar_benchmark -- --save-baseline head-sha # Run on base branch to get a baseline - name: Checkout base branch @@ -49,8 +49,8 @@ jobs: - name: Run benchmarks # args: --workspace --bench ${{ matrix.bench }} -- --save-baseline base-sha run: - cargo bench --workspace --bench dep_graph_benchmark --bench emitter_benchmark --bench - pipeline_benchmark --bench tar_benchmark -- --save-baseline base-sha + cargo bench --workspace --bench emitter_benchmark --bench pipeline_benchmark --bench + tar_benchmark -- --save-baseline base-sha # Compare diffs - name: Install critcmp diff --git a/.moon/toolchain.yml b/.moon/toolchain.yml index 8d2c21b8540..c9a934ccdb3 100644 --- a/.moon/toolchain.yml +++ b/.moon/toolchain.yml @@ -1,7 +1,7 @@ $schema: '../website/static/schemas/toolchain.json' rust: - version: '1.72.1' + version: '1.73.0' bins: - 'cargo-make' - 'cargo-nextest' diff --git a/.yarn/versions/22a0caa2.yml b/.yarn/versions/22a0caa2.yml new file mode 100644 index 00000000000..997894edcae --- /dev/null +++ b/.yarn/versions/22a0caa2.yml @@ -0,0 +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/visualizer': minor + '@moonrepo/types': minor + '@moonrepo/report': minor + +declined: + - '@moonrepo/runtime' + - website diff --git a/Cargo.lock b/Cargo.lock index e1e0b9db207..9040ec64626 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,9 +93,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -632,9 +632,9 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a1f1117a8ff2f3547295da90f473c392d8d1107c90cea1ea82b1a544a97a4a" +checksum = "e3f9629bc6c4388ea699781dc988c2b99766d7679b151c81990b4fa1208fafd3" dependencies = [ "serde", "toml 0.8.2", @@ -800,9 +800,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.4" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ "clap_builder", "clap_derive", @@ -810,9 +810,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.4" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" dependencies = [ "anstream", "anstyle", @@ -823,11 +823,11 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.1" +version = "4.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4110a1e6af615a9e6d0a36f805d5c99099f8bab9b8042f5bc1fa220a4a89e36f" +checksum = "e3ae8ba90b9d8b007efe66e55e48fb936272f5ca00349b5b0e89877520d35ea7" dependencies = [ - "clap 4.4.4", + "clap 4.4.6", ] [[package]] @@ -1158,7 +1158,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.4.4", + "clap 4.4.6", "criterion-plot", "futures", "is-terminal", @@ -2987,9 +2987,9 @@ name = "moon" version = "0.1.0" dependencies = [ "miette", + "moon_action_graph", "moon_config", "moon_deno_platform", - "moon_dep_graph", "moon_node_platform", "moon_platform", "moon_platform_detector", @@ -3008,6 +3008,7 @@ name = "moon_action" version = "0.1.0" dependencies = [ "miette", + "moon_action_graph", "moon_common", "moon_platform_runtime", "moon_target", @@ -3019,13 +3020,36 @@ dependencies = [ name = "moon_action_context" version = "0.1.0" dependencies = [ - "clap 4.4.4", + "clap 4.4.6", "moon_common", "moon_target", "rustc-hash", "serde", ] +[[package]] +name = "moon_action_graph" +version = "0.1.0" +dependencies = [ + "miette", + "moon_common", + "moon_config", + "moon_platform", + "moon_platform_runtime", + "moon_project", + "moon_project_graph", + "moon_query", + "moon_task", + "moon_test_utils2", + "petgraph", + "rustc-hash", + "serde", + "starbase_sandbox", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "moon_action_pipeline" version = "0.1.0" @@ -3037,10 +3061,10 @@ dependencies = [ "moon", "moon_action", "moon_action_context", + "moon_action_graph", "moon_actions", "moon_api", "moon_cache_item", - "moon_dep_graph", "moon_emitter", "moon_hash", "moon_logger", @@ -3181,7 +3205,7 @@ version = "1.14.5" dependencies = [ "bytes", "ci_env", - "clap 4.4.4", + "clap 4.4.6", "clap_complete", "clap_lex 0.5.1", "console", @@ -3194,6 +3218,7 @@ dependencies = [ "mimalloc", "moon", "moon_action_context", + "moon_action_graph", "moon_action_pipeline", "moon_actions", "moon_api", @@ -3201,7 +3226,6 @@ dependencies = [ "moon_codegen", "moon_common", "moon_config", - "moon_dep_graph", "moon_lang", "moon_node_lang", "moon_node_platform", @@ -3367,33 +3391,6 @@ dependencies = [ "rustc-hash", ] -[[package]] -name = "moon_dep_graph" -version = "0.1.0" -dependencies = [ - "criterion", - "miette", - "moon", - "moon_action", - "moon_common", - "moon_config", - "moon_logger", - "moon_platform", - "moon_project", - "moon_project_graph", - "moon_query", - "moon_target", - "moon_task", - "moon_test_utils", - "moon_utils", - "moon_workspace", - "petgraph", - "rustc-hash", - "starbase_styles", - "thiserror", - "tokio", -] - [[package]] name = "moon_emitter" version = "0.1.0" @@ -3733,6 +3730,7 @@ dependencies = [ "moon_query", "moon_task", "moon_task_builder", + "moon_test_utils2", "moon_vcs", "once_map", "petgraph", @@ -3969,6 +3967,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "moon_test_utils2" +version = "0.1.0" +dependencies = [ + "miette", + "moon_config", + "moon_node_platform", + "moon_platform", + "moon_project_graph", + "moon_rust_platform", + "moon_system_platform", + "moon_vcs", + "proto_core", + "starbase_events", + "starbase_sandbox", +] + [[package]] name = "moon_time" version = "0.1.0" @@ -4377,9 +4392,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.7.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4" dependencies = [ "memchr", "thiserror", @@ -4388,9 +4403,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8" dependencies = [ "pest", "pest_generator", @@ -4398,9 +4413,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a" dependencies = [ "pest", "pest_meta", @@ -4411,9 +4426,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d" dependencies = [ "once_cell", "pest", @@ -4646,9 +4661,9 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.19.2" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c68fe1824ab8c19d63392f686a9f48e03425f2e687b272b421170867de3b19b" +checksum = "95344f1074fd611a4652e1f8d16be6991059c312831f40483bbe750a84558a09" dependencies = [ "cached", "extism", @@ -4677,9 +4692,9 @@ dependencies = [ [[package]] name = "proto_pdk_api" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b86ccfd83ad829a3666f84aacbd55e60cf0044608db9b1dfe890b80f0bafe9" +checksum = "ef5c2ebb2bebee2804153fabaffdf0ed19268d56b814fec5670f0207adde625b" dependencies = [ "anyhow", "semver", @@ -4819,13 +4834,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.5" +version = "1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" dependencies = [ "aho-corasick 1.0.1", "memchr", - "regex-automata 0.3.8", + "regex-automata 0.3.9", "regex-syntax 0.7.5", ] @@ -4840,9 +4855,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick 1.0.1", "memchr", @@ -4872,9 +4887,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ "base64", "bytes", @@ -4902,6 +4917,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", @@ -5466,9 +5482,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "starbase" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc2dc5364b3a7b113afd1f3d609edc1ae4c9f742f345949669c7ae5b9ac1944" +checksum = "0ce70d3d623e4e26202bdac9fedda7f6e91a12c12f98147eaf2e624c7525c457" dependencies = [ "async-trait", "chrono", @@ -5677,6 +5693,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-interface" version = "0.25.9" diff --git a/Cargo.toml b/Cargo.toml index bbe61df8608..3a3819e8f38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,8 +28,8 @@ cached = "0.46.0" chrono = { version = "0.4.31", features = ["serde"] } cd_env = "0.1.2" ci_env = "0.2.1" -clap = { version = "4.4.4", features = ["derive", "env", "wrap_help"] } -clap_complete = "4.4.1" +clap = { version = "4.4.6", features = ["derive", "env", "wrap_help"] } +clap_complete = "4.4.3" console = "0.15.7" criterion = { version = "0.5.1", features = ["async_tokio"] } miette = "5.10.0" @@ -39,10 +39,10 @@ pathdiff = "0.2.1" petgraph = { version = "0.6.4", default-features = false, features = [ "serde-1", ] } -proto_core = "0.19.2" +proto_core = "0.19.3" relative-path = { version = "1.9.0", features = ["serde"] } -regex = "1.9.5" -reqwest = { version = "0.11.20", default-features = false, features = [ +regex = "1.9.6" +reqwest = { version = "0.11.22", default-features = false, features = [ "rustls-tls-native-roots", # We don't use openssl but its required for musl builds "native-tls-vendored", @@ -60,7 +60,7 @@ semver = "1.0.19" serde = { version = "1.0.188", features = ["derive", "rc"] } serde_json = "1.0.107" serde_yaml = "0.9.25" -starbase = "0.2.7" +starbase = "0.2.8" starbase_archive = { version = "0.2.3", default-features = false, features = [ "tar-gz", ] } @@ -75,7 +75,7 @@ starbase_utils = { version = "0.3.4", default-features = false, features = [ "yaml", ] } tera = { version = "1.19.1", features = ["preserve_order"] } -thiserror = "1.0.48" +thiserror = "1.0.49" tokio = { version = "1.32.0", default-features = false, features = [ "process", "rt", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 9faaea65203..a3f77f1fbe6 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -16,6 +16,7 @@ crate-type = ["rlib"] [dependencies] moon = { path = "../core/moon" } moon_action_context = { path = "../core/action-context" } +moon_action_graph = { path = "../../nextgen/action-graph" } moon_action_pipeline = { path = "../core/action-pipeline" } moon_actions = { path = "../core/actions" } moon_api = { path = "../../nextgen/api" } @@ -23,7 +24,6 @@ moon_cache = { path = "../../nextgen/cache" } moon_codegen = { path = "../../nextgen/codegen" } moon_common = { path = "../../nextgen/common" } moon_config = { path = "../../nextgen/config" } -moon_dep_graph = { path = "../core/dep-graph" } moon_lang = { path = "../core/lang" } moon_node_lang = { path = "../node/lang" } moon_node_platform = { path = "../node/platform" } diff --git a/crates/cli/src/app.rs b/crates/cli/src/app.rs index b38baaecf09..2eed58de202 100644 --- a/crates/cli/src/app.rs +++ b/crates/cli/src/app.rs @@ -7,7 +7,7 @@ use crate::commands::clean::CleanArgs; use crate::commands::completions::CompletionsArgs; use crate::commands::docker::DockerScaffoldArgs; use crate::commands::generate::GenerateArgs; -use crate::commands::graph::dep::DepGraphArgs; +use crate::commands::graph::action::ActionGraphArgs; use crate::commands::graph::project::ProjectGraphArgs; use crate::commands::init::InitArgs; use crate::commands::migrate::FromPackageJsonArgs; @@ -178,13 +178,22 @@ pub enum Commands { // PROJECTS + // moon action-graph [target] + #[command( + alias = "ag", + name = "action-graph", + about = "Display an interactive dependency graph of all tasks and actions." + )] + ActionGraph(ActionGraphArgs), + // moon dep-graph [target] #[command( name = "dep-graph", about = "Display an interactive dependency graph of all tasks and actions.", - alias = "dg" + alias = "dg", + hide = true )] - DepGraph(DepGraphArgs), + DepGraph(ActionGraphArgs), // moon project #[command( diff --git a/crates/cli/src/commands/ci.rs b/crates/cli/src/commands/ci.rs index 05ddd85a683..b9d6de486f9 100644 --- a/crates/cli/src/commands/ci.rs +++ b/crates/cli/src/commands/ci.rs @@ -3,11 +3,11 @@ use crate::queries::touched_files::{query_touched_files, QueryTouchedFilesOption use ci_env::CiOutput; use clap::Args; use itertools::Itertools; -use moon::{build_dep_graph, generate_project_graph}; +use moon::{build_action_graph, generate_project_graph}; use moon_action_context::ActionContext; +use moon_action_graph::ActionGraph; use moon_action_pipeline::Pipeline; use moon_common::path::WorkspaceRelativePathBuf; -use moon_dep_graph::DepGraph; use moon_project_graph::ProjectGraph; use moon_target::Target; use moon_terminal::safe_exit; @@ -162,31 +162,31 @@ fn distribute_targets_across_jobs( } /// Generate a dependency graph with the runnable targets. -fn generate_dep_graph( +fn generate_action_graph( provider: &CiOutput, project_graph: &ProjectGraph, targets: &TargetList, touched_files: &FxHashSet, -) -> AppResult { - print_header(provider, "Generating dependency graph"); +) -> AppResult { + print_header(provider, "Generating action graph"); - let mut dep_builder = build_dep_graph(project_graph); + let mut action_graph_builder = build_action_graph(project_graph)?; + + // Run dependents to ensure consumers still work correctly + action_graph_builder.include_dependents(); for target in targets { // Run the target and its dependencies - dep_builder.run_target(target, Some(touched_files))?; - - // And also run its dependents to ensure consumers still work correctly - dep_builder.run_dependents_for_target(target)?; + action_graph_builder.run_task_by_target(target, Some(touched_files))?; } - let dep_graph = dep_builder.build(); + let action_graph = action_graph_builder.build()?; println!("Target count: {}", targets.len()); - println!("Action count: {}", dep_graph.get_node_count()); + println!("Action count: {}", action_graph.get_node_count()); print_footer(provider); - Ok(dep_graph) + Ok(action_graph) } #[system] @@ -210,9 +210,10 @@ pub async fn ci( } let targets = distribute_targets_across_jobs(&ci_provider, args, targets); - let dep_graph = generate_dep_graph(&ci_provider, &project_graph, &targets, &touched_files)?; + let action_graph = + generate_action_graph(&ci_provider, &project_graph, &targets, &touched_files)?; - if dep_graph.is_empty() { + if action_graph.is_empty() { println!( "{}", color::invalid("No targets to run based on touched files") @@ -239,7 +240,7 @@ pub async fn ci( let results = pipeline .generate_report("ciReport.json") - .run(dep_graph, Some(context)) + .run(action_graph, Some(context)) .await?; print_footer(&ci_provider); diff --git a/crates/cli/src/commands/docker/setup.rs b/crates/cli/src/commands/docker/setup.rs index f9bf319ff12..c938e66e5f3 100644 --- a/crates/cli/src/commands/docker/setup.rs +++ b/crates/cli/src/commands/docker/setup.rs @@ -1,6 +1,6 @@ use super::MANIFEST_NAME; use crate::commands::docker::scaffold::DockerManifest; -use moon::{build_dep_graph, generate_project_graph}; +use moon::{build_action_graph, generate_project_graph}; use moon_action_pipeline::Pipeline; use moon_terminal::safe_exit; use moon_workspace::Workspace; @@ -18,17 +18,17 @@ pub async fn setup(workspace: ResourceMut) { let manifest: DockerManifest = json::read_file(manifest_path)?; let project_graph = generate_project_graph(workspace).await?; - let mut dep_builder = build_dep_graph(&project_graph); + let mut action_graph_builder = build_action_graph(&project_graph)?; for project_id in &manifest.focused_projects { let project = project_graph.get(project_id)?; - dep_builder.install_deps(&project, None)?; + action_graph_builder.install_deps(&project, None)?; } - let dep_graph = dep_builder.build(); + let action_graph = action_graph_builder.build()?; Pipeline::new(workspace.to_owned(), project_graph) - .run(dep_graph, None) + .run(action_graph, None) .await?; } diff --git a/crates/cli/src/commands/graph/action.rs b/crates/cli/src/commands/graph/action.rs new file mode 100644 index 00000000000..9ece3882321 --- /dev/null +++ b/crates/cli/src/commands/graph/action.rs @@ -0,0 +1,80 @@ +use crate::commands::graph::utils::{action_graph_repr, respond_to_request, setup_server}; +use clap::Args; +use miette::IntoDiagnostic; +use moon::{build_action_graph, generate_project_graph}; +use moon_target::TargetLocator; +use moon_workspace::Workspace; +use starbase::{system, SystemResult}; + +#[derive(Args, Clone, Debug)] +pub struct ActionGraphArgs { + #[arg(help = "Target to *only* graph")] + target: Option, + + #[arg(long, help = "Include 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, +} + +pub async fn internal_action_graph( + args: &ActionGraphArgs, + workspace: &mut Workspace, +) -> SystemResult { + let project_graph = generate_project_graph(workspace).await?; + let mut action_graph_builder = build_action_graph(&project_graph)?; + + // Focus a target and its dependencies/dependents + if let Some(locator) = args.target.clone() { + if args.dependents { + action_graph_builder.include_dependents(); + } + + action_graph_builder.run_task_by_target_locator(locator, None)?; + + // Show all targets and actions + } else { + for project in project_graph.get_all()? { + for task in project.tasks.values() { + action_graph_builder.run_task(&project, task, None)?; + } + } + } + + let action_graph = action_graph_builder.build()?; + + if args.dot { + println!("{}", action_graph.to_dot()); + + return Ok(()); + } + + let graph_info = action_graph_repr(&action_graph).await; + + if args.json { + println!("{}", serde_json::to_string(&graph_info).into_diagnostic()?); + + return Ok(()); + } + + let (server, mut tera) = setup_server().await?; + let url = format!("http://{}", server.server_addr()); + let _ = open::that(&url); + + println!("Started server on {url}"); + + for req in server.incoming_requests() { + respond_to_request(req, &mut tera, &graph_info, "Action graph".to_owned())?; + } + + Ok(()) +} + +#[system] +pub async fn action_graph(args: ArgsRef, workspace: ResourceMut) { + internal_action_graph(args, workspace).await?; +} diff --git a/crates/cli/src/commands/graph/dep.rs b/crates/cli/src/commands/graph/dep.rs index 7d1921c0bbd..c1cccba5336 100644 --- a/crates/cli/src/commands/graph/dep.rs +++ b/crates/cli/src/commands/graph/dep.rs @@ -1,66 +1,15 @@ -use crate::commands::graph::utils::{dep_graph_repr, respond_to_request, setup_server}; -use clap::Args; -use miette::IntoDiagnostic; -use moon::{build_dep_graph, generate_project_graph}; -use moon_target::TargetLocator; +use super::action::*; +use moon_common::color; use moon_workspace::Workspace; use starbase::system; - -#[derive(Args, Clone, Debug)] -pub struct DepGraphArgs { - #[arg(help = "Target to *only* graph")] - target: Option, - - #[arg(long, help = "Print the graph in DOT format")] - dot: bool, - - #[arg(long, help = "Print the graph in JSON format")] - json: bool, -} +use tracing::warn; #[system] -pub async fn dep_graph(args: ArgsRef, workspace: ResourceMut) { - let project_graph = generate_project_graph(workspace).await?; - let mut dep_builder = build_dep_graph(&project_graph); - - // Focus a target and its dependencies/dependents - if let Some(locator) = args.target.clone() { - for target in dep_builder.run_targets_by_locator(&[locator], None)? { - dep_builder.run_dependents_for_target(&target)?; - } - - // Show all targets and actions - } else { - for project in project_graph.get_all_unexpanded() { - for task in project.tasks.values() { - dep_builder.run_target(&task.target, None)?; - } - } - } - - let dep_graph = dep_builder.build(); - - if args.dot { - println!("{}", dep_graph.to_dot()); - - return Ok(()); - } - - let graph_info = dep_graph_repr(&dep_graph).await; - - if args.json { - println!("{}", serde_json::to_string(&graph_info).into_diagnostic()?); - - return Ok(()); - } - - let (server, mut tera) = setup_server().await?; - let url = format!("http://{}", server.server_addr()); - let _ = open::that(&url); - - println!("Started server on {url}"); +pub async fn dep_graph(args: ArgsRef, workspace: ResourceMut) { + warn!( + "This command is deprecated. Use {} instead.", + color::shell("moon action-graph") + ); - for req in server.incoming_requests() { - respond_to_request(req, &mut tera, &graph_info, "Dependency graph".to_owned())?; - } + internal_action_graph(args, workspace).await?; } diff --git a/crates/cli/src/commands/graph/mod.rs b/crates/cli/src/commands/graph/mod.rs index c10e3a5284b..b441f32da5e 100644 --- a/crates/cli/src/commands/graph/mod.rs +++ b/crates/cli/src/commands/graph/mod.rs @@ -1,3 +1,4 @@ +pub mod action; pub mod dep; mod dto; pub mod project; diff --git a/crates/cli/src/commands/graph/utils.rs b/crates/cli/src/commands/graph/utils.rs index ea1eaf715af..c74bbf2e9cb 100644 --- a/crates/cli/src/commands/graph/utils.rs +++ b/crates/cli/src/commands/graph/utils.rs @@ -1,6 +1,6 @@ use super::dto::{GraphEdgeDto, GraphInfoDto, GraphNodeDto}; use miette::IntoDiagnostic; -use moon_dep_graph::DepGraph; +use moon_action_graph::ActionGraph; use moon_project_graph::ProjectGraph; use petgraph::{graph::NodeIndex, Graph}; use rustc_hash::FxHashMap; @@ -84,8 +84,8 @@ pub async fn project_graph_repr(project_graph: &ProjectGraph) -> GraphInfoDto { } /// Get a serialized representation of the dependency graph. -pub async fn dep_graph_repr(dep_graph: &DepGraph) -> GraphInfoDto { - let labeled_graph = dep_graph.labeled_graph(); +pub async fn action_graph_repr(action_graph: &ActionGraph) -> GraphInfoDto { + let labeled_graph = action_graph.labeled_graph(); extract_nodes_and_edges_from_graph(&labeled_graph, false) } diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 17e344a8429..380a42ffccb 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -4,7 +4,7 @@ use crate::helpers::map_list; use crate::queries::touched_files::{query_touched_files, QueryTouchedFilesOptions}; use clap::Args; use miette::miette; -use moon::{build_dep_graph, generate_project_graph}; +use moon::{build_action_graph, generate_project_graph}; use moon_action_context::{ActionContext, ProfileType}; use moon_action_pipeline::Pipeline; use moon_common::is_test_env; @@ -26,10 +26,7 @@ pub struct RunArgs { #[arg(required = true, help = "List of targets to run")] pub targets: Vec, - #[arg( - long, - help = "Run dependents of the primary targets, as well as dependencies" - )] + #[arg(long, help = "Run dependents of the primary targets")] pub dependents: bool, #[arg( @@ -136,21 +133,34 @@ pub async fn run_target( }; // Generate a dependency graph for all the targets that need to be ran - let mut dep_builder = build_dep_graph(&project_graph); + let mut action_graph_builder = build_action_graph(&project_graph)?; + + // Run dependents for all primary targets + if args.dependents { + action_graph_builder.include_dependents(); + } if let Some(query_input) = &args.query { - dep_builder.set_query(query_input)?; + action_graph_builder.set_query(query_input)?; } // Run targets, optionally based on affected files - let primary_targets = dep_builder.run_targets_by_locator( - target_locators, - if should_run_affected { - Some(&touched_files) - } else { - None - }, - )?; + let mut primary_targets = vec![]; + + for locator in target_locators { + primary_targets.extend( + action_graph_builder + .run_task_by_target_locator( + locator, + if should_run_affected { + Some(&touched_files) + } else { + None + }, + )? + .0, + ); + } if primary_targets.is_empty() { let targets_list = map_list(target_locators, |id| color::label(id)); @@ -183,13 +193,6 @@ pub async fn run_target( )); } - // Run dependents for all primary targets - if args.dependents { - for target in &primary_targets { - dep_builder.run_dependents_for_target(target)?; - } - } - // Process all tasks in the graph let context = ActionContext { affected_only: should_run_affected, @@ -203,7 +206,7 @@ pub async fn run_target( ..ActionContext::default() }; - let dep_graph = dep_builder.build(); + let action_graph = action_graph_builder.build()?; let mut pipeline = Pipeline::new(workspace.to_owned(), project_graph); if let Some(concurrency) = concurrency { @@ -213,7 +216,7 @@ pub async fn run_target( let results = pipeline .bail_on_error() .generate_report("runReport.json") - .run(dep_graph, Some(context)) + .run(action_graph, Some(context)) .await?; pipeline.render_stats(&results, true)?; diff --git a/crates/cli/src/commands/syncs/projects.rs b/crates/cli/src/commands/syncs/projects.rs index a091e2952d8..910f7bd8339 100644 --- a/crates/cli/src/commands/syncs/projects.rs +++ b/crates/cli/src/commands/syncs/projects.rs @@ -1,5 +1,5 @@ use crate::helpers::create_progress_bar; -use moon::{build_dep_graph, generate_project_graph}; +use moon::{build_action_graph, generate_project_graph}; use moon_action_pipeline::Pipeline; use moon_workspace::Workspace; use starbase::{system, SystemResult}; @@ -10,17 +10,17 @@ pub async fn internal_sync(workspace: &mut Workspace) -> SystemResult { let project_graph = generate_project_graph(workspace).await?; let mut project_count = 0; - let mut dep_builder = build_dep_graph(&project_graph); + let mut action_graph_builder = build_action_graph(&project_graph)?; for project in project_graph.get_all_unexpanded() { - dep_builder.sync_project(project)?; + action_graph_builder.sync_project(project)?; project_count += 1; } - let dep_graph = dep_builder.build(); + let action_graph = action_graph_builder.build()?; let mut pipeline = Pipeline::new(workspace.to_owned(), project_graph); - let results = pipeline.run(dep_graph, None).await?; + let results = pipeline.run(action_graph, None).await?; done( format!("Successfully synced {project_count} projects"), diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index dc23eb7d190..87bc399194b 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -15,7 +15,7 @@ use crate::commands::clean::clean; use crate::commands::completions; use crate::commands::docker; use crate::commands::generate::generate; -use crate::commands::graph::{dep::dep_graph, project::project_graph}; +use crate::commands::graph::{action::action_graph, dep::dep_graph, project::project_graph}; use crate::commands::init::init; use crate::commands::migrate; use crate::commands::node; @@ -138,6 +138,7 @@ pub async fn run_cli() -> AppResult { app.startup(systems::load_workspace); match cli.command { + Commands::ActionGraph(args) => app.execute_with_args(action_graph, args), Commands::Bin(args) => app.execute_with_args(bin, args), Commands::Ci(args) => { app.execute(systems::check_for_new_version); diff --git a/crates/cli/tests/action_graph_test.rs b/crates/cli/tests/action_graph_test.rs new file mode 100644 index 00000000000..8e643c07735 --- /dev/null +++ b/crates/cli/tests/action_graph_test.rs @@ -0,0 +1,173 @@ +use moon_test_utils::{ + assert_snapshot, create_sandbox_with_config, get_project_graph_aliases_fixture_configs, + get_tasks_fixture_configs, +}; + +mod action_graph { + use super::*; + + #[test] + fn all_by_default() { + let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); + + let sandbox = create_sandbox_with_config( + "tasks", + Some(workspace_config), + Some(toolchain_config), + Some(tasks_config), + ); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("action-graph").arg("--dot"); + }); + + let dot = assert.output(); + + // Snapshot is not deterministic + assert_eq!(dot.split('\n').count(), 450); + } + + #[test] + fn focused_by_target() { + let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); + + let sandbox = create_sandbox_with_config( + "tasks", + Some(workspace_config), + Some(toolchain_config), + Some(tasks_config), + ); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("ag").arg("--dot").arg("basic:lint"); + }); + + assert_snapshot!(assert.output()); + } + + #[test] + fn focused_by_task_in_cwd() { + let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); + + let sandbox = create_sandbox_with_config( + "tasks", + Some(workspace_config), + Some(toolchain_config), + Some(tasks_config), + ); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("action-graph").arg("--dot").arg("lint"); + cmd.current_dir(sandbox.path().join("basic")); + }); + + assert_snapshot!(assert.output()); + } + + #[test] + fn includes_dependencies_when_focused() { + let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); + + let sandbox = create_sandbox_with_config( + "tasks", + Some(workspace_config), + Some(toolchain_config), + Some(tasks_config), + ); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("action-graph").arg("--dot").arg("chain:e"); + }); + + assert_snapshot!(assert.output()); + } + + #[test] + fn includes_dependents_when_focused() { + let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); + + let sandbox = create_sandbox_with_config( + "tasks", + Some(workspace_config), + Some(toolchain_config), + Some(tasks_config), + ); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("action-graph").arg("--dot").arg("basic:build"); + }); + + assert_snapshot!(assert.output()); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("action-graph") + .arg("--dot") + .arg("--dependents") + .arg("basic:build"); + }); + + assert_snapshot!(assert.output()); + } + + #[test] + fn outputs_json() { + let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); + + let sandbox = create_sandbox_with_config( + "tasks", + Some(workspace_config), + Some(toolchain_config), + Some(tasks_config), + ); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("action-graph").arg("--json").arg("basic:lint"); + }); + + assert_snapshot!(assert.output()); + } + + mod aliases { + use super::*; + + #[test] + fn can_focus_using_an_alias() { + let (workspace_config, toolchain_config, tasks_config) = + get_project_graph_aliases_fixture_configs(); + + let sandbox = create_sandbox_with_config( + "project-graph/aliases", + Some(workspace_config), + Some(toolchain_config), + Some(tasks_config), + ); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("action-graph") + .arg("--dot") + .arg("@scope/pkg-foo:test"); + }); + + assert_snapshot!(assert.output()); + } + + #[test] + fn resolves_aliases_in_task_deps() { + let (workspace_config, toolchain_config, tasks_config) = + get_project_graph_aliases_fixture_configs(); + + let sandbox = create_sandbox_with_config( + "project-graph/aliases", + Some(workspace_config), + Some(toolchain_config), + Some(tasks_config), + ); + + let assert = sandbox.run_moon(|cmd| { + cmd.arg("action-graph").arg("--dot").arg("node:aliasDeps"); + }); + + assert_snapshot!(assert.output()); + } + } +} diff --git a/crates/cli/tests/dep_graph_test.rs b/crates/cli/tests/dep_graph_test.rs deleted file mode 100644 index b4cc902b775..00000000000 --- a/crates/cli/tests/dep_graph_test.rs +++ /dev/null @@ -1,158 +0,0 @@ -use moon_test_utils::{ - assert_snapshot, create_sandbox_with_config, get_project_graph_aliases_fixture_configs, - get_tasks_fixture_configs, -}; - -#[test] -fn all_by_default() { - let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); - - let sandbox = create_sandbox_with_config( - "tasks", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - let assert = sandbox.run_moon(|cmd| { - cmd.arg("dep-graph").arg("--dot"); - }); - - let dot = assert.output(); - - // Snapshot is not deterministic - assert_eq!(dot.split('\n').count(), 568); -} - -#[test] -fn focused_by_target() { - let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); - - let sandbox = create_sandbox_with_config( - "tasks", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - let assert = sandbox.run_moon(|cmd| { - cmd.arg("dep-graph").arg("--dot").arg("basic:lint"); - }); - - assert_snapshot!(assert.output()); -} - -#[test] -fn focused_by_task_in_cwd() { - let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); - - let sandbox = create_sandbox_with_config( - "tasks", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - let assert = sandbox.run_moon(|cmd| { - cmd.arg("dep-graph").arg("--dot").arg("lint"); - cmd.current_dir(sandbox.path().join("basic")); - }); - - assert_snapshot!(assert.output()); -} - -#[test] -fn includes_dependencies_when_focused() { - let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); - - let sandbox = create_sandbox_with_config( - "tasks", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - let assert = sandbox.run_moon(|cmd| { - cmd.arg("dep-graph").arg("--dot").arg("chain:e"); - }); - - assert_snapshot!(assert.output()); -} - -#[test] -fn includes_dependents_when_focused() { - let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); - - let sandbox = create_sandbox_with_config( - "tasks", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - let assert = sandbox.run_moon(|cmd| { - cmd.arg("dep-graph").arg("--dot").arg("basic:build"); - }); - - assert_snapshot!(assert.output()); -} - -#[test] -fn outputs_json() { - let (workspace_config, toolchain_config, tasks_config) = get_tasks_fixture_configs(); - - let sandbox = create_sandbox_with_config( - "tasks", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - let assert = sandbox.run_moon(|cmd| { - cmd.arg("dep-graph").arg("--json").arg("basic:lint"); - }); - - assert_snapshot!(assert.output()); -} - -mod aliases { - use super::*; - - #[test] - fn can_focus_using_an_alias() { - let (workspace_config, toolchain_config, tasks_config) = - get_project_graph_aliases_fixture_configs(); - - let sandbox = create_sandbox_with_config( - "project-graph/aliases", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - let assert = sandbox.run_moon(|cmd| { - cmd.arg("dep-graph").arg("--dot").arg("@scope/pkg-foo:test"); - }); - - assert_snapshot!(assert.output()); - } - - #[test] - fn resolves_aliases_in_task_deps() { - let (workspace_config, toolchain_config, tasks_config) = - get_project_graph_aliases_fixture_configs(); - - let sandbox = create_sandbox_with_config( - "project-graph/aliases", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - let assert = sandbox.run_moon(|cmd| { - cmd.arg("dep-graph").arg("--dot").arg("node:aliasDeps"); - }); - - assert_snapshot!(assert.output()); - } -} diff --git a/crates/cli/tests/snapshots/action_graph_test__action_graph__aliases__can_focus_using_an_alias.snap b/crates/cli/tests/snapshots/action_graph_test__action_graph__aliases__can_focus_using_an_alias.snap new file mode 100644 index 00000000000..c29a6676117 --- /dev/null +++ b/crates/cli/tests/snapshots/action_graph_test__action_graph__aliases__can_focus_using_an_alias.snap @@ -0,0 +1,19 @@ +--- +source: crates/cli/tests/action_graph_test.rs +expression: assert.output() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(18.0.0)" ] + 2 [ label="InstallNodeDeps(18.0.0)" ] + 3 [ label="SyncNodeProject(nodeNameScope)" ] + 4 [ label="RunTask(nodeNameScope:test)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 1 [ ] + 4 -> 2 [ ] + 4 -> 3 [ ] +} + + + diff --git a/crates/cli/tests/snapshots/action_graph_test__action_graph__aliases__resolves_aliases_in_task_deps.snap b/crates/cli/tests/snapshots/action_graph_test__action_graph__aliases__resolves_aliases_in_task_deps.snap new file mode 100644 index 00000000000..4c87d471b7b --- /dev/null +++ b/crates/cli/tests/snapshots/action_graph_test__action_graph__aliases__resolves_aliases_in_task_deps.snap @@ -0,0 +1,33 @@ +--- +source: crates/cli/tests/action_graph_test.rs +expression: assert.output() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(18.0.0)" ] + 2 [ label="InstallNodeDeps(18.0.0)" ] + 3 [ label="SyncNodeProject(node)" ] + 4 [ label="SyncNodeProject(nodeNameScope)" ] + 5 [ label="SyncNodeProject(nodeNameOnly)" ] + 6 [ label="RunTask(node:aliasDeps)" ] + 7 [ label="RunTask(nodeNameScope:standard)" ] + 8 [ label="RunTask(nodeNameOnly:standard)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 4 -> 1 [ ] + 5 -> 1 [ ] + 3 -> 1 [ ] + 3 -> 4 [ ] + 3 -> 5 [ ] + 7 -> 2 [ ] + 7 -> 4 [ ] + 8 -> 2 [ ] + 8 -> 5 [ ] + 6 -> 2 [ ] + 6 -> 3 [ ] + 6 -> 7 [ ] + 6 -> 8 [ ] +} + + + diff --git a/crates/cli/tests/snapshots/action_graph_test__action_graph__focused_by_target.snap b/crates/cli/tests/snapshots/action_graph_test__action_graph__focused_by_target.snap new file mode 100644 index 00000000000..d3ee89ce3a8 --- /dev/null +++ b/crates/cli/tests/snapshots/action_graph_test__action_graph__focused_by_target.snap @@ -0,0 +1,19 @@ +--- +source: crates/cli/tests/action_graph_test.rs +expression: assert.output() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(18.0.0)" ] + 2 [ label="InstallNodeDeps(18.0.0)" ] + 3 [ label="SyncNodeProject(basic)" ] + 4 [ label="RunTask(basic:lint)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 1 [ ] + 4 -> 2 [ ] + 4 -> 3 [ ] +} + + + diff --git a/crates/cli/tests/snapshots/action_graph_test__action_graph__focused_by_task_in_cwd.snap b/crates/cli/tests/snapshots/action_graph_test__action_graph__focused_by_task_in_cwd.snap new file mode 100644 index 00000000000..d3ee89ce3a8 --- /dev/null +++ b/crates/cli/tests/snapshots/action_graph_test__action_graph__focused_by_task_in_cwd.snap @@ -0,0 +1,19 @@ +--- +source: crates/cli/tests/action_graph_test.rs +expression: assert.output() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(18.0.0)" ] + 2 [ label="InstallNodeDeps(18.0.0)" ] + 3 [ label="SyncNodeProject(basic)" ] + 4 [ label="RunTask(basic:lint)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 1 [ ] + 4 -> 2 [ ] + 4 -> 3 [ ] +} + + + diff --git a/crates/cli/tests/snapshots/action_graph_test__action_graph__includes_dependencies_when_focused.snap b/crates/cli/tests/snapshots/action_graph_test__action_graph__includes_dependencies_when_focused.snap new file mode 100644 index 00000000000..cd1c38ff8b7 --- /dev/null +++ b/crates/cli/tests/snapshots/action_graph_test__action_graph__includes_dependencies_when_focused.snap @@ -0,0 +1,23 @@ +--- +source: crates/cli/tests/action_graph_test.rs +expression: assert.output() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(18.0.0)" ] + 2 [ label="InstallNodeDeps(18.0.0)" ] + 3 [ label="SyncNodeProject(chain)" ] + 4 [ label="RunTask(chain:e)" ] + 5 [ label="RunTask(chain:f)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 1 [ ] + 5 -> 2 [ ] + 5 -> 3 [ ] + 4 -> 2 [ ] + 4 -> 3 [ ] + 4 -> 5 [ ] +} + + + diff --git a/crates/cli/tests/snapshots/action_graph_test__action_graph__includes_dependents_when_focused-2.snap b/crates/cli/tests/snapshots/action_graph_test__action_graph__includes_dependents_when_focused-2.snap new file mode 100644 index 00000000000..476525f957f --- /dev/null +++ b/crates/cli/tests/snapshots/action_graph_test__action_graph__includes_dependents_when_focused-2.snap @@ -0,0 +1,83 @@ +--- +source: crates/cli/tests/action_graph_test.rs +expression: assert.output() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(18.0.0)" ] + 2 [ label="InstallNodeDeps(18.0.0)" ] + 3 [ label="SyncNodeProject(basic)" ] + 4 [ label="RunTask(basic:build)" ] + 5 [ label="SyncNodeProject(buildA)" ] + 6 [ label="SyncNodeProject(buildC)" ] + 7 [ label="SyncNodeProject(noTasks)" ] + 8 [ label="RunTask(buildA:build)" ] + 9 [ label="RunTask(buildC:build)" ] + 10 [ label="SetupSystemTool" ] + 11 [ label="SyncSystemProject(scopeDeps)" ] + 12 [ label="SyncNodeProject(buildB)" ] + 13 [ label="RunTask(scopeDeps:build)" ] + 14 [ label="RunTask(buildB:build)" ] + 15 [ label="RunTask(scopeDeps:buildNoDupes)" ] + 16 [ label="SyncSystemProject(mergeAllStrategies)" ] + 17 [ label="RunTask(mergeAllStrategies:standard)" ] + 18 [ label="SyncSystemProject(mergePrepend)" ] + 19 [ label="RunTask(mergePrepend:standard)" ] + 20 [ label="SyncSystemProject(mergeReplace)" ] + 21 [ label="RunTask(mergeReplace:standard)" ] + 22 [ label="SyncSystemProject(mergeAppend)" ] + 23 [ label="RunTask(mergeAppend:standard)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 1 [ ] + 4 -> 2 [ ] + 4 -> 3 [ ] + 6 -> 1 [ ] + 7 -> 1 [ ] + 5 -> 1 [ ] + 5 -> 6 [ ] + 5 -> 3 [ ] + 5 -> 7 [ ] + 9 -> 2 [ ] + 9 -> 6 [ ] + 10 -> 0 [ ] + 12 -> 1 [ ] + 11 -> 10 [ ] + 11 -> 7 [ ] + 11 -> 5 [ ] + 11 -> 12 [ ] + 11 -> 6 [ ] + 14 -> 2 [ ] + 14 -> 12 [ ] + 15 -> 11 [ ] + 15 -> 8 [ ] + 15 -> 14 [ ] + 15 -> 9 [ ] + 16 -> 10 [ ] + 16 -> 12 [ ] + 17 -> 16 [ ] + 17 -> 14 [ ] + 18 -> 10 [ ] + 18 -> 12 [ ] + 19 -> 18 [ ] + 19 -> 14 [ ] + 20 -> 10 [ ] + 20 -> 12 [ ] + 21 -> 20 [ ] + 21 -> 14 [ ] + 22 -> 10 [ ] + 22 -> 12 [ ] + 23 -> 22 [ ] + 23 -> 14 [ ] + 13 -> 11 [ ] + 13 -> 8 [ ] + 13 -> 14 [ ] + 13 -> 9 [ ] + 8 -> 2 [ ] + 8 -> 5 [ ] + 8 -> 4 [ ] + 8 -> 9 [ ] +} + + + diff --git a/crates/cli/tests/snapshots/action_graph_test__action_graph__includes_dependents_when_focused.snap b/crates/cli/tests/snapshots/action_graph_test__action_graph__includes_dependents_when_focused.snap new file mode 100644 index 00000000000..d9e4c9d82eb --- /dev/null +++ b/crates/cli/tests/snapshots/action_graph_test__action_graph__includes_dependents_when_focused.snap @@ -0,0 +1,19 @@ +--- +source: crates/cli/tests/action_graph_test.rs +expression: assert.output() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(18.0.0)" ] + 2 [ label="InstallNodeDeps(18.0.0)" ] + 3 [ label="SyncNodeProject(basic)" ] + 4 [ label="RunTask(basic:build)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 1 [ ] + 4 -> 2 [ ] + 4 -> 3 [ ] +} + + + diff --git a/crates/cli/tests/snapshots/action_graph_test__action_graph__outputs_json.snap b/crates/cli/tests/snapshots/action_graph_test__action_graph__outputs_json.snap new file mode 100644 index 00000000000..e8b1e3d5858 --- /dev/null +++ b/crates/cli/tests/snapshots/action_graph_test__action_graph__outputs_json.snap @@ -0,0 +1,7 @@ +--- +source: crates/cli/tests/action_graph_test.rs +expression: assert.output() +--- +{"nodes":[{"id":0,"label":"SyncWorkspace"},{"id":2,"label":"InstallNodeDeps(18.0.0)"},{"id":4,"label":"RunTask(basic:lint)"},{"id":1,"label":"SetupNodeTool(18.0.0)"},{"id":3,"label":"SyncNodeProject(basic)"}],"edges":[{"id":"1 -> 0","label":"","source":1,"target":0},{"id":"2 -> 1","label":"","source":2,"target":1},{"id":"3 -> 1","label":"","source":3,"target":1},{"id":"4 -> 2","label":"","source":4,"target":2},{"id":"4 -> 3","label":"","source":4,"target":3}]} + + diff --git a/crates/cli/tests/snapshots/dep_graph_test__aliases__can_focus_using_an_alias.snap b/crates/cli/tests/snapshots/dep_graph_test__aliases__can_focus_using_an_alias.snap deleted file mode 100644 index f44262d50d1..00000000000 --- a/crates/cli/tests/snapshots/dep_graph_test__aliases__can_focus_using_an_alias.snap +++ /dev/null @@ -1,27 +0,0 @@ ---- -source: crates/cli/tests/dep_graph_test.rs -expression: assert.output() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(nodeNameScope)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(nodeNameScope:test)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="SyncNodeProject(node)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 6 [ label="SyncNodeProject(nodeNameOnly)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 7 [ label="RunTarget(node:test)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] - 5 -> 1 [ arrowhead=box, arrowtail=box] - 5 -> 3 [ arrowhead=box, arrowtail=box] - 6 -> 1 [ arrowhead=box, arrowtail=box] - 5 -> 6 [ arrowhead=box, arrowtail=box] - 7 -> 2 [ arrowhead=box, arrowtail=box] - 7 -> 5 [ arrowhead=box, arrowtail=box] -} - - - diff --git a/crates/cli/tests/snapshots/dep_graph_test__aliases__resolves_aliases_in_task_deps.snap b/crates/cli/tests/snapshots/dep_graph_test__aliases__resolves_aliases_in_task_deps.snap deleted file mode 100644 index d186394e820..00000000000 --- a/crates/cli/tests/snapshots/dep_graph_test__aliases__resolves_aliases_in_task_deps.snap +++ /dev/null @@ -1,32 +0,0 @@ ---- -source: crates/cli/tests/dep_graph_test.rs -expression: assert.output() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(node)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="SyncNodeProject(nodeNameScope)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="SyncNodeProject(nodeNameOnly)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 6 [ label="RunTarget(node:aliasDeps)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 7 [ label="RunTarget(nodeNameScope:standard)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 8 [ label="RunTarget(nodeNameOnly:standard)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 4 [ arrowhead=box, arrowtail=box] - 5 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 5 [ arrowhead=box, arrowtail=box] - 6 -> 2 [ arrowhead=box, arrowtail=box] - 6 -> 3 [ arrowhead=box, arrowtail=box] - 7 -> 2 [ arrowhead=box, arrowtail=box] - 7 -> 4 [ arrowhead=box, arrowtail=box] - 8 -> 2 [ arrowhead=box, arrowtail=box] - 8 -> 5 [ arrowhead=box, arrowtail=box] - 6 -> 7 [ arrowhead=box, arrowtail=box] - 6 -> 8 [ arrowhead=box, arrowtail=box] -} - - - diff --git a/crates/cli/tests/snapshots/dep_graph_test__focused_by_target.snap b/crates/cli/tests/snapshots/dep_graph_test__focused_by_target.snap deleted file mode 100644 index 97327f0d549..00000000000 --- a/crates/cli/tests/snapshots/dep_graph_test__focused_by_target.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: crates/cli/tests/dep_graph_test.rs -expression: assert.output() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(basic)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(basic:lint)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] -} - - - diff --git a/crates/cli/tests/snapshots/dep_graph_test__focused_by_task_in_cwd.snap b/crates/cli/tests/snapshots/dep_graph_test__focused_by_task_in_cwd.snap deleted file mode 100644 index 97327f0d549..00000000000 --- a/crates/cli/tests/snapshots/dep_graph_test__focused_by_task_in_cwd.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: crates/cli/tests/dep_graph_test.rs -expression: assert.output() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(basic)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(basic:lint)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] -} - - - diff --git a/crates/cli/tests/snapshots/dep_graph_test__includes_dependencies_when_focused.snap b/crates/cli/tests/snapshots/dep_graph_test__includes_dependencies_when_focused.snap deleted file mode 100644 index 3dd11d10b8c..00000000000 --- a/crates/cli/tests/snapshots/dep_graph_test__includes_dependencies_when_focused.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/cli/tests/dep_graph_test.rs -expression: assert.output() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(chain)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(chain:e)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="RunTarget(chain:f)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] - 5 -> 2 [ arrowhead=box, arrowtail=box] - 5 -> 3 [ arrowhead=box, arrowtail=box] - 4 -> 5 [ arrowhead=box, arrowtail=box] -} - - - diff --git a/crates/cli/tests/snapshots/dep_graph_test__includes_dependents_when_focused.snap b/crates/cli/tests/snapshots/dep_graph_test__includes_dependents_when_focused.snap deleted file mode 100644 index e0cf39256be..00000000000 --- a/crates/cli/tests/snapshots/dep_graph_test__includes_dependents_when_focused.snap +++ /dev/null @@ -1,35 +0,0 @@ ---- -source: crates/cli/tests/dep_graph_test.rs -expression: assert.output() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(18.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(basic)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(basic:build)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="SyncNodeProject(buildA)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 6 [ label="SyncNodeProject(buildC)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 7 [ label="SyncNodeProject(noTasks)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 8 [ label="RunTarget(buildA:build)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 9 [ label="RunTarget(buildC:build)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] - 5 -> 1 [ arrowhead=box, arrowtail=box] - 6 -> 1 [ arrowhead=box, arrowtail=box] - 5 -> 6 [ arrowhead=box, arrowtail=box] - 5 -> 3 [ arrowhead=box, arrowtail=box] - 7 -> 1 [ arrowhead=box, arrowtail=box] - 5 -> 7 [ arrowhead=box, arrowtail=box] - 8 -> 2 [ arrowhead=box, arrowtail=box] - 8 -> 5 [ arrowhead=box, arrowtail=box] - 9 -> 2 [ arrowhead=box, arrowtail=box] - 9 -> 6 [ arrowhead=box, arrowtail=box] - 8 -> 4 [ arrowhead=box, arrowtail=box] - 8 -> 9 [ arrowhead=box, arrowtail=box] -} - - - diff --git a/crates/cli/tests/snapshots/dep_graph_test__outputs_json.snap b/crates/cli/tests/snapshots/dep_graph_test__outputs_json.snap deleted file mode 100644 index 5758c460cc9..00000000000 --- a/crates/cli/tests/snapshots/dep_graph_test__outputs_json.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/cli/tests/dep_graph_test.rs -expression: assert.output() ---- -{"nodes":[{"id":2,"label":"InstallNodeDeps(18.0.0)"},{"id":4,"label":"RunTarget(basic:lint)"},{"id":1,"label":"SetupNodeTool(18.0.0)"},{"id":3,"label":"SyncNodeProject(basic)"}],"edges":[{"id":"2 -> 1","label":"","source":2,"target":1},{"id":"3 -> 1","label":"","source":3,"target":1},{"id":"4 -> 2","label":"","source":4,"target":2},{"id":"4 -> 3","label":"","source":4,"target":3}]} - - diff --git a/crates/cli/tests/snapshots/run_node_test__inherits_moon_env_vars.snap b/crates/cli/tests/snapshots/run_node_test__inherits_moon_env_vars.snap index d0f06aad674..ffc9bdd73a3 100644 --- a/crates/cli/tests/snapshots/run_node_test__inherits_moon_env_vars.snap +++ b/crates/cli/tests/snapshots/run_node_test__inherits_moon_env_vars.snap @@ -12,7 +12,7 @@ MOON_PROJECT_ID=node MOON_PROJECT_ROOT=/base MOON_PROJECT_SNAPSHOT=/.moon/cache/states/node/snapshot.json MOON_PROJECT_SOURCE=base -MOON_RUNNING_ACTION=run-target +MOON_RUNNING_ACTION=run-task MOON_TARGET=node:envVarsMoon MOON_TOOLCHAIN_DIR=~/.proto MOON_WORKING_DIR= diff --git a/crates/cli/tests/snapshots/run_rust_test__inherits_moon_env_vars.snap b/crates/cli/tests/snapshots/run_rust_test__inherits_moon_env_vars.snap index 357a9e1044c..4cdfd4a9bd7 100644 --- a/crates/cli/tests/snapshots/run_rust_test__inherits_moon_env_vars.snap +++ b/crates/cli/tests/snapshots/run_rust_test__inherits_moon_env_vars.snap @@ -12,7 +12,7 @@ MOON_PROJECT_ID=rust MOON_PROJECT_ROOT= MOON_PROJECT_SNAPSHOT=/.moon/cache/states/rust/snapshot.json MOON_PROJECT_SOURCE=. -MOON_RUNNING_ACTION=run-target +MOON_RUNNING_ACTION=run-task MOON_TARGET=rust:envVarsMoon MOON_TOOLCHAIN_DIR=~/.proto MOON_WORKING_DIR= diff --git a/crates/cli/tests/snapshots/run_system_test__unix__inherits_moon_env_vars.snap b/crates/cli/tests/snapshots/run_system_test__unix__inherits_moon_env_vars.snap index a7b53256da3..e207b53c5ba 100644 --- a/crates/cli/tests/snapshots/run_system_test__unix__inherits_moon_env_vars.snap +++ b/crates/cli/tests/snapshots/run_system_test__unix__inherits_moon_env_vars.snap @@ -11,7 +11,7 @@ MOON_PROJECT_ID=unix MOON_PROJECT_ROOT=/unix MOON_PROJECT_SNAPSHOT=/.moon/cache/states/unix/snapshot.json MOON_PROJECT_SOURCE=unix -MOON_RUNNING_ACTION=run-target +MOON_RUNNING_ACTION=run-task MOON_TARGET=unix:envVarsMoon MOON_TOOLCHAIN_DIR=~/.proto MOON_WORKING_DIR= diff --git a/crates/cli/tests/snapshots/run_test__errors_for_cycle_in_task_deps.snap b/crates/cli/tests/snapshots/run_test__errors_for_cycle_in_task_deps.snap index bbac63af8b6..c021624872c 100644 --- a/crates/cli/tests/snapshots/run_test__errors_for_cycle_in_task_deps.snap +++ b/crates/cli/tests/snapshots/run_test__errors_for_cycle_in_task_deps.snap @@ -2,7 +2,7 @@ source: crates/cli/tests/run_test.rs expression: assert.output() --- -Error: × A dependency cycle has been detected for RunTarget(depsC:taskCycle). +Error: × A dependency cycle has been detected for RunTask(depsC:taskCycle). diff --git a/crates/core/action-pipeline/Cargo.toml b/crates/core/action-pipeline/Cargo.toml index aa1e4647436..dc6691a25e9 100644 --- a/crates/core/action-pipeline/Cargo.toml +++ b/crates/core/action-pipeline/Cargo.toml @@ -4,20 +4,13 @@ version = "0.1.0" edition = "2021" publish = false -[lib] -bench = false - -[[bench]] -name = "pipeline_benchmark" -harness = false - [dependencies] moon_action = { path = "../action" } moon_action_context = { path = "../action-context" } +moon_action_graph = { path = "../../../nextgen/action-graph" } moon_actions = { path = "../actions" } moon_api = { path = "../../../nextgen/api" } moon_cache_item = { path = "../../../nextgen/cache-item" } -moon_dep_graph = { path = "../dep-graph" } moon_emitter = { path = "../emitter" } moon_hash = { path = "../../../nextgen/hash" } moon_logger = { path = "../logger" } diff --git a/crates/core/action-pipeline/benches/pipeline_benchmark.rs b/crates/core/action-pipeline/benches/pipeline_benchmark.rs deleted file mode 100644 index 9025569d393..00000000000 --- a/crates/core/action-pipeline/benches/pipeline_benchmark.rs +++ /dev/null @@ -1,63 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use moon::{build_dep_graph, generate_project_graph, load_workspace_from}; -use moon_action_pipeline::Pipeline; -use moon_dep_graph::DepGraph; -use moon_project_graph::ProjectGraph; -use moon_target::Target; -use moon_test_utils::{create_sandbox_with_config, get_cases_fixture_configs}; -use moon_workspace::Workspace; - -fn generate_dep_graph(_workspace: &Workspace, project_graph: &ProjectGraph) -> DepGraph { - let mut dep_graph = build_dep_graph(project_graph); - - dep_graph - .run_target(Target::parse("base:standard").unwrap(), None) - .unwrap(); - - dep_graph - .run_target(Target::parse("depsA:dependencyOrder").unwrap(), None) - .unwrap(); - - dep_graph - .run_target(Target::parse("outputs:withDeps").unwrap(), None) - .unwrap(); - - dep_graph - .run_target(Target::parse("passthroughArgs:c").unwrap(), None) - .unwrap(); - - dep_graph - .run_target(Target::parse("targetScopeB:self").unwrap(), None) - .unwrap(); - - dep_graph.build() -} - -pub fn pipeline_benchmark(c: &mut Criterion) { - let (workspace_config, toolchain_config, tasks_config) = get_cases_fixture_configs(); - - let sandbox = create_sandbox_with_config( - "cases", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - c.bench_function("pipeline", |b| { - b.iter(|| async { - let mut workspace = load_workspace_from(sandbox.path()).await.unwrap(); - let project_graph = generate_project_graph(&mut workspace).await.unwrap(); - let dep_graph = generate_dep_graph(&workspace, &project_graph); - - black_box( - Pipeline::new(workspace, project_graph) - .run(dep_graph, None) - .await - .unwrap(), - ); - }) - }); -} - -criterion_group!(pipeline, pipeline_benchmark); -criterion_main!(pipeline); diff --git a/crates/core/action-pipeline/src/actions/install_deps.rs b/crates/core/action-pipeline/src/actions/install_deps.rs index c0b7eca3d15..117bedabaf8 100644 --- a/crates/core/action-pipeline/src/actions/install_deps.rs +++ b/crates/core/action-pipeline/src/actions/install_deps.rs @@ -1,3 +1,4 @@ +use super::should_skip_action_matching; use miette::IntoDiagnostic; use moon_action::{Action, ActionStatus}; use moon_action_context::ActionContext; @@ -63,6 +64,15 @@ pub async fn install_deps( return Ok(ActionStatus::Skipped); } + if should_skip_action_matching("MOON_SKIP_INSTALL_DEPS", &install_key) { + debug!( + target: LOG_TARGET, + "Skipping install deps action because MOON_SKIP_INSTALL_DEPS is set", + ); + + return Ok(ActionStatus::Skipped); + } + // When the install is happening as a child process of another install, avoid recursion if env::var("MOON_INSTALLING_DEPS").unwrap_or_default() == install_key { debug!( diff --git a/crates/core/action-pipeline/src/actions/mod.rs b/crates/core/action-pipeline/src/actions/mod.rs index d2b07d67dd5..b05f60452e2 100644 --- a/crates/core/action-pipeline/src/actions/mod.rs +++ b/crates/core/action-pipeline/src/actions/mod.rs @@ -1,5 +1,74 @@ +use std::env; + pub mod install_deps; -pub mod run_target; +pub mod run_task; pub mod setup_tool; pub mod sync_project; pub mod sync_workspace; + +pub fn should_skip_action(key: &str) -> bool { + env::var(key).is_ok_and(|v| matches_pattern(&v, "")) +} + +pub fn should_skip_action_matching>(key: &str, pattern: V) -> bool { + env::var(key).is_ok_and(|v| matches_pattern(&v, pattern.as_ref())) +} + +fn matches_pattern(value: &str, pattern: &str) -> bool { + if value.contains(',') { + return value.split(',').any(|v| matches_pattern(v, pattern)); + } + + let pattern = pattern.to_lowercase(); + + if value == "*" || value == "*:*" || value == "true" || value == pattern { + return true; + } + + if pattern.contains(':') { + let mut left = pattern.split(':'); + let mut right = value.split(':'); + + return match ((left.next(), left.next()), (right.next(), right.next())) { + #[allow(clippy::nonminimal_bool)] + ((Some(a1), Some(a2)), (Some(b1), Some(b2))) => { + // foo:bar == foo:bar + a1 == b1 && a2 == b2 || + // foo:bar == foo:* + a1 == b1 && b2 == "*" || + // foo:bar == *:bar + a2 == b2 && b1 == "*" + } + ((Some(a1), Some(_)), (Some(b1), None)) => { + // foo:bar == foo + a1 == b1 + } + _ => false, + }; + } + + false +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn patterns() { + assert!(matches_pattern("*", "")); + assert!(matches_pattern("*:*", "")); + assert!(matches_pattern("true", "")); + + assert!(matches_pattern("*", "node:20.0.0")); + assert!(matches_pattern("node:*", "node:20.0.0")); + assert!(matches_pattern("node", "node:20.0.0")); + assert!(matches_pattern("node:20.0.0", "node:20.0.0")); + assert!(!matches_pattern("rust", "node:20.0.0")); + assert!(!matches_pattern("node:19.0.0", "node:20.0.0")); + + assert!(matches_pattern("foo,bar", "foo")); + assert!(matches_pattern("foo,bar", "bar")); + assert!(!matches_pattern("foo,bar", "baz")); + } +} diff --git a/crates/core/action-pipeline/src/actions/run_target.rs b/crates/core/action-pipeline/src/actions/run_task.rs similarity index 96% rename from crates/core/action-pipeline/src/actions/run_target.rs rename to crates/core/action-pipeline/src/actions/run_task.rs index 3f256a457ba..ec6c9e1fa55 100644 --- a/crates/core/action-pipeline/src/actions/run_target.rs +++ b/crates/core/action-pipeline/src/actions/run_task.rs @@ -13,9 +13,9 @@ use std::env; use std::sync::Arc; use tokio::sync::RwLock; -const LOG_TARGET: &str = "moon:action:run-target"; +const LOG_TARGET: &str = "moon:action:run-task"; -pub async fn run_target( +pub async fn run_task( action: &mut Action, context: Arc>, emitter: Arc>, @@ -24,7 +24,7 @@ pub async fn run_target( target: &Target, runtime: &Runtime, ) -> miette::Result { - env::set_var("MOON_RUNNING_ACTION", "run-target"); + env::set_var("MOON_RUNNING_ACTION", "run-task"); let emitter = emitter.read().await; let workspace = workspace.read().await; @@ -33,7 +33,7 @@ pub async fn run_target( debug!( target: LOG_TARGET, - "Running target {}", + "Running task {}", color::label(&task.target) ); diff --git a/crates/core/action-pipeline/src/actions/setup_tool.rs b/crates/core/action-pipeline/src/actions/setup_tool.rs index f2b4582f9b0..f2332fe8a68 100644 --- a/crates/core/action-pipeline/src/actions/setup_tool.rs +++ b/crates/core/action-pipeline/src/actions/setup_tool.rs @@ -1,3 +1,4 @@ +use super::should_skip_action_matching; use moon_action::{Action, ActionStatus}; use moon_action_context::ActionContext; use moon_cache_item::cache_item; @@ -38,6 +39,18 @@ pub async fn setup_tool( runtime.label() ); + if should_skip_action_matching( + "MOON_SKIP_SETUP_TOOL", + format!("{}:{}", runtime, runtime.requirement), + ) { + debug!( + target: LOG_TARGET, + "Skipping setup tool action because MOON_SKIP_SETUP_TOOL is set", + ); + + return Ok(ActionStatus::Skipped); + } + let workspace = workspace.write().await; let context = context.read().await; diff --git a/crates/core/action-pipeline/src/actions/sync_project.rs b/crates/core/action-pipeline/src/actions/sync_project.rs index 9f02277f7b7..ae55242425d 100644 --- a/crates/core/action-pipeline/src/actions/sync_project.rs +++ b/crates/core/action-pipeline/src/actions/sync_project.rs @@ -1,3 +1,4 @@ +use super::should_skip_action_matching; use moon_action::{Action, ActionStatus}; use moon_action_context::ActionContext; use moon_logger::debug; @@ -34,6 +35,15 @@ pub async fn sync_project( color::id(&project.id) ); + if should_skip_action_matching("MOON_SKIP_SYNC_PROJECT", &project.id) { + debug!( + target: LOG_TARGET, + "Skipping sync project action because MOON_SKIP_SYNC_PROJECT is set", + ); + + return Ok(ActionStatus::Skipped); + } + // Create a snapshot for tasks to reference workspace .cache_engine diff --git a/crates/core/action-pipeline/src/actions/sync_workspace.rs b/crates/core/action-pipeline/src/actions/sync_workspace.rs index fef41499c47..6c1b17a7e0b 100644 --- a/crates/core/action-pipeline/src/actions/sync_workspace.rs +++ b/crates/core/action-pipeline/src/actions/sync_workspace.rs @@ -1,3 +1,4 @@ +use super::should_skip_action; use moon_action::{Action, ActionStatus}; use moon_action_context::ActionContext; use moon_actions::{sync_codeowners, sync_vcs_hooks}; @@ -28,6 +29,15 @@ pub async fn sync_workspace( debug!(target: LOG_TARGET, "Syncing workspace"); + if should_skip_action("MOON_SKIP_SYNC_WORKSPACE") { + debug!( + target: LOG_TARGET, + "Skipping sync workspace action because MOON_SKIP_SYNC_WORKSPACE is set", + ); + + return Ok(ActionStatus::Skipped); + } + if workspace.config.codeowners.sync_on_run { debug!( target: LOG_TARGET, diff --git a/crates/core/action-pipeline/src/estimator.rs b/crates/core/action-pipeline/src/estimator.rs index dc823417a19..86daf2561ec 100644 --- a/crates/core/action-pipeline/src/estimator.rs +++ b/crates/core/action-pipeline/src/estimator.rs @@ -67,12 +67,12 @@ impl Estimator { } match node { - ActionNode::SetupTool(_) - | ActionNode::InstallDeps(_) - | ActionNode::InstallProjectDeps(_, _) => { + ActionNode::SetupTool { .. } + | ActionNode::InstallDeps { .. } + | ActionNode::InstallProjectDeps { .. } => { install_duration += task_duration; } - ActionNode::RunTarget(_, target) => { + ActionNode::RunTask { target, .. } => { let task_id = target.task_id.to_string(); if let Some(task) = tasks.get_mut(&task_id) { diff --git a/crates/core/action-pipeline/src/pipeline.rs b/crates/core/action-pipeline/src/pipeline.rs index a98765d0f2d..c5816ba92fb 100644 --- a/crates/core/action-pipeline/src/pipeline.rs +++ b/crates/core/action-pipeline/src/pipeline.rs @@ -7,18 +7,20 @@ use crate::subscribers::moonbase::MoonbaseSubscriber; use console::Term; use moon_action::{Action, ActionNode, ActionStatus}; use moon_action_context::ActionContext; -use moon_dep_graph::DepGraph; +use moon_action_graph::ActionGraph; use moon_emitter::{Emitter, Event}; -use moon_logger::{debug, error, trace}; +use moon_logger::{debug, error, trace, warn}; use moon_notifier::WebhooksSubscriber; use moon_project_graph::ProjectGraph; use moon_terminal::{label_checkpoint, label_to_the_moon, Checkpoint, ExtendedTerm}; use moon_utils::{is_ci, is_test_env, time}; use moon_workspace::Workspace; use starbase_styles::color; +use std::mem; use std::sync::Arc; use std::time::{Duration, Instant}; -use tokio::sync::{RwLock, Semaphore}; +use tokio::sync::{RwLock, RwLockReadGuard, Semaphore}; +use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; const LOG_TARGET: &str = "moon:action-pipeline"; @@ -68,7 +70,7 @@ impl Pipeline { pub async fn run( &mut self, - dep_graph: DepGraph, + action_graph: ActionGraph, context: Option, ) -> miette::Result { let start = Instant::now(); @@ -78,21 +80,14 @@ impl Pipeline { )); let workspace = Arc::clone(&self.workspace); let project_graph = Arc::clone(&self.project_graph); - let mut results: ActionResults = vec![]; - let mut passed_count = 0; - let mut cached_count = 0; - let mut failed_count = 0; - // Queue actions in topological order that need to be processed, - // grouped into batches based on dependency requirements - let total_actions_count = dep_graph.get_node_count(); - let batches = dep_graph.sort_batched_topological()?; - let batches_count = batches.len(); + // Queue actions in topological order that need to be processed + let total_actions_count = action_graph.get_node_count(); let local_emitter = emitter.read().await; debug!( target: LOG_TARGET, - "Running {} actions across {} batches", total_actions_count, batches_count + "Running {} actions", total_actions_count ); local_emitter @@ -117,121 +112,173 @@ impl Pipeline { self.concurrency.unwrap_or_else(num_cpus::get), )); - for (b, batch) in batches.into_iter().enumerate() { - let batch_index = b + 1; - let batch_target_name = format!("{LOG_TARGET}:batch:{batch_index}"); - let actions_count = batch.len(); - let mut action_handles = vec![]; + let mut results: ActionResults = vec![]; + let mut action_handles = vec![]; + let mut persistent_nodes = vec![]; + let mut action_graph_iter = action_graph.try_iter()?; + + action_graph_iter.monitor_completed(); - trace!( - target: &batch_target_name, - "Running {} actions in batch {}", - actions_count, - batch_index - ); + while action_graph_iter.has_pending() { + let Some(node_index) = action_graph_iter.next() else { + // Nothing new to run since they're waiting on currently + // running actions, so exhaust the current list + self.run_handles(mem::take(&mut action_handles), &mut results, &local_emitter) + .await?; - for (i, node_index) in batch.into_iter().enumerate() { - let action_index = i + 1; - - if let Some(node) = dep_graph.get_node_from_index(&node_index) { - let context_clone = Arc::clone(&context); - let emitter_clone = Arc::clone(&emitter); - let workspace_clone = Arc::clone(&workspace); - let project_graph_clone = Arc::clone(&project_graph); - let cancel_token_clone = cancel_token.clone(); - - let mut action = Action::new(node.to_owned()); - action.log_target = format!("{batch_target_name}:{action_index}"); - - let Ok(permit) = semaphore.clone().acquire_owned().await else { - break; // Should error? - }; - - action_handles.push(tokio::spawn(async move { - let result = tokio::select! { - biased; - - _ = cancel_token_clone.cancelled() => { - Err(PipelineError::Aborted("Received ctrl + c, shutting down".into()).into()) - } - res = process_action( - action, - context_clone, - emitter_clone, - workspace_clone, - project_graph_clone, - ) => res - }; - - drop(permit); - - result - })); - } else { - return Err(PipelineError::UnknownActionNode.into()); - } + continue; + }; + + let Some(node) = action_graph.get_node_from_index(&node_index) else { + warn!( + target: LOG_TARGET, + "Received a graph index {} with no associated node, unable to process", + node_index.index() + ); + + continue; + }; + + // Run these later as a parallel batch + if node.is_persistent() { + trace!( + target: LOG_TARGET, + // index = node_index.index(), + "Marking action {} as persistent, will defer run", + node_index.index() + ); + + // Must mark as completed otherwise the loop hangs + action_graph_iter.mark_completed(node_index); + + persistent_nodes.push(node_index); + + continue; } - // Wait for all actions in this batch to complete - let mut abort_error: Option = None; - let mut show_abort_log = false; + let context_clone = Arc::clone(&context); + let emitter_clone = Arc::clone(&emitter); + let workspace_clone = Arc::clone(&workspace); + let project_graph_clone = Arc::clone(&project_graph); + let cancel_token_clone = cancel_token.clone(); + let sender = action_graph_iter.sender.clone(); + let mut action = Action::new(node.to_owned()); + action.node_index = node_index.index(); + + let Ok(permit) = semaphore.clone().acquire_owned().await else { + continue; // Should error? + }; - for handle in action_handles { - if abort_error.is_some() { - if !handle.is_finished() { - handle.abort(); + action_handles.push(tokio::spawn(async move { + let result = tokio::select! { + biased; + + _ = cancel_token_clone.cancelled() => { + Err(PipelineError::Aborted("Received ctrl + c, shutting down".into()).into()) } - } else { - match handle.await { - Ok(Ok(mut result)) => { - if result.has_failed() { - failed_count += 1; - } else if result.was_cached() { - cached_count += 1; - } else { - passed_count += 1; - } - - show_abort_log = result.should_abort(); - - if self.bail && result.should_bail() || result.should_abort() { - abort_error = Some(result.get_error()); - } else { - results.push(result); - } - } - Ok(Err(error)) => { - abort_error = Some(error); - } - _ => { - abort_error = - Some(PipelineError::Aborted("Unknown error!".into()).into()); - } - }; + res = process_action( + action, + context_clone, + emitter_clone, + workspace_clone, + project_graph_clone, + ) => res + }; + + if let Ok(action) = &result { + let _ = sender.send(action.node_index); } + + drop(permit); + + result + })); + + // Run this in isolation by exhausting the current list of handles + if node.is_interactive() || semaphore.available_permits() == 0 { + self.run_handles(mem::take(&mut action_handles), &mut results, &local_emitter) + .await?; } + } - if let Some(abort_error) = abort_error { - if show_abort_log { - error!( - target: &batch_target_name, - "Encountered a critical error, aborting the action pipeline" - ); + if !persistent_nodes.is_empty() { + trace!( + target: LOG_TARGET, + "Running {} persistent actions", + persistent_nodes.len(), + ); + } + + for node_index in persistent_nodes { + let Some(node) = action_graph.get_node_from_index(&node_index) else { + warn!( + target: LOG_TARGET, + "Received a graph index {} with no associated node, unable to process", + node_index.index() + ); + + continue; + }; + + let context_clone = Arc::clone(&context); + let emitter_clone = Arc::clone(&emitter); + let workspace_clone = Arc::clone(&workspace); + let project_graph_clone = Arc::clone(&project_graph); + let cancel_token_clone = cancel_token.clone(); + let sender = action_graph_iter.sender.clone(); + let mut action = Action::new(node.to_owned()); + action.node_index = node_index.index(); + + let Ok(permit) = semaphore.clone().acquire_owned().await else { + continue; // Should error? + }; + + action_handles.push(tokio::spawn(async move { + let result = tokio::select! { + biased; + + _ = cancel_token_clone.cancelled() => { + Err(PipelineError::Aborted("Received ctrl + c, shutting down".into()).into()) + } + res = process_action( + action, + context_clone, + emitter_clone, + workspace_clone, + project_graph_clone, + ) => res + }; + + if let Ok(action) = &result { + let _ = sender.send(action.node_index); } - local_emitter - .emit(Event::PipelineAborted { - error: abort_error.to_string(), - }) - .await?; + drop(permit); - return Err(abort_error); - } + result + })); } + // Run any remaining actions + self.run_handles(action_handles, &mut results, &local_emitter) + .await?; + let duration = start.elapsed(); let estimate = Estimator::calculate(&results, duration); let context = Arc::into_inner(context).unwrap().into_inner(); + let mut passed_count = 0; + let mut cached_count = 0; + let mut failed_count = 0; + + for result in &results { + if result.has_failed() { + failed_count += 1; + } else if result.was_cached() { + cached_count += 1; + } else { + passed_count += 1; + } + } debug!( target: LOG_TARGET, @@ -256,6 +303,58 @@ impl Pipeline { Ok(results) } + async fn run_handles( + &self, + handles: Vec>>, + results: &mut ActionResults, + emitter: &RwLockReadGuard<'_, Emitter>, + ) -> miette::Result<()> { + let mut abort_error: Option = None; + let mut show_abort_log = false; + + for handle in handles { + if abort_error.is_some() { + if !handle.is_finished() { + handle.abort(); + } + } else { + match handle.await { + Ok(Ok(mut result)) => { + show_abort_log = result.should_abort(); + + if self.bail && result.should_bail() || result.should_abort() { + abort_error = Some(result.get_error()); + } else { + results.push(result); + } + } + Ok(Err(error)) => { + abort_error = Some(error); + } + _ => { + abort_error = Some(PipelineError::Aborted("Unknown error!".into()).into()); + } + }; + } + } + + if let Some(abort_error) = abort_error { + if show_abort_log { + error!("Encountered a critical error, aborting the action pipeline"); + } + + emitter + .emit(Event::PipelineAborted { + error: abort_error.to_string(), + }) + .await?; + + return Err(abort_error); + } + + Ok(()) + } + pub fn render_summary(&self, results: &ActionResults) -> miette::Result<()> { let term = Term::buffered_stdout(); term.line("")?; @@ -269,9 +368,7 @@ impl Pipeline { term.line(label_checkpoint( match &result.node { - Some(ActionNode::RunTarget(_, target)) => target.as_str(), - Some(ActionNode::RunInteractiveTarget(_, target)) => target.as_str(), - Some(ActionNode::RunPersistentTarget(_, target)) => target.as_str(), + Some(ActionNode::RunTask { target, .. }) => target.as_str(), _ => &result.label, }, Checkpoint::RunFailed, @@ -373,14 +470,7 @@ impl Pipeline { let mut skipped_count = 0; for result in results { - if compact - && !matches!( - result.node.as_ref().unwrap(), - ActionNode::RunTarget(_, _) - | ActionNode::RunInteractiveTarget(_, _) - | ActionNode::RunPersistentTarget(_, _) - ) - { + if compact && !matches!(result.node.as_ref().unwrap(), ActionNode::RunTask { .. }) { continue; } diff --git a/crates/core/action-pipeline/src/processor.rs b/crates/core/action-pipeline/src/processor.rs index 4a099f08047..af3ed5693a4 100644 --- a/crates/core/action-pipeline/src/processor.rs +++ b/crates/core/action-pipeline/src/processor.rs @@ -1,5 +1,5 @@ use crate::actions::install_deps::install_deps; -use crate::actions::run_target::run_target; +use crate::actions::run_task::run_task; use crate::actions::setup_tool::setup_tool; use crate::actions::sync_project::sync_project; use crate::actions::sync_workspace::sync_workspace; @@ -53,7 +53,7 @@ pub async fn process_action( let result = match &node { // Setup and install the specific tool - ActionNode::SetupTool(runtime) => { + ActionNode::SetupTool { runtime } => { local_emitter .emit(Event::ToolInstalling { runtime }) .await?; @@ -71,7 +71,7 @@ pub async fn process_action( } // Install dependencies in the workspace root - ActionNode::InstallDeps(runtime) => { + ActionNode::InstallDeps { runtime } => { local_emitter .emit(Event::DependenciesInstalling { project: None, @@ -93,7 +93,10 @@ pub async fn process_action( } // Install dependencies in the project root - ActionNode::InstallProjectDeps(runtime, project_id) => { + ActionNode::InstallProjectDeps { + runtime, + project: project_id, + } => { let project = local_project_graph.get(project_id)?; local_emitter @@ -118,7 +121,10 @@ pub async fn process_action( } // Sync a project within the graph - ActionNode::SyncProject(runtime, project_id) => { + ActionNode::SyncProject { + runtime, + project: project_id, + } => { let project = local_project_graph.get(project_id)?; local_emitter @@ -165,14 +171,14 @@ pub async fn process_action( } // Run a task within a project - ActionNode::RunTarget(runtime, target) - | ActionNode::RunInteractiveTarget(runtime, target) - | ActionNode::RunPersistentTarget(runtime, target) => { + ActionNode::RunTask { + runtime, target, .. + } => { let project = local_project_graph.get(target.get_project_id().unwrap())?; local_emitter.emit(Event::TargetRunning { target }).await?; - let run_result = run_target( + let run_result = run_task( &mut action, context, emitter, @@ -207,7 +213,10 @@ pub async fn process_action( if action.has_failed() { // If these fail, we should abort instead of trying to continue - if matches!(node, ActionNode::SetupTool(_)) || matches!(node, ActionNode::InstallDeps(_)) { + if matches!( + node, + ActionNode::SetupTool { .. } | ActionNode::InstallDeps { .. } + ) { action.abort(); } } diff --git a/crates/core/action-pipeline/src/subscribers/moonbase.rs b/crates/core/action-pipeline/src/subscribers/moonbase.rs index 18e63479b4d..20b900df961 100644 --- a/crates/core/action-pipeline/src/subscribers/moonbase.rs +++ b/crates/core/action-pipeline/src/subscribers/moonbase.rs @@ -459,9 +459,13 @@ impl Subscriber for MoonbaseSubscriber { let archive_path = archive_path.to_owned(); // Create a fake action label so that we can check the CI cache - let action_label = - ActionNode::RunTarget(Runtime::system(), (*target).to_owned()) - .label(); + let action_label = ActionNode::RunTask { + interactive: false, + persistent: false, + runtime: Runtime::system(), + target: (*target).to_owned(), + } + .label(); let job_id = self.job_ids.get(&action_label).cloned(); // Run this in the background so we don't slow down the pipeline diff --git a/crates/core/action-pipeline/tests/estimator_test.rs b/crates/core/action-pipeline/tests/estimator_test.rs index 13bea75f702..80ab2ba684a 100644 --- a/crates/core/action-pipeline/tests/estimator_test.rs +++ b/crates/core/action-pipeline/tests/estimator_test.rs @@ -7,6 +7,15 @@ use std::time::Duration; const NANOS_PER_MILLI: u32 = 1_000_000; const HALF_SECOND: u32 = NANOS_PER_MILLI * 500; +fn create_run_task_action(runtime: Runtime, target: &str) -> ActionNode { + ActionNode::RunTask { + interactive: false, + persistent: false, + runtime, + target: target.into(), + } +} + mod estimator { use super::*; @@ -31,7 +40,7 @@ mod estimator { let est = Estimator::calculate( &[Action { duration: Some(Duration::new(10, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "proj:task".into())), + node: Some(create_run_task_action(Runtime::system(), "proj:task")), ..Action::default() }], Duration::new(5, 0), @@ -58,27 +67,27 @@ mod estimator { &[ Action { duration: Some(Duration::new(10, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "a:build".into())), + node: Some(create_run_task_action(Runtime::system(), "a:build")), ..Action::default() }, Action { duration: Some(Duration::new(5, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "a:lint".into())), + node: Some(create_run_task_action(Runtime::system(), "a:lint")), ..Action::default() }, Action { duration: Some(Duration::new(15, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "b:build".into())), + node: Some(create_run_task_action(Runtime::system(), "b:build")), ..Action::default() }, Action { duration: Some(Duration::new(8, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "c:test".into())), + node: Some(create_run_task_action(Runtime::system(), "c:test")), ..Action::default() }, Action { duration: Some(Duration::new(12, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "d:lint".into())), + node: Some(create_run_task_action(Runtime::system(), "d:lint")), ..Action::default() }, ], @@ -113,17 +122,21 @@ mod estimator { &[ Action { duration: Some(Duration::new(10, 0)), - node: Some(ActionNode::SetupTool(Runtime::system())), + node: Some(ActionNode::SetupTool { + runtime: Runtime::system(), + }), ..Action::default() }, Action { duration: Some(Duration::new(25, 0)), - node: Some(ActionNode::InstallDeps(Runtime::system())), + node: Some(ActionNode::InstallDeps { + runtime: Runtime::system(), + }), ..Action::default() }, Action { duration: Some(Duration::new(10, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "proj:task".into())), + node: Some(create_run_task_action(Runtime::system(), "proj:task")), ..Action::default() }, ], @@ -153,7 +166,7 @@ mod estimator { let est = Estimator::calculate( &[Action { duration: Some(Duration::new(3, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "proj:task".into())), + node: Some(create_run_task_action(Runtime::system(), "proj:task")), status: ActionStatus::Cached, ..Action::default() }], @@ -181,37 +194,41 @@ mod estimator { &[ Action { duration: Some(Duration::new(10, 0)), - node: Some(ActionNode::SetupTool(Runtime::system())), + node: Some(ActionNode::SetupTool { + runtime: Runtime::system(), + }), ..Action::default() }, Action { duration: Some(Duration::new(25, 0)), - node: Some(ActionNode::InstallDeps(Runtime::system())), + node: Some(ActionNode::InstallDeps { + runtime: Runtime::system(), + }), ..Action::default() }, Action { duration: Some(Duration::new(10, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "a:build".into())), + node: Some(create_run_task_action(Runtime::system(), "a:build")), ..Action::default() }, Action { duration: Some(Duration::new(5, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "a:lint".into())), + node: Some(create_run_task_action(Runtime::system(), "a:lint")), ..Action::default() }, Action { duration: Some(Duration::new(15, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "b:build".into())), + node: Some(create_run_task_action(Runtime::system(), "b:build")), ..Action::default() }, Action { duration: Some(Duration::new(8, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "c:test".into())), + node: Some(create_run_task_action(Runtime::system(), "c:test")), ..Action::default() }, Action { duration: Some(Duration::new(12, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "d:lint".into())), + node: Some(create_run_task_action(Runtime::system(), "d:lint")), ..Action::default() }, ], @@ -250,37 +267,41 @@ mod estimator { &[ Action { duration: Some(Duration::new(10, 0)), - node: Some(ActionNode::SetupTool(Runtime::system())), + node: Some(ActionNode::SetupTool { + runtime: Runtime::system(), + }), ..Action::default() }, Action { duration: Some(Duration::new(25, 0)), - node: Some(ActionNode::InstallDeps(Runtime::system())), + node: Some(ActionNode::InstallDeps { + runtime: Runtime::system(), + }), ..Action::default() }, Action { duration: Some(Duration::new(10, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "a:build".into())), + node: Some(create_run_task_action(Runtime::system(), "a:build")), ..Action::default() }, Action { duration: Some(Duration::new(5, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "a:lint".into())), + node: Some(create_run_task_action(Runtime::system(), "a:lint")), ..Action::default() }, Action { duration: Some(Duration::new(15, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "b:build".into())), + node: Some(create_run_task_action(Runtime::system(), "b:build")), ..Action::default() }, Action { duration: Some(Duration::new(8, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "c:test".into())), + node: Some(create_run_task_action(Runtime::system(), "c:test")), ..Action::default() }, Action { duration: Some(Duration::new(12, 0)), - node: Some(ActionNode::RunTarget(Runtime::system(), "d:lint".into())), + node: Some(create_run_task_action(Runtime::system(), "d:lint")), ..Action::default() }, ], diff --git a/crates/core/action/Cargo.toml b/crates/core/action/Cargo.toml index 7d8a6a0046b..5c9a8f1bf49 100644 --- a/crates/core/action/Cargo.toml +++ b/crates/core/action/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" publish = false [dependencies] +moon_action_graph = { path = "../../../nextgen/action-graph" } moon_common = { path = "../../../nextgen/common" } moon_platform_runtime = { path = "../../../nextgen/platform-runtime" } moon_target = { path = "../../../nextgen/target" } diff --git a/crates/core/action/src/action.rs b/crates/core/action/src/action.rs index a36a3226909..deedebcf05d 100644 --- a/crates/core/action/src/action.rs +++ b/crates/core/action/src/action.rs @@ -1,4 +1,4 @@ -use crate::node::ActionNode; +use moon_action_graph::ActionNode; use moon_common::color; use moon_utils::time::{chrono::prelude::*, now_timestamp}; use serde::{Deserialize, Serialize}; @@ -102,6 +102,9 @@ pub struct Action { #[serde(skip)] pub node: Option, + #[serde(skip)] + pub node_index: usize, + pub started_at: Option, #[serde(skip)] @@ -124,6 +127,7 @@ impl Action { label: node.label(), log_target: String::new(), node: Some(node), + node_index: 0, started_at: None, start_time: None, status: ActionStatus::Running, diff --git a/crates/core/action/src/lib.rs b/crates/core/action/src/lib.rs index b195cc47f6e..4a5e97f1cc3 100644 --- a/crates/core/action/src/lib.rs +++ b/crates/core/action/src/lib.rs @@ -1,5 +1,4 @@ mod action; -mod node; pub use action::*; -pub use node::*; +pub use moon_action_graph::ActionNode; diff --git a/crates/core/action/src/node.rs b/crates/core/action/src/node.rs deleted file mode 100644 index f79e2a63f54..00000000000 --- a/crates/core/action/src/node.rs +++ /dev/null @@ -1,83 +0,0 @@ -use moon_common::Id; -use moon_platform_runtime::Runtime; -use moon_target::Target; -use serde::Serialize; -use std::hash::{Hash, Hasher}; - -#[derive(Clone, Debug, Eq, Serialize)] -#[serde(tag = "action", content = "params")] -pub enum ActionNode { - /// Install tool dependencies in the workspace root. - InstallDeps(Runtime), - - /// Install tool dependencies in the project root. - InstallProjectDeps(Runtime, Id), - - /// Run a target (project task). - RunTarget(Runtime, Target), - - /// Run a target (project task) interactively with stdin. - RunInteractiveTarget(Runtime, Target), - - /// Run a target (project task) that never terminates. - RunPersistentTarget(Runtime, Target), - - /// Setup a tool + version for the provided platform. - SetupTool(Runtime), - - /// Sync a project with language specific semantics. - SyncProject(Runtime, Id), - - /// Sync the entire moon workspace. - SyncWorkspace, -} - -impl ActionNode { - pub fn label(&self) -> String { - match self { - ActionNode::InstallDeps(runtime) => { - if runtime.requirement.is_global() { - format!("Install{}Deps", runtime) - } else { - format!("Install{}Deps({})", runtime, runtime.requirement) - } - } - ActionNode::InstallProjectDeps(runtime, id) => { - if runtime.requirement.is_global() { - format!("Install{}DepsInProject({id})", runtime) - } else { - format!( - "Install{}DepsInProject({}, {id})", - runtime, runtime.requirement - ) - } - } - ActionNode::RunTarget(_, id) => format!("RunTarget({id})"), - ActionNode::RunInteractiveTarget(_, id) => format!("RunInteractiveTarget({id})"), - ActionNode::RunPersistentTarget(_, id) => format!("RunPersistentTarget({id})"), - ActionNode::SetupTool(runtime) => { - if runtime.requirement.is_global() { - format!("Setup{}Tool", runtime) - } else { - format!("Setup{}Tool({})", runtime, runtime.requirement) - } - } - ActionNode::SyncProject(runtime, id) => { - format!("Sync{}Project({id})", runtime) - } - ActionNode::SyncWorkspace => "SyncWorkspace".into(), - } - } -} - -impl PartialEq for ActionNode { - fn eq(&self, other: &Self) -> bool { - self.label() == other.label() - } -} - -impl Hash for ActionNode { - fn hash(&self, state: &mut H) { - self.label().hash(state); - } -} diff --git a/crates/core/dep-graph/Cargo.toml b/crates/core/dep-graph/Cargo.toml deleted file mode 100644 index 29484bc5c05..00000000000 --- a/crates/core/dep-graph/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "moon_dep_graph" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -bench = false - -[[bench]] -name = "dep_graph_benchmark" -harness = false - -[dependencies] -moon_action = { path = "../action" } -moon_common = { path = "../../../nextgen/common" } -moon_logger = { path = "../logger" } -moon_platform = { path = "../platform" } -moon_project = { path = "../../../nextgen/project" } -moon_project_graph = { path = "../../../nextgen/project-graph" } -moon_query = { path = "../../../nextgen/query" } -moon_target = { path = "../../../nextgen/target" } -moon_task = { path = "../../../nextgen/task" } -miette = { workspace = true } -petgraph = { workspace = true } -rustc-hash = { workspace = true } -starbase_styles = { workspace = true } -thiserror = { workspace = true } - -[dev-dependencies] -moon = { path = "../moon" } -moon_config = { path = "../../../nextgen/config" } -moon_test_utils = { path = "../test-utils" } -moon_utils = { path = "../utils" } -moon_workspace = { path = "../../../nextgen/workspace" } -criterion = { workspace = true } -tokio = { workspace = true } diff --git a/crates/core/dep-graph/benches/dep_graph_benchmark.rs b/crates/core/dep-graph/benches/dep_graph_benchmark.rs deleted file mode 100644 index 6d33f8bc7a6..00000000000 --- a/crates/core/dep-graph/benches/dep_graph_benchmark.rs +++ /dev/null @@ -1,49 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use moon::{build_dep_graph, generate_project_graph, load_workspace_from}; -use moon_target::Target; -use moon_test_utils::{create_sandbox_with_config, get_cases_fixture_configs}; - -pub fn build_benchmark(c: &mut Criterion) { - let (workspace_config, toolchain_config, tasks_config) = get_cases_fixture_configs(); - - let sandbox = create_sandbox_with_config( - "cases", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - c.bench_function("dep_graph_build", |b| { - b.to_async(tokio::runtime::Runtime::new().unwrap()) - .iter(|| async { - let mut workspace = load_workspace_from(sandbox.path()).await.unwrap(); - let project_graph = generate_project_graph(&mut workspace).await.unwrap(); - let mut dep_graph = build_dep_graph(&project_graph); - - dep_graph - .run_target(Target::parse("base:standard").unwrap(), None) - .unwrap(); - - dep_graph - .run_target(Target::parse("depsA:dependencyOrder").unwrap(), None) - .unwrap(); - - dep_graph - .run_target(Target::parse("outputs:withDeps").unwrap(), None) - .unwrap(); - - dep_graph - .run_target(Target::parse("passthroughArgs:c").unwrap(), None) - .unwrap(); - - dep_graph - .run_target(Target::parse("targetScopeB:self").unwrap(), None) - .unwrap(); - - black_box(dep_graph.build()); - }) - }); -} - -criterion_group!(dep_graph, build_benchmark); -criterion_main!(dep_graph); diff --git a/crates/core/dep-graph/src/dep_builder.rs b/crates/core/dep-graph/src/dep_builder.rs deleted file mode 100644 index fa199c1162e..00000000000 --- a/crates/core/dep-graph/src/dep_builder.rs +++ /dev/null @@ -1,474 +0,0 @@ -use crate::dep_graph::{DepGraph, DepGraphType, IndicesType}; -use moon_action::ActionNode; -use moon_common::path::WorkspaceRelativePathBuf; -use moon_common::Id; -use moon_logger::{debug, map_list, trace}; -use moon_platform::{PlatformManager, Runtime}; -use moon_project::Project; -use moon_project_graph::ProjectGraph; -use moon_query::{build_query, Criteria}; -use moon_target::{Target, TargetError, TargetLocator, TargetScope}; -use moon_task::Task; -use petgraph::graph::NodeIndex; -use petgraph::Graph; -use rustc_hash::{FxHashMap, FxHashSet}; -use starbase_styles::color; -use std::mem; - -const LOG_TARGET: &str = "moon:dep-graph"; - -type RuntimePair = (Runtime, Runtime); -type TouchedFilePaths = FxHashSet; - -/// A directed acyclic graph (DAG) for the work that needs to be processed, based on a -/// project or task's dependency chain. This is also known as a "task graph" (not to -/// be confused with our tasks) or a "dependency graph". -pub struct DepGraphBuilder<'ws> { - all_query: Option, - graph: DepGraphType, - indices: IndicesType, - project_graph: &'ws ProjectGraph, - runtimes: FxHashMap, -} - -impl<'ws> DepGraphBuilder<'ws> { - pub fn new(project_graph: &'ws ProjectGraph) -> Self { - debug!(target: LOG_TARGET, "Creating dependency graph"); - - let mut graph = Graph::new(); - let mut indices = FxHashMap::default(); - - // Always sync the workspace - let node = ActionNode::SyncWorkspace; - - trace!( - target: LOG_TARGET, - "Adding {} to graph", - color::muted_light(node.label()) - ); - - indices.insert(node.to_owned(), graph.add_node(node)); - - DepGraphBuilder { - all_query: None, - graph, - indices, - project_graph, - runtimes: FxHashMap::default(), - } - } - - pub fn build(&mut self) -> DepGraph { - DepGraph::new(mem::take(&mut self.graph), mem::take(&mut self.indices)) - } - - pub fn set_query(&mut self, input: &str) -> miette::Result<()> { - self.all_query = Some(build_query(input)?); - - Ok(()) - } - - pub fn get_index_from_node(&self, node: &ActionNode) -> Option<&NodeIndex> { - self.indices.get(node) - } - - // Projects support overriding the the version of their language (tool), - // so we need to account for this via the runtime. However, some actions require - // the workspace version of the language, so we must extract 2 runtimes here. - pub fn get_runtimes_from_project( - &mut self, - project: &Project, - task: Option<&Task>, - ) -> (Runtime, Runtime) { - let key = match task { - Some(task) => task.target.id.clone(), - None => project.id.to_string(), - }; - - if let Some(pair) = self.runtimes.get(&key) { - return pair.clone(); - } - - let mut project_runtime = Runtime::system(); - let mut workspace_runtime = Runtime::system(); - - if let Some(platform) = PlatformManager::read().find(|p| match task { - Some(task) => p.matches(&task.platform, None), - None => p.matches(&project.language.clone().into(), None), - }) { - project_runtime = platform.get_runtime_from_config(Some(&project.config)); - workspace_runtime = platform.get_runtime_from_config(None); - } - - let pair = (project_runtime, workspace_runtime); - - self.runtimes.insert(key, pair.clone()); - - pair - } - - pub fn install_deps( - &mut self, - project: &Project, - task: Option<&Task>, - ) -> miette::Result { - let (project_runtime, workspace_runtime) = self.get_runtimes_from_project(project, task); - let mut installs_in_project = false; - - // If project is NOT in the package manager workspace, then we should - // install dependencies in the project, not the workspace root. - if let Ok(platform) = PlatformManager::read().get(project.language.clone()) { - if !platform.is_project_in_dependency_workspace(project.source.as_str())? { - installs_in_project = true; - - debug!( - target: LOG_TARGET, - "Project {} not within dependency workspaces, will be handled externally", - color::id(&project.id), - ); - } - } - - // When installing dependencies in the project, we will use the - // overridden version if it is available. Otherwise when installing - // in the root, we should *always* use the workspace version. - Ok(if installs_in_project { - self.install_project_deps(&project_runtime, &project.id) - } else { - self.install_workspace_deps(&workspace_runtime) - }) - } - - pub fn install_project_deps(&mut self, runtime: &Runtime, project_id: &Id) -> NodeIndex { - let node = ActionNode::InstallProjectDeps(runtime.clone(), project_id.to_owned()); - - if let Some(index) = self.get_index_from_node(&node) { - return *index; - } - - trace!( - target: LOG_TARGET, - "Adding {} to graph", - color::muted_light(node.label()) - ); - - // Before we install deps, we must ensure the language has been installed - let setup_tool_index = self.setup_tool(runtime); - let index = self.insert_node(&node); - - self.graph.add_edge(index, setup_tool_index, ()); - - index - } - - pub fn install_workspace_deps(&mut self, runtime: &Runtime) -> NodeIndex { - let node = ActionNode::InstallDeps(runtime.clone()); - - if let Some(index) = self.get_index_from_node(&node) { - return *index; - } - - trace!( - target: LOG_TARGET, - "Adding {} to graph", - color::muted_light(node.label()) - ); - - // Before we install deps, we must ensure the language has been installed - let setup_tool_index = self.setup_tool(runtime); - let index = self.insert_node(&node); - - self.graph.add_edge(index, setup_tool_index, ()); - - index - } - - pub fn run_dependents_for_target>(&mut self, target: T) -> miette::Result<()> { - let target = target.as_ref(); - - trace!( - target: LOG_TARGET, - "Adding dependents to run for target {}", - color::label(target), - ); - - if let TargetScope::Project(project_locator) = &target.scope { - let project = self.project_graph.get(project_locator)?; - let dependents = self.project_graph.dependents_of(&project)?; - - for dependent_id in dependents { - let dep_project = self.project_graph.get(dependent_id)?; - - if dep_project.tasks.contains_key(&target.task_id) { - self.run_target(&dep_project.get_task(&target.task_id)?.target, None)?; - } - } - } - - Ok(()) - } - - pub fn run_target>( - &mut self, - target: T, - touched_files: Option<&TouchedFilePaths>, - ) -> miette::Result<(FxHashSet, FxHashSet)> { - let target = target.as_ref(); - let mut inserted_targets = FxHashSet::default(); - let mut inserted_indexes = FxHashSet::default(); - - match &target.scope { - // :task - TargetScope::All => { - let mut projects = vec![]; - - if let Some(all_query) = &self.all_query { - projects.extend(self.project_graph.query(all_query)?); - } else { - projects.extend(self.project_graph.get_all()?); - }; - - for project in projects { - if project.tasks.contains_key(&target.task_id) { - let all_target = Target::new(&project.id, &target.task_id)?; - - if let Some(index) = - self.run_target_by_project(&all_target, &project, touched_files)? - { - inserted_targets.insert(all_target); - inserted_indexes.insert(index); - } - } - } - } - // ^:task - TargetScope::Deps => { - return Err(TargetError::NoDepsInRunContext.into()); - } - // project:task - TargetScope::Project(project_locator) => { - let project = self.project_graph.get(project_locator)?; - let task = project.get_task(&target.task_id)?; - - if let Some(index) = - self.run_target_by_project(&task.target, &project, touched_files)? - { - inserted_targets.insert(task.target.to_owned()); - inserted_indexes.insert(index); - } - } - // #tag:task - TargetScope::Tag(tag) => { - let projects = self - .project_graph - .query(build_query(format!("tag={}", tag))?)?; - - for project in projects { - if project.tasks.contains_key(&target.task_id) { - let tag_target = Target::new(&project.id, &target.task_id)?; - - if let Some(index) = - self.run_target_by_project(&tag_target, &project, touched_files)? - { - inserted_targets.insert(tag_target); - inserted_indexes.insert(index); - } - } - } - } - // ~:task - TargetScope::OwnSelf => { - return Err(TargetError::NoSelfInRunContext.into()); - } - }; - - Ok((inserted_targets, inserted_indexes)) - } - - pub fn run_target_by_project>( - &mut self, - target: T, - project: &Project, - touched_files: Option<&TouchedFilePaths>, - ) -> miette::Result> { - let target = target.as_ref(); - let task = project.get_task(&target.task_id)?; - let (runtime, _) = self.get_runtimes_from_project(project, Some(task)); - - let node = if task.is_persistent() { - ActionNode::RunPersistentTarget(runtime, target.clone()) - } else if task.is_interactive() { - ActionNode::RunInteractiveTarget(runtime, target.clone()) - } else { - ActionNode::RunTarget(runtime, target.clone()) - }; - - if let Some(index) = self.get_index_from_node(&node) { - return Ok(Some(*index)); - } - - // Compare against touched files if provided - if let Some(touched) = touched_files { - if !task.is_affected(touched)? { - trace!( - target: LOG_TARGET, - "Target {} not affected based on touched files, skipping", - color::label(target), - ); - - return Ok(None); - } - } - - trace!( - target: LOG_TARGET, - "Adding {} to graph", - color::muted_light(node.label()) - ); - - // We should install deps & sync projects *before* running targets - let install_deps_index = self.install_deps(project, Some(task))?; - let sync_project_index = self.sync_project(project)?; - let index = self.insert_node(&node); - - self.graph.add_edge(index, install_deps_index, ()); - self.graph.add_edge(index, sync_project_index, ()); - - // And we also need to wait on all dependent targets - if !task.deps.is_empty() { - trace!( - target: LOG_TARGET, - "Adding dependencies {} for target {}", - map_list(&task.deps, |f| color::symbol(f)), - color::label(target), - ); - - // We don't pass touched files to dependencies, because if the parent - // task is affected/going to run, then so should all of these! - for dep_index in self.run_target_task_dependencies(task, None)? { - self.graph.add_edge(index, dep_index, ()); - } - } - - Ok(Some(index)) - } - - pub fn run_target_task_dependencies( - &mut self, - task: &Task, - touched_files: Option<&TouchedFilePaths>, - ) -> miette::Result> { - let parallel = task.options.run_deps_in_parallel; - let mut indexes = vec![]; - let mut previous_target_index = None; - - for dep_target in &task.deps { - let (_, dep_indexes) = self.run_target(dep_target, touched_files)?; - - for dep_index in dep_indexes { - // When parallel, parent depends on child - if parallel { - indexes.push(dep_index); - - // When serial, next child depends on previous child - } else if let Some(prev) = previous_target_index { - self.graph.add_edge(dep_index, prev, ()); - } - - previous_target_index = Some(dep_index); - } - } - - if !parallel { - indexes.push(previous_target_index.unwrap()); - } - - Ok(indexes) - } - - pub fn run_targets_by_locator( - &mut self, - target_locators: &[TargetLocator], - touched_files: Option<&TouchedFilePaths>, - ) -> miette::Result> { - let mut qualified_targets = vec![]; - let mut project = None; - - for locator in target_locators { - let result = match locator { - TargetLocator::Qualified(target) => self.run_target(target, touched_files)?, - TargetLocator::TaskFromWorkingDir(task_id) => { - if project.is_none() { - project = Some(self.project_graph.get_from_path(None)?); - } - - self.run_target( - Target::new(&project.as_ref().unwrap().id, task_id)?, - touched_files, - )? - } - }; - - qualified_targets.extend(result.0); - } - - Ok(qualified_targets) - } - - pub fn setup_tool(&mut self, runtime: &Runtime) -> NodeIndex { - let node = ActionNode::SetupTool(runtime.clone()); - - if let Some(index) = self.get_index_from_node(&node) { - return *index; - } - - trace!( - target: LOG_TARGET, - "Adding {} to graph", - color::muted_light(node.label()) - ); - - self.insert_node(&node) - } - - pub fn sync_project(&mut self, project: &Project) -> miette::Result { - let (runtime, _) = self.get_runtimes_from_project(project, None); - let node = ActionNode::SyncProject(runtime.clone(), project.id.clone()); - - if let Some(index) = self.get_index_from_node(&node) { - return Ok(*index); - } - - trace!( - target: LOG_TARGET, - "Adding {} to graph", - color::muted_light(node.label()) - ); - - // Syncing depends on the language's tool to be installed - let setup_tool_index = self.setup_tool(&runtime); - let index = self.insert_node(&node); - - self.graph.add_edge(index, setup_tool_index, ()); - - // And we should also depend on other projects - for dep_project_id in self.project_graph.dependencies_of(project)? { - let dep_project = self.project_graph.get(dep_project_id)?; - let dep_index = self.sync_project(&dep_project)?; - - if index != dep_index { - self.graph.add_edge(index, dep_index, ()); - } - } - - Ok(index) - } - - // PRIVATE - - fn insert_node(&mut self, node: &ActionNode) -> NodeIndex { - let index = self.graph.add_node(node.to_owned()); - self.indices.insert(node.to_owned(), index); - index - } -} diff --git a/crates/core/dep-graph/src/dep_graph.rs b/crates/core/dep-graph/src/dep_graph.rs deleted file mode 100644 index 2d55a71c113..00000000000 --- a/crates/core/dep-graph/src/dep_graph.rs +++ /dev/null @@ -1,215 +0,0 @@ -use crate::errors::DepGraphError; -use moon_action::ActionNode; -use petgraph::algo::toposort; -use petgraph::dot::{Config, Dot}; -use petgraph::graph::DiGraph; -use petgraph::graph::NodeIndex; -use petgraph::visit::EdgeRef; -use rustc_hash::{FxHashMap, FxHashSet}; - -pub type DepGraphType = DiGraph; -pub type IndicesType = FxHashMap; -pub type BatchedTopoSort = Vec>; - -/// A directed acyclic graph (DAG) for the work that needs to be processed, based on a -/// project or task's dependency chain. This is also known as a "task graph" (not to -/// be confused with our tasks) or a "dependency graph". -pub struct DepGraph { - graph: DepGraphType, - - indices: IndicesType, -} - -impl DepGraph { - pub fn new(graph: DepGraphType, indices: IndicesType) -> Self { - DepGraph { graph, indices } - } - - pub fn is_empty(&self) -> bool { - self.get_node_count() == 0 - } - - pub fn get_index_from_node(&self, node: &ActionNode) -> Option<&NodeIndex> { - self.indices.get(node) - } - - pub fn get_node_count(&self) -> usize { - self.graph.node_count() - } - - pub fn get_node_from_index(&self, index: &NodeIndex) -> Option<&ActionNode> { - self.graph.node_weight(*index) - } - - pub fn sort_topological(&self) -> miette::Result> { - let list = match toposort(&self.graph, None) { - Ok(nodes) => nodes, - Err(error) => { - return Err(DepGraphError::CycleDetected( - self.get_node_from_index(&error.node_id()).unwrap().label(), - ) - .into()); - } - }; - - Ok(list.into_iter().rev().collect()) - } - - pub fn sort_batched_topological(&self) -> miette::Result { - let mut batches: BatchedTopoSort = vec![]; - - // Count how many times an index is referenced across nodes and edges - let mut node_counts = FxHashMap::::default(); - - for ix in self.graph.node_indices() { - node_counts.entry(ix).and_modify(|e| *e += 1).or_insert(0); - - for dep_ix in self.graph.neighbors(ix) { - node_counts - .entry(dep_ix) - .and_modify(|e| *e += 1) - .or_insert(0); - } - } - - // Gather root nodes (count of 0) - let mut root_nodes = FxHashSet::::default(); - - for (ix, count) in &node_counts { - if *count == 0 { - root_nodes.insert(*ix); - } - } - - // If no root nodes are found, but nodes exist, then we have a cycle - let has_sync_workspace = root_nodes.contains(&NodeIndex::new(0)); - - if (!has_sync_workspace && root_nodes.is_empty() - || has_sync_workspace && root_nodes.len() == 1) - && !node_counts.is_empty() - { - self.detect_cycle()?; - } - - while !root_nodes.is_empty() { - let mut next_root_nodes = FxHashSet::::default(); - - // Decrement dependencies of the current batch nodes - for ix in &root_nodes { - for dep_ix in self.graph.neighbors(*ix) { - let count = node_counts - .entry(dep_ix) - .and_modify(|e| *e -= 1) - .or_insert(0); - - // And create a new batch if the count is 0 - if *count == 0 { - next_root_nodes.insert(dep_ix); - } - } - } - - // Push the previous batch onto the list - batches.push(root_nodes.into_iter().collect()); - - // And reset the current nodes - root_nodes = next_root_nodes; - } - - // Move persistent targets to the end - let mut sorted_batches: BatchedTopoSort = vec![]; - let mut persistent: Vec = vec![]; - - for mut batch in batches.into_iter().rev() { - batch.retain(|ix| match self.graph.node_weight(*ix).unwrap() { - // Interactive tasks must be the only item in their batch - // for stdin to work correctly - ActionNode::RunInteractiveTarget(_, _) => { - sorted_batches.push(vec![*ix]); - false - } - // Persistent tasks always run last as a combined batch, - // as their processes may never end - ActionNode::RunPersistentTarget(_, _) => { - persistent.push(*ix); - false - } - _ => true, - }); - - if !batch.is_empty() { - sorted_batches.push(batch); - } - } - - if !persistent.is_empty() { - sorted_batches.push(persistent); - } - - Ok(sorted_batches) - } - - /// Get a labelled representation of the dep graph (which can be serialized easily). - pub fn labeled_graph(&self) -> DiGraph { - let graph = self.graph.clone(); - graph.map(|_, n| n.label(), |_, _| String::new()) - } - - pub fn to_dot(&self) -> String { - let graph = self.graph.map(|_, n| n.label(), |_, e| e); - - let dot = Dot::with_attr_getters( - &graph, - &[Config::EdgeNoLabel, Config::NodeNoLabel], - &|_, e| { - if e.source().index() == 0 { - String::from("arrowhead=none") - } else { - String::from("arrowhead=box, arrowtail=box") - } - }, - &|_, n| { - let id = n.1; - - format!("label=\"{id}\" style=filled, shape=oval, fillcolor=gray, fontcolor=black") - }, - ); - - format!("{dot:?}") - } - - #[track_caller] - fn detect_cycle(&self) -> miette::Result<()> { - // use petgraph::algo::kosaraju_scc; - - // let scc = kosaraju_scc(&self.graph); - - // // Remove the sync workspace node - // let scc = scc - // .into_iter() - // .filter(|list| !(list.len() == 1 && list[0].index() == 0)) - // .collect::>>(); - - // // The cycle is always the last sequence in the list - // let Some(cycle) = scc.last() else { - // return Err(DepGraphError::CycleDetected("(unknown)".into()).into()); - // }; - - // let path = cycle - // .iter() - // .filter_map(|i| self.get_node_from_index(i).map(|n| n.label())) - // .collect::>() - // .join(" → "); - - // Err(DepGraphError::CycleDetected(path).into()) - - if let Err(error) = toposort(&self.graph, None) { - return Err(DepGraphError::CycleDetected( - self.get_node_from_index(&error.node_id()).unwrap().label(), - ) - .into()); - } - - Ok(()) - } -} diff --git a/crates/core/dep-graph/src/errors.rs b/crates/core/dep-graph/src/errors.rs deleted file mode 100644 index 7d6b1e150ec..00000000000 --- a/crates/core/dep-graph/src/errors.rs +++ /dev/null @@ -1,12 +0,0 @@ -use miette::Diagnostic; -use starbase_styles::{Style, Stylize}; -use thiserror::Error; - -#[derive(Error, Debug, Diagnostic)] -pub enum DepGraphError { - #[error("A dependency cycle has been detected for {}.", .0.style(Style::File))] - CycleDetected(String), - - #[error("Unknown node {0} found in dependency graph. How did this get here?")] - UnknownNode(usize), -} diff --git a/crates/core/dep-graph/src/lib.rs b/crates/core/dep-graph/src/lib.rs deleted file mode 100644 index 66f77643c72..00000000000 --- a/crates/core/dep-graph/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod dep_builder; -mod dep_graph; -mod errors; - -pub use dep_builder::DepGraphBuilder; -pub use dep_graph::*; -pub use errors::DepGraphError; diff --git a/crates/core/dep-graph/tests/dep_graph_test.rs b/crates/core/dep-graph/tests/dep_graph_test.rs deleted file mode 100644 index 33115869642..00000000000 --- a/crates/core/dep-graph/tests/dep_graph_test.rs +++ /dev/null @@ -1,679 +0,0 @@ -use moon::{build_dep_graph, generate_project_graph, load_workspace_from}; -use moon_common::path::WorkspaceRelativePathBuf; -use moon_config::{ - PartialInheritedTasksConfig, PartialNodeConfig, PartialToolchainConfig, PartialWorkspaceConfig, - PartialWorkspaceProjects, UnresolvedVersionSpec, -}; -use moon_dep_graph::BatchedTopoSort; -use moon_project_graph::ProjectGraph; -use moon_target::Target; -use moon_test_utils::{assert_snapshot, create_input_paths, create_sandbox_with_config, Sandbox}; -use moon_workspace::Workspace; -use petgraph::graph::NodeIndex; -use rustc_hash::{FxHashMap, FxHashSet}; - -async fn create_project_graph() -> (Workspace, ProjectGraph, Sandbox) { - let workspace_config = PartialWorkspaceConfig { - projects: Some(PartialWorkspaceProjects::Sources(FxHashMap::from_iter([ - ("advanced".into(), "advanced".to_owned()), - ("basic".into(), "basic".to_owned()), - ("emptyConfig".into(), "empty-config".to_owned()), - ("noConfig".into(), "no-config".to_owned()), - // Deps - ("foo".into(), "deps/foo".to_owned()), - ("bar".into(), "deps/bar".to_owned()), - ("baz".into(), "deps/baz".to_owned()), - // Tasks - ("tasks".into(), "tasks".to_owned()), - ("platforms".into(), "platforms".to_owned()), - ]))), - ..PartialWorkspaceConfig::default() - }; - let toolchain_config = PartialToolchainConfig { - node: Some(PartialNodeConfig { - version: Some(UnresolvedVersionSpec::parse("16.0.0").unwrap()), - dedupe_on_lockfile_change: Some(false), - ..PartialNodeConfig::default() - }), - ..PartialToolchainConfig::default() - }; - let tasks_config = PartialInheritedTasksConfig { - file_groups: Some(FxHashMap::from_iter([ - ( - "sources".into(), - create_input_paths(["src/**/*", "types/**/*"]), - ), - ("tests".into(), create_input_paths(["tests/**/*"])), - ])), - ..PartialInheritedTasksConfig::default() - }; - - let sandbox = create_sandbox_with_config( - "projects", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - let mut workspace = load_workspace_from(sandbox.path()).await.unwrap(); - let project_graph = generate_project_graph(&mut workspace).await.unwrap(); - - (workspace, project_graph, sandbox) -} - -async fn create_tasks_project_graph() -> (Workspace, ProjectGraph, Sandbox) { - let workspace_config = PartialWorkspaceConfig { - projects: Some(PartialWorkspaceProjects::Sources(FxHashMap::from_iter([ - ("basic".into(), "basic".to_owned()), - ("buildA".into(), "build-a".to_owned()), - ("buildB".into(), "build-b".to_owned()), - ("buildC".into(), "build-c".to_owned()), - ("chain".into(), "chain".to_owned()), - ("cycle".into(), "cycle".to_owned()), - ("inputA".into(), "input-a".to_owned()), - ("inputB".into(), "input-b".to_owned()), - ("inputC".into(), "input-c".to_owned()), - ( - "mergeAllStrategies".into(), - "merge-all-strategies".to_owned(), - ), - ("mergeAppend".into(), "merge-append".to_owned()), - ("mergePrepend".into(), "merge-prepend".to_owned()), - ("mergeReplace".into(), "merge-replace".to_owned()), - ("noTasks".into(), "no-tasks".to_owned()), - ("persistent".into(), "persistent".to_owned()), - ("interactive".into(), "interactive".to_owned()), - ]))), - ..PartialWorkspaceConfig::default() - }; - let toolchain_config = PartialToolchainConfig { - node: Some(PartialNodeConfig { - version: Some(UnresolvedVersionSpec::parse("16.0.0").unwrap()), - ..PartialNodeConfig::default() - }), - ..PartialToolchainConfig::default() - }; - let tasks_config = PartialInheritedTasksConfig { - file_groups: Some(FxHashMap::from_iter([( - "sources".into(), - create_input_paths(["src/**/*"]), - )])), - ..PartialInheritedTasksConfig::default() - }; - - let sandbox = create_sandbox_with_config( - "tasks", - Some(workspace_config), - Some(toolchain_config), - Some(tasks_config), - ); - - let mut workspace = load_workspace_from(sandbox.path()).await.unwrap(); - let project_graph = generate_project_graph(&mut workspace).await.unwrap(); - - (workspace, project_graph, sandbox) -} - -fn sort_batches(batches: BatchedTopoSort) -> BatchedTopoSort { - let mut list: BatchedTopoSort = vec![]; - - for batch in batches { - let mut new_batch = batch.clone(); - new_batch.sort(); - list.push(new_batch); - } - - list -} - -#[tokio::test] -// #[should_panic(expected = "A dependency cycle has been detected for RunTarget(cycle:a)")] -#[should_panic(expected = "A dependency cycle has been detected for RunTarget(cycle:c)")] -async fn detects_cycles() { - let (_workspace, projects, _sandbox) = create_tasks_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::new("cycle", "a").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("cycle", "b").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("cycle", "c").unwrap(), None) - .unwrap(); - let graph = graph.build(); - - assert_eq!( - sort_batches(graph.sort_batched_topological().unwrap()), - vec![vec![NodeIndex::new(0)], vec![NodeIndex::new(1)]] - ); -} - -mod run_target { - use super::*; - - #[tokio::test] - async fn single_targets() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::new("tasks", "test").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("tasks", "lint").unwrap(), None) - .unwrap(); - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - - assert_eq!( - graph.sort_topological().unwrap(), - vec![ - NodeIndex::new(0), - NodeIndex::new(1), - NodeIndex::new(2), // sync project - NodeIndex::new(3), // test - NodeIndex::new(4), // lint - NodeIndex::new(5), - ] - ); - assert_eq!( - sort_batches(graph.sort_batched_topological().unwrap()), - vec![ - vec![NodeIndex::new(1)], - vec![NodeIndex::new(2), NodeIndex::new(3)], - vec![NodeIndex::new(0), NodeIndex::new(4), NodeIndex::new(5)] - ] - ); - } - - #[tokio::test] - async fn deps_chain_target() { - let (_workspace, projects, _sandbox) = create_tasks_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::new("basic", "test").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("basic", "lint").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("chain", "a").unwrap(), None) - .unwrap(); - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - assert_eq!( - graph.sort_topological().unwrap(), - vec![ - NodeIndex::new(0), - NodeIndex::new(1), - NodeIndex::new(2), // sync project - NodeIndex::new(3), // test - NodeIndex::new(4), // lint - NodeIndex::new(5), // sync project - NodeIndex::new(6), - NodeIndex::new(12), // f - NodeIndex::new(11), // e - NodeIndex::new(10), // d - NodeIndex::new(9), // c - NodeIndex::new(8), // b - NodeIndex::new(7), // a - ] - ); - assert_eq!( - sort_batches(graph.sort_batched_topological().unwrap()), - vec![ - vec![NodeIndex::new(1)], - vec![NodeIndex::new(2), NodeIndex::new(6)], - vec![NodeIndex::new(12)], - vec![NodeIndex::new(11)], - vec![NodeIndex::new(10)], - vec![NodeIndex::new(9)], - vec![NodeIndex::new(3), NodeIndex::new(8)], - vec![ - NodeIndex::new(0), - NodeIndex::new(4), - NodeIndex::new(5), - NodeIndex::new(7) - ] - ] - ); - } - - #[tokio::test] - async fn moves_persistent_tasks_last() { - let (_workspace, projects, _sandbox) = create_tasks_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::new("persistent", "dev").unwrap(), None) - .unwrap(); - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - - assert_eq!( - sort_batches(graph.sort_batched_topological().unwrap()), - vec![ - vec![NodeIndex::new(1)], - vec![ - NodeIndex::new(2), - NodeIndex::new(5), - NodeIndex::new(6), - NodeIndex::new(7) - ], - vec![NodeIndex::new(4), NodeIndex::new(12), NodeIndex::new(13)], - vec![NodeIndex::new(3), NodeIndex::new(11)], - vec![NodeIndex::new(0)], - vec![ - NodeIndex::new(8), - NodeIndex::new(9), - NodeIndex::new(10), - NodeIndex::new(14) - ], - ] - ); - } - - #[tokio::test] - async fn isolates_interactive_tasks() { - let (_workspace, projects, _sandbox) = create_tasks_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::new("interactive", "one").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("chain", "c").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("interactive", "two").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("basic", "lint").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("interactive", "depOnOne").unwrap(), None) - .unwrap(); - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - - assert_eq!( - sort_batches(graph.sort_batched_topological().unwrap()), - vec![ - vec![NodeIndex::new(1)], - vec![NodeIndex::new(2), NodeIndex::new(5)], - vec![NodeIndex::new(9)], - vec![NodeIndex::new(3), NodeIndex::new(8)], - vec![NodeIndex::new(4)], // interactive - vec![NodeIndex::new(7), NodeIndex::new(11)], - vec![NodeIndex::new(13)], // interactive - vec![NodeIndex::new(10)], // interactive - vec![NodeIndex::new(0), NodeIndex::new(6), NodeIndex::new(12)], - ] - ); - } - - #[tokio::test] - async fn avoids_dupe_targets() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::new("tasks", "lint").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("tasks", "lint").unwrap(), None) - .unwrap(); - graph - .run_target(&Target::new("tasks", "lint").unwrap(), None) - .unwrap(); - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - - assert_eq!( - graph.sort_topological().unwrap(), - vec![ - NodeIndex::new(0), - NodeIndex::new(1), - NodeIndex::new(2), // sync project - NodeIndex::new(3), // lint - NodeIndex::new(4), - ] - ); - assert_eq!( - sort_batches(graph.sort_batched_topological().unwrap()), - vec![ - vec![NodeIndex::new(1)], - vec![NodeIndex::new(2), NodeIndex::new(3)], - vec![NodeIndex::new(0), NodeIndex::new(4)] - ] - ); - } - - #[tokio::test] - async fn runs_all_projects_for_target_all_scope() { - let (_workspace, projects, _sandbox) = create_tasks_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::parse(":build").unwrap(), None) - .unwrap(); - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - - assert_eq!( - graph.sort_topological().unwrap(), - vec![ - NodeIndex::new(0), - NodeIndex::new(1), - NodeIndex::new(2), // sync project: basic - NodeIndex::new(4), // basic:build - NodeIndex::new(5), // sync project: build-c - NodeIndex::new(6), // sync project: build-a - NodeIndex::new(3), // build-c:build - NodeIndex::new(8), // build-a:build - NodeIndex::new(9), // sync project: build-b - NodeIndex::new(7), // build-b:build - NodeIndex::new(10), // notasks - NodeIndex::new(11) - ] - ); - assert_eq!( - sort_batches(graph.sort_batched_topological().unwrap()), - vec![ - vec![NodeIndex::new(1)], - vec![ - NodeIndex::new(2), - NodeIndex::new(4), - NodeIndex::new(5), - NodeIndex::new(6) - ], - vec![ - NodeIndex::new(3), - NodeIndex::new(8), - NodeIndex::new(9), - NodeIndex::new(10) - ], - vec![NodeIndex::new(0), NodeIndex::new(7), NodeIndex::new(11)], - ] - ); - } - - #[tokio::test] - #[should_panic(expected = "Dependencies scope (^:) is not supported in run contexts.")] - async fn errors_for_target_deps_scope() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::parse("^:lint").unwrap(), None) - .unwrap(); - } - - #[tokio::test] - #[should_panic(expected = "Self scope (~:) is not supported in run contexts.")] - async fn errors_for_target_self_scope() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::parse("~:lint").unwrap(), None) - .unwrap(); - } - - #[tokio::test] - #[should_panic(expected = "No project has been configured with the name or alias unknown")] - async fn errors_for_unknown_project() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::new("unknown", "test").unwrap(), None) - .unwrap(); - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - } - - #[tokio::test] - #[should_panic(expected = "Unknown task build for project tasks.")] - async fn errors_for_unknown_task() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::new("tasks", "build").unwrap(), None) - .unwrap(); - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - } -} - -mod run_target_if_touched { - use super::*; - - #[tokio::test] - async fn skips_if_untouched_project() { - let (_workspace, projects, _sandbox) = create_tasks_project_graph().await; - - let mut touched_files = FxHashSet::default(); - touched_files.insert(WorkspaceRelativePathBuf::from("input-a/a.ts")); - touched_files.insert(WorkspaceRelativePathBuf::from("input-c/c.ts")); - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::new("inputA", "a").unwrap(), Some(&touched_files)) - .unwrap(); - graph - .run_target(&Target::new("inputB", "b").unwrap(), Some(&touched_files)) - .unwrap(); - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - } - - #[tokio::test] - async fn skips_if_untouched_task() { - let (_workspace, projects, _sandbox) = create_tasks_project_graph().await; - - let mut touched_files = FxHashSet::default(); - touched_files.insert(WorkspaceRelativePathBuf::from("input-a/a2.ts")); - touched_files.insert(WorkspaceRelativePathBuf::from("input-b/b2.ts")); - touched_files.insert(WorkspaceRelativePathBuf::from("input-c/any.ts")); - - let mut graph = build_dep_graph(&projects); - graph - .run_target(&Target::new("inputA", "a").unwrap(), Some(&touched_files)) - .unwrap(); - graph - .run_target(&Target::new("inputB", "b2").unwrap(), Some(&touched_files)) - .unwrap(); - graph - .run_target(&Target::new("inputC", "c").unwrap(), Some(&touched_files)) - .unwrap(); - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - } -} - -mod sync_project { - use super::*; - use moon_dep_graph::DepGraphBuilder; - - fn sync_projects(graph: &mut DepGraphBuilder, projects: &ProjectGraph, ids: &[&str]) { - for id in ids { - let project = projects.get(id).unwrap(); - - graph.sync_project(&project).unwrap(); - } - } - - #[tokio::test] - async fn isolated_projects() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - let mut graph = build_dep_graph(&projects); - - sync_projects( - &mut graph, - &projects, - &["advanced", "basic", "emptyConfig", "noConfig"], - ); - - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - - assert_eq!( - graph.sort_topological().unwrap(), - vec![ - NodeIndex::new(0), - NodeIndex::new(1), // advanced - NodeIndex::new(2), // noConfig - NodeIndex::new(4), - NodeIndex::new(5), // basic - NodeIndex::new(3), // emptyConfig - NodeIndex::new(6), - ] - ); - assert_eq!( - sort_batches(graph.sort_batched_topological().unwrap()), - vec![ - vec![NodeIndex::new(4)], - vec![NodeIndex::new(1), NodeIndex::new(5)], - vec![ - NodeIndex::new(0), - NodeIndex::new(2), - NodeIndex::new(3), - NodeIndex::new(6) - ] - ] - ); - } - - #[tokio::test] - async fn projects_with_deps() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - let mut graph = build_dep_graph(&projects); - - sync_projects(&mut graph, &projects, &["foo", "bar", "baz", "basic"]); - - let graph = graph.build(); - - // Not deterministic! - // assert_snapshot!(graph.to_dot()); - - assert_eq!( - graph.sort_topological().unwrap(), - vec![ - NodeIndex::new(0), - NodeIndex::new(1), // baz - NodeIndex::new(3), // bar - NodeIndex::new(4), - NodeIndex::new(5), // foo - NodeIndex::new(2), // noConfig - NodeIndex::new(7), // basic - NodeIndex::new(6), - ] - ); - assert_eq!( - sort_batches(graph.sort_batched_topological().unwrap()), - vec![ - vec![NodeIndex::new(3)], - vec![ - NodeIndex::new(1), - NodeIndex::new(4), - NodeIndex::new(5), - NodeIndex::new(7) - ], - vec![NodeIndex::new(0), NodeIndex::new(2), NodeIndex::new(6)] - ] - ); - } - - #[tokio::test] - async fn projects_with_tasks() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - let mut graph = build_dep_graph(&projects); - - sync_projects(&mut graph, &projects, &["noConfig", "tasks"]); - - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - - assert_eq!( - graph.sort_topological().unwrap(), - vec![ - NodeIndex::new(0), - NodeIndex::new(1), // noConfig - NodeIndex::new(2), - NodeIndex::new(3), // tasks - NodeIndex::new(4), - ] - ); - assert_eq!( - sort_batches(graph.sort_batched_topological().unwrap()), - vec![ - vec![NodeIndex::new(1), NodeIndex::new(3)], - vec![NodeIndex::new(0), NodeIndex::new(2), NodeIndex::new(4)] - ] - ); - } - - #[tokio::test] - async fn avoids_dupe_projects() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - let mut graph = build_dep_graph(&projects); - - sync_projects(&mut graph, &projects, &["advanced", "advanced", "advanced"]); - - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - } - - #[tokio::test] - #[should_panic(expected = "No project has been configured with the name or alias unknown")] - async fn errors_for_unknown_project() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - let mut graph = build_dep_graph(&projects); - - sync_projects(&mut graph, &projects, &["unknown"]); - - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - } -} - -mod installs_deps { - use super::*; - - #[tokio::test] - async fn tool_is_based_on_task_platform() { - let (_workspace, projects, _sandbox) = create_project_graph().await; - let mut graph = build_dep_graph(&projects); - - graph - .run_target(&Target::new("platforms", "system").unwrap(), None) - .unwrap(); - - graph - .run_target(&Target::new("platforms", "node").unwrap(), None) - .unwrap(); - - let graph = graph.build(); - - assert_snapshot!(graph.to_dot()); - } -} diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__installs_deps__tool_is_based_on_task_platform.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__installs_deps__tool_is_based_on_task_platform.snap deleted file mode 100644 index 89abacad753..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__installs_deps__tool_is_based_on_task_platform.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupSystemTool" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallSystemDeps" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncSystemProject(platforms)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(platforms:system)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 6 [ label="InstallNodeDeps(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 7 [ label="RunTarget(platforms:node)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] - 6 -> 5 [ arrowhead=box, arrowtail=box] - 7 -> 6 [ arrowhead=box, arrowtail=box] - 7 -> 3 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__avoids_dupe_targets.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__avoids_dupe_targets.snap deleted file mode 100644 index ff64ce9d402..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__avoids_dupe_targets.snap +++ /dev/null @@ -1,16 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(tasks)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(tasks:lint)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__deps_chain_target.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__deps_chain_target.snap deleted file mode 100644 index d58328cca47..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__deps_chain_target.snap +++ /dev/null @@ -1,44 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(basic)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(basic:test)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="RunTarget(basic:lint)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 6 [ label="SyncNodeProject(chain)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 7 [ label="RunTarget(chain:a)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 8 [ label="RunTarget(chain:b)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 9 [ label="RunTarget(chain:c)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 10 [ label="RunTarget(chain:d)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 11 [ label="RunTarget(chain:e)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 12 [ label="RunTarget(chain:f)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] - 5 -> 2 [ arrowhead=box, arrowtail=box] - 5 -> 3 [ arrowhead=box, arrowtail=box] - 6 -> 1 [ arrowhead=box, arrowtail=box] - 7 -> 2 [ arrowhead=box, arrowtail=box] - 7 -> 6 [ arrowhead=box, arrowtail=box] - 8 -> 2 [ arrowhead=box, arrowtail=box] - 8 -> 6 [ arrowhead=box, arrowtail=box] - 9 -> 2 [ arrowhead=box, arrowtail=box] - 9 -> 6 [ arrowhead=box, arrowtail=box] - 10 -> 2 [ arrowhead=box, arrowtail=box] - 10 -> 6 [ arrowhead=box, arrowtail=box] - 11 -> 2 [ arrowhead=box, arrowtail=box] - 11 -> 6 [ arrowhead=box, arrowtail=box] - 12 -> 2 [ arrowhead=box, arrowtail=box] - 12 -> 6 [ arrowhead=box, arrowtail=box] - 11 -> 12 [ arrowhead=box, arrowtail=box] - 10 -> 11 [ arrowhead=box, arrowtail=box] - 9 -> 10 [ arrowhead=box, arrowtail=box] - 8 -> 9 [ arrowhead=box, arrowtail=box] - 7 -> 8 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__isolates_interactive_tasks.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__isolates_interactive_tasks.snap deleted file mode 100644 index 8fcb6cfd772..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__isolates_interactive_tasks.snap +++ /dev/null @@ -1,45 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(interactive)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunInteractiveTarget(interactive:one)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="SyncNodeProject(chain)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 6 [ label="RunTarget(chain:c)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 7 [ label="RunTarget(chain:d)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 8 [ label="RunTarget(chain:e)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 9 [ label="RunTarget(chain:f)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 10 [ label="RunInteractiveTarget(interactive:two)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 11 [ label="SyncNodeProject(basic)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 12 [ label="RunTarget(basic:lint)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 13 [ label="RunInteractiveTarget(interactive:depOnOne)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] - 5 -> 1 [ arrowhead=box, arrowtail=box] - 6 -> 2 [ arrowhead=box, arrowtail=box] - 6 -> 5 [ arrowhead=box, arrowtail=box] - 7 -> 2 [ arrowhead=box, arrowtail=box] - 7 -> 5 [ arrowhead=box, arrowtail=box] - 8 -> 2 [ arrowhead=box, arrowtail=box] - 8 -> 5 [ arrowhead=box, arrowtail=box] - 9 -> 2 [ arrowhead=box, arrowtail=box] - 9 -> 5 [ arrowhead=box, arrowtail=box] - 8 -> 9 [ arrowhead=box, arrowtail=box] - 7 -> 8 [ arrowhead=box, arrowtail=box] - 6 -> 7 [ arrowhead=box, arrowtail=box] - 10 -> 2 [ arrowhead=box, arrowtail=box] - 10 -> 3 [ arrowhead=box, arrowtail=box] - 11 -> 1 [ arrowhead=box, arrowtail=box] - 12 -> 2 [ arrowhead=box, arrowtail=box] - 12 -> 11 [ arrowhead=box, arrowtail=box] - 13 -> 2 [ arrowhead=box, arrowtail=box] - 13 -> 3 [ arrowhead=box, arrowtail=box] - 13 -> 4 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__moves_persistent_tasks_last.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__moves_persistent_tasks_last.snap deleted file mode 100644 index f79a3d06563..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__moves_persistent_tasks_last.snap +++ /dev/null @@ -1,54 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(persistent)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="SyncNodeProject(buildA)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="SyncNodeProject(buildC)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 6 [ label="SyncNodeProject(basic)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 7 [ label="SyncNodeProject(noTasks)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 8 [ label="RunPersistentTarget(persistent:dev)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 9 [ label="RunPersistentTarget(persistent:devMiddleman)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 10 [ label="RunPersistentTarget(persistent:local)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 11 [ label="RunTarget(buildA:build)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 12 [ label="RunTarget(basic:build)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 13 [ label="RunTarget(buildC:build)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 14 [ label="RunPersistentTarget(persistent:persistent)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 1 [ arrowhead=box, arrowtail=box] - 5 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 5 [ arrowhead=box, arrowtail=box] - 6 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 6 [ arrowhead=box, arrowtail=box] - 7 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 7 [ arrowhead=box, arrowtail=box] - 3 -> 4 [ arrowhead=box, arrowtail=box] - 3 -> 5 [ arrowhead=box, arrowtail=box] - 8 -> 2 [ arrowhead=box, arrowtail=box] - 8 -> 3 [ arrowhead=box, arrowtail=box] - 9 -> 2 [ arrowhead=box, arrowtail=box] - 9 -> 3 [ arrowhead=box, arrowtail=box] - 10 -> 2 [ arrowhead=box, arrowtail=box] - 10 -> 3 [ arrowhead=box, arrowtail=box] - 11 -> 2 [ arrowhead=box, arrowtail=box] - 11 -> 4 [ arrowhead=box, arrowtail=box] - 12 -> 2 [ arrowhead=box, arrowtail=box] - 12 -> 6 [ arrowhead=box, arrowtail=box] - 13 -> 2 [ arrowhead=box, arrowtail=box] - 13 -> 5 [ arrowhead=box, arrowtail=box] - 11 -> 12 [ arrowhead=box, arrowtail=box] - 11 -> 13 [ arrowhead=box, arrowtail=box] - 10 -> 11 [ arrowhead=box, arrowtail=box] - 9 -> 10 [ arrowhead=box, arrowtail=box] - 14 -> 2 [ arrowhead=box, arrowtail=box] - 14 -> 3 [ arrowhead=box, arrowtail=box] - 8 -> 9 [ arrowhead=box, arrowtail=box] - 8 -> 14 [ arrowhead=box, arrowtail=box] - 8 -> 13 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__runs_all_projects_for_target_all_scope.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__runs_all_projects_for_target_all_scope.snap deleted file mode 100644 index d89757aa9a0..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__runs_all_projects_for_target_all_scope.snap +++ /dev/null @@ -1,38 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(buildA)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="SyncNodeProject(buildC)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="SyncNodeProject(basic)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 6 [ label="SyncNodeProject(noTasks)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 7 [ label="RunTarget(buildA:build)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 8 [ label="RunTarget(basic:build)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 9 [ label="RunTarget(buildC:build)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 10 [ label="SyncNodeProject(buildB)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 11 [ label="RunTarget(buildB:build)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 4 [ arrowhead=box, arrowtail=box] - 5 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 5 [ arrowhead=box, arrowtail=box] - 6 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 6 [ arrowhead=box, arrowtail=box] - 7 -> 2 [ arrowhead=box, arrowtail=box] - 7 -> 3 [ arrowhead=box, arrowtail=box] - 8 -> 2 [ arrowhead=box, arrowtail=box] - 8 -> 5 [ arrowhead=box, arrowtail=box] - 9 -> 2 [ arrowhead=box, arrowtail=box] - 9 -> 4 [ arrowhead=box, arrowtail=box] - 7 -> 8 [ arrowhead=box, arrowtail=box] - 7 -> 9 [ arrowhead=box, arrowtail=box] - 10 -> 1 [ arrowhead=box, arrowtail=box] - 11 -> 2 [ arrowhead=box, arrowtail=box] - 11 -> 10 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__single_targets.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__single_targets.snap deleted file mode 100644 index 474248a08a3..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target__single_targets.snap +++ /dev/null @@ -1,19 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(tasks)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(tasks:test)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="RunTarget(tasks:lint)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] - 5 -> 2 [ arrowhead=box, arrowtail=box] - 5 -> 3 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target_if_touched__skips_if_untouched_project.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target_if_touched__skips_if_untouched_project.snap deleted file mode 100644 index e6651f5ad65..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target_if_touched__skips_if_untouched_project.snap +++ /dev/null @@ -1,16 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(inputA)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(inputA:a)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target_if_touched__skips_if_untouched_task.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target_if_touched__skips_if_untouched_task.snap deleted file mode 100644 index 2bd726be5e4..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__run_target_if_touched__skips_if_untouched_task.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="InstallNodeDeps(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(inputB)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="RunTarget(inputB:b2)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="SyncNodeProject(inputC)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 6 [ label="RunTarget(inputC:c)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 2 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] - 5 -> 1 [ arrowhead=box, arrowtail=box] - 6 -> 2 [ arrowhead=box, arrowtail=box] - 6 -> 5 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__sync_project__avoids_dupe_projects.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__sync_project__avoids_dupe_projects.snap deleted file mode 100644 index d30c9e696f1..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__sync_project__avoids_dupe_projects.snap +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="SyncNodeProject(advanced)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__sync_project__isolated_projects.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__sync_project__isolated_projects.snap deleted file mode 100644 index 8f5e9c77c16..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__sync_project__isolated_projects.snap +++ /dev/null @@ -1,19 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="SyncNodeProject(advanced)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SyncNodeProject(basic)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="SetupSystemTool" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 5 [ label="SyncSystemProject(noConfig)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 6 [ label="SyncSystemProject(emptyConfig)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 3 -> 1 [ arrowhead=box, arrowtail=box] - 5 -> 4 [ arrowhead=box, arrowtail=box] - 3 -> 5 [ arrowhead=box, arrowtail=box] - 6 -> 4 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/dep-graph/tests/snapshots/dep_graph_test__sync_project__projects_with_tasks.snap b/crates/core/dep-graph/tests/snapshots/dep_graph_test__sync_project__projects_with_tasks.snap deleted file mode 100644 index 0366366dcd1..00000000000 --- a/crates/core/dep-graph/tests/snapshots/dep_graph_test__sync_project__projects_with_tasks.snap +++ /dev/null @@ -1,14 +0,0 @@ ---- -source: crates/core/dep-graph/tests/dep_graph_test.rs -expression: graph.to_dot() ---- -digraph { - 0 [ label="SyncWorkspace" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 1 [ label="SetupSystemTool" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 [ label="SyncSystemProject(noConfig)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="SetupNodeTool(16.0.0)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 4 [ label="SyncNodeProject(tasks)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 2 -> 1 [ arrowhead=box, arrowtail=box] - 4 -> 3 [ arrowhead=box, arrowtail=box] -} - diff --git a/crates/core/emitter/Cargo.toml b/crates/core/emitter/Cargo.toml index d2de0a23737..b247df3f450 100644 --- a/crates/core/emitter/Cargo.toml +++ b/crates/core/emitter/Cargo.toml @@ -4,13 +4,6 @@ version = "0.1.0" edition = "2021" publish = false -[lib] -bench = false - -[[bench]] -name = "emitter_benchmark" -harness = false - [dependencies] moon_action = { path = "../action" } moon_action_context = { path = "../action-context" } diff --git a/crates/core/emitter/benches/emitter_benchmark.rs b/crates/core/emitter/benches/emitter_benchmark.rs deleted file mode 100644 index b7f616103c4..00000000000 --- a/crates/core/emitter/benches/emitter_benchmark.rs +++ /dev/null @@ -1,62 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use moon_action_context::ActionContext; -use moon_emitter::{Emitter, Event, EventFlow, Subscriber}; -use moon_test_utils::get_fixtures_path; -use moon_workspace::Workspace; -use std::sync::Arc; -use tokio::sync::RwLock; - -struct TestSubscriber; - -#[async_trait::async_trait] -impl Subscriber for TestSubscriber { - async fn on_emit<'e>( - &mut self, - _event: &Event<'e>, - _workspace: &Workspace, - ) -> miette::Result { - Ok(EventFlow::Continue) - } -} - -pub fn emit_benchmark(c: &mut Criterion) { - let workspace_root = get_fixtures_path("cases"); - let runtime = tokio::runtime::Runtime::new().unwrap(); - - c.bench_function("emitter_emit", |b| { - b.to_async(&runtime).iter(|| async { - let workspace = Workspace::load_from(&workspace_root).unwrap(); - let emitter = Emitter::new(Arc::new(RwLock::new(workspace))); - let context = ActionContext::default(); - - emitter - .emit(Event::PipelineStarted { - actions_count: 1, - context: &context, - }) - .await - .unwrap(); - - emitter - .emit(Event::PipelineStarted { - actions_count: 1, - context: &context, - }) - .await - .unwrap(); - - emitter - .emit(Event::PipelineStarted { - actions_count: 1, - context: &context, - }) - .await - .unwrap(); - - black_box(emitter); - }) - }); -} - -criterion_group!(emitter, emit_benchmark); -criterion_main!(emitter); diff --git a/crates/core/moon/Cargo.toml b/crates/core/moon/Cargo.toml index 1a59b7dec0d..0aad8788f05 100644 --- a/crates/core/moon/Cargo.toml +++ b/crates/core/moon/Cargo.toml @@ -5,9 +5,9 @@ edition = "2021" publish = false [dependencies] +moon_action_graph = { path = "../../../nextgen/action-graph" } moon_config = { path = "../../../nextgen/config" } moon_deno_platform = { path = "../../deno/platform" } -moon_dep_graph = { path = "../dep-graph" } moon_node_platform = { path = "../../node/platform" } moon_platform = { path = "../platform" } moon_platform_detector = { path = "../platform-detector" } diff --git a/crates/core/moon/src/lib.rs b/crates/core/moon/src/lib.rs index 133add9662a..0203e9c6bbc 100644 --- a/crates/core/moon/src/lib.rs +++ b/crates/core/moon/src/lib.rs @@ -1,6 +1,6 @@ +use moon_action_graph::ActionGraphBuilder; use moon_config::LanguageType; use moon_deno_platform::DenoPlatform; -use moon_dep_graph::DepGraphBuilder; use moon_node_platform::NodePlatform; use moon_platform::{PlatformManager, PlatformType}; use moon_platform_detector::{detect_project_language, detect_task_platform}; @@ -127,8 +127,8 @@ pub async fn load_workspace_with_toolchain() -> miette::Result { Ok(workspace) } -pub fn build_dep_graph(project_graph: &ProjectGraph) -> DepGraphBuilder { - DepGraphBuilder::new(project_graph) +pub fn build_action_graph(project_graph: &ProjectGraph) -> miette::Result { + ActionGraphBuilder::new(project_graph) } pub async fn create_project_graph_context(workspace: &Workspace) -> ProjectGraphBuilderContext { diff --git a/crates/core/notifier/Cargo.toml b/crates/core/notifier/Cargo.toml index 1a7cc428d19..fa46d16da0e 100644 --- a/crates/core/notifier/Cargo.toml +++ b/crates/core/notifier/Cargo.toml @@ -17,4 +17,4 @@ serde = { workspace = true } serde_json = { workspace = true } starbase_styles = { workspace = true } tokio = { workspace = true } -uuid = { version = "1.4.1", features = ["v4"] } +uuid = { workspace = true } diff --git a/crates/core/test-utils/Cargo.toml b/crates/core/test-utils/Cargo.toml index 2be0ecf114e..a793a3fa654 100644 --- a/crates/core/test-utils/Cargo.toml +++ b/crates/core/test-utils/Cargo.toml @@ -10,7 +10,7 @@ moon_config = { path = "../../../nextgen/config" } assert_cmd = "2.0.12" assert_fs = "1.0.13" clean-path = "0.2.1" -insta = "1.32.0" +insta = "1.33.0" predicates = "3.0.4" pretty_assertions = "1.4.0" rustc-hash = { workspace = true } diff --git a/crates/node/platform/src/actions/run_target.rs b/crates/node/platform/src/actions/run_target.rs index 8f34ed35770..94137aef49e 100644 --- a/crates/node/platform/src/actions/run_target.rs +++ b/crates/node/platform/src/actions/run_target.rs @@ -16,7 +16,7 @@ use rustc_hash::FxHashMap; use starbase_styles::color; use std::path::Path; -const LOG_TARGET: &str = "moon:node-platform:run-target"; +const LOG_TARGET: &str = "moon:node-platform:run-task"; fn create_node_options( node_config: &NodeConfig, diff --git a/crates/rust/lang/Cargo.toml b/crates/rust/lang/Cargo.toml index 2bd2c2f35d1..9ae21e0b0a1 100644 --- a/crates/rust/lang/Cargo.toml +++ b/crates/rust/lang/Cargo.toml @@ -9,7 +9,7 @@ moon_lang = { path = "../../core/lang" } moon_logger = { path = "../../core/logger" } cached = { workspace = true } cargo-lock = "9.0.0" -cargo_toml = "0.16.2" +cargo_toml = "0.16.3" miette = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } diff --git a/nextgen/action-graph/Cargo.toml b/nextgen/action-graph/Cargo.toml new file mode 100644 index 00000000000..f46608af2df --- /dev/null +++ b/nextgen/action-graph/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "moon_action_graph" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Dependency graph for actions (tasks)." +homepage = "https://moonrepo.dev/moon" +repository = "https://github.com/moonrepo/moon" + +[dependencies] +moon_common = { path = "../common" } +# TODO remove +moon_platform = { path = "../../crates/core/platform" } +moon_platform_runtime = { path = "../platform-runtime" } +moon_project = { path = "../project" } +moon_project_graph = { path = "../project-graph" } +moon_task = { path = "../task" } +moon_query = { path = "../query" } +miette = { workspace = true } +petgraph = { workspace = true } +rustc-hash = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } + +[dev-dependencies] +moon_config = { path = "../config" } +moon_test_utils2 = { path = "../test-utils" } +starbase_sandbox = { workspace = true } diff --git a/nextgen/action-graph/src/action_graph.rs b/nextgen/action-graph/src/action_graph.rs new file mode 100644 index 00000000000..7723bc352c5 --- /dev/null +++ b/nextgen/action-graph/src/action_graph.rs @@ -0,0 +1,186 @@ +use crate::action_graph_error::ActionGraphError; +use crate::action_node::ActionNode; +use moon_common::is_test_env; +use petgraph::dot::{Config, Dot}; +use petgraph::prelude::*; +use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences}; +use rustc_hash::FxHashSet; +use std::sync::{mpsc, Arc, RwLock}; +use std::thread::spawn; +use tracing::{debug, trace}; + +pub type GraphType = DiGraph; + +pub struct ActionGraph { + graph: GraphType, +} + +impl ActionGraph { + pub fn new(graph: GraphType) -> Self { + debug!("Creating action graph"); + + ActionGraph { graph } + } + + pub fn try_iter(&self) -> miette::Result { + ActionGraphIter::new(&self.graph) + } + + pub fn is_empty(&self) -> bool { + self.get_node_count() == 0 + } + + pub fn get_node_count(&self) -> usize { + self.graph.node_count() + } + + pub fn get_node_from_index(&self, index: &NodeIndex) -> Option<&ActionNode> { + self.graph.node_weight(*index) + } + + pub fn labeled_graph(&self) -> DiGraph { + self.graph.map(|_, n| n.label(), |_, _| String::new()) + } + + pub fn to_dot(&self) -> String { + type DotGraph = DiGraph; + + let is_test = is_test_env() || cfg!(debug_assertions); + let graph = self.graph.map(|_, n| n.label(), |_, _| ()); + + let edge = |_: &DotGraph, e: <&DotGraph as IntoEdgeReferences>::EdgeRef| { + if is_test { + String::new() + } else if e.source().index() == 0 { + String::from("arrowhead=none") + } else { + String::from("arrowhead=box, arrowtail=box") + } + }; + + let node = |_: &DotGraph, n: <&DotGraph as IntoNodeReferences>::NodeRef| { + if is_test { + format!("label=\"{}\" ", n.1) + } else { + format!( + "label=\"{}\" style=filled, shape=oval, fillcolor=gray, fontcolor=black ", + n.1 + ) + } + }; + + let dot = Dot::with_attr_getters( + &graph, + &[Config::EdgeNoLabel, Config::NodeNoLabel], + &edge, + &node, + ); + + format!("{dot:?}") + } +} + +pub struct ActionGraphIter<'graph> { + graph: &'graph GraphType, + indices: Vec, + visited: FxHashSet, + completed: Arc>>, + + pub receiver: Option>, + pub sender: mpsc::Sender, +} + +impl<'graph> ActionGraphIter<'graph> { + pub fn new(graph: &'graph GraphType) -> miette::Result { + match petgraph::algo::toposort(graph, None) { + Ok(mut indices) => { + indices.reverse(); + + debug!( + order = ?indices.iter().map(|i| i.index()).collect::>(), + "Iterating action graph topologically", + ); + + let (sender, receiver) = mpsc::channel(); + + Ok(Self { + graph, + indices, + visited: FxHashSet::default(), + completed: Arc::new(RwLock::new(FxHashSet::default())), + receiver: Some(receiver), + sender, + }) + } + Err(cycle) => Err(ActionGraphError::CycleDetected( + graph + .node_weight(cycle.node_id()) + .map(|n| n.label()) + .unwrap_or_else(|| "(unknown)".into()), + ) + .into()), + } + } + + pub fn has_pending(&self) -> bool { + self.completed.read().unwrap().len() < self.graph.node_count() + } + + pub fn mark_completed(&mut self, index: NodeIndex) { + self.completed.write().unwrap().insert(index); + } + + pub fn monitor_completed(&mut self) { + let completed = Arc::clone(&self.completed); + let receiver = self.receiver.take().unwrap(); + + spawn(move || { + while let Ok(idx) = receiver.recv() { + let index = NodeIndex::new(idx); + + trace!(index = index.index(), "Marking action as complete"); + + completed.write().unwrap().insert(index); + } + }); + } +} + +// This is based on the `Topo` struct from petgraph! +impl<'graph> Iterator for ActionGraphIter<'graph> { + type Item = NodeIndex; + + fn next(&mut self) -> Option { + let completed = self.completed.read().unwrap(); + + for idx in &self.indices { + if self.visited.contains(idx) || completed.contains(idx) { + continue; + } + + // Ensure all dependencies of the index have completed + let mut deps = vec![]; + + if self + .graph + .neighbors_directed(*idx, Direction::Outgoing) + .all(|dep| { + deps.push(dep.index()); + completed.contains(&dep) + }) + { + self.visited.insert(*idx); + + trace!( + index = idx.index(), + deps = ?deps, + "Running action", + ); + + return Some(*idx); + } + } + + None + } +} diff --git a/nextgen/action-graph/src/action_graph_builder.rs b/nextgen/action-graph/src/action_graph_builder.rs new file mode 100644 index 00000000000..3dba99fe2c6 --- /dev/null +++ b/nextgen/action-graph/src/action_graph_builder.rs @@ -0,0 +1,428 @@ +use crate::action_graph::ActionGraph; +use crate::action_node::ActionNode; +use moon_common::{color, path::WorkspaceRelativePathBuf}; +use moon_platform::{PlatformManager, Runtime}; +use moon_project::Project; +use moon_project_graph::ProjectGraph; +use moon_query::{build_query, Criteria}; +use moon_task::{Target, TargetError, TargetLocator, TargetScope, Task}; +use petgraph::prelude::*; +use rustc_hash::{FxHashMap, FxHashSet}; +use tracing::{debug, trace}; + +type TouchedFilePaths = FxHashSet; + +pub struct ActionGraphBuilder<'app> { + all_query: Option, + dependents: bool, + graph: DiGraph, + indices: FxHashMap, + platform_manager: &'app PlatformManager, + project_graph: &'app ProjectGraph, +} + +impl<'app> ActionGraphBuilder<'app> { + pub fn new(project_graph: &'app ProjectGraph) -> miette::Result { + ActionGraphBuilder::with_platforms(PlatformManager::read(), project_graph) + } + + pub fn with_platforms( + platform_manager: &'app PlatformManager, + project_graph: &'app ProjectGraph, + ) -> miette::Result { + debug!("Building action graph"); + + Ok(ActionGraphBuilder { + all_query: None, + graph: DiGraph::new(), + dependents: false, + indices: FxHashMap::default(), + platform_manager, + project_graph, + }) + } + + pub fn build(self) -> miette::Result { + Ok(ActionGraph::new(self.graph)) + } + + pub fn get_index_from_node(&self, node: &ActionNode) -> Option<&NodeIndex> { + self.indices.get(node) + } + + pub fn get_runtime( + &self, + project: &Project, + task: Option<&Task>, + allow_override: bool, + ) -> Runtime { + if let Some(platform) = self.platform_manager.find(|p| match task { + Some(task) => p.matches(&task.platform, None), + None => p.matches(&project.platform, None), + }) { + return platform.get_runtime_from_config(if allow_override { + Some(&project.config) + } else { + None + }); + } + + Runtime::system() + } + + pub fn include_dependents(&mut self) { + self.dependents = true; + } + + pub fn set_query(&mut self, input: &str) -> miette::Result<()> { + self.all_query = Some(build_query(input)?); + + Ok(()) + } + + // ACTIONS + + pub fn install_deps( + &mut self, + project: &Project, + task: Option<&Task>, + ) -> miette::Result> { + let mut in_project = false; + + // If project is NOT in the package manager workspace, then we should + // install dependencies in the project, not the workspace root. + if let Ok(platform) = self.platform_manager.get(project.language.clone()) { + if !platform.is_project_in_dependency_workspace(project.source.as_str())? { + in_project = true; + + debug!( + "Project {} is not within the dependency manager workspace, dependencies will be installed within the project instead of the root", + color::id(&project.id), + ); + } + } + + let node = if in_project { + ActionNode::InstallProjectDeps { + project: project.id.to_owned(), + runtime: self.get_runtime(project, task, true), + } + } else { + ActionNode::InstallDeps { + runtime: self.get_runtime(project, task, false), + } + }; + + if node.get_runtime().platform.is_system() { + return Ok(None); + } + + if let Some(index) = self.get_index_from_node(&node) { + return Ok(Some(*index)); + } + + // Before we install deps, we must ensure the language has been installed + let setup_tool_index = self.setup_tool(node.get_runtime()); + let index = self.insert_node(node); + + self.link_requirements(index, vec![setup_tool_index]); + + Ok(Some(index)) + } + + pub fn run_task( + &mut self, + project: &Project, + task: &Task, + touched_files: Option<&TouchedFilePaths>, + ) -> miette::Result> { + let node = ActionNode::RunTask { + interactive: task.is_interactive(), + persistent: task.is_persistent(), + runtime: self.get_runtime(project, Some(task), true), + target: task.target.to_owned(), + }; + + if let Some(index) = self.get_index_from_node(&node) { + return Ok(Some(*index)); + } + + // Compare against touched files if provided + if let Some(touched) = touched_files { + if !task.is_affected(touched)? { + return Ok(None); + } + } + + // We should install deps & sync projects *before* running targets + let mut reqs = vec![]; + + if let Some(install_deps_index) = self.install_deps(project, Some(task))? { + reqs.push(install_deps_index); + } + + reqs.push(self.sync_project(project)?); + + let index = self.insert_node(node); + + // And we also need to create edges for task dependencies + if !task.deps.is_empty() { + trace!( + task = task.target.as_str(), + deps = ?task.deps.iter().map(|d| d.as_str()).collect::>(), + "Linking dependencies for task", + ); + + reqs.extend(self.run_task_dependencies(task)?); + } + + self.link_requirements(index, reqs); + + // And possibly dependents + if self.dependents { + self.run_task_dependents(task)?; + } + + Ok(Some(index)) + } + + // We don't pass touched files to dependencies, because if the parent + // task is affected/going to run, then so should all of these! + pub fn run_task_dependencies(&mut self, task: &Task) -> miette::Result> { + let parallel = task.options.run_deps_in_parallel; + let mut indices = vec![]; + let mut previous_target_index = None; + + for dep_target in &task.deps { + let (_, dep_indices) = self.run_task_by_target(dep_target, None)?; + + for dep_index in dep_indices { + // When parallel, parent depends on child + if parallel { + indices.push(dep_index); + + // When serial, next child depends on previous child + } else if let Some(prev) = previous_target_index { + self.link_requirements(dep_index, vec![prev]); + } + + previous_target_index = Some(dep_index); + } + } + + if !parallel { + indices.push(previous_target_index.unwrap()); + } + + Ok(indices) + } + + // This is costly, is there a better way to do this? + pub fn run_task_dependents(&mut self, task: &Task) -> miette::Result> { + let mut indices = vec![]; + + if let TargetScope::Project(project_locator) = &task.target.scope { + let project = self.project_graph.get(project_locator)?; + + // From self project + for dep_task in project.tasks.values() { + if dep_task.deps.contains(&task.target) { + if dep_task.is_persistent() { + continue; + } + + if let Some(index) = self.run_task(&project, dep_task, None)? { + indices.push(index); + } + } + } + + // From other projects + for dependent_id in self.project_graph.dependents_of(&project)? { + let dep_project = self.project_graph.get(dependent_id)?; + + for dep_task in dep_project.tasks.values() { + if dep_task.is_persistent() { + continue; + } + + if dep_task.deps.contains(&task.target) { + if let Some(index) = self.run_task(&dep_project, dep_task, None)? { + indices.push(index); + } + } + } + } + } + + Ok(indices) + } + + pub fn run_task_by_target>( + &mut self, + target: T, + touched_files: Option<&TouchedFilePaths>, + ) -> miette::Result<(FxHashSet, FxHashSet)> { + let target = target.as_ref(); + let mut inserted_targets = FxHashSet::default(); + let mut inserted_indices = FxHashSet::default(); + + match &target.scope { + // :task + TargetScope::All => { + let mut projects = vec![]; + + if let Some(all_query) = &self.all_query { + projects.extend(self.project_graph.query(all_query)?); + } else { + projects.extend(self.project_graph.get_all()?); + }; + + 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 Some(index) = self.run_task(&project, task, touched_files)? { + inserted_targets.insert(task.target.clone()); + inserted_indices.insert(index); + } + } + } + } + // ^:task + TargetScope::Deps => { + return Err(TargetError::NoDepsInRunContext.into()); + } + // project:task + TargetScope::Project(project_locator) => { + let project = self.project_graph.get(project_locator)?; + let task = project.get_task(&target.task_id)?; + + if let Some(index) = self.run_task(&project, task, touched_files)? { + inserted_targets.insert(task.target.to_owned()); + inserted_indices.insert(index); + } + } + // #tag:task + TargetScope::Tag(tag) => { + let projects = self + .project_graph + .query(build_query(format!("tag={}", tag))?)?; + + 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 Some(index) = self.run_task(&project, task, touched_files)? { + inserted_targets.insert(task.target.clone()); + inserted_indices.insert(index); + } + } + } + } + // ~:task + TargetScope::OwnSelf => { + return Err(TargetError::NoSelfInRunContext.into()); + } + }; + + Ok((inserted_targets, inserted_indices)) + } + + pub fn run_task_by_target_locator>( + &mut self, + target_locator: T, + touched_files: Option<&TouchedFilePaths>, + ) -> miette::Result<(FxHashSet, FxHashSet)> { + match target_locator.as_ref() { + TargetLocator::Qualified(target) => self.run_task_by_target(target, touched_files), + TargetLocator::TaskFromWorkingDir(task_id) => self.run_task_by_target( + Target::new(&self.project_graph.get_from_path(None)?.id, task_id)?, + touched_files, + ), + } + } + + pub fn setup_tool(&mut self, runtime: &Runtime) -> NodeIndex { + let node = ActionNode::SetupTool { + runtime: runtime.to_owned(), + }; + + if let Some(index) = self.get_index_from_node(&node) { + return *index; + } + + let sync_workspace_index = self.sync_workspace(); + let index = self.insert_node(node); + + self.link_requirements(index, vec![sync_workspace_index]); + + index + } + + pub fn sync_project(&mut self, project: &Project) -> miette::Result { + let node = ActionNode::SyncProject { + project: project.id.clone(), + runtime: self.get_runtime(project, None, true), + }; + + if let Some(index) = self.get_index_from_node(&node) { + return Ok(*index); + } + + // Syncing requires the language's tool to be installed + let setup_tool_index = self.setup_tool(node.get_runtime()); + let index = self.insert_node(node); + let mut reqs = vec![setup_tool_index]; + + // And we should also depend on other projects + for dep_project_id in self.project_graph.dependencies_of(project)? { + let dep_project = self.project_graph.get(dep_project_id)?; + let dep_project_index = self.sync_project(&dep_project)?; + + if index != dep_project_index { + reqs.push(dep_project_index); + } + } + + self.link_requirements(index, reqs); + + Ok(index) + } + + pub fn sync_workspace(&mut self) -> NodeIndex { + let node = ActionNode::SyncWorkspace; + + if let Some(index) = self.get_index_from_node(&node) { + return *index; + } + + self.insert_node(node) + } + + // PRIVATE + + fn link_requirements(&mut self, index: NodeIndex, reqs: Vec) { + trace!( + index = index.index(), + requires = ?reqs.iter().map(|i| i.index()).collect::>(), + "Linking requirements for index" + ); + + for req in reqs { + self.graph.add_edge(index, req, ()); + } + } + + fn insert_node(&mut self, node: ActionNode) -> NodeIndex { + let index = self.graph.add_node(node.clone()); + + debug!( + index = index.index(), + "Adding {} to graph", + color::muted_light(node.label()) + ); + + self.indices.insert(node, index); + + index + } +} diff --git a/nextgen/action-graph/src/action_graph_error.rs b/nextgen/action-graph/src/action_graph_error.rs new file mode 100644 index 00000000000..59e37ddd11e --- /dev/null +++ b/nextgen/action-graph/src/action_graph_error.rs @@ -0,0 +1,9 @@ +use miette::Diagnostic; +use moon_common::{Style, Stylize}; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +pub enum ActionGraphError { + #[error("A dependency cycle has been detected for {}.", .0.style(Style::Label))] + CycleDetected(String), +} diff --git a/nextgen/action-graph/src/action_node.rs b/nextgen/action-graph/src/action_node.rs new file mode 100644 index 00000000000..8c15a04ba7b --- /dev/null +++ b/nextgen/action-graph/src/action_node.rs @@ -0,0 +1,108 @@ +use moon_common::Id; +use moon_platform_runtime::Runtime; +use moon_task::Target; +use serde::Serialize; +use std::hash::{Hash, Hasher}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +#[serde(tag = "action", content = "params")] +pub enum ActionNode { + /// Install tool dependencies in the workspace root. + InstallDeps { runtime: Runtime }, + + /// Install tool dependencies in the project root. + InstallProjectDeps { project: Id, runtime: Runtime }, + + /// Run a project's task. + RunTask { + interactive: bool, // Interactively with stdin + persistent: bool, // Never terminates + runtime: Runtime, + target: Target, + }, + + /// Setup a tool + version for the provided platform. + SetupTool { runtime: Runtime }, + + /// Sync a project with language specific semantics. + SyncProject { project: Id, runtime: Runtime }, + + /// Sync the entire moon workspace. + /// Install system dependencies. + SyncWorkspace, +} + +impl ActionNode { + pub fn get_runtime(&self) -> &Runtime { + match self { + Self::InstallDeps { runtime } => runtime, + Self::InstallProjectDeps { runtime, .. } => runtime, + Self::RunTask { runtime, .. } => runtime, + Self::SetupTool { runtime } => runtime, + Self::SyncProject { runtime, .. } => runtime, + Self::SyncWorkspace => unreachable!(), + } + } + + pub fn is_interactive(&self) -> bool { + match self { + Self::RunTask { interactive, .. } => *interactive, + _ => false, + } + } + + pub fn is_persistent(&self) -> bool { + match self { + Self::RunTask { persistent, .. } => *persistent, + _ => false, + } + } + + pub fn label(&self) -> String { + match self { + Self::InstallDeps { runtime } => { + format!("Install{runtime}Deps({})", runtime.requirement) + } + Self::InstallProjectDeps { runtime, project } => { + format!( + "Install{runtime}DepsInProject({}, {project})", + runtime.requirement + ) + } + Self::RunTask { + interactive, + persistent, + target, + .. + } => { + format!( + "Run{}Task({target})", + if *persistent { + "Persistent" + } else if *interactive { + "Interactive" + } else { + "" + } + ) + } + Self::SetupTool { runtime } => { + if runtime.platform.is_system() { + "SetupSystemTool".into() + } else { + format!("Setup{runtime}Tool({})", runtime.requirement) + } + } + Self::SyncProject { runtime, project } => { + format!("Sync{runtime}Project({project})") + } + Self::SyncWorkspace => "SyncWorkspace".into(), + } + } +} + +impl Hash for ActionNode { + fn hash(&self, state: &mut H) { + self.label().hash(state); + } +} diff --git a/nextgen/action-graph/src/lib.rs b/nextgen/action-graph/src/lib.rs new file mode 100644 index 00000000000..913205c5436 --- /dev/null +++ b/nextgen/action-graph/src/lib.rs @@ -0,0 +1,9 @@ +mod action_graph; +mod action_graph_builder; +mod action_graph_error; +mod action_node; + +pub use action_graph::*; +pub use action_graph_builder::*; +pub use action_graph_error::*; +pub use action_node::*; diff --git a/nextgen/action-graph/tests/__fixtures__/dep-workspace/.moon/toolchain.yml b/nextgen/action-graph/tests/__fixtures__/dep-workspace/.moon/toolchain.yml new file mode 100644 index 00000000000..88c6fbe7e3e --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/dep-workspace/.moon/toolchain.yml @@ -0,0 +1,2 @@ +node: + version: '20.0.0' diff --git a/nextgen/action-graph/tests/__fixtures__/dep-workspace/in/moon.yml b/nextgen/action-graph/tests/__fixtures__/dep-workspace/in/moon.yml new file mode 100644 index 00000000000..f1de67fbd7f --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/dep-workspace/in/moon.yml @@ -0,0 +1 @@ +language: javascript diff --git a/nextgen/action-graph/tests/__fixtures__/dep-workspace/out/moon.yml b/nextgen/action-graph/tests/__fixtures__/dep-workspace/out/moon.yml new file mode 100644 index 00000000000..30d34adef1c --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/dep-workspace/out/moon.yml @@ -0,0 +1 @@ +language: typescript diff --git a/nextgen/action-graph/tests/__fixtures__/dep-workspace/package.json b/nextgen/action-graph/tests/__fixtures__/dep-workspace/package.json new file mode 100644 index 00000000000..ab0250ab062 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/dep-workspace/package.json @@ -0,0 +1,3 @@ +{ + "workspaces": ["in"] +} diff --git a/nextgen/action-graph/tests/__fixtures__/projects/.moon/toolchain.yml b/nextgen/action-graph/tests/__fixtures__/projects/.moon/toolchain.yml new file mode 100644 index 00000000000..edf14aa87de --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/projects/.moon/toolchain.yml @@ -0,0 +1,6 @@ +node: + version: '20.0.0' + packageManager: 'npm' + +rust: + version: '1.70.0' diff --git a/nextgen/action-graph/tests/__fixtures__/projects/bar/moon.yml b/nextgen/action-graph/tests/__fixtures__/projects/bar/moon.yml new file mode 100644 index 00000000000..f1de67fbd7f --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/projects/bar/moon.yml @@ -0,0 +1 @@ +language: javascript diff --git a/nextgen/action-graph/tests/__fixtures__/projects/baz/moon.yml b/nextgen/action-graph/tests/__fixtures__/projects/baz/moon.yml new file mode 100644 index 00000000000..0a620fec5d0 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/projects/baz/moon.yml @@ -0,0 +1,5 @@ +language: typescript + +toolchain: + node: + version: '18.0.0' diff --git a/nextgen/action-graph/tests/__fixtures__/projects/foo/moon.yml b/nextgen/action-graph/tests/__fixtures__/projects/foo/moon.yml new file mode 100644 index 00000000000..38118b204e1 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/projects/foo/moon.yml @@ -0,0 +1 @@ +dependsOn: [bar] diff --git a/nextgen/action-graph/tests/__fixtures__/projects/package.json b/nextgen/action-graph/tests/__fixtures__/projects/package.json new file mode 100644 index 00000000000..b203b4c6c45 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/projects/package.json @@ -0,0 +1,3 @@ +{ + "workspaces": ["*"] +} diff --git a/nextgen/action-graph/tests/__fixtures__/projects/qux/moon.yml b/nextgen/action-graph/tests/__fixtures__/projects/qux/moon.yml new file mode 100644 index 00000000000..22761ba7ee1 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/projects/qux/moon.yml @@ -0,0 +1 @@ +language: rust diff --git a/nextgen/action-graph/tests/__fixtures__/tasks/base/moon.yml b/nextgen/action-graph/tests/__fixtures__/tasks/base/moon.yml new file mode 100644 index 00000000000..606e4cef104 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/tasks/base/moon.yml @@ -0,0 +1,3 @@ +tasks: + build: + command: 'build' diff --git a/nextgen/action-graph/tests/__fixtures__/tasks/client/moon.yml b/nextgen/action-graph/tests/__fixtures__/tasks/client/moon.yml new file mode 100644 index 00000000000..59d5f8f9696 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/tasks/client/moon.yml @@ -0,0 +1,14 @@ +language: 'javascript' + +dependsOn: ['common', 'server'] + +tags: ['frontend'] + +tasks: + build: + command: 'build' + deps: ['^:build'] + lint: + command: 'lint' + test: + command: 'test' diff --git a/nextgen/action-graph/tests/__fixtures__/tasks/common/moon.yml b/nextgen/action-graph/tests/__fixtures__/tasks/common/moon.yml new file mode 100644 index 00000000000..6a36e76ede4 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/tasks/common/moon.yml @@ -0,0 +1,9 @@ +dependsOn: ['base'] + +tags: ['frontend'] + +tasks: + build: + command: 'build' + lint: + command: 'lint' diff --git a/nextgen/action-graph/tests/__fixtures__/tasks/deps-external/moon.yml b/nextgen/action-graph/tests/__fixtures__/tasks/deps-external/moon.yml new file mode 100644 index 00000000000..bd188ab4774 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/tasks/deps-external/moon.yml @@ -0,0 +1,3 @@ +tasks: + external: + deps: ['deps:base'] diff --git a/nextgen/action-graph/tests/__fixtures__/tasks/deps/moon.yml b/nextgen/action-graph/tests/__fixtures__/tasks/deps/moon.yml new file mode 100644 index 00000000000..0bead8f5582 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/tasks/deps/moon.yml @@ -0,0 +1,31 @@ +tasks: + base: {} + + a: + command: 'a' + b: + command: 'b' + c: + command: 'd' + + parallel: + deps: [c, a, b] + + serial: + deps: [b, c, a] + options: + runDepsInParallel: false + + chain1: + deps: ['chain2'] + chain2: + deps: ['chain3'] + chain3: {} + + internal: + deps: ['base'] + + cycle1: + deps: ['cycle2'] + cycle2: + deps: ['cycle1'] diff --git a/nextgen/action-graph/tests/__fixtures__/tasks/server/moon.yml b/nextgen/action-graph/tests/__fixtures__/tasks/server/moon.yml new file mode 100644 index 00000000000..1cdb6dfa0c2 --- /dev/null +++ b/nextgen/action-graph/tests/__fixtures__/tasks/server/moon.yml @@ -0,0 +1,10 @@ +language: 'rust' + +tasks: + build: + command: 'build' + deps: ['^:build'] + lint: + command: 'lint' + test: + command: 'test' diff --git a/nextgen/action-graph/tests/action_graph_test.rs b/nextgen/action-graph/tests/action_graph_test.rs new file mode 100644 index 00000000000..9bf27f8eee7 --- /dev/null +++ b/nextgen/action-graph/tests/action_graph_test.rs @@ -0,0 +1,1009 @@ +#![allow(clippy::disallowed_names)] + +mod utils; + +use moon_action_graph::*; +use moon_common::path::WorkspaceRelativePathBuf; +use moon_common::Id; +use moon_platform_runtime::*; +use moon_project_graph::ProjectGraph; +use moon_task::{Target, TargetLocator, Task}; +use moon_test_utils2::generate_project_graph; +use rustc_hash::FxHashSet; +use starbase_sandbox::{assert_snapshot, create_sandbox}; +use utils::ActionGraphContainer; + +fn create_task(id: &str, project: &str) -> Task { + Task { + id: Id::raw(id), + target: Target::new(project, id).unwrap(), + ..Task::default() + } +} + +async fn create_project_graph() -> ProjectGraph { + generate_project_graph("projects").await +} + +fn create_node_runtime() -> Runtime { + Runtime::new( + PlatformType::Node, + RuntimeReq::with_version(Version::new(20, 0, 0)), + ) +} + +fn create_rust_runtime() -> Runtime { + Runtime::new( + PlatformType::Rust, + RuntimeReq::with_version(Version::new(1, 70, 0)), + ) +} + +fn topo(graph: ActionGraph) -> Vec { + let mut nodes = vec![]; + let mut iter = graph.try_iter().unwrap(); + + while iter.has_pending() { + if let Some(index) = iter.next() { + nodes.push(graph.get_node_from_index(&index).unwrap().to_owned()); + iter.mark_completed(index); + } + } + + nodes +} + +mod action_graph { + use super::*; + + #[tokio::test] + #[should_panic(expected = "A dependency cycle has been detected for RunTask(deps:cycle2).")] + async fn errors_on_cycle() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let project = container.project_graph.get("deps").unwrap(); + + builder + .run_task(&project, project.get_task("cycle1").unwrap(), None) + .unwrap(); + builder + .run_task(&project, project.get_task("cycle2").unwrap(), None) + .unwrap(); + + builder.build().unwrap().try_iter().unwrap(); + } + + mod install_deps { + use super::*; + + #[tokio::test] + async fn graphs() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let bar = container.project_graph.get("bar").unwrap(); + builder.install_deps(&bar, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: create_node_runtime() + }, + ActionNode::InstallDeps { + runtime: create_node_runtime() + } + ] + ); + } + + #[tokio::test] + async fn ignores_dupes() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let bar = container.project_graph.get("bar").unwrap(); + builder.install_deps(&bar, None).unwrap(); + builder.install_deps(&bar, None).unwrap(); + builder.install_deps(&bar, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: create_node_runtime() + }, + ActionNode::InstallDeps { + runtime: create_node_runtime() + } + ] + ); + } + + #[tokio::test] + async fn installs_in_project_when_not_in_depman_workspace() { + let sandbox = create_sandbox("dep-workspace"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let inside = container.project_graph.get("in").unwrap(); + builder.install_deps(&inside, None).unwrap(); + + let outside = container.project_graph.get("out").unwrap(); + builder.install_deps(&outside, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: create_node_runtime() + }, + ActionNode::InstallDeps { + runtime: create_node_runtime() + }, + ActionNode::InstallProjectDeps { + project: Id::raw("out"), + runtime: create_node_runtime() + }, + ] + ); + } + } + + mod run_task { + use super::*; + + #[tokio::test] + async fn graphs() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let project = container.project_graph.get("bar").unwrap(); + + let mut task = create_task("build", "bar"); + task.platform = PlatformType::Node; + + builder.run_task(&project, &task, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: create_node_runtime() + }, + ActionNode::InstallDeps { + runtime: create_node_runtime() + }, + ActionNode::SyncProject { + project: Id::raw("bar"), + runtime: create_node_runtime() + }, + ActionNode::RunTask { + interactive: false, + persistent: false, + runtime: create_node_runtime(), + target: task.target + } + ] + ); + } + + #[tokio::test] + async fn ignores_dupes() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let project = container.project_graph.get("bar").unwrap(); + + let mut task = create_task("build", "bar"); + task.platform = PlatformType::Node; + + builder.run_task(&project, &task, None).unwrap(); + builder.run_task(&project, &task, None).unwrap(); + builder.run_task(&project, &task, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: create_node_runtime() + }, + ActionNode::InstallDeps { + runtime: create_node_runtime() + }, + ActionNode::SyncProject { + project: Id::raw("bar"), + runtime: create_node_runtime() + }, + ActionNode::RunTask { + interactive: false, + persistent: false, + runtime: create_node_runtime(), + target: task.target + } + ] + ); + } + + #[tokio::test] + async fn doesnt_graph_if_not_affected_by_touched_files() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let project = container.project_graph.get("bar").unwrap(); + + let mut task = create_task("build", "bar"); + task.platform = PlatformType::Node; + + builder + // Empty set works fine, just needs to be some + .run_task(&project, &task, Some(&FxHashSet::default())) + .unwrap(); + + let graph = builder.build().unwrap(); + + assert!(topo(graph).is_empty()); + } + + #[tokio::test] + async fn graphs_if_affected_by_touched_files() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let file = WorkspaceRelativePathBuf::from("bar/file.js"); + + let project = container.project_graph.get("bar").unwrap(); + + let mut task = create_task("build", "bar"); + task.platform = PlatformType::Node; + task.input_files.insert(file.clone()); + + builder + .run_task(&project, &task, Some(&FxHashSet::from_iter([file]))) + .unwrap(); + + let graph = builder.build().unwrap(); + + assert!(!topo(graph).is_empty()); + } + + #[tokio::test] + async fn task_can_have_a_diff_platform_from_project() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + // node + let project = container.project_graph.get("bar").unwrap(); + + let mut task = create_task("build", "bar"); + task.platform = PlatformType::Rust; + + builder.run_task(&project, &task, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: create_rust_runtime() + }, + ActionNode::InstallDeps { + runtime: create_rust_runtime() + }, + ActionNode::SetupTool { + runtime: create_node_runtime() + }, + ActionNode::SyncProject { + project: Id::raw("bar"), + runtime: create_node_runtime() + }, + ActionNode::RunTask { + interactive: false, + persistent: false, + runtime: create_rust_runtime(), + target: task.target + } + ] + ); + } + + #[tokio::test] + async fn sets_interactive() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let project = container.project_graph.get("bar").unwrap(); + + let mut task = create_task("build", "bar"); + task.options.interactive = true; + + builder.run_task(&project, &task, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_eq!( + topo(graph).last().unwrap(), + &ActionNode::RunTask { + interactive: true, + persistent: false, + runtime: Runtime::system(), + target: task.target + } + ); + } + + #[tokio::test] + async fn sets_persistent() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let project = container.project_graph.get("bar").unwrap(); + + let mut task = create_task("build", "bar"); + task.options.persistent = true; + + builder.run_task(&project, &task, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_eq!( + topo(graph).last().unwrap(), + &ActionNode::RunTask { + interactive: false, + persistent: true, + runtime: Runtime::system(), + target: task.target + } + ); + } + } + + mod run_task_dependencies { + use super::*; + + #[tokio::test] + async fn runs_deps_in_parallel() { + let sandbox = create_sandbox("tasks"); + 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(); + + builder.run_task(&project, task, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + } + + #[tokio::test] + async fn runs_deps_in_serial() { + let sandbox = create_sandbox("tasks"); + 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(); + + builder.run_task(&project, task, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + } + + #[tokio::test] + async fn can_create_a_chain() { + let sandbox = create_sandbox("tasks"); + 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(); + + builder.run_task(&project, task, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + } + + #[tokio::test] + async fn doesnt_include_dependents() { + let sandbox = create_sandbox("tasks"); + 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(); + + builder.run_task(&project, task, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + } + + #[tokio::test] + async fn includes_dependents() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder.include_dependents(); + + let project = container.project_graph.get("deps").unwrap(); + let task = project.get_task("base").unwrap(); + + builder.run_task(&project, task, None).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + } + } + + mod run_task_by_target { + use super::*; + + #[tokio::test] + #[should_panic(expected = "Dependencies scope (^:) is not supported in run contexts.")] + async fn errors_on_parent_scope() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder + .run_task_by_target(Target::parse("^:build").unwrap(), None) + .unwrap(); + } + + #[tokio::test] + #[should_panic(expected = "Self scope (~:) is not supported in run contexts.")] + async fn errors_on_self_scope() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder + .run_task_by_target(Target::parse("~:build").unwrap(), None) + .unwrap(); + } + + #[tokio::test] + async fn runs_all() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder + .run_task_by_target(Target::parse(":build").unwrap(), None) + .unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + } + + #[tokio::test] + async fn runs_all_with_query() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder.set_query("language=rust").unwrap(); + + builder + .run_task_by_target(Target::parse(":build").unwrap(), None) + .unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + } + + #[tokio::test] + async fn runs_all_no_nodes() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder + .run_task_by_target(Target::parse(":unknown").unwrap(), None) + .unwrap(); + + let graph = builder.build().unwrap(); + + assert!(graph.is_empty()); + } + + #[tokio::test] + async fn runs_by_project() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder + .run_task_by_target(Target::parse("client:lint").unwrap(), None) + .unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + } + + #[tokio::test] + #[should_panic(expected = "No project has been configured with the name or alias unknown.")] + async fn errors_for_unknown_project() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder + .run_task_by_target(Target::parse("unknown:build").unwrap(), None) + .unwrap(); + } + + #[tokio::test] + #[should_panic(expected = "Unknown task unknown for project server.")] + async fn errors_for_unknown_project_task() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder + .run_task_by_target(Target::parse("server:unknown").unwrap(), None) + .unwrap(); + } + + #[tokio::test] + async fn runs_tag() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder + .run_task_by_target(Target::parse("#frontend:lint").unwrap(), None) + .unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + } + + #[tokio::test] + async fn runs_tag_no_nodes() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder + .run_task_by_target(Target::parse("#unknown:lint").unwrap(), None) + .unwrap(); + + let graph = builder.build().unwrap(); + + assert!(graph.is_empty()); + } + } + + mod run_task_by_target_locator { + use super::*; + + #[tokio::test] + async fn runs_by_target() { + let sandbox = create_sandbox("tasks"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + builder + .run_task_by_target_locator( + TargetLocator::Qualified(Target::parse("server:build").unwrap()), + None, + ) + .unwrap(); + + let graph = builder.build().unwrap(); + + 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_task_by_target_locator( + TargetLocator::TaskFromWorkingDir(Id::raw("lint")), + None, + ) + .unwrap(); + + let graph = builder.build().unwrap(); + + 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_task_by_target_locator( + TargetLocator::TaskFromWorkingDir(Id::raw("lint")), + None, + ) + .unwrap(); + } + } + + mod setup_tool { + use super::*; + + #[tokio::test] + async fn graphs() { + let pg = ProjectGraph::default(); + let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let system = Runtime::system(); + let node = Runtime::new( + PlatformType::Node, + RuntimeReq::with_version(Version::new(1, 2, 3)), + ); + + builder.setup_tool(&system); + builder.setup_tool(&node); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { runtime: system }, + ActionNode::SetupTool { runtime: node }, + ] + ); + } + + #[tokio::test] + async fn graphs_same_platform() { + let pg = ProjectGraph::default(); + let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + + let node1 = Runtime::new( + PlatformType::Node, + RuntimeReq::with_version(Version::new(1, 2, 3)), + ); + let node2 = Runtime::new_override( + PlatformType::Node, + RuntimeReq::with_version(Version::new(4, 5, 6)), + ); + let node3 = Runtime::new(PlatformType::Node, RuntimeReq::Global); + + builder.setup_tool(&node1); + builder.setup_tool(&node2); + builder.setup_tool(&node3); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { runtime: node1 }, + ActionNode::SetupTool { runtime: node2 }, + ActionNode::SetupTool { runtime: node3 }, + ] + ); + } + + #[tokio::test] + async fn ignores_dupes() { + let pg = ProjectGraph::default(); + let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + let system = Runtime::system(); + + builder.setup_tool(&system); + builder.setup_tool(&system); + + let graph = builder.build().unwrap(); + + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { runtime: system }, + ] + ); + } + } + + mod sync_project { + use super::*; + + #[tokio::test] + async fn graphs_single() { + let pg = create_project_graph().await; + let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + + let bar = pg.get("bar").unwrap(); + builder.sync_project(&bar).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: Runtime::system() + }, + ActionNode::SyncProject { + project: Id::raw("bar"), + runtime: Runtime::system() + } + ] + ); + } + + #[tokio::test] + async fn graphs_single_with_dep() { + let pg = create_project_graph().await; + let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + + let foo = pg.get("foo").unwrap(); + builder.sync_project(&foo).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: Runtime::system() + }, + ActionNode::SyncProject { + project: Id::raw("bar"), + runtime: Runtime::system() + }, + ActionNode::SyncProject { + project: Id::raw("foo"), + runtime: Runtime::system() + } + ] + ); + } + + #[tokio::test] + async fn graphs_multiple() { + let pg = create_project_graph().await; + let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + + let foo = pg.get("foo").unwrap(); + builder.sync_project(&foo).unwrap(); + + let bar = pg.get("bar").unwrap(); + builder.sync_project(&bar).unwrap(); + + let qux = pg.get("qux").unwrap(); + builder.sync_project(&qux).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: Runtime::system() + }, + ActionNode::SyncProject { + project: Id::raw("bar"), + runtime: Runtime::system() + }, + ActionNode::SyncProject { + project: Id::raw("foo"), + runtime: Runtime::system() + }, + ActionNode::SyncProject { + project: Id::raw("qux"), + runtime: Runtime::system() + }, + ] + ); + } + + #[tokio::test] + async fn ignores_dupes() { + let pg = create_project_graph().await; + let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + + let foo = pg.get("foo").unwrap(); + + builder.sync_project(&foo).unwrap(); + builder.sync_project(&foo).unwrap(); + builder.sync_project(&foo).unwrap(); + + let graph = builder.build().unwrap(); + + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: Runtime::system() + }, + ActionNode::SyncProject { + project: Id::raw("bar"), + runtime: Runtime::system() + }, + ActionNode::SyncProject { + project: Id::raw("foo"), + runtime: Runtime::system() + } + ] + ); + } + + #[tokio::test] + async fn inherits_platform_tool() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let bar = container.project_graph.get("bar").unwrap(); + builder.sync_project(&bar).unwrap(); + + let qux = container.project_graph.get("qux").unwrap(); + builder.sync_project(&qux).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: create_node_runtime() + }, + ActionNode::SyncProject { + project: Id::raw("bar"), + runtime: create_node_runtime() + }, + ActionNode::SetupTool { + runtime: create_rust_runtime() + }, + ActionNode::SyncProject { + project: Id::raw("qux"), + runtime: create_rust_runtime() + }, + ] + ); + } + + #[tokio::test] + async fn supports_platform_override() { + let sandbox = create_sandbox("projects"); + let container = ActionGraphContainer::new(sandbox.path()).await; + let mut builder = container.create_builder(); + + let bar = container.project_graph.get("bar").unwrap(); + builder.sync_project(&bar).unwrap(); + + let baz = container.project_graph.get("baz").unwrap(); + builder.sync_project(&baz).unwrap(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!( + topo(graph), + vec![ + ActionNode::SyncWorkspace, + ActionNode::SetupTool { + runtime: create_node_runtime() + }, + ActionNode::SyncProject { + project: Id::raw("bar"), + runtime: create_node_runtime() + }, + ActionNode::SetupTool { + runtime: Runtime::new_override( + PlatformType::Node, + RuntimeReq::with_version(Version::new(18, 0, 0)) + ) + }, + ActionNode::SyncProject { + project: Id::raw("baz"), + runtime: Runtime::new_override( + PlatformType::Node, + RuntimeReq::with_version(Version::new(18, 0, 0)) + ) + }, + ] + ); + } + } + + mod sync_workspace { + use super::*; + + #[tokio::test] + async fn graphs() { + let pg = ProjectGraph::default(); + + let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + builder.sync_workspace(); + + let graph = builder.build().unwrap(); + + assert_snapshot!(graph.to_dot()); + assert_eq!(topo(graph), vec![ActionNode::SyncWorkspace]); + } + + #[tokio::test] + async fn ignores_dupes() { + let pg = ProjectGraph::default(); + + let mut builder = ActionGraphBuilder::new(&pg).unwrap(); + builder.sync_workspace(); + builder.sync_workspace(); + builder.sync_workspace(); + + let graph = builder.build().unwrap(); + + assert_eq!(topo(graph), vec![ActionNode::SyncWorkspace]); + } + } +} diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__install_deps__graphs.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__install_deps__graphs.snap new file mode 100644 index 00000000000..10d4bd747e0 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__install_deps__graphs.snap @@ -0,0 +1,12 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(20.0.0)" ] + 2 [ label="InstallNodeDeps(20.0.0)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__install_deps__installs_in_project_when_not_in_depman_workspace.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__install_deps__installs_in_project_when_not_in_depman_workspace.snap new file mode 100644 index 00000000000..3e423cd537e --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__install_deps__installs_in_project_when_not_in_depman_workspace.snap @@ -0,0 +1,14 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(20.0.0)" ] + 2 [ label="InstallNodeDeps(20.0.0)" ] + 3 [ label="InstallNodeDepsInProject(20.0.0, out)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 1 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task__graphs.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task__graphs.snap new file mode 100644 index 00000000000..cb49bf8219b --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task__graphs.snap @@ -0,0 +1,17 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(20.0.0)" ] + 2 [ label="InstallNodeDeps(20.0.0)" ] + 3 [ label="SyncNodeProject(bar)" ] + 4 [ label="RunTask(bar:build)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 1 [ ] + 4 -> 2 [ ] + 4 -> 3 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task__task_can_have_a_diff_platform_from_project.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task__task_can_have_a_diff_platform_from_project.snap new file mode 100644 index 00000000000..38f0da434df --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task__task_can_have_a_diff_platform_from_project.snap @@ -0,0 +1,19 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupRustTool(1.70.0)" ] + 2 [ label="InstallRustDeps(1.70.0)" ] + 3 [ label="SetupNodeTool(20.0.0)" ] + 4 [ label="SyncNodeProject(bar)" ] + 5 [ label="RunTask(bar:build)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 0 [ ] + 4 -> 3 [ ] + 5 -> 2 [ ] + 5 -> 4 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_all.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_all.snap new file mode 100644 index 00000000000..e03fe506210 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_all.snap @@ -0,0 +1,31 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(base)" ] + 3 [ label="RunTask(base:build)" ] + 4 [ label="SyncSystemProject(server)" ] + 5 [ label="RunTask(server:build)" ] + 6 [ label="SyncSystemProject(common)" ] + 7 [ label="RunTask(common:build)" ] + 8 [ label="SyncSystemProject(client)" ] + 9 [ label="RunTask(client:build)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 2 [ ] + 4 -> 1 [ ] + 5 -> 4 [ ] + 6 -> 1 [ ] + 6 -> 2 [ ] + 7 -> 6 [ ] + 8 -> 1 [ ] + 8 -> 4 [ ] + 8 -> 6 [ ] + 9 -> 8 [ ] + 9 -> 7 [ ] + 9 -> 5 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_all_with_query.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_all_with_query.snap new file mode 100644 index 00000000000..6dc1e1b0df3 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_all_with_query.snap @@ -0,0 +1,14 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(server)" ] + 3 [ label="RunTask(server:build)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 2 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_by_project.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_by_project.snap new file mode 100644 index 00000000000..6e78aa95cfd --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_by_project.snap @@ -0,0 +1,23 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(client)" ] + 3 [ label="SyncSystemProject(server)" ] + 4 [ label="SyncSystemProject(common)" ] + 5 [ label="SyncSystemProject(base)" ] + 6 [ label="RunTask(client:lint)" ] + 1 -> 0 [ ] + 3 -> 1 [ ] + 5 -> 1 [ ] + 4 -> 1 [ ] + 4 -> 5 [ ] + 2 -> 1 [ ] + 2 -> 3 [ ] + 2 -> 4 [ ] + 6 -> 2 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_tag.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_tag.snap new file mode 100644 index 00000000000..8943f0f12b9 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target__runs_tag.snap @@ -0,0 +1,25 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(client)" ] + 3 [ label="SyncSystemProject(server)" ] + 4 [ label="SyncSystemProject(common)" ] + 5 [ label="SyncSystemProject(base)" ] + 6 [ label="RunTask(client:lint)" ] + 7 [ label="RunTask(common:lint)" ] + 1 -> 0 [ ] + 3 -> 1 [ ] + 5 -> 1 [ ] + 4 -> 1 [ ] + 4 -> 5 [ ] + 2 -> 1 [ ] + 2 -> 3 [ ] + 2 -> 4 [ ] + 6 -> 2 [ ] + 7 -> 4 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target_locator__runs_by_file_path.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target_locator__runs_by_file_path.snap new file mode 100644 index 00000000000..d074f59cf86 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target_locator__runs_by_file_path.snap @@ -0,0 +1,14 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(server)" ] + 3 [ label="RunTask(server:lint)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 2 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target_locator__runs_by_target.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target_locator__runs_by_target.snap new file mode 100644 index 00000000000..6dc1e1b0df3 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_by_target_locator__runs_by_target.snap @@ -0,0 +1,14 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(server)" ] + 3 [ label="RunTask(server:build)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 2 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__can_create_a_chain.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__can_create_a_chain.snap new file mode 100644 index 00000000000..290953fed5f --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__can_create_a_chain.snap @@ -0,0 +1,20 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(deps)" ] + 3 [ label="RunTask(deps:chain1)" ] + 4 [ label="RunTask(deps:chain2)" ] + 5 [ label="RunTask(deps:chain3)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 5 -> 2 [ ] + 4 -> 2 [ ] + 4 -> 5 [ ] + 3 -> 2 [ ] + 3 -> 4 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__doesnt_include_dependents.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__doesnt_include_dependents.snap new file mode 100644 index 00000000000..7de75775a1d --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__doesnt_include_dependents.snap @@ -0,0 +1,14 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(deps)" ] + 3 [ label="RunTask(deps:base)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 2 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__includes_dependents.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__includes_dependents.snap new file mode 100644 index 00000000000..7375595d48b --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__includes_dependents.snap @@ -0,0 +1,23 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(deps)" ] + 3 [ label="RunTask(deps:base)" ] + 4 [ label="RunTask(deps:internal)" ] + 5 [ label="SyncSystemProject(deps-external)" ] + 6 [ label="RunTask(deps-external:external)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 2 [ ] + 4 -> 2 [ ] + 4 -> 3 [ ] + 5 -> 1 [ ] + 5 -> 2 [ ] + 6 -> 5 [ ] + 6 -> 3 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__runs_deps_in_parallel.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__runs_deps_in_parallel.snap new file mode 100644 index 00000000000..f5d5002c747 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__runs_deps_in_parallel.snap @@ -0,0 +1,23 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(deps)" ] + 3 [ label="RunTask(deps:parallel)" ] + 4 [ label="RunTask(deps:c)" ] + 5 [ label="RunTask(deps:a)" ] + 6 [ label="RunTask(deps:b)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 4 -> 2 [ ] + 5 -> 2 [ ] + 6 -> 2 [ ] + 3 -> 2 [ ] + 3 -> 4 [ ] + 3 -> 5 [ ] + 3 -> 6 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__runs_deps_in_serial.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__runs_deps_in_serial.snap new file mode 100644 index 00000000000..e444a36cc07 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__run_task_dependencies__runs_deps_in_serial.snap @@ -0,0 +1,23 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(deps)" ] + 3 [ label="RunTask(deps:serial)" ] + 4 [ label="RunTask(deps:b)" ] + 5 [ label="RunTask(deps:c)" ] + 6 [ label="RunTask(deps:a)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 4 -> 2 [ ] + 5 -> 2 [ ] + 5 -> 4 [ ] + 6 -> 2 [ ] + 6 -> 5 [ ] + 3 -> 2 [ ] + 3 -> 6 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__setup_tool__graphs.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__setup_tool__graphs.snap new file mode 100644 index 00000000000..e8e7634758e --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__setup_tool__graphs.snap @@ -0,0 +1,12 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SetupNodeTool(1.2.3)" ] + 1 -> 0 [ ] + 2 -> 0 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__setup_tool__graphs_same_platform.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__setup_tool__graphs_same_platform.snap new file mode 100644 index 00000000000..1c0fe1c6ada --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__setup_tool__graphs_same_platform.snap @@ -0,0 +1,14 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(1.2.3)" ] + 2 [ label="SetupNodeTool(4.5.6)" ] + 3 [ label="SetupNodeTool(global)" ] + 1 -> 0 [ ] + 2 -> 0 [ ] + 3 -> 0 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs.snap new file mode 100644 index 00000000000..46d410389a3 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs.snap @@ -0,0 +1,15 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(foo)" ] + 3 [ label="SyncSystemProject(bar)" ] + 1 -> 0 [ ] + 3 -> 1 [ ] + 2 -> 1 [ ] + 2 -> 3 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs_multiple.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs_multiple.snap new file mode 100644 index 00000000000..6f9dda954a9 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs_multiple.snap @@ -0,0 +1,17 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(foo)" ] + 3 [ label="SyncSystemProject(bar)" ] + 4 [ label="SyncSystemProject(qux)" ] + 1 -> 0 [ ] + 3 -> 1 [ ] + 2 -> 1 [ ] + 2 -> 3 [ ] + 4 -> 1 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs_single.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs_single.snap new file mode 100644 index 00000000000..ddfdb147fe1 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs_single.snap @@ -0,0 +1,12 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(bar)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs_single_with_dep.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs_single_with_dep.snap new file mode 100644 index 00000000000..46d410389a3 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__graphs_single_with_dep.snap @@ -0,0 +1,15 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupSystemTool" ] + 2 [ label="SyncSystemProject(foo)" ] + 3 [ label="SyncSystemProject(bar)" ] + 1 -> 0 [ ] + 3 -> 1 [ ] + 2 -> 1 [ ] + 2 -> 3 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__inherits_platform_tool.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__inherits_platform_tool.snap new file mode 100644 index 00000000000..8a93b55531b --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__inherits_platform_tool.snap @@ -0,0 +1,16 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(20.0.0)" ] + 2 [ label="SyncNodeProject(bar)" ] + 3 [ label="SetupRustTool(1.70.0)" ] + 4 [ label="SyncRustProject(qux)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 0 [ ] + 4 -> 3 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__supports_platform_override.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__supports_platform_override.snap new file mode 100644 index 00000000000..8f0bbd2e090 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_project__supports_platform_override.snap @@ -0,0 +1,16 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] + 1 [ label="SetupNodeTool(20.0.0)" ] + 2 [ label="SyncNodeProject(bar)" ] + 3 [ label="SetupNodeTool(18.0.0)" ] + 4 [ label="SyncNodeProject(baz)" ] + 1 -> 0 [ ] + 2 -> 1 [ ] + 3 -> 0 [ ] + 4 -> 3 [ ] +} + diff --git a/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_workspace__graphs.snap b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_workspace__graphs.snap new file mode 100644 index 00000000000..35bf4b9bad6 --- /dev/null +++ b/nextgen/action-graph/tests/snapshots/action_graph_test__action_graph__sync_workspace__graphs.snap @@ -0,0 +1,8 @@ +--- +source: nextgen/action-graph/tests/action_graph_test.rs +expression: graph.to_dot() +--- +digraph { + 0 [ label="SyncWorkspace" ] +} + diff --git a/nextgen/action-graph/tests/utils.rs b/nextgen/action-graph/tests/utils.rs new file mode 100644 index 00000000000..0c61e18096c --- /dev/null +++ b/nextgen/action-graph/tests/utils.rs @@ -0,0 +1,27 @@ +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, +}; +use std::path::{Path, PathBuf}; + +pub struct ActionGraphContainer { + pub platform_manager: PlatformManager, + pub project_graph: ProjectGraph, + pub workspace_root: PathBuf, +} + +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_root: root.to_path_buf(), + } + } + + pub fn create_builder(&self) -> ActionGraphBuilder { + ActionGraphBuilder::with_platforms(&self.platform_manager, &self.project_graph).unwrap() + } +} diff --git a/nextgen/config/tests/inherited_tasks_config_test.rs b/nextgen/config/tests/inherited_tasks_config_test.rs index bdf2be65f73..b77ab251d92 100644 --- a/nextgen/config/tests/inherited_tasks_config_test.rs +++ b/nextgen/config/tests/inherited_tasks_config_test.rs @@ -198,8 +198,6 @@ fileGroups: let url = server.url("/config.yml"); - dbg!(&url); - sandbox.create_file( "tasks.yml", format!( diff --git a/nextgen/hash/Cargo.toml b/nextgen/hash/Cargo.toml index 0d8c9f8c316..c4282bafd0a 100644 --- a/nextgen/hash/Cargo.toml +++ b/nextgen/hash/Cargo.toml @@ -12,7 +12,7 @@ moon_config = { version = "0.1.0", path = "../config" } miette = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -sha2 = "0.10.7" +sha2 = "0.10.8" starbase_utils = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } diff --git a/nextgen/platform-runtime/src/lib.rs b/nextgen/platform-runtime/src/lib.rs index f6683fae574..872419ee011 100644 --- a/nextgen/platform-runtime/src/lib.rs +++ b/nextgen/platform-runtime/src/lib.rs @@ -4,6 +4,7 @@ use std::fmt; use std::hash::{Hash, Hasher}; #[derive(Clone, Debug, Eq, PartialEq, Serialize)] +#[serde(untagged)] pub enum RuntimeReq { // Use tool available on PATH Global, @@ -12,6 +13,10 @@ pub enum RuntimeReq { } impl RuntimeReq { + pub fn with_version(version: Version) -> Self { + Self::Toolchain(UnresolvedVersionSpec::Version(version)) + } + pub fn is_global(&self) -> bool { matches!(self, Self::Global) } diff --git a/nextgen/project-builder/src/project_builder.rs b/nextgen/project-builder/src/project_builder.rs index 5fbbbf469ef..51aec8e3699 100644 --- a/nextgen/project-builder/src/project_builder.rs +++ b/nextgen/project-builder/src/project_builder.rs @@ -1,12 +1,13 @@ use moon_common::path::WorkspaceRelativePath; use moon_common::{color, consts, Id}; use moon_config::{ - DependencyConfig, DependencySource, InheritedTasksManager, InheritedTasksResult, LanguageType, - PlatformType, ProjectConfig, ProjectDependsOn, TaskConfig, ToolchainConfig, + DependencyConfig, DependencyScope, DependencySource, InheritedTasksManager, + InheritedTasksResult, LanguageType, PlatformType, ProjectConfig, ProjectDependsOn, TaskConfig, + ToolchainConfig, }; use moon_file_group::FileGroup; use moon_project::Project; -use moon_task::Task; +use moon_task::{TargetScope, Task}; use moon_task_builder::{DetectPlatformEvent, TasksBuilder, TasksBuilderContext}; use rustc_hash::FxHashMap; use starbase_events::{Emitter, Event}; @@ -197,11 +198,13 @@ impl<'app> ProjectBuilder<'app> { #[tracing::instrument(name = "project", skip_all)] pub async fn build(mut self) -> miette::Result { + let tasks = self.build_tasks().await?; + let mut project = Project { alias: self.alias.map(|a| a.to_owned()), - dependencies: self.build_dependencies()?, + dependencies: self.build_dependencies(&tasks)?, file_groups: self.build_file_groups()?, - tasks: self.build_tasks().await?, + tasks, id: self.id.to_owned(), language: self.language, platform: self.platform, @@ -220,7 +223,10 @@ impl<'app> ProjectBuilder<'app> { Ok(project) } - fn build_dependencies(&self) -> miette::Result> { + fn build_dependencies( + &self, + tasks: &BTreeMap, + ) -> miette::Result> { let mut deps = FxHashMap::default(); trace!(id = self.id.as_str(), "Building project dependencies"); @@ -237,7 +243,41 @@ impl<'app> ProjectBuilder<'app> { deps.insert(dep_config.id.clone(), dep_config); } + } + + // Tasks can depend on arbitray projects, so include them also + for task_config in tasks.values() { + for task_dep in &task_config.deps { + if let TargetScope::Project(dep_id) = &task_dep.scope { + // Already a dependency, or references self + if deps.contains_key(dep_id) + || self.id == dep_id + || self.alias.as_ref().is_some_and(|a| *a == dep_id.as_str()) + { + continue; + } + + trace!( + id = self.id.as_str(), + dep = dep_id.as_str(), + task = task_config.target.as_str(), + "Marking arbitrary project as a peer dependency because of a task dependency" + ); + + deps.insert( + dep_id.to_owned(), + DependencyConfig { + id: dep_id.to_owned(), + scope: DependencyScope::Peer, + source: DependencySource::Implicit, + via: Some(task_config.target.to_string()), + }, + ); + } + } + } + if !deps.is_empty() { trace!( id = self.id.as_str(), deps = ?deps.keys().map(|k| k.as_str()).collect::>(), diff --git a/nextgen/project-graph/Cargo.toml b/nextgen/project-graph/Cargo.toml index a7f09387974..d0ad40416a8 100644 --- a/nextgen/project-graph/Cargo.toml +++ b/nextgen/project-graph/Cargo.toml @@ -32,5 +32,6 @@ thiserror = { workspace = true } tracing = { workspace = true } [dev-dependencies] +moon_test_utils2 = { path = "../test-utils" } starbase_sandbox = { workspace = true } tokio = { workspace = true } diff --git a/nextgen/project-graph/src/project_graph.rs b/nextgen/project-graph/src/project_graph.rs index c06508c90db..38b15e90294 100644 --- a/nextgen/project-graph/src/project_graph.rs +++ b/nextgen/project-graph/src/project_graph.rs @@ -34,10 +34,22 @@ pub struct ProjectNode { pub source: WorkspaceRelativePathBuf, } +impl ProjectNode { + pub fn new(index: usize) -> Self { + ProjectNode { + index: NodeIndex::new(index), + ..ProjectNode::default() + } + } +} + #[derive(Default)] pub struct ProjectGraph { pub check_boundaries: bool, + /// Cache of file path lookups, mapped by starting path to project ID (as a string). + fs_cache: OnceMap, + /// Directed-acyclic graph (DAG) of non-expanded projects and their dependencies. graph: GraphType, @@ -67,6 +79,7 @@ impl ProjectGraph { projects: Arc::new(RwLock::new(FxHashMap::default())), working_dir: workspace_root.to_owned(), workspace_root: workspace_root.to_owned(), + fs_cache: OnceMap::new(), query_cache: OnceMap::new(), check_boundaries: false, } @@ -162,36 +175,7 @@ impl ProjectGraph { current_file }; - // Find the deepest matching path in case sub-projects are being used - let mut remaining_length = 1000; // Start with a really fake number - let mut possible_id = String::new(); - - for (id, node) in &self.nodes { - if !file.starts_with(node.source.as_str()) { - continue; - } - - if let Ok(diff) = file.relative_to(node.source.as_str()) { - let diff_comps = diff.components().count(); - - // Exact match, abort - if diff_comps == 0 { - possible_id = id.as_str().to_owned(); - break; - } - - if diff_comps < remaining_length { - remaining_length = diff_comps; - possible_id = id.as_str().to_owned(); - } - } - } - - if possible_id.is_empty() { - return Err(ProjectGraphError::MissingFromPath(file.to_path_buf()).into()); - } - - self.get(&possible_id) + self.get(self.internal_search(file)?) } /// Return a list of IDs for all projects currently within the graph. @@ -312,10 +296,10 @@ impl ProjectGraph { let query = query.as_ref(); let query_input = query .input - .clone() + .as_ref() .expect("Querying the project graph requires a query input string."); - self.query_cache.try_insert(query_input.clone(), |_| { + self.query_cache.try_insert(query_input.to_owned(), |_| { debug!("Querying projects with {}", color::shell(query_input)); let mut project_ids = vec![]; @@ -345,6 +329,41 @@ impl ProjectGraph { }) } + fn internal_search(&self, search: &Path) -> miette::Result<&str> { + self.fs_cache.try_insert(search.to_path_buf(), |_| { + // Find the deepest matching path in case sub-projects are being used + let mut remaining_length = 1000; // Start with a really fake number + let mut possible_id = String::new(); + + for (id, node) in &self.nodes { + if !search.starts_with(node.source.as_str()) { + continue; + } + + if let Ok(diff) = search.relative_to(node.source.as_str()) { + let diff_comps = diff.components().count(); + + // Exact match, abort + if diff_comps == 0 { + possible_id = id.as_str().to_owned(); + break; + } + + if diff_comps < remaining_length { + remaining_length = diff_comps; + possible_id = id.as_str().to_owned(); + } + } + } + + if possible_id.is_empty() { + return Err(ProjectGraphError::MissingFromPath(search.to_path_buf()).into()); + } + + Ok(possible_id) + }) + } + fn resolve_id(&self, alias_or_id: &str) -> Id { Id::raw(if self.nodes.contains_key(alias_or_id) { alias_or_id diff --git a/nextgen/project-graph/src/project_graph_builder.rs b/nextgen/project-graph/src/project_graph_builder.rs index 8e02337e838..b3a721cc6f7 100644 --- a/nextgen/project-graph/src/project_graph_builder.rs +++ b/nextgen/project-graph/src/project_graph_builder.rs @@ -1,5 +1,4 @@ -use crate::project_events::ExtendProjectEvent; -use crate::project_events::ExtendProjectGraphEvent; +use crate::project_events::{ExtendProjectEvent, ExtendProjectGraphEvent}; use crate::project_graph::{GraphType, ProjectGraph, ProjectNode}; use crate::project_graph_cache::ProjectsState; use crate::project_graph_error::ProjectGraphError; @@ -7,17 +6,13 @@ use crate::project_graph_hash::ProjectGraphHash; use crate::projects_locator::locate_projects_with_globs; use async_recursion::async_recursion; use moon_cache::CacheEngine; -use moon_common::is_test_env; use moon_common::path::{to_virtual_string, WorkspaceRelativePath, WorkspaceRelativePathBuf}; -use moon_common::{color, consts, Id}; -use moon_config::{ - DependencyScope, InheritedTasksManager, ToolchainConfig, WorkspaceConfig, WorkspaceProjects, -}; +use moon_common::{color, consts, is_test_env, Id}; +use moon_config::{InheritedTasksManager, ToolchainConfig, WorkspaceConfig, WorkspaceProjects}; use moon_hash::HashEngine; use moon_project::Project; use moon_project_builder::{DetectLanguageEvent, ProjectBuilder, ProjectBuilderContext}; use moon_project_constraints::{enforce_project_type_relationships, enforce_tag_relationships}; -use moon_task::TargetScope; use moon_task_builder::DetectPlatformEvent; use moon_vcs::BoxedVcs; use petgraph::graph::DiGraph; @@ -256,36 +251,6 @@ impl<'app> ProjectGraphBuilder<'app> { } } - // Tasks can depend on arbitray projects, so include them also - for (task_id, task_config) in &project.tasks { - for task_dep in &task_config.deps { - if let TargetScope::Project(dep_id) = &task_dep.scope { - if - // Already a dependency - project.dependencies.contains_key(dep_id) || - // Don't reference itself - project.matches_locator(dep_id.as_str()) - { - continue; - } - - if cycle.contains(dep_id) { - warn!( - id = id.as_str(), - dependency_id = dep_id.as_str(), - task_id = task_id.as_str(), - "Encountered a dependency cycle (from task); will disconnect nodes to avoid recursion", - ); - } else { - edges.push(( - self.internal_load(dep_id, cycle).await?, - DependencyScope::Peer, - )); - } - } - } - } - // Insert into the graph and connect edges let index = self.graph.add_node(project); diff --git a/nextgen/project-graph/tests/project_graph_test.rs b/nextgen/project-graph/tests/project_graph_test.rs index 380722c6b97..96bd361e5d4 100644 --- a/nextgen/project-graph/tests/project_graph_test.rs +++ b/nextgen/project-graph/tests/project_graph_test.rs @@ -1,124 +1,26 @@ use moon_common::{path::WorkspaceRelativePathBuf, Id}; -use moon_config::PartialTaskConfig; use moon_config::{ - DependencyConfig, DependencyScope, DependencySource, InheritedTasksEntry, - InheritedTasksManager, NodeConfig, PartialInheritedTasksConfig, ToolchainConfig, - WorkspaceConfig, WorkspaceProjects, WorkspaceProjectsConfig, + DependencyConfig, DependencyScope, DependencySource, InheritedTasksManager, WorkspaceProjects, + WorkspaceProjectsConfig, }; use moon_project::{FileGroup, Project}; -use moon_project_builder::DetectLanguageEvent; use moon_project_graph::{ ExtendProjectData, ExtendProjectEvent, ExtendProjectGraphData, ExtendProjectGraphEvent, - ProjectGraph, ProjectGraphBuilder, ProjectGraphBuilderContext, + ProjectGraph, ProjectGraphBuilder, }; use moon_query::build_query; use moon_task::Target; -use moon_task_builder::DetectPlatformEvent; -use moon_vcs::{BoxedVcs, Git}; +use moon_test_utils2::*; use rustc_hash::{FxHashMap, FxHashSet}; -use starbase_events::{Emitter, EventState}; +use starbase_events::EventState; use starbase_sandbox::{assert_snapshot, create_sandbox, Sandbox}; -use starbase_utils::string_vec; -use starbase_utils::{fs, json}; -use std::collections::BTreeMap; +use starbase_utils::{fs, json, string_vec}; use std::fs::OpenOptions; use std::io::Write; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::sync::Arc; use tokio::sync::RwLock; -#[derive(Default)] -struct GraphContainer { - pub inherited_tasks: InheritedTasksManager, - pub toolchain_config: ToolchainConfig, - pub workspace_config: WorkspaceConfig, - pub workspace_root: PathBuf, - pub vcs: Option, -} - -impl GraphContainer { - pub fn new(root: &Path) -> Self { - let mut graph = Self { - workspace_root: root.to_path_buf(), - ..Default::default() - }; - - // Add a global task to all projects - graph.inherited_tasks.configs.insert( - "*".into(), - InheritedTasksEntry { - input: ".moon/tasks.yml".into(), - config: PartialInheritedTasksConfig { - tasks: Some(BTreeMap::from_iter([( - "global".into(), - PartialTaskConfig::default(), - )])), - ..PartialInheritedTasksConfig::default() - }, - }, - ); - - // Always use the node platform - graph.toolchain_config.node = Some(NodeConfig::default()); - - // Use folders as project names - graph.workspace_config.projects = WorkspaceProjects::Globs(string_vec!["*"]); - - graph - } - - pub fn new_with_vcs(root: &Path) -> Self { - let mut container = Self::new(root); - container.vcs = Some(Box::new(Git::load(root, "master", &[]).unwrap())); - container - } - - pub fn create_context(&self) -> ProjectGraphBuilderContext { - ProjectGraphBuilderContext { - extend_project: Emitter::::new(), - extend_project_graph: Emitter::::new(), - detect_language: Emitter::::new(), - detect_platform: Emitter::::new(), - inherited_tasks: &self.inherited_tasks, - toolchain_config: &self.toolchain_config, - vcs: self.vcs.as_ref(), - working_dir: &self.workspace_root, - workspace_config: &self.workspace_config, - workspace_root: &self.workspace_root, - } - } - - pub async fn build_graph<'l>(&self, context: ProjectGraphBuilderContext<'l>) -> ProjectGraph { - let mut builder = ProjectGraphBuilder::new(context).await.unwrap(); - builder.load_all().await.unwrap(); - - let mut graph = builder.build().await.unwrap(); - graph.check_boundaries = true; - graph.get_all().unwrap(); - graph - } - - pub async fn build_graph_for<'l>( - &self, - context: ProjectGraphBuilderContext<'l>, - ids: &[&str], - ) -> ProjectGraph { - let mut builder = ProjectGraphBuilder::new(context).await.unwrap(); - - for id in ids { - builder.load(id).await.unwrap(); - } - - let graph = builder.build().await.unwrap(); - - for id in ids { - graph.get(id).unwrap(); - } - - graph - } -} - pub fn append_file>(path: P, data: &str) { let mut file = OpenOptions::new() .write(true) @@ -145,19 +47,6 @@ fn get_ids_from_projects(projects: Vec>) -> Vec { mod project_graph { use super::*; - async fn generate_project_graph(fixture: &str) -> ProjectGraph { - let sandbox = create_sandbox(fixture); - - generate_project_graph_from_sandbox(sandbox.path()).await - } - - async fn generate_project_graph_from_sandbox(path: &Path) -> ProjectGraph { - let container = GraphContainer::new(path); - let context = container.create_context(); - - container.build_graph(context).await - } - #[tokio::test] async fn gets_by_id() { let graph = generate_project_graph("dependencies").await; @@ -219,7 +108,7 @@ 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 container = GraphContainer::new(&root); + let mut container = ProjectGraphContainer::new(&root); container.workspace_config.projects = WorkspaceProjects::Globs(string_vec!["*", "."]); @@ -235,7 +124,7 @@ mod project_graph { #[tokio::test] async fn paths() { let sandbox = create_sandbox("dependencies"); - let mut container = GraphContainer::new(sandbox.path()); + let mut container = ProjectGraphContainer::new(sandbox.path()); container.workspace_config.projects = WorkspaceProjects::Sources(FxHashMap::from_iter([ @@ -252,7 +141,7 @@ mod project_graph { #[tokio::test] async fn paths_and_globs() { let sandbox = create_sandbox("dependencies"); - let mut container = GraphContainer::new(sandbox.path()); + let mut container = ProjectGraphContainer::new(sandbox.path()); container.workspace_config.projects = WorkspaceProjects::Both(WorkspaceProjectsConfig { @@ -308,7 +197,7 @@ mod project_graph { sandbox.enable_git(); sandbox.create_file(".gitignore", "*-other"); - let container = GraphContainer::new_with_vcs(sandbox.path()); + let container = ProjectGraphContainer::with_vcs(sandbox.path()); let context = container.create_context(); let graph = container.build_graph(context).await; @@ -348,7 +237,7 @@ mod project_graph { async fn do_generate(root: &Path) -> ProjectGraph { let cache_engine = CacheEngine::new(root).unwrap(); let hash_engine = HashEngine::new(&cache_engine.cache_dir).unwrap(); - let container = GraphContainer::new_with_vcs(root); + let container = ProjectGraphContainer::with_vcs(root); let context = container.create_context(); let mut builder = ProjectGraphBuilder::generate(context, &cache_engine, &hash_engine) @@ -538,13 +427,12 @@ mod project_graph { async fn generate_inheritance_project_graph(fixture: &str) -> ProjectGraph { let sandbox = create_sandbox(fixture); - let mut container = GraphContainer::new(sandbox.path()); - container.inherited_tasks = - InheritedTasksManager::load(sandbox.path(), sandbox.path().join(".moon")).unwrap(); - - let context = container.create_context(); - - container.build_graph(context).await + generate_project_graph_with_changes(sandbox.path(), |container| { + container.inherited_tasks = + InheritedTasksManager::load(sandbox.path(), sandbox.path().join(".moon")) + .unwrap(); + }) + .await } #[tokio::test] @@ -802,7 +690,7 @@ mod project_graph { #[tokio::test] async fn no_depends_on() { let sandbox = create_sandbox("dependency-types"); - let container = GraphContainer::new(sandbox.path()); + let container = ProjectGraphContainer::new(sandbox.path()); let context = container.create_context(); let graph = container.build_graph_for(context, &["no-depends-on"]).await; @@ -812,7 +700,7 @@ mod project_graph { #[tokio::test] async fn some_depends_on() { let sandbox = create_sandbox("dependency-types"); - let container = GraphContainer::new(sandbox.path()); + let container = ProjectGraphContainer::new(sandbox.path()); let context = container.create_context(); let graph = container .build_graph_for(context, &["some-depends-on"]) @@ -824,7 +712,7 @@ mod project_graph { #[tokio::test] async fn from_task_deps() { let sandbox = create_sandbox("dependency-types"); - let container = GraphContainer::new(sandbox.path()); + let container = ProjectGraphContainer::new(sandbox.path()); let context = container.create_context(); let graph = container .build_graph_for(context, &["from-task-deps"]) @@ -836,7 +724,7 @@ mod project_graph { #[tokio::test] async fn self_task_deps() { let sandbox = create_sandbox("dependency-types"); - let container = GraphContainer::new(sandbox.path()); + let container = ProjectGraphContainer::new(sandbox.path()); let context = container.create_context(); let graph = container .build_graph_for(context, &["self-task-deps"]) @@ -852,7 +740,7 @@ mod project_graph { async fn generate_aliases_project_graph() -> ProjectGraph { let sandbox = create_sandbox("aliases"); - let container = GraphContainer::new(sandbox.path()); + let container = ProjectGraphContainer::new(sandbox.path()); let context = container.create_context(); // Set aliases for projects @@ -1036,7 +924,7 @@ mod project_graph { func(&sandbox); - let mut container = GraphContainer::new(sandbox.path()); + let mut container = ProjectGraphContainer::new(sandbox.path()); container .workspace_config @@ -1178,7 +1066,7 @@ mod project_graph { func(&sandbox); - let mut container = GraphContainer::new(sandbox.path()); + let mut container = ProjectGraphContainer::new(sandbox.path()); container .workspace_config @@ -1464,7 +1352,7 @@ mod project_graph { #[tokio::test] async fn renders_partial() { let sandbox = create_sandbox("dependencies"); - let container = GraphContainer::new(sandbox.path()); + let container = ProjectGraphContainer::new(sandbox.path()); let context = container.create_context(); let graph = container.build_graph_for(context, &["b"]).await; diff --git a/nextgen/query/Cargo.toml b/nextgen/query/Cargo.toml index ee70bb86985..a27804767ed 100644 --- a/nextgen/query/Cargo.toml +++ b/nextgen/query/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/moonrepo/moon" moon_common = { version = "0.1.0", path = "../common" } moon_config = { version = "0.1.0", path = "../config" } miette = { workspace = true } -pest = "2.7.3" -pest_derive = "2.7.3" +pest = "2.7.4" +pest_derive = "2.7.4" starbase_utils = { workspace = true } thiserror = { workspace = true } diff --git a/nextgen/task/src/lib.rs b/nextgen/task/src/lib.rs index 6ad258d2350..7b3bb75e447 100644 --- a/nextgen/task/src/lib.rs +++ b/nextgen/task/src/lib.rs @@ -2,6 +2,6 @@ mod task; mod task_options; pub use moon_config::{TaskConfig, TaskOptionsConfig, TaskType}; -pub use moon_target::{Target, TargetScope}; +pub use moon_target::*; pub use task::*; pub use task_options::*; diff --git a/nextgen/test-utils/Cargo.toml b/nextgen/test-utils/Cargo.toml new file mode 100644 index 00000000000..f290514b133 --- /dev/null +++ b/nextgen/test-utils/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "moon_test_utils2" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Testing utilities." +homepage = "https://moonrepo.dev/moon" +repository = "https://github.com/moonrepo/moon" + +[dependencies] +moon_config = { path = "../config" } +moon_project_graph = { path = "../project-graph" } +moon_vcs = { path = "../vcs" } +miette = { workspace = true } +proto_core = { workspace = true } +starbase_events = { workspace = true } +starbase_sandbox = { workspace = true } + +# TODO +moon_platform = { path = "../../crates/core/platform" } +moon_node_platform = { path = "../../crates/node/platform" } +moon_rust_platform = { path = "../../crates/rust/platform" } +moon_system_platform = { path = "../../crates/system/platform" } diff --git a/nextgen/test-utils/src/lib.rs b/nextgen/test-utils/src/lib.rs new file mode 100644 index 00000000000..5b73bce286f --- /dev/null +++ b/nextgen/test-utils/src/lib.rs @@ -0,0 +1,5 @@ +mod platform_manager; +mod project_graph; + +pub use platform_manager::*; +pub use project_graph::*; diff --git a/nextgen/test-utils/src/platform_manager.rs b/nextgen/test-utils/src/platform_manager.rs new file mode 100644 index 00000000000..50fffc38380 --- /dev/null +++ b/nextgen/test-utils/src/platform_manager.rs @@ -0,0 +1,32 @@ +use moon_config::{PlatformType, ToolchainConfig, ToolsConfig}; +use moon_node_platform::NodePlatform; +use moon_platform::PlatformManager; +use moon_rust_platform::RustPlatform; +use moon_system_platform::SystemPlatform; +use proto_core::ProtoEnvironment; +use std::path::Path; +use std::sync::Arc; + +pub async fn generate_platform_manager_from_sandbox(root: &Path) -> PlatformManager { + let proto = Arc::new(ProtoEnvironment::new_testing(root)); + let config = ToolchainConfig::load_from(root, &ToolsConfig::default()).unwrap(); + let mut manager = PlatformManager::default(); + + if let Some(node_config) = &config.node { + manager.register( + PlatformType::Node, + Box::new(NodePlatform::new(node_config, &None, root, proto.clone())), + ); + } + + if let Some(rust_config) = &config.rust { + manager.register( + PlatformType::Rust, + Box::new(RustPlatform::new(rust_config, root, proto.clone())), + ); + } + + manager.register(PlatformType::System, Box::::default()); + + manager +} diff --git a/nextgen/test-utils/src/project_graph.rs b/nextgen/test-utils/src/project_graph.rs new file mode 100644 index 00000000000..5478968b90d --- /dev/null +++ b/nextgen/test-utils/src/project_graph.rs @@ -0,0 +1,132 @@ +use moon_config::{ + InheritedTasksEntry, InheritedTasksManager, NodeConfig, PartialInheritedTasksConfig, + PartialTaskConfig, ToolchainConfig, ToolsConfig, WorkspaceConfig, WorkspaceProjects, +}; +use moon_project_graph::{ + DetectLanguageEvent, DetectPlatformEvent, ExtendProjectEvent, ExtendProjectGraphEvent, + ProjectGraph, ProjectGraphBuilder, ProjectGraphBuilderContext, +}; +use moon_vcs::{BoxedVcs, Git}; +use starbase_events::Emitter; +use starbase_sandbox::create_sandbox; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +#[derive(Default)] +pub struct ProjectGraphContainer { + pub inherited_tasks: InheritedTasksManager, + pub toolchain_config: ToolchainConfig, + pub workspace_config: WorkspaceConfig, + pub workspace_root: PathBuf, + pub vcs: Option, +} + +impl ProjectGraphContainer { + pub fn new(root: &Path) -> Self { + let proto = ToolsConfig::default(); + let mut graph = Self { + inherited_tasks: InheritedTasksManager::load_from(root).unwrap(), + toolchain_config: ToolchainConfig::load_from(root, &proto).unwrap(), + workspace_root: root.to_path_buf(), + ..Default::default() + }; + + // Add a global task to all projects + graph.inherited_tasks.configs.insert( + "*".into(), + InheritedTasksEntry { + input: ".moon/tasks.yml".into(), + config: PartialInheritedTasksConfig { + tasks: Some(BTreeMap::from_iter([( + "global".into(), + PartialTaskConfig::default(), + )])), + ..PartialInheritedTasksConfig::default() + }, + }, + ); + + // Always use the node platform + if graph.toolchain_config.node.is_none() { + graph.toolchain_config.node = Some(NodeConfig::default()); + } + + // Use folders as project names + graph.workspace_config.projects = WorkspaceProjects::Globs(vec!["*".into()]); + + graph + } + + pub fn with_vcs(root: &Path) -> Self { + let mut container = Self::new(root); + container.vcs = Some(Box::new(Git::load(root, "master", &[]).unwrap())); + container + } + + pub fn create_context(&self) -> ProjectGraphBuilderContext { + ProjectGraphBuilderContext { + extend_project: Emitter::::new(), + extend_project_graph: Emitter::::new(), + detect_language: Emitter::::new(), + detect_platform: Emitter::::new(), + inherited_tasks: &self.inherited_tasks, + toolchain_config: &self.toolchain_config, + vcs: self.vcs.as_ref(), + working_dir: &self.workspace_root, + workspace_config: &self.workspace_config, + workspace_root: &self.workspace_root, + } + } + + pub async fn build_graph<'l>(&self, context: ProjectGraphBuilderContext<'l>) -> ProjectGraph { + let mut builder = ProjectGraphBuilder::new(context).await.unwrap(); + builder.load_all().await.unwrap(); + + let mut graph = builder.build().await.unwrap(); + graph.check_boundaries = true; + graph.get_all().unwrap(); + graph + } + + pub async fn build_graph_for<'l>( + &self, + context: ProjectGraphBuilderContext<'l>, + ids: &[&str], + ) -> ProjectGraph { + let mut builder = ProjectGraphBuilder::new(context).await.unwrap(); + + for id in ids { + builder.load(id).await.unwrap(); + } + + let mut graph = builder.build().await.unwrap(); + graph.check_boundaries = true; + + for id in ids { + graph.get(id).unwrap(); + } + + graph + } +} + +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 { + generate_project_graph_with_changes(root, |_| {}).await +} + +pub async fn generate_project_graph_with_changes(root: &Path, mut op: F) -> ProjectGraph +where + F: FnMut(&mut ProjectGraphContainer), +{ + let mut container = ProjectGraphContainer::new(root); + + op(&mut container); + + let context = container.create_context(); + + container.build_graph(context).await +} diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index c966243c309..fb7fbc7d05d 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -10,6 +10,49 @@ - More accurately monitors signals (ctrl+c) and shutdowns. - Tasks can now be configured with a timeout. +## Unreleased + +#### 💥 Breaking + +- Tasks that depend (via `deps`) on other tasks from arbitrary projects (the parent project doesn't + implicitly or explicitly depend on the other project) will now automatically mark that other + project as a "peer" dependency. For example, "b" becomes a peer dependency for "a". + +#### 🎉 Release + +- Rewrote the dependency graph from the ground-up: + - Now known as the action graph. + - All actions now depend on the `SyncWorkspace` action, instead of this action running + arbitrarily. + - Cleaned up dependency chains between actions, greatly reducing the number of nodes in the graph. + - Renamed `RunTarget` to `RunTask`, including interactive and persistent variants. +- Updated the action graph to process using a topological queue, which executes actions on-demand in + the thread pool when they are ready (dependencies have been met). Previously, we would sort + topologically _into batches_, which worked, but resulted in many threads uselessly waiting for an + action to run, which was blocked waiting for the current batch to complete. + - For large graphs, this should result in a significant performance improvement. + - Persistent tasks will still be ran as a batch, but since it's the last operation, it's fine. +- Released a new GitHub action, + [`moonrepo/setup-toolchain`](https://github.com/marketplace/actions/setup-proto-and-moon-toolchains), + that replaces both `setup-moon-action` and `setup-proto`. + +#### 🚀 Updates + +- Added a `moon action-graph` command. +- Added a `--dependents` argument to `moon action-graph`. +- Added the ability to skip non-`RunTask` actions using environment variables. +- Deprecated the `moon dep-graph` command. + +#### 🐞 Fixes + +- Fixed an issue where task dependents (via `moon ci` or `moon run --dependents`) wouldn't always + locate all downstream tasks. + +#### ⚙️ Internal + +- Added in-memory caching to project graph file system lookup operations. +- Updated Rust to v1.72. + ## 1.14.5 #### 🐞 Fixes diff --git a/packages/report/tests/action.test.ts b/packages/report/tests/action.test.ts index c1fa88e3a07..b20ce300f99 100644 --- a/packages/report/tests/action.test.ts +++ b/packages/report/tests/action.test.ts @@ -10,7 +10,7 @@ const action: Action = { }, error: null, flaky: false, - label: 'RunTarget(app:build)', + label: 'RunTask(app:build)', nodeIndex: 8, status: 'passed', finishedAt: '2022-09-12T22:50:12.932311Z', diff --git a/packages/report/tests/report.test.ts b/packages/report/tests/report.test.ts index 023d48c7b9a..789f54cc2c6 100644 --- a/packages/report/tests/report.test.ts +++ b/packages/report/tests/report.test.ts @@ -13,7 +13,7 @@ function mockReport(): RunReport { }, error: null, flaky: false, - label: 'RunTarget(types:build)', + label: 'RunTask(types:build)', nodeIndex: 5, status: 'cached', finishedAt: '2022-09-12T22:50:12.932311Z', @@ -28,7 +28,7 @@ function mockReport(): RunReport { }, error: null, flaky: true, - label: 'RunTarget(runtime:typecheck)', + label: 'RunTask(runtime:typecheck)', nodeIndex: 4, status: 'passed', finishedAt: '2022-09-12T22:50:12.932311Z', @@ -43,7 +43,7 @@ function mockReport(): RunReport { }, error: null, flaky: false, - label: 'RunTarget(types:typecheck)', + label: 'RunTask(types:typecheck)', nodeIndex: 6, status: 'passed', finishedAt: '2022-09-12T22:50:12.932311Z', @@ -58,7 +58,7 @@ function mockReport(): RunReport { }, error: null, flaky: false, - label: 'RunTarget(website:typecheck)', + label: 'RunTask(website:typecheck)', nodeIndex: 8, status: 'passed', finishedAt: '2022-09-12T22:50:12.932311Z', @@ -102,10 +102,10 @@ describe('sortReport()', () => { sortReport(report, 'time', 'asc'); expect(report.actions.map((a) => a.label)).toEqual([ - 'RunTarget(types:build)', - 'RunTarget(website:typecheck)', - 'RunTarget(types:typecheck)', - 'RunTarget(runtime:typecheck)', + 'RunTask(types:build)', + 'RunTask(website:typecheck)', + 'RunTask(types:typecheck)', + 'RunTask(runtime:typecheck)', ]); }); @@ -114,10 +114,10 @@ describe('sortReport()', () => { sortReport(report, 'time', 'desc'); expect(report.actions.map((a) => a.label)).toEqual([ - 'RunTarget(runtime:typecheck)', - 'RunTarget(types:typecheck)', - 'RunTarget(website:typecheck)', - 'RunTarget(types:build)', + 'RunTask(runtime:typecheck)', + 'RunTask(types:typecheck)', + 'RunTask(website:typecheck)', + 'RunTask(types:build)', ]); }); @@ -126,10 +126,10 @@ describe('sortReport()', () => { sortReport(report, 'label', 'asc'); expect(report.actions.map((a) => a.label)).toEqual([ - 'RunTarget(runtime:typecheck)', - 'RunTarget(types:build)', - 'RunTarget(types:typecheck)', - 'RunTarget(website:typecheck)', + 'RunTask(runtime:typecheck)', + 'RunTask(types:build)', + 'RunTask(types:typecheck)', + 'RunTask(website:typecheck)', ]); }); @@ -138,10 +138,10 @@ describe('sortReport()', () => { sortReport(report, 'label', 'desc'); expect(report.actions.map((a) => a.label)).toEqual([ - 'RunTarget(website:typecheck)', - 'RunTarget(types:typecheck)', - 'RunTarget(types:build)', - 'RunTarget(runtime:typecheck)', + 'RunTask(website:typecheck)', + 'RunTask(types:typecheck)', + 'RunTask(types:build)', + 'RunTask(runtime:typecheck)', ]); }); }); @@ -156,7 +156,7 @@ describe('prepareReportActions()', () => { secs: 0, }, icon: '🟪', - label: 'RunTarget(types:build)', + label: 'RunTask(types:build)', status: 'cached', time: '0s', }, @@ -167,7 +167,7 @@ describe('prepareReportActions()', () => { secs: 1922, }, icon: '🟩', - label: 'RunTarget(runtime:typecheck)', + label: 'RunTask(runtime:typecheck)', status: 'passed', time: '32m 2s', }, @@ -178,7 +178,7 @@ describe('prepareReportActions()', () => { secs: 64, }, icon: '🟩', - label: 'RunTarget(types:typecheck)', + label: 'RunTask(types:typecheck)', status: 'passed', time: '1m 4s', }, @@ -189,7 +189,7 @@ describe('prepareReportActions()', () => { secs: 34, }, icon: '🟩', - label: 'RunTarget(website:typecheck)', + label: 'RunTask(website:typecheck)', status: 'passed', time: '34.4s', }, diff --git a/packages/types/src/common.ts b/packages/types/src/common.ts index dc18029f2ea..196b6cd03e0 100644 --- a/packages/types/src/common.ts +++ b/packages/types/src/common.ts @@ -8,6 +8,7 @@ export interface Duration { } export interface Runtime { - platform: Capitalize; - version?: string; + platform: PlatformType; + requirement?: string; + overridden?: boolean; } diff --git a/packages/types/src/pipeline.ts b/packages/types/src/pipeline.ts index 22008cecc66..a03a78b5533 100644 --- a/packages/types/src/pipeline.ts +++ b/packages/types/src/pipeline.ts @@ -19,6 +19,7 @@ export interface Attempt { } export interface Action { + allowFailure?: boolean; attempts: Attempt[] | null; createdAt: string; duration: Duration | null; @@ -75,37 +76,52 @@ export interface RunReport { export type ActionNode = | ActionNodeInstallDeps | ActionNodeInstallProjectDeps - | ActionNodeRunPersistentTarget - | ActionNodeRunTarget + | ActionNodeRunTask | ActionNodeSetupTool - | ActionNodeSyncProject; + | ActionNodeSyncProject + | ActionNodeSyncWorkspace; export interface ActionNodeInstallDeps { action: 'InstallDeps'; - params: Runtime; + params: { + runtime: Runtime; + }; } export interface ActionNodeInstallProjectDeps { action: 'InstallProjectDeps'; - params: [Runtime, string]; -} - -export interface ActionNodeRunTarget { - action: 'RunTarget'; - params: [Runtime, string]; + params: { + runtime: Runtime; + project: string; + }; } -export interface ActionNodeRunPersistentTarget { - action: 'RunPersistentTarget'; - params: [Runtime, string]; +export interface ActionNodeRunTask { + action: 'RunTask'; + params: { + interactive: boolean; + persistent: boolean; + runtime: Runtime; + target: string; + }; } export interface ActionNodeSetupTool { action: 'SetupTool'; - params: Runtime; + params: { + runtime: Runtime; + }; } export interface ActionNodeSyncProject { action: 'SyncProject'; - params: [Runtime, string]; + params: { + runtime: Runtime; + project: string; + }; +} + +export interface ActionNodeSyncWorkspace { + action: 'SyncWorkspace'; + params: {}; } diff --git a/packages/visualizer/src/helpers/render.ts b/packages/visualizer/src/helpers/render.ts index f755c987a17..f57f42dd8f8 100644 --- a/packages/visualizer/src/helpers/render.ts +++ b/packages/visualizer/src/helpers/render.ts @@ -9,8 +9,8 @@ function getActionType(label: string) { return 'sync-workspace'; } - if (label.startsWith('RunTarget') || label.startsWith('RunPersistentTarget')) { - return 'run-target'; + if (label.startsWith('Run') && (label.includes('Target') || label.includes('Task'))) { + return 'run-task'; } if (label.startsWith('Sync') && label.includes('Project')) { @@ -105,7 +105,7 @@ export function render(element: HTMLElement, data: GraphInfo) { }, }, { - selector: 'node[type="run-target"]', + selector: 'node[type="run-task"]', style: { // @ts-expect-error Types incorrect 'background-gradient-stop-colors': '#6e58d1 #4a2ec6 #3b259e', diff --git a/rust-toolchain.toml b/rust-toolchain.toml index afe2102addd..2bc9147a8a8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,4 +2,4 @@ # The default profile includes rustc, rust-std, cargo, rust-docs, rustfmt and clippy. # https://rust-lang.github.io/rustup/concepts/profiles.html profile = "default" -channel = "1.72.1" +channel = "1.73.0" diff --git a/website/blog/2022-12-19_v0.21.mdx b/website/blog/2022-12-19_v0.21.mdx index d44c09c073a..0351aded720 100644 --- a/website/blog/2022-12-19_v0.21.mdx +++ b/website/blog/2022-12-19_v0.21.mdx @@ -84,7 +84,7 @@ tasks: Thanks to the amazing contribution from [Diptesh Choudhuri](https://github.com/IgnisDa), we now have interactive project and dependency graphs when running the [`moon project-graph`](../docs/commands/project-graph) and -[`moon dep-graph`](../docs/commands/dep-graph) commands respectively. +[`moon dep-graph`](../docs/commands/action-graph) commands respectively. This is only the first iteration of these graphs. Expect more advanced features in the future, like filtering, node/edge inspection, exporting, and more! diff --git a/website/blog/2023-10-09_moon-v1.15.mdx b/website/blog/2023-10-09_moon-v1.15.mdx new file mode 100644 index 00000000000..6db633dcc32 --- /dev/null +++ b/website/blog/2023-10-09_moon-v1.15.mdx @@ -0,0 +1,143 @@ +--- +slug: moon-v1.15 +title: moon v1.15 - Next-generation action graph +authors: [milesj] +tags: [action, dependency, graph, pipeline, railway] +image: ./img/moon/v1.15.png +--- + +In this release, we've taken the new step in modernizing our action pipeline, by rewriting the +dependency graph. + + + +## Hello action graph, goodbye dependency graph + +For the past few months, we've been working on a rewrite of our action pipeline, which consists of +the project graph, dependency graph, task executor, process pipeline, and more. It's a slow process, +with many different pieces that must land in sequence, but we're almost done. The next step in this +process is the [introduction of the new action graph](/docs/how-it-works/action-graph), which +replaces the previous dependency graph. + +For the most part, the graphs work in a similar fashion, but since we rewrote it from the ground up, +we were able to resolve any discrepancies and performance issues. The biggest changes between the +new and old graphs are: + +- All actions now depend on the `SyncWorkspace` action, instead of this action running arbitrarily. +- Cleaned up dependency chains between actions, greatly reducing the number of nodes in the graph. +- Renamed `RunTarget` to `RunTask`, including interactive and persistent variants. +- And lastly, we ditched our batched task approach for a ready queue. Continue reading for more + information! + +### A new performant thread pool + +In the old dependency graph, when we'd execute a task, we'd order the graph topologically and then +group actions into batches (or buckets) based on their dependency chains. Batches would then be +executed in order within the thread pool. This approach worked well, but had one major flaw: it +wasn't as performant as could be. For example, if our thread pool size was 12, and a batch only had +2 tasks in it, what were the other 10 threads doing? Absolutely nothing. They were sitting idly, +waiting for a task. + +And now with the new action graph, we take full advantage of all threads in the pool. Instead of the +batched approach above, we now use a topological task-ready queue, where a thread without work (or +is waiting for work) can poll the graph for a new task to run. A task is considered ready to run if +it either has no dependencies, or all of its dependencies (in the chain) have been ran. + +For large graphs, this should result in a significant performance improvement! + +### Automatic peer dependencies (breaking) + +Because of these graph changes, we do have a minor "breaking change". Tasks that depend (via `deps`) +on other tasks from arbitrary projects (the parent project doesn't implicitly or explicitly depend +on the other project) will now automatically mark that other project as a "peer" dependency. For +example, "b" becomes a peer dependency for "a". + +```yaml title="a/moon.yml" +tasks: + build: + deps: ['b:build'] +``` + +Now internally becomes: + +```yaml title="a/moon.yml" +dependsOn: + - id: 'b' + scope: 'peer' + +tasks: + build: + deps: ['b:build'] +``` + +We're marking this as a breaking change as this could subtly introduce cycles in the project graph +that weren't present before, and for Node.js projects, this may inject `peerDependencies`. However, +this change was necessary to ensure accurate dependency chains in the graph. + +## New `moonrepo/setup-toolchain` GitHub action + +We've begun a process to deprecate the +[moonrepo/setup-moon-action](https://github.com/moonrepo/setup-moon-action) and +[moonrepo/setup-proto](https://github.com/moonrepo/setup-proto) GitHub actions, and instead, combine +and replace them with a new [moonrepo/setup-toolchain](https://github.com/moonrepo/setup-toolchain) +action. Why a new action instead of fixing the others? + +The biggest problem was that both previous actions shared about 90% of the same code, but were +slightly different in how they installed the binaries and cached the toolchain. It was was also +confusing for consumers to understand and know which action to use (because they shouldn't be used +together). + +To remedy this, we're prototyping the new +[moonrepo/setup-toolchain](https://github.com/moonrepo/setup-toolchain) action, which has been +working quite well. It aims to solve the following: + +- Installs `proto` globally so that installed tools can also be executed globally. +- Conditionally installs `moon` globally if the repository is using moon (attempts to detect a + `.moon` directory). +- Caches the toolchain (`~/.proto`) so subsequent runs are faster. +- Hashes `.prototools` and `.moon/toolchain.yml` files to generate a unique cache key. +- Cleans the toolchain before caching to remove unused or stale tools. +- Can auto-install tools when used. + +```diff +# ... +jobs: + ci: + name: CI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 +- - uses: moonrepo/setup-moon-action@v1 ++ - uses: moonrepo/setup-toolchain@v0 +``` + +## Now supported in Railway + +If you're a big fan of [Railway](https://railway.app/) (like we are), and you're deploying a Node.js +backed application, then you'll be happy to hear that Railway now officially and natively supports +moon! We spent some time over the past month +[integrating moon support into their Nixpacks architecture](https://nixpacks.com/docs/providers/node). + +To make use of this, set the `NIXPACKS_MOON_APP_NAME` environment variable to the name of your moon +project that you want to be deployed. This will then automatically run `moon run :build` and +`moon run :start` respectively. To customize the task names, you can set the +`NIXPACKS_MOON_BUILD_TASK` and `NIXPACKS_MOON_START_TASK` environment variables. + +:::info + +This is currently only supported for Node.js projects, but will be expanded to other languages in +the future! + +::: + +## Other changes + +View the [official release](https://github.com/moonrepo/moon/releases/tag/v1.15.0) for a full list +of changes. + +- Added a `moon action-graph` command. +- Added a `--dependents` argument to `moon action-graph`. +- Added the ability to skip non-`RunTask` actions using environment variables. +- Deprecated the `moon dep-graph` command. diff --git a/website/blog/img/moon/v1.15.png b/website/blog/img/moon/v1.15.png new file mode 100644 index 00000000000..22cf321649f Binary files /dev/null and b/website/blog/img/moon/v1.15.png differ diff --git a/website/docs/commands/dep-graph.mdx b/website/docs/commands/action-graph.mdx similarity index 58% rename from website/docs/commands/dep-graph.mdx rename to website/docs/commands/action-graph.mdx index f916862a1df..2b8c6710fa1 100644 --- a/website/docs/commands/dep-graph.mdx +++ b/website/docs/commands/action-graph.mdx @@ -1,21 +1,26 @@ --- -title: dep-graph +title: action-graph --- -The `moon dep-graph [target]` (or `moon dg`) command will generate and serve a visual dependency -graph of all actions, targets, and tasks within the workspace, and can also output the graph in -[Graphviz DOT format](https://graphviz.org/doc/info/lang.html). +import VersionLabel from '@site/src/components/Docs/VersionLabel'; + + + +The `moon action-graph [target]` (or `moon ag`) command will generate and serve a visual graph of +all actions and tasks within the workspace, known as the +[action graph](../how-it-works/action-graph). In other tools, this is sometimes referred to as a +dependency graph or task graph. ```shell # Run the visualizer locally -$ moon dep-graph +$ moon action-graph # Export to DOT format -$ moon dep-graph --dot > graph.dot +$ moon action-graph --dot > graph.dot ``` > A target can be passed to focus the graph, including dependencies _and_ dependents. For example, -> `moon dep-graph app:build`. +> `moon action-graph app:build`. ### Arguments @@ -34,7 +39,7 @@ digraph { 0 [ label="SetupNodeTool" style=filled, shape=oval, fillcolor=black, fontcolor=white] 1 [ label="InstallNodeDeps" style=filled, shape=oval, fillcolor=gray, fontcolor=black] 2 [ label="SyncNodeProject(node)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] - 3 [ label="RunTarget(node:standard)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] + 3 [ label="RunTask(node:standard)" style=filled, shape=oval, fillcolor=gray, fontcolor=black] 1 -> 0 [ arrowhead=box, arrowtail=box] 2 -> 0 [ arrowhead=box, arrowtail=box] 3 -> 1 [ arrowhead=box, arrowtail=box] diff --git a/website/docs/concepts/project.mdx b/website/docs/concepts/project.mdx index f49c31d5423..6dccd3206de 100644 --- a/website/docs/concepts/project.mdx +++ b/website/docs/concepts/project.mdx @@ -36,7 +36,7 @@ pattern that makes the most sense for your company or team! ## Dependencies Projects can depend on other projects within the [workspace](./workspace) to build a -[project graph](../how-it-works/dep-graph), and in turn, a dependency graph for executing +[project graph](../how-it-works/action-graph), and in turn, an action graph for executing [tasks](./task). Project dependencies are divided into 2 categories: - **Explicit dependencies** - These are dependencies that are explicitly defined in a project's diff --git a/website/docs/concepts/task.mdx b/website/docs/concepts/task.mdx index 74e4637fb7d..94449e36b45 100644 --- a/website/docs/concepts/task.mdx +++ b/website/docs/concepts/task.mdx @@ -30,7 +30,7 @@ Tasks are grouped into 1 of the following types based on their configured parame ## Modes Alongside types, tasks can also grouped into a special mode that provides unique handling within the -dependency graph and pipelines. +action graph and pipelines. ### Local only @@ -52,8 +52,8 @@ tasks: Tasks that need to interact with the user via terminal prompts are known as interactive tasks. Because interactive tasks require stdin, and it's not possible to have multiple parallel running -tasks interact with stdin, we isolate interactive tasks from other tasks in the dependency graph. -This ensures that only 1 interactive task is ran at a time. +tasks interact with stdin, we isolate interactive tasks from other tasks in the action graph. This +ensures that only 1 interactive task is ran at a time. To mark a task as interactive, enable the [`options.interactive`](../config/project#interactive) setting. @@ -72,8 +72,8 @@ Tasks that never complete, like servers and watchers, are known as persistent ta tasks are typically problematic when it comes to dependency graphs, because if they run in the middle of the graph, subsequent tasks will never run because the persistent task never completes! -However in moon, this is a non-issue, as we collect all persistent tasks within the dependency graph -and run them _last as a batch_. This is perfect for a few reasons: +However in moon, this is a non-issue, as we collect all persistent tasks within the action graph and +run them _last as a batch_. This is perfect for a few reasons: - All persistent tasks are ran in parallel, so they don't block each other. - Running both the backend API and frontend webapp in parallel is a breeze. diff --git a/website/docs/config/project.mdx b/website/docs/config/project.mdx index c34f5b84f06..3b55865fa2f 100644 --- a/website/docs/config/project.mdx +++ b/website/docs/config/project.mdx @@ -830,8 +830,8 @@ tasks: Marks the task as persistent (continuously running). [Persistent tasks](../concepts/task#persistent) -are handled differently than non-persistent tasks in the dependency graph. When running a target, -all persistent tasks are _ran last_ and _in parallel_, after all their dependencies have completed. +are handled differently than non-persistent tasks in the action graph. When running a target, all +persistent tasks are _ran last_ and _in parallel_, after all their dependencies have completed. This is extremely useful for running a server (or a watcher) in the background while other tasks are running. diff --git a/website/docs/editors/vscode.mdx b/website/docs/editors/vscode.mdx index 94cbcb22e9e..80acb0987d4 100644 --- a/website/docs/editors/vscode.mdx +++ b/website/docs/editors/vscode.mdx @@ -59,7 +59,7 @@ Information about the last ran target will be displayed in a beautiful table wit Only tasks ran from the [projects view](#projects) or on the command line will be displayed here. This table displays all actions that were ran alongside the running primary target(s). They are -ordered topologically via the dependency graph. +ordered topologically via the action graph. diff --git a/website/docs/guides/webhooks.mdx b/website/docs/guides/webhooks.mdx index 00296fb6697..095f6461cbe 100644 --- a/website/docs/guides/webhooks.mdx +++ b/website/docs/guides/webhooks.mdx @@ -409,7 +409,8 @@ Triggered when a [target](../concepts/target) has finished running. If the run f field will be set with the error message. For more information about the action, refer to the [`action.finished`](#actionfinished) event. Ran -targets can be scoped with the `RunTarget(...)` and `RunPersistentTarget(...)` labels. +targets can be scoped with the `RunTask(...)`, `RunInteractiveTask(...)`, and +`RunPersistentTask(...)` labels. ```json { diff --git a/website/docs/how-it-works/dep-graph.mdx b/website/docs/how-it-works/action-graph.mdx similarity index 70% rename from website/docs/how-it-works/dep-graph.mdx rename to website/docs/how-it-works/action-graph.mdx index a7803c5ccb5..8c3af6798b6 100644 --- a/website/docs/how-it-works/dep-graph.mdx +++ b/website/docs/how-it-works/action-graph.mdx @@ -1,18 +1,17 @@ --- -title: Dependency graph +title: Action graph --- -import DepGraph from '@site/src/components/Docs/DepGraph'; +import ActionGraph from '@site/src/components/Docs/ActionGraph'; -When you run a [task](../config/project#tasks-1) on the command line, we generate a dependency graph -to ensure [dependencies](../config/project#deps) of tasks have ran before running run the primary -task. +When you run a [task](../config/project#tasks-1) on the command line, we generate an action graph to +ensure [dependencies](../config/project#deps) of tasks have ran before running run the primary task. -The dependency graph is a representation of all [tasks](../concepts/task), derived from the +The action graph is a representation of all [tasks](../concepts/task), derived from the [project graph](./project-graph), and is also represented internally as a directed acyclic graph (DAG). - + ## Actions @@ -21,7 +20,7 @@ represent each node in the graph as an action to perform. This allows us to be m efficient with how we run tasks, and allows us to provide more functionality and automation than other runners. -The following actions compose our dependency graph: +The following actions compose our action graph: ### Sync workspace @@ -36,12 +35,12 @@ tier 3 language into the toolchain. For other tiers, this is basically a no-oper - When the tool has already been installed, this action will be skipped. - Actions will be scoped by language and version, also known as a runtime. For example, `SetupNodeTool(18.1.0)` or `SetupDenoTool(1.31.0)`. -- Tools that require a global language binary will display the version as "global". For example, - `SetupNodeTool(global)`. +- Tools that require a global binary (found on `PATH`) will display the version as "global". For + example, `SetupNodeTool(global)`. ### Install dependencies -Before we run a target, we ensure that all language dependencies (`node_modules` for example) have +Before we run a task, we ensure that all language dependencies (`node_modules` for example) have been installed, by automatically installing them if we detect changes since the last run. We achieve this by comparing lockfile modified timestamps, parsing manifest files, and hashing resolved dependency versions. @@ -58,7 +57,7 @@ dependency versions. ### Sync project To ensure a consistently healthy project and repository, we run a process known as syncing -_everytime_ a target is ran. Actions will be scoped by language, for example, +_everytime_ a task is ran. Actions will be scoped by language, for example, `SyncNodeProject(example)`. What is synced or considered healthcare is dependent on the language and its ecosystem. @@ -74,26 +73,27 @@ What is synced or considered healthcare is dependent on the language and its eco > This action depends on the setup toolchain action, in case it requires binaries or functionality > that the toolchain provides. -### Run target +### Run task -The primary action in the graph is the run [target](../concepts/target) action, which runs a -project's task as a child process. Tasks can depend on other tasks, and they'll be effectively -orchestrated and executed by running in topological order _and_ in batches via a worker pool. +The primary action in the graph is the run [task](../concepts/task) action, which runs a project's +task as a child process, derived from a [target](../concepts/target). Tasks can depend on other +tasks, and they'll be effectively orchestrated and executed by running in topological order using a +thread pool. > This action depends on the previous actions, as the toolchain is used for running the task's > command, and the outcome of the task is best when the project state is healthy and deterministic. -### Run interactive target +### Run interactive task -Like the base run target, but runs the [task interactively](../concepts/task#interactive) with stdin +Like the base run task, but runs the [task interactively](../concepts/task#interactive) with stdin capabilities. All interactive tasks are run in isolation in the graph. -### Run persistent target +### Run persistent task -Like the base run target, but runs the [task in a persistent process](../concepts/task#persistent) +Like the base run task, but runs the [task in a persistent process](../concepts/task#persistent) that never exits. All persistent tasks are run in parallel as the last batch in the graph. ## What is the graph used for? -Without the dependency graph, tasks would not efficiently, or possibly at all! The graph helps to +Without the action graph, tasks would not efficiently run, or possibly at all! The graph helps to run tasks in parallel, in the correct order, and to ensure a reliable outcome. diff --git a/website/docs/how-it-works/project-graph.mdx b/website/docs/how-it-works/project-graph.mdx index fddb4ca2814..1db3ddef951 100644 --- a/website/docs/how-it-works/project-graph.mdx +++ b/website/docs/how-it-works/project-graph.mdx @@ -82,7 +82,7 @@ integration. Great question, the project graph is used throughout the codebase to accomplish a variety of functions, but mainly: -- Is fed into the [dependency graph](./dep-graph) to determine relationships of tasks between other +- Is fed into the [action graph](./action-graph) to determine relationships of tasks between other tasks, and across projects. - Powers our [Docker](../guides/docker) layer caching and scaffolding implementations. - Utilized for [project syncing](../commands/sync) to ensure a healthy repository state. diff --git a/website/docs/install.mdx b/website/docs/install.mdx index 08b1dfb671c..ead8b814672 100644 --- a/website/docs/install.mdx +++ b/website/docs/install.mdx @@ -50,12 +50,8 @@ current session. To persist across sessions, update `PATH` manually. moon can be installed and managed in [proto's toolchain](/proto) using a TOML plugin. This will install moon to `~/.proto/tools/moon` and make the binary available at `~/.proto/bin`. -```toml title=".prototools" -[plugins] -moon = "source:https://raw.githubusercontent.com/moonrepo/moon/master/proto-plugin.toml" -``` - ```shell +proto add-plugin moon "source:https://raw.githubusercontent.com/moonrepo/moon/master/proto-plugin.toml" proto install moon ``` diff --git a/website/docs/run-task.mdx b/website/docs/run-task.mdx index df4cbd5b2fb..48f72e2e5ef 100644 --- a/website/docs/run-task.mdx +++ b/website/docs/run-task.mdx @@ -23,7 +23,7 @@ $ moon app:build When this command is ran, it will do the following: -- Generate a directed acyclic graph, known as the dependency graph. +- Generate a directed acyclic graph, known as the action (dependency) graph. - Insert [`deps`](./config/project#deps) as targets into the graph. - Insert the primary target into the graph. - Run all tasks in the graph in parallel and in topological order (the dependency chain). diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 7eb8d9187b4..e33332855d9 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -211,6 +211,14 @@ const config = { '@docusaurus/plugin-client-redirects', { redirects: [ + { + from: '/docs/how-it-works/dep-graph', + to: '/docs/how-it-works/action-graph', + }, + { + from: '/docs/commands/dep-graph', + to: '/docs/commands/action-graph', + }, { from: '/docs/config/global-project', to: '/docs/config/tasks', diff --git a/website/sidebars.js b/website/sidebars.js index 3ead0dd75f5..346a5822d06 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -10,7 +10,7 @@ const sidebars = { label: 'How it works', collapsed: true, collapsible: true, - items: ['how-it-works/languages', 'how-it-works/project-graph', 'how-it-works/dep-graph'], + items: ['how-it-works/languages', 'how-it-works/project-graph', 'how-it-works/action-graph'], link: { type: 'generated-index', title: 'How it works', @@ -21,8 +21,10 @@ const sidebars = { 'mental-model', 'languages', 'project-graph', + 'action-graph', 'dep-graph', 'dependency-graph', + 'task-graph', ], }, }, @@ -97,12 +99,12 @@ const sidebars = { label: 'Commands', items: [ 'commands/overview', + 'commands/action-graph', 'commands/bin', 'commands/ci', 'commands/check', 'commands/clean', 'commands/completions', - 'commands/dep-graph', { type: 'category', label: 'docker', diff --git a/website/src/components/Docs/DepGraph.tsx b/website/src/components/Docs/ActionGraph.tsx similarity index 87% rename from website/src/components/Docs/DepGraph.tsx rename to website/src/components/Docs/ActionGraph.tsx index 84b701602d8..a8a1e52764b 100644 --- a/website/src/components/Docs/DepGraph.tsx +++ b/website/src/components/Docs/ActionGraph.tsx @@ -1,13 +1,25 @@ import React, { useEffect, useRef } from 'react'; import { renderGraph } from '../../utils/renderGraph'; -export default function DepGraph() { +export default function ActionGraph() { const graphRef = useRef(null); useEffect(() => { if (graphRef.current) { renderGraph(graphRef.current, { edges: [ + { + data: { + source: 'sync-workspace', + target: 'node-tool', + }, + }, + { + data: { + source: 'sync-workspace', + target: 'system-tool', + }, + }, { data: { source: 'node-tool', @@ -125,21 +137,21 @@ export default function DepGraph() { { data: { id: 'target-clean', - label: 'RunTarget(example:clean)', + label: 'RunTask(example:clean)', type: 'sm', }, }, { data: { id: 'target-build', - label: 'RunTarget(example:build)', + label: 'RunTask(example:build)', type: 'sm', }, }, { data: { id: 'target-package', - label: 'RunTarget(example:package)', + label: 'RunTask(example:package)', type: 'sm', }, }, diff --git a/website/src/utils/renderGraph.ts b/website/src/utils/renderGraph.ts index 6a6e0e8089f..cf91cd2385f 100644 --- a/website/src/utils/renderGraph.ts +++ b/website/src/utils/renderGraph.ts @@ -50,6 +50,13 @@ export function renderGraph(element: HTMLElement, graph: cytoscape.ElementsDefin width: 60, }, }, + { + selector: 'node[type="run-task"], node[type="sm"]', + style: { + // @ts-expect-error Types incorrect + 'background-gradient-stop-colors': '#6e58d1 #4a2ec6 #3b259e', + }, + }, { selector: 'node[type="run-target"], node[type="sm"]', style: {