diff --git a/crates/turborepo-lib/src/commands/config.rs b/crates/turborepo-lib/src/commands/config.rs index 125022da15d07..d40edcec0cac2 100644 --- a/crates/turborepo-lib/src/commands/config.rs +++ b/crates/turborepo-lib/src/commands/config.rs @@ -24,6 +24,7 @@ struct ConfigOutput<'a> { scm_base: Option<&'a str>, scm_head: Option<&'a str>, cache_dir: &'a Utf8Path, + concurrency: Option<&'a str>, } pub async fn run(repo_root: AbsoluteSystemPathBuf, args: Args) -> Result<(), cli::Error> { @@ -55,6 +56,7 @@ pub async fn run(repo_root: AbsoluteSystemPathBuf, args: Args) -> Result<(), cli scm_base: config.scm_base(), scm_head: config.scm_head(), cache_dir: config.cache_dir(), + concurrency: config.concurrency.as_deref() })? ); Ok(()) diff --git a/crates/turborepo-lib/src/commands/mod.rs b/crates/turborepo-lib/src/commands/mod.rs index d95dc00002cf2..ffaca101c4dd7 100644 --- a/crates/turborepo-lib/src/commands/mod.rs +++ b/crates/turborepo-lib/src/commands/mod.rs @@ -122,6 +122,10 @@ impl CommandBase { ) .with_run_summary(args.run_args().and_then(|args| args.summarize())) .with_allow_no_turbo_json(args.allow_no_turbo_json.then_some(true)) + .with_concurrency( + args.execution_args() + .and_then(|args| args.concurrency.clone()), + ) .build() } diff --git a/crates/turborepo-lib/src/config/env.rs b/crates/turborepo-lib/src/config/env.rs index 160c6ba066251..99d9c9cbe6bc1 100644 --- a/crates/turborepo-lib/src/config/env.rs +++ b/crates/turborepo-lib/src/config/env.rs @@ -43,6 +43,7 @@ const TURBO_MAPPING: &[(&str, &str)] = [ ("turbo_allow_no_turbo_json", "allow_no_turbo_json"), ("turbo_cache", "cache"), ("turbo_tui_scrollback_length", "tui_scrollback_length"), + ("turbo_concurrency", "concurrency"), ] .as_slice(); @@ -202,6 +203,12 @@ impl ResolvedConfigurationOptions for EnvVars { ) })?; + let concurrency = self + .output_map + .get("concurrency") + .filter(|s| !s.is_empty()) + .cloned(); + let output = ConfigurationOptions { api_url: self.output_map.get("api_url").cloned(), login_url: self.output_map.get("login_url").cloned(), @@ -210,6 +217,7 @@ impl ResolvedConfigurationOptions for EnvVars { token: self.output_map.get("token").cloned(), scm_base: self.output_map.get("scm_base").cloned(), scm_head: self.output_map.get("scm_head").cloned(), + concurrency, cache, // Processed booleans signature, @@ -326,6 +334,7 @@ mod test { env.insert("turbo_allow_no_turbo_json".into(), "true".into()); env.insert("turbo_remote_cache_upload_timeout".into(), "200".into()); env.insert("turbo_tui_scrollback_length".into(), "2048".into()); + env.insert("turbo_concurrency".into(), "50%".into()); let config = EnvVars::new(&env) .unwrap() @@ -354,6 +363,7 @@ mod test { config.root_turbo_json_path, Some(AbsoluteSystemPathBuf::new(root_turbo_json).unwrap()) ); + assert_eq!(config.concurrency, Some("50%".to_owned())); } #[test] @@ -378,6 +388,7 @@ mod test { env.insert("turbo_run_summary".into(), "".into()); env.insert("turbo_allow_no_turbo_json".into(), "".into()); env.insert("turbo_tui_scrollback_length".into(), "".into()); + env.insert("turbo_concurrency".into(), "".into()); let config = EnvVars::new(&env) .unwrap() @@ -405,5 +416,6 @@ mod test { config.tui_scrollback_length(), DEFAULT_TUI_SCROLLBACK_LENGTH ); + assert_eq!(config.concurrency, None); } } diff --git a/crates/turborepo-lib/src/config/mod.rs b/crates/turborepo-lib/src/config/mod.rs index 257180e3ce263..66f64d0506e7c 100644 --- a/crates/turborepo-lib/src/config/mod.rs +++ b/crates/turborepo-lib/src/config/mod.rs @@ -297,6 +297,7 @@ pub struct ConfigurationOptions { pub(crate) run_summary: Option, pub(crate) allow_no_turbo_json: Option, pub(crate) tui_scrollback_length: Option, + pub(crate) concurrency: Option, } #[derive(Default)] diff --git a/crates/turborepo-lib/src/config/turbo_json.rs b/crates/turborepo-lib/src/config/turbo_json.rs index 6e233b08040a3..9250195bf0dc1 100644 --- a/crates/turborepo-lib/src/config/turbo_json.rs +++ b/crates/turborepo-lib/src/config/turbo_json.rs @@ -43,6 +43,7 @@ impl<'a> TurboJsonReader<'a> { opts.daemon = turbo_json.daemon.map(|daemon| *daemon.as_inner()); opts.env_mode = turbo_json.env_mode; opts.cache_dir = cache_dir; + opts.concurrency = turbo_json.concurrency; Ok(opts) } } diff --git a/crates/turborepo-lib/src/opts.rs b/crates/turborepo-lib/src/opts.rs index 20e4a99250e6f..10dffad49cef4 100644 --- a/crates/turborepo-lib/src/opts.rs +++ b/crates/turborepo-lib/src/opts.rs @@ -312,7 +312,7 @@ impl<'a> TryFrom> for RunOpts { fn try_from(inputs: OptsInputs) -> Result { let concurrency = inputs - .execution_args + .config .concurrency .as_deref() .map(parse_concurrency) diff --git a/crates/turborepo-lib/src/turbo_json/mod.rs b/crates/turborepo-lib/src/turbo_json/mod.rs index feb757fcd2243..d9e4e33d0a21c 100644 --- a/crates/turborepo-lib/src/turbo_json/mod.rs +++ b/crates/turborepo-lib/src/turbo_json/mod.rs @@ -158,6 +158,9 @@ pub struct RawTurboJson { #[serde(skip_serializing_if = "Option::is_none")] pub boundaries: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub concurrency: Option, + #[deserializable(rename = "//")] #[serde(skip)] _comment: Option, diff --git a/docs/site/content/docs/reference/configuration.mdx b/docs/site/content/docs/reference/configuration.mdx index d41efd2f6929d..731ebbcca4f3e 100644 --- a/docs/site/content/docs/reference/configuration.mdx +++ b/docs/site/content/docs/reference/configuration.mdx @@ -91,6 +91,22 @@ Select a terminal UI for the repository. } ``` +### `concurrency` + +Default: `"10"` + +Set/limit the maximum concurrency for task execution. Must be an integer greater than or equal to `1` or a percentage value like `50%`. + +- Use `1` to force serial execution (one task at a time). +- Use `100%` to use all available logical processors. +- This option is ignored if the [`--parallel`](#--parallel) flag is also passed. + +```jsonc title="./turbo.json" +{ + "concurrency": "1" +} +``` + ### `dangerouslyDisablePackageManagerCheck` Default: `false` diff --git a/docs/site/content/docs/reference/system-environment-variables.mdx b/docs/site/content/docs/reference/system-environment-variables.mdx index 3907fccd2af54..432fa5ce3e7b7 100644 --- a/docs/site/content/docs/reference/system-environment-variables.mdx +++ b/docs/site/content/docs/reference/system-environment-variables.mdx @@ -328,6 +328,16 @@ System environment variables are always overridden by flag values provided direc Enables TUI when passed true or 1, disables when passed false or 0. + + + TURBO_CONCURRENCY + + + Controls{' '} + concurrency{' '} + settings in run or watch mode. + + diff --git a/turborepo-tests/integration/tests/config.t b/turborepo-tests/integration/tests/config.t index 77b4fb1c5ea35..7b96e8f003d45 100644 --- a/turborepo-tests/integration/tests/config.t +++ b/turborepo-tests/integration/tests/config.t @@ -19,7 +19,8 @@ Run test run "envMode": "strict", "scmBase": null, "scmHead": null, - "cacheDir": ".turbo[\\/]+cache" (re) + "cacheDir": ".turbo[\\/]+cache", (re) + "concurrency": null } Run test run with api overloaded @@ -111,3 +112,15 @@ Add env var: `TURBO_CACHE_DIR` Add flag: `--cache-dir` $ ${TURBO} --cache-dir FifthDimension/Nebulo9 config | jq -r .cacheDir FifthDimension[\\/]Nebulo9 (re) + +No concurrency by default + $ ${TURBO} config | jq -r .concurrency + null + +Add env var: `TURBO_CONCURRENCY` + $ TURBO_CONCURRENCY=5 ${TURBO} config | jq -r .concurrency + 5 + +Add flag: `--concurrency` + $ ${TURBO} --concurrency=5 config | jq -r .concurrency + 5 diff --git a/turborepo-tests/integration/tests/persistent-dependencies/10-too-many.t b/turborepo-tests/integration/tests/persistent-dependencies/10-too-many.t index 329713344fd58..8aa3a54a05ed0 100644 --- a/turborepo-tests/integration/tests/persistent-dependencies/10-too-many.t +++ b/turborepo-tests/integration/tests/persistent-dependencies/10-too-many.t @@ -8,6 +8,13 @@ [1] + $ TURBO_CONCURRENCY=1 ${TURBO} run build + x Invalid task configuration + `-> x You have 2 persistent tasks but `turbo` is configured for + | concurrency of 1. Set --concurrency to at least 3 + + [1] + $ ${TURBO} run build --concurrency=2 x Invalid task configuration `-> x You have 2 persistent tasks but `turbo` is configured for @@ -15,7 +22,18 @@ [1] + $ TURBO_CONCURRENCY=2 ${TURBO} run build + x Invalid task configuration + `-> x You have 2 persistent tasks but `turbo` is configured for + | concurrency of 2. Set --concurrency to at least 3 + + [1] + $ ${TURBO} run build --concurrency=3 > tmp.log 2>&1 $ grep -E "2 successful, 2 total" tmp.log Tasks: 2 successful, 2 total + $ TURBO_CONCURRENCY=3 ${TURBO} run build > tmp-env.log 2>&1 + $ grep -E "2 successful, 2 total" tmp-env.log + Tasks: 2 successful, 2 total +