diff --git a/Cargo.lock b/Cargo.lock index 6eaad0a580924..b817469229c5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6242,6 +6242,7 @@ dependencies = [ "hmac", "insta", "libc", + "miette", "os_str_bytes", "path-clean", "petgraph", diff --git a/crates/turborepo-cache/Cargo.toml b/crates/turborepo-cache/Cargo.toml index 5407fb706a531..acfdf821d4010 100644 --- a/crates/turborepo-cache/Cargo.toml +++ b/crates/turborepo-cache/Cargo.toml @@ -30,6 +30,7 @@ bytes.workspace = true camino = { workspace = true } futures = { workspace = true } hmac = "0.12.1" +miette = { workspace = true } os_str_bytes = "6.5.0" path-clean = { workspace = true } petgraph = "0.6.3" diff --git a/crates/turborepo-cache/src/async_cache.rs b/crates/turborepo-cache/src/async_cache.rs index 3d20ea0bb95e5..23126ce07a5c1 100644 --- a/crates/turborepo-cache/src/async_cache.rs +++ b/crates/turborepo-cache/src/async_cache.rs @@ -227,7 +227,8 @@ mod tests { use crate::{ test_cases::{get_test_cases, TestCase}, - AsyncCache, CacheHitMetadata, CacheOpts, CacheSource, RemoteCacheOpts, + AsyncCache, CacheActions, CacheConfig, CacheHitMetadata, CacheOpts, CacheSource, + RemoteCacheOpts, }; #[tokio::test] @@ -255,9 +256,16 @@ mod tests { let opts = CacheOpts { cache_dir: Utf8PathBuf::from(".turbo/cache"), - remote_cache_read_only: false, - skip_remote: false, - skip_filesystem: true, + cache: CacheConfig { + local: CacheActions { + read: false, + write: false, + }, + remote: CacheActions { + read: true, + write: true, + }, + }, workers: 10, remote_cache_opts: Some(RemoteCacheOpts { unused_team_id: Some("my-team".to_string()), @@ -337,9 +345,16 @@ mod tests { let opts = CacheOpts { cache_dir: Utf8PathBuf::from(".turbo/cache"), - remote_cache_read_only: false, - skip_remote: true, - skip_filesystem: false, + cache: CacheConfig { + local: CacheActions { + read: true, + write: true, + }, + remote: CacheActions { + read: false, + write: false, + }, + }, workers: 10, remote_cache_opts: Some(RemoteCacheOpts { unused_team_id: Some("my-team".to_string()), @@ -429,9 +444,16 @@ mod tests { let opts = CacheOpts { cache_dir: Utf8PathBuf::from(".turbo/cache"), - remote_cache_read_only: false, - skip_remote: false, - skip_filesystem: false, + cache: CacheConfig { + local: CacheActions { + read: true, + write: true, + }, + remote: CacheActions { + read: true, + write: true, + }, + }, workers: 10, remote_cache_opts: Some(RemoteCacheOpts { unused_team_id: Some("my-team".to_string()), diff --git a/crates/turborepo-cache/src/config.rs b/crates/turborepo-cache/src/config.rs new file mode 100644 index 0000000000000..3c1289965bbf6 --- /dev/null +++ b/crates/turborepo-cache/src/config.rs @@ -0,0 +1,231 @@ +use std::str::FromStr; + +use miette::{Diagnostic, SourceSpan}; +use thiserror::Error; + +use crate::{CacheActions, CacheConfig}; + +#[derive(Debug, Error, Diagnostic, PartialEq)] +pub enum Error { + #[error("keys cannot be duplicated, found `{key}` multiple times")] + DuplicateKeys { + #[source_code] + text: String, + key: &'static str, + #[label] + span: Option, + }, + #[error("actions cannot be duplicated, found `{action}` multiple times")] + DuplicateActions { + #[source_code] + text: String, + action: &'static str, + #[label] + span: Option, + }, + #[error("invalid cache type and action pair, found `{pair}`, expected colon separated pair")] + InvalidCacheTypeAndAction { + #[source_code] + text: String, + pair: String, + #[label] + span: Option, + }, + #[error("invalid cache action `{c}`")] + InvalidCacheAction { + #[source_code] + text: String, + c: char, + #[label] + span: Option, + }, + #[error("invalid cache type `{s}`, expected `local` or `remote`")] + InvalidCacheType { + #[source_code] + text: String, + s: String, + #[label] + span: Option, + }, +} + +impl Error { + pub fn add_text(mut self, new_text: impl Into) -> Self { + match &mut self { + Self::DuplicateKeys { text, .. } => *text = new_text.into(), + Self::DuplicateActions { text, .. } => *text = new_text.into(), + Self::InvalidCacheTypeAndAction { text, .. } => *text = new_text.into(), + Self::InvalidCacheAction { text, .. } => *text = new_text.into(), + Self::InvalidCacheType { text, .. } => *text = new_text.into(), + } + + self + } + + pub fn add_span(mut self, new_span: SourceSpan) -> Self { + match &mut self { + Self::DuplicateKeys { span, .. } => *span = Some(new_span), + Self::DuplicateActions { span, .. } => *span = Some(new_span), + Self::InvalidCacheTypeAndAction { span, .. } => *span = Some(new_span), + Self::InvalidCacheAction { span, .. } => *span = Some(new_span), + Self::InvalidCacheType { span, .. } => *span = Some(new_span), + } + + self + } +} + +impl FromStr for CacheConfig { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut cache = CacheConfig { + local: CacheActions { + read: false, + write: false, + }, + remote: CacheActions { + read: false, + write: false, + }, + }; + + if s.is_empty() { + return Ok(cache); + } + + let mut seen_local = false; + let mut seen_remote = false; + let mut idx = 0; + + for action in s.split(',') { + let (key, value) = action + .split_once(':') + .ok_or(Error::InvalidCacheTypeAndAction { + text: s.to_string(), + pair: action.to_string(), + span: Some(SourceSpan::new(idx.into(), action.len().into())), + })?; + + match key { + "local" => { + if seen_local { + return Err(Error::DuplicateKeys { + text: s.to_string(), + key: "local", + span: Some(SourceSpan::new(idx.into(), key.len().into())), + }); + } + + seen_local = true; + cache.local = CacheActions::from_str(value).map_err(|err| { + err.add_text(s).add_span(SourceSpan::new( + (idx + key.len() + 1).into(), + key.len().into(), + )) + })?; + } + "remote" => { + if seen_remote { + return Err(Error::DuplicateKeys { + text: s.to_string(), + key: "remote", + span: Some(SourceSpan::new(idx.into(), key.len().into())), + }); + } + + seen_remote = true; + cache.remote = CacheActions::from_str(value).map_err(|err| { + err.add_text(s).add_span(SourceSpan::new( + (idx + key.len() + 1).into(), + value.len().into(), + )) + })? + } + ty => { + return Err(Error::InvalidCacheType { + text: s.to_string(), + s: ty.to_string(), + span: Some(SourceSpan::new(idx.into(), ty.len().into())), + }) + } + } + + idx += action.len() + 1; + } + Ok(cache) + } +} + +impl FromStr for CacheActions { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut cache = CacheActions { + read: false, + write: false, + }; + + for c in s.chars() { + match c { + 'r' => { + if cache.read { + return Err(Error::DuplicateActions { + text: s.to_string(), + action: "r (read)", + span: None, + }); + } + cache.read = true; + } + + 'w' => { + if cache.write { + return Err(Error::DuplicateActions { + text: s.to_string(), + action: "w (write)", + span: None, + }); + } + cache.write = true; + } + _ => { + return Err(Error::InvalidCacheAction { + c, + text: String::new(), + span: None, + }) + } + } + } + + Ok(cache) + } +} + +#[cfg(test)] +mod test { + use test_case::test_case; + + use super::*; + + #[test_case("local:r,remote:w", Ok(CacheConfig { local: CacheActions { read: true, write: false }, remote: CacheActions { read: false, write: true } }) ; "local:r,remote:w" + )] + #[test_case("local:r", Ok(CacheConfig { local: CacheActions { read: true, write: false }, remote: CacheActions { read: false, write: false } }) ; "local:r" + )] + #[test_case("local:", Ok(CacheConfig { local: CacheActions { read: false, write: false }, remote: CacheActions { read: false, write: false } }) ; "empty action" + )] + #[test_case("local:,remote:", Ok(CacheConfig { local: CacheActions { read: false, write: false }, remote: CacheActions { read: false, write: false } }) ; "multiple empty actions" + )] + #[test_case("local:,remote:r", Ok(CacheConfig { local: CacheActions { read: false, write: false }, remote: CacheActions { read: true, write: false } }) ; "local: empty, remote:r" + )] + #[test_case("", Ok(CacheConfig { local: CacheActions { read: false, write: false }, remote: CacheActions { read: false, write: false } }) ; "empty" + )] + #[test_case("local:r,local:w", Err(Error::DuplicateKeys { text: "local:r,local:w".to_string(), key: "local", span: Some(SourceSpan::new(8.into(), 5.into())) }) ; "duplicate local key" + )] + #[test_case("local:rr", Err(Error::DuplicateActions { text: "local:rr".to_string(), action: "r (read)", span: Some(SourceSpan::new(6.into(), 5.into())) }) ; "duplicate action")] + #[test_case("remote:r,local=rx", Err(Error::InvalidCacheTypeAndAction { text: "remote:r,local=rx".to_string(), pair: "local=rx".to_string(), span: Some(SourceSpan::new(9.into(), 8.into())) }) ; "invalid key action pair")] + #[test_case("local:rx", Err(Error::InvalidCacheAction { c: 'x', text: "local:rx".to_string(), span: Some(SourceSpan::new(6.into(), 5.into())) }) ; "invalid action")] + #[test_case("file:r", Err(Error::InvalidCacheType { s: "file".to_string(), text: "file:r".to_string(), span: Some(SourceSpan::new(0.into(), 4.into())) }) ; "invalid cache type")] + fn test_cache_config(s: &str, expected: Result) { + assert_eq!(CacheConfig::from_str(s), expected); + } +} diff --git a/crates/turborepo-cache/src/lib.rs b/crates/turborepo-cache/src/lib.rs index c05f4dcce5edd..65bcadbc7e5db 100644 --- a/crates/turborepo-cache/src/lib.rs +++ b/crates/turborepo-cache/src/lib.rs @@ -7,6 +7,7 @@ mod async_cache; /// The core cache creation and restoration logic. pub mod cache_archive; +pub mod config; /// File system cache pub mod fs; /// Remote cache @@ -61,6 +62,8 @@ pub enum CacheError { LinkTargetDoesNotExist(String, #[backtrace] Backtrace), #[error("Invalid tar, link target does not exist on header")] LinkTargetNotOnHeader(#[backtrace] Backtrace), + #[error(transparent)] + Config(#[from] config::Error), #[error("attempted to restore unsupported file type: {0:?}")] RestoreUnsupportedFileType(tar::EntryType, #[backtrace] Backtrace), // We don't pass the `FileType` because there's no simple @@ -105,12 +108,43 @@ pub struct CacheHitMetadata { pub time_saved: u64, } -#[derive(Clone, Debug, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct CacheActions { + pub read: bool, + pub write: bool, +} + +impl CacheActions { + pub fn should_use(&self) -> bool { + self.read || self.write + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub struct CacheConfig { + pub local: CacheActions, + pub remote: CacheActions, +} + +impl CacheConfig { + pub fn skip_writes(&self) -> bool { + !self.local.write && !self.remote.write + } +} + +impl Default for CacheActions { + fn default() -> Self { + Self { + read: true, + write: true, + } + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct CacheOpts { pub cache_dir: Utf8PathBuf, - pub remote_cache_read_only: bool, - pub skip_remote: bool, - pub skip_filesystem: bool, + pub cache: CacheConfig, pub workers: u32, pub remote_cache_opts: Option, } diff --git a/crates/turborepo-cache/src/multiplexer.rs b/crates/turborepo-cache/src/multiplexer.rs index e5b1ab8db661c..44bc51afaeaf8 100644 --- a/crates/turborepo-cache/src/multiplexer.rs +++ b/crates/turborepo-cache/src/multiplexer.rs @@ -11,7 +11,7 @@ use turborepo_api_client::{APIAuth, APIClient}; use crate::{ fs::FSCache, http::{HTTPCache, UploadMap}, - CacheError, CacheHitMetadata, CacheOpts, + CacheConfig, CacheError, CacheHitMetadata, CacheOpts, }; pub struct CacheMultiplexer { @@ -23,7 +23,7 @@ pub struct CacheMultiplexer { // Just for keeping track of whether we've already printed a warning about the remote cache // being read-only should_print_skipping_remote_put: AtomicBool, - remote_cache_read_only: bool, + cache_config: CacheConfig, fs: Option, http: Option, } @@ -37,8 +37,8 @@ impl CacheMultiplexer { api_auth: Option, analytics_recorder: Option, ) -> Result { - let use_fs_cache = !opts.skip_filesystem; - let use_http_cache = !opts.skip_remote; + let use_fs_cache = opts.cache.local.should_use(); + let use_http_cache = opts.cache.remote.should_use(); // Since the above two flags are not mutually exclusive it is possible to // configure yourself out of having a cache. We should tell you about it @@ -67,7 +67,7 @@ impl CacheMultiplexer { Ok(CacheMultiplexer { should_print_skipping_remote_put: AtomicBool::new(true), should_use_http_cache: AtomicBool::new(http_cache.is_some()), - remote_cache_read_only: opts.remote_cache_read_only, + cache_config: opts.cache, fs: fs_cache, http: http_cache, }) @@ -95,14 +95,20 @@ impl CacheMultiplexer { files: &[AnchoredSystemPathBuf], duration: u64, ) -> Result<(), CacheError> { - self.fs - .as_ref() - .map(|fs| fs.put(anchor, key, files, duration)) - .transpose()?; + if self.cache_config.local.write { + self.fs + .as_ref() + .map(|fs| fs.put(anchor, key, files, duration)) + .transpose()?; + } let http_result = match self.get_http_cache() { Some(http) => { - if self.remote_cache_read_only { + if self.cache_config.remote.write { + let http_result = http.put(anchor, key, files, duration).await; + + Some(http_result) + } else { if self .should_print_skipping_remote_put .load(Ordering::Relaxed) @@ -115,10 +121,6 @@ impl CacheMultiplexer { // Cache is functional but running in read-only mode, so we don't want to try to // write to it None - } else { - let http_result = http.put(anchor, key, files, duration).await; - - Some(http_result) } } _ => None, @@ -144,25 +146,31 @@ impl CacheMultiplexer { anchor: &AbsoluteSystemPath, key: &str, ) -> Result)>, CacheError> { - if let Some(fs) = &self.fs { - if let response @ Ok(Some(_)) = fs.fetch(anchor, key) { - return response; + if self.cache_config.local.read { + if let Some(fs) = &self.fs { + if let response @ Ok(Some(_)) = fs.fetch(anchor, key) { + return response; + } } } - if let Some(http) = self.get_http_cache() { - if let Ok(Some((CacheHitMetadata { source, time_saved }, files))) = - http.fetch(key).await - { - // Store this into fs cache. We can ignore errors here because we know - // we have previously successfully stored in HTTP cache, and so the overall - // result is a success at fetching. Storing in lower-priority caches is an - // optimization. - if let Some(fs) = &self.fs { - let _ = fs.put(anchor, key, &files, time_saved); - } + if self.cache_config.remote.read { + if let Some(http) = self.get_http_cache() { + if let Ok(Some((CacheHitMetadata { source, time_saved }, files))) = + http.fetch(key).await + { + // Store this into fs cache. We can ignore errors here because we know + // we have previously successfully stored in HTTP cache, and so the overall + // result is a success at fetching. Storing in lower-priority caches is an + // optimization. + if self.cache_config.local.write { + if let Some(fs) = &self.fs { + let _ = fs.put(anchor, key, &files, time_saved); + } + } - return Ok(Some((CacheHitMetadata { source, time_saved }, files))); + return Ok(Some((CacheHitMetadata { source, time_saved }, files))); + } } } @@ -171,23 +179,27 @@ impl CacheMultiplexer { #[tracing::instrument(skip_all)] pub async fn exists(&self, key: &str) -> Result, CacheError> { - if let Some(fs) = &self.fs { - match fs.exists(key) { - cache_hit @ Ok(Some(_)) => { - return cache_hit; + if self.cache_config.local.read { + if let Some(fs) = &self.fs { + match fs.exists(key) { + cache_hit @ Ok(Some(_)) => { + return cache_hit; + } + Ok(None) => {} + Err(err) => debug!("failed to check fs cache: {:?}", err), } - Ok(None) => {} - Err(err) => debug!("failed to check fs cache: {:?}", err), } } - if let Some(http) = self.get_http_cache() { - match http.exists(key).await { - cache_hit @ Ok(Some(_)) => { - return cache_hit; + if self.cache_config.remote.read { + if let Some(http) = self.get_http_cache() { + match http.exists(key).await { + cache_hit @ Ok(Some(_)) => { + return cache_hit; + } + Ok(None) => {} + Err(err) => debug!("failed to check http cache: {:?}", err), } - Ok(None) => {} - Err(err) => debug!("failed to check http cache: {:?}", err), } } diff --git a/crates/turborepo-lib/src/cli/mod.rs b/crates/turborepo-lib/src/cli/mod.rs index 136cfa7154d0d..a5e7cd9b2a337 100644 --- a/crates/turborepo-lib/src/cli/mod.rs +++ b/crates/turborepo-lib/src/cli/mod.rs @@ -33,7 +33,6 @@ use crate::{ }; mod error; - // Global turbo sets this environment variable to its cwd so that local // turbo can use it for package inference. pub const INVOCATION_DIR_ENV_VAR: &str = "TURBO_INVOCATION_DIR"; @@ -724,9 +723,6 @@ pub struct ExecutionArgs { /// Run turbo in single-package mode #[clap(long)] pub single_package: bool, - /// Ignore the existing cache (to force execution) - #[clap(long, default_missing_value = "true")] - pub force: Option>, /// Specify whether or not to do framework inference for tasks #[clap(long, value_name = "BOOL", action = ArgAction::Set, default_value = "true", default_missing_value = "true", num_args = 0..=1)] pub framework_inference: bool, @@ -769,10 +765,6 @@ pub struct ExecutionArgs { pub only: bool, #[clap(long, hide = true)] pub pkg_inference_root: Option, - /// Ignore the local filesystem cache for all tasks. Only - /// allow reading and caching artifacts using the remote cache. - #[clap(long, default_missing_value = "true")] - pub remote_only: Option>, /// Use "none" to remove prefixes from task logs. Use "task" to get task id /// prefixing. Use "auto" to let turbo decide how to prefix the logs /// based on the execution environment. In most cases this will be the same @@ -791,11 +783,6 @@ pub struct ExecutionArgs { } impl ExecutionArgs { - pub fn remote_only(&self) -> Option { - let remote_only = self.remote_only?; - Some(remote_only.unwrap_or(true)) - } - fn track(&self, telemetry: &CommandEventBuilder) { // default to false track_usage!(telemetry, self.framework_inference, |val: bool| !val); @@ -803,9 +790,7 @@ impl ExecutionArgs { track_usage!(telemetry, self.continue_execution, |val| val); track_usage!(telemetry, self.single_package, |val| val); track_usage!(telemetry, self.only, |val| val); - track_usage!(telemetry, self.remote_only().unwrap_or_default(), |val| val); track_usage!(telemetry, &self.cache_dir, Option::is_some); - track_usage!(telemetry, &self.force, Option::is_some); track_usage!(telemetry, &self.pkg_inference_root, Option::is_some); if let Some(concurrency) = &self.concurrency { @@ -848,6 +833,29 @@ impl ExecutionArgs { ArgGroup::new("daemon-group").multiple(false).required(false), ])] pub struct RunArgs { + /// Set the cache behavior for this run. Pass a list of comma-separated key, + /// value pairs to enable reading and writing to either the local or + /// remote cache. + #[clap(long, conflicts_with_all = &["force", "remote_only", "remote_cache_read_only", "no_cache"])] + pub cache: Option, + /// Ignore the existing cache (to force execution). Equivalent to + /// `--cache=local:w,remote:w` + #[clap(long, default_missing_value = "true")] + pub force: Option>, + /// Ignore the local filesystem cache for all tasks. Only + /// allow reading and caching artifacts using the remote cache. + /// Equivalent to `--cache=remote:rw` + #[clap(long, default_missing_value = "true", group = "cache-group")] + pub remote_only: Option>, + /// Treat remote cache as read only. Equivalent to + /// `--cache=remote:r;local:rw` + #[clap(long, default_missing_value = "true")] + pub remote_cache_read_only: Option>, + /// Avoid saving task results to the cache. Useful for development/watch + /// tasks. Equivalent to `--cache=local:r,remote:r` + #[clap(long)] + pub no_cache: bool, + /// Set the number of concurrent cache operations (default 10) #[clap(long, default_value_t = DEFAULT_NUM_WORKERS)] pub cache_workers: u32, @@ -859,12 +867,6 @@ pub struct RunArgs { /// is provided #[clap(long, num_args = 0..=1, default_missing_value = "", value_parser = validate_graph_extension)] pub graph: Option, - - /// Avoid saving task results to the cache. Useful for development/watch - /// tasks. - #[clap(long)] - pub no_cache: bool, - // clap does not have negation flags such as --daemon and --no-daemon // so we need to use a group to enforce that only one of them is set. // ----------------------- @@ -887,9 +889,6 @@ pub struct RunArgs { /// All identifying data omitted from the profile. #[clap(long, value_parser=NonEmptyStringValueParser::new(), conflicts_with = "profile")] pub anon_profile: Option, - /// Treat remote cache as read only - #[clap(long, default_missing_value = "true")] - pub remote_cache_read_only: Option>, /// Generate a summary of the turbo run #[clap(long, default_missing_value = "true")] pub summarize: Option>, @@ -906,6 +905,9 @@ pub struct RunArgs { impl Default for RunArgs { fn default() -> Self { Self { + remote_only: None, + cache: None, + force: None, cache_workers: DEFAULT_NUM_WORKERS, dry_run: None, graph: None, @@ -923,6 +925,11 @@ impl Default for RunArgs { } impl RunArgs { + pub fn remote_only(&self) -> Option { + let remote_only = self.remote_only?; + Some(remote_only.unwrap_or(true)) + } + /// Some(true) means force the daemon /// Some(false) means force no daemon /// None means use the default detection @@ -957,6 +964,8 @@ impl RunArgs { pub fn track(&self, telemetry: &CommandEventBuilder) { // default to true track_usage!(telemetry, self.no_cache, |val| val); + track_usage!(telemetry, self.remote_only().unwrap_or_default(), |val| val); + track_usage!(telemetry, &self.force, Option::is_some); track_usage!(telemetry, self.daemon, |val| val); track_usage!(telemetry, self.no_daemon, |val| val); track_usage!(telemetry, self.parallel, |val| val); @@ -1443,7 +1452,6 @@ mod test { fn get_default_execution_args() -> ExecutionArgs { ExecutionArgs { output_logs: None, - remote_only: None, framework_inference: true, ..ExecutionArgs::default() } @@ -1779,10 +1787,12 @@ mod test { command: Some(Command::Run { execution_args: Box::new(ExecutionArgs { tasks: vec!["build".to_string()], - force: Some(Some(true)), ..get_default_execution_args() }), - run_args: Box::new(get_default_run_args()) + run_args: Box::new(RunArgs { + force: Some(Some(true)), + ..get_default_run_args() + }) }), ..Args::default() } ; @@ -2078,10 +2088,12 @@ mod test { command: Some(Command::Run { execution_args: Box::new(ExecutionArgs { tasks: vec!["build".to_string()], - remote_only: None, ..get_default_execution_args() }), - run_args: Box::new(get_default_run_args()) + run_args: Box::new(RunArgs { + remote_only: None, + ..get_default_run_args() + }) }), ..Args::default() } ; @@ -2093,10 +2105,12 @@ mod test { command: Some(Command::Run { execution_args: Box::new(ExecutionArgs { tasks: vec!["build".to_string()], - remote_only: Some(Some(true)), ..get_default_execution_args() }), - run_args: Box::new(get_default_run_args()) + run_args: Box::new(RunArgs { + remote_only: Some(Some(true)), + ..get_default_run_args() + }) }), ..Args::default() } ; @@ -2108,10 +2122,12 @@ mod test { command: Some(Command::Run { execution_args: Box::new(ExecutionArgs { tasks: vec!["build".to_string()], - remote_only: Some(Some(true)), ..get_default_execution_args() }), - run_args: Box::new(get_default_run_args()) + run_args: Box::new(RunArgs { + remote_only: Some(Some(true)), + ..get_default_run_args() + }) }), ..Args::default() } ; @@ -2123,10 +2139,12 @@ mod test { command: Some(Command::Run { execution_args: Box::new(ExecutionArgs { tasks: vec!["build".to_string()], - remote_only: Some(Some(false)), ..get_default_execution_args() }), - run_args: Box::new(get_default_run_args()) + run_args: Box::new(RunArgs { + remote_only: Some(Some(false)), + ..get_default_run_args() + }) }), ..Args::default() } ; diff --git a/crates/turborepo-lib/src/commands/mod.rs b/crates/turborepo-lib/src/commands/mod.rs index 04f1568bc0df8..439be0cd13ae4 100644 --- a/crates/turborepo-lib/src/commands/mod.rs +++ b/crates/turborepo-lib/src/commands/mod.rs @@ -93,20 +93,23 @@ impl CommandBase { ) .with_force( self.args - .execution_args() + .run_args() .and_then(|args| args.force.map(|value| value.unwrap_or(true))), ) .with_log_order(self.args.execution_args().and_then(|args| args.log_order)) - .with_remote_only( - self.args - .execution_args() - .and_then(|args| args.remote_only()), - ) + .with_remote_only(self.args.run_args().and_then(|args| args.remote_only())) .with_remote_cache_read_only( self.args .run_args() .and_then(|args| args.remote_cache_read_only()), ) + .with_cache( + self.args + .run_args() + .and_then(|args| args.cache.as_deref()) + .map(|cache| cache.parse()) + .transpose()?, + ) .with_run_summary(self.args.run_args().and_then(|args| args.summarize())) .with_allow_no_turbo_json(self.args.allow_no_turbo_json.then_some(true)) .build() diff --git a/crates/turborepo-lib/src/config/env.rs b/crates/turborepo-lib/src/config/env.rs index 1f81dba3b0640..5d9aedcf7406f 100644 --- a/crates/turborepo-lib/src/config/env.rs +++ b/crates/turborepo-lib/src/config/env.rs @@ -39,6 +39,7 @@ const TURBO_MAPPING: &[(&str, &str)] = [ ("turbo_remote_cache_read_only", "remote_cache_read_only"), ("turbo_run_summary", "run_summary"), ("turbo_allow_no_turbo_json", "allow_no_turbo_json"), + ("turbo_cache", "cache"), ] .as_slice(); @@ -77,12 +78,6 @@ impl ResolvedConfigurationOptions for EnvVars { .map(|value| value.ok_or_else(|| Error::InvalidPreflight)) .transpose()?; - // Process enabled - let enabled = self - .truthy_value("enabled") - .map(|value| value.ok_or_else(|| Error::InvalidRemoteCacheEnabled)) - .transpose()?; - let force = self.truthy_value("force").flatten(); let remote_only = self.truthy_value("remote_only").flatten(); let remote_cache_read_only = self.truthy_value("remote_cache_read_only").flatten(); @@ -162,10 +157,15 @@ 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(), + cache: self + .output_map + .get("cache") + .map(|c| c.parse()) + .transpose()?, // Processed booleans signature, preflight, - enabled, + enabled: None, ui, allow_no_package_manager, daemon, diff --git a/crates/turborepo-lib/src/config/mod.rs b/crates/turborepo-lib/src/config/mod.rs index a32b592d7eb24..55c772a23dbe4 100644 --- a/crates/turborepo-lib/src/config/mod.rs +++ b/crates/turborepo-lib/src/config/mod.rs @@ -19,6 +19,7 @@ use thiserror::Error; use tracing::debug; use turbo_json::TurboJsonReader; use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf}; +use turborepo_cache::CacheConfig; use turborepo_errors::TURBO_SITE; use turborepo_repository::package_graph::PackageName; @@ -81,6 +82,8 @@ pub enum Error { config_path: AbsoluteSystemPathBuf, error: io::Error, }, + #[error(transparent)] + Cache(#[from] turborepo_cache::config::Error), #[error( "Package tasks (#) are not allowed in single-package repositories: found \ {task_id}" @@ -252,6 +255,8 @@ pub struct ConfigurationOptions { pub(crate) root_turbo_json_path: Option, pub(crate) force: Option, pub(crate) log_order: Option, + #[serde(skip)] + pub(crate) cache: Option, pub(crate) remote_only: Option, pub(crate) remote_cache_read_only: Option, pub(crate) run_summary: Option, @@ -368,6 +373,10 @@ impl ConfigurationOptions { }) } + pub fn cache(&self) -> Option { + self.cache + } + pub fn force(&self) -> bool { self.force.unwrap_or_default() } diff --git a/crates/turborepo-lib/src/opts.rs b/crates/turborepo-lib/src/opts.rs index 9b58a2996cd29..91a659f680b98 100644 --- a/crates/turborepo-lib/src/opts.rs +++ b/crates/turborepo-lib/src/opts.rs @@ -32,6 +32,11 @@ pub enum Error { or equal to 1: {1}" )] ConcurrencyOutOfBounds(#[backtrace] backtrace::Backtrace, String), + #[error( + "Cannot set `cache` config and other cache options (`force`, `remoteOnly`, \ + `remoteCacheReadOnly`) at the same time" + )] + OverlappingCacheOptions, #[error(transparent)] Path(#[from] turbopath::PathError), #[error(transparent)] @@ -100,16 +105,16 @@ impl Opts { return Err(Error::ExpectedRun(Backtrace::capture())); }; - let run_and_execution_args = OptsInputs { + let inputs = OptsInputs { run_args: run_args.as_ref(), execution_args: execution_args.as_ref(), config, api_auth: &api_auth, }; - let run_opts = RunOpts::try_from(run_and_execution_args)?; - let cache_opts = CacheOpts::from(run_and_execution_args); - let scope_opts = ScopeOpts::try_from(run_and_execution_args)?; - let runcache_opts = RunCacheOpts::from(run_and_execution_args); + let run_opts = RunOpts::try_from(inputs)?; + let cache_opts = CacheOpts::try_from(inputs)?; + let scope_opts = ScopeOpts::try_from(inputs)?; + let runcache_opts = RunCacheOpts::from(inputs); Ok(Self { run_opts, @@ -128,18 +133,14 @@ struct OptsInputs<'a> { api_auth: &'a Option, } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct RunCacheOpts { - pub(crate) skip_reads: bool, - pub(crate) skip_writes: bool, pub(crate) task_output_logs_override: Option, } impl<'a> From> for RunCacheOpts { fn from(inputs: OptsInputs<'a>) -> Self { RunCacheOpts { - skip_reads: inputs.config.force(), - skip_writes: inputs.run_args.no_cache, task_output_logs_override: inputs.execution_args.output_logs, } } @@ -359,19 +360,52 @@ impl<'a> TryFrom> for ScopeOpts { } } -impl<'a> From> for CacheOpts { - fn from(inputs: OptsInputs<'a>) -> Self { +impl<'a> TryFrom> for CacheOpts { + type Error = self::Error; + + fn try_from(inputs: OptsInputs<'a>) -> Result { let is_linked = turborepo_api_client::is_linked(inputs.api_auth); - let skip_remote = if !is_linked { - true + let cache = inputs.config.cache(); + let has_old_cache_config = inputs.config.remote_only() + || inputs.config.force() + || inputs.run_args.no_cache + || inputs.config.remote_cache_read_only(); + + if has_old_cache_config && cache.is_some() { + return Err(Error::OverlappingCacheOptions); + } + + let mut cache = cache.unwrap_or_default(); + + if inputs.config.remote_only() { + cache.local.read = false; + cache.local.write = false; + } + + if inputs.config.force() { + cache.local.read = false; + cache.remote.read = false; + } + + if inputs.run_args.no_cache { + cache.local.write = false; + cache.remote.write = false; + } + + if !is_linked { + cache.remote.read = false; + cache.remote.write = false; } else if let Some(enabled) = inputs.config.enabled { // We're linked, but if the user has explicitly enabled or disabled, use that // value - !enabled - } else { - false + cache.remote.read = enabled; + cache.remote.write = enabled; }; + if inputs.config.remote_cache_read_only() { + cache.remote.write = false; + } + // Note that we don't currently use the team_id value here. In the future, we // should probably verify that we only use the signature value when the // configured team_id matches the final resolved team_id. @@ -383,14 +417,12 @@ impl<'a> From> for CacheOpts { signature, )); - CacheOpts { + Ok(CacheOpts { cache_dir: inputs.config.cache_dir().into(), - skip_filesystem: inputs.config.remote_only(), - remote_cache_read_only: inputs.config.remote_cache_read_only(), + cache, workers: inputs.run_args.cache_workers, - skip_remote, remote_cache_opts, - } + }) } } @@ -410,14 +442,20 @@ impl ScopeOpts { #[cfg(test)] mod test { + use test_case::test_case; + use turbopath::AbsoluteSystemPathBuf; + use turborepo_api_client::APIAuth; use turborepo_cache::CacheOpts; + use turborepo_ui::ColorConfig; - use super::RunOpts; + use super::{OptsInputs, RunOpts}; use crate::{ - cli::DryRunMode, + cli::{Command, DryRunMode, RunArgs}, + commands::CommandBase, opts::{Opts, RunCacheOpts, ScopeOpts}, turbo_json::UIMode, + Args, }; #[derive(Default)] @@ -551,4 +589,86 @@ mod test { let synthesized = opts.synthesize_command(); assert_eq!(synthesized, expected); } + + #[test_case( + RunArgs { + no_cache: true, + ..Default::default() + }, "no-cache" + )] + #[test_case( + RunArgs { + force: Some(Some(true)), + ..Default::default() + }, "force" + )] + #[test_case( + RunArgs { + remote_only: Some(Some(true)), + ..Default::default() + }, "remote-only" + )] + #[test_case( + RunArgs { + remote_cache_read_only: Some(Some(true)), + ..Default::default() + }, "remote-cache-read-only" + )] + #[test_case( + RunArgs { + no_cache: true, + cache: Some("remote:w,local:rw".to_string()), + ..Default::default() + }, "no-cache_remote_w,local_rw" + )] + #[test_case( + RunArgs { + remote_only: Some(Some(true)), + cache: Some("remote:r,local:rw".to_string()), + ..Default::default() + }, "remote-only_remote_r,local_rw" + )] + #[test_case( + RunArgs { + force: Some(Some(true)), + cache: Some("remote:r,local:r".to_string()), + ..Default::default() + }, "force_remote_r,local_r" + )] + #[test_case( + RunArgs { + remote_cache_read_only: Some(Some(true)), + cache: Some("remote:rw,local:r".to_string()), + ..Default::default() + }, "remote-cache-read-only_remote_rw,local_r" + )] + fn test_resolve_cache_config(run_args: RunArgs, name: &str) -> Result<(), anyhow::Error> { + let mut args = Args::default(); + args.command = Some(Command::Run { + execution_args: Box::default(), + run_args: Box::new(run_args), + }); + let base = CommandBase::new( + args, + AbsoluteSystemPathBuf::default(), + "1.0.0", + ColorConfig::new(true), + ); + + let cache_opts = CacheOpts::try_from(OptsInputs { + run_args: base.args().run_args().unwrap(), + execution_args: base.args().execution_args().unwrap(), + config: base.config()?, + api_auth: &Some(APIAuth { + team_id: Some("my-team".to_string()), + token: "my-token".to_string(), + team_slug: None, + }), + }) + .map(|cache_opts| cache_opts.cache); + + insta::assert_debug_snapshot!(name, cache_opts); + + Ok(()) + } } diff --git a/crates/turborepo-lib/src/run/builder.rs b/crates/turborepo-lib/src/run/builder.rs index aeb4ac081ae5e..9795b65f347f7 100644 --- a/crates/turborepo-lib/src/run/builder.rs +++ b/crates/turborepo-lib/src/run/builder.rs @@ -457,7 +457,8 @@ impl RunBuilder { let run_cache = Arc::new(RunCache::new( async_cache, &self.repo_root, - &self.opts.runcache_opts, + self.opts.runcache_opts, + &self.opts.cache_opts, color_selector, daemon.clone(), self.color_config, diff --git a/crates/turborepo-lib/src/run/cache.rs b/crates/turborepo-lib/src/run/cache.rs index 02c9eedce5b9b..b70abdd6e48c3 100644 --- a/crates/turborepo-lib/src/run/cache.rs +++ b/crates/turborepo-lib/src/run/cache.rs @@ -10,7 +10,9 @@ use tracing::{debug, error, log::warn}; use turbopath::{ AbsoluteSystemPath, AbsoluteSystemPathBuf, AnchoredSystemPath, AnchoredSystemPathBuf, }; -use turborepo_cache::{http::UploadMap, AsyncCache, CacheError, CacheHitMetadata, CacheSource}; +use turborepo_cache::{ + http::UploadMap, AsyncCache, CacheError, CacheHitMetadata, CacheOpts, CacheSource, +}; use turborepo_repository::package_graph::PackageInfo; use turborepo_scm::SCM; use turborepo_telemetry::events::{task::PackageTaskEventBuilder, TrackedErrors}; @@ -65,10 +67,12 @@ pub trait CacheOutput { } impl RunCache { + #[allow(clippy::too_many_arguments)] pub fn new( cache: AsyncCache, repo_root: &AbsoluteSystemPath, - opts: &RunCacheOpts, + run_cache_opts: RunCacheOpts, + cache_opts: &CacheOpts, color_selector: ColorSelector, daemon_client: Option>, ui: ColorConfig, @@ -77,14 +81,14 @@ impl RunCache { let task_output_logs = if is_dry_run { Some(OutputLogsMode::None) } else { - opts.task_output_logs_override + run_cache_opts.task_output_logs_override }; RunCache { task_output_logs, cache, warnings: Default::default(), - reads_disabled: opts.skip_reads, - writes_disabled: opts.skip_writes, + reads_disabled: !cache_opts.cache.remote.read && !cache_opts.cache.local.read, + writes_disabled: !cache_opts.cache.remote.write && !cache_opts.cache.local.write, repo_root: repo_root.to_owned(), color_selector, daemon_client, diff --git a/crates/turborepo-lib/src/run/mod.rs b/crates/turborepo-lib/src/run/mod.rs index 8120f4dcd569b..448b8c02d4726 100644 --- a/crates/turborepo-lib/src/run/mod.rs +++ b/crates/turborepo-lib/src/run/mod.rs @@ -114,7 +114,7 @@ impl Run { ); } - let use_http_cache = !self.opts.cache_opts.skip_remote; + let use_http_cache = self.opts.cache_opts.cache.remote.should_use(); if use_http_cache { cprintln!(self.color_config, GREY, "• Remote caching enabled"); } else { @@ -277,7 +277,7 @@ impl Run { } pub async fn run(&self, ui_sender: Option, is_watch: bool) -> Result { - let skip_cache_writes = self.opts.runcache_opts.skip_writes; + let skip_cache_writes = self.opts.cache_opts.cache.skip_writes(); if let Some(subscriber) = self.signal_handler.subscribe() { let run_cache = self.run_cache.clone(); tokio::spawn(async move { diff --git a/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__force.snap b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__force.snap new file mode 100644 index 0000000000000..8cb25abf8d6bf --- /dev/null +++ b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__force.snap @@ -0,0 +1,16 @@ +--- +source: crates/turborepo-lib/src/opts.rs +expression: cache_opts +--- +Ok( + CacheConfig { + local: CacheActions { + read: false, + write: true, + }, + remote: CacheActions { + read: false, + write: true, + }, + }, +) diff --git a/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__force_remote_r,local_r.snap b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__force_remote_r,local_r.snap new file mode 100644 index 0000000000000..cbc9a96ee2d30 --- /dev/null +++ b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__force_remote_r,local_r.snap @@ -0,0 +1,7 @@ +--- +source: crates/turborepo-lib/src/opts.rs +expression: cache_opts +--- +Err( + OverlappingCacheOptions, +) diff --git a/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__no-cache.snap b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__no-cache.snap new file mode 100644 index 0000000000000..418a3f24a6b8a --- /dev/null +++ b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__no-cache.snap @@ -0,0 +1,16 @@ +--- +source: crates/turborepo-lib/src/opts.rs +expression: cache_opts +--- +Ok( + CacheConfig { + local: CacheActions { + read: true, + write: false, + }, + remote: CacheActions { + read: true, + write: false, + }, + }, +) diff --git a/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__no-cache_remote_w,local_rw.snap b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__no-cache_remote_w,local_rw.snap new file mode 100644 index 0000000000000..cbc9a96ee2d30 --- /dev/null +++ b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__no-cache_remote_w,local_rw.snap @@ -0,0 +1,7 @@ +--- +source: crates/turborepo-lib/src/opts.rs +expression: cache_opts +--- +Err( + OverlappingCacheOptions, +) diff --git a/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-cache-read-only.snap b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-cache-read-only.snap new file mode 100644 index 0000000000000..4cf87e441ba45 --- /dev/null +++ b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-cache-read-only.snap @@ -0,0 +1,16 @@ +--- +source: crates/turborepo-lib/src/opts.rs +expression: cache_opts +--- +Ok( + CacheConfig { + local: CacheActions { + read: true, + write: true, + }, + remote: CacheActions { + read: true, + write: false, + }, + }, +) diff --git a/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-cache-read-only_remote_rw,local_r.snap b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-cache-read-only_remote_rw,local_r.snap new file mode 100644 index 0000000000000..cbc9a96ee2d30 --- /dev/null +++ b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-cache-read-only_remote_rw,local_r.snap @@ -0,0 +1,7 @@ +--- +source: crates/turborepo-lib/src/opts.rs +expression: cache_opts +--- +Err( + OverlappingCacheOptions, +) diff --git a/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-only.snap b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-only.snap new file mode 100644 index 0000000000000..1c74ad6f2c749 --- /dev/null +++ b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-only.snap @@ -0,0 +1,16 @@ +--- +source: crates/turborepo-lib/src/opts.rs +expression: cache_opts +--- +Ok( + CacheConfig { + local: CacheActions { + read: false, + write: false, + }, + remote: CacheActions { + read: true, + write: true, + }, + }, +) diff --git a/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-only_remote_r,local_rw.snap b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-only_remote_r,local_rw.snap new file mode 100644 index 0000000000000..cbc9a96ee2d30 --- /dev/null +++ b/crates/turborepo-lib/src/snapshots/turborepo_lib__opts__test__remote-only_remote_r,local_rw.snap @@ -0,0 +1,7 @@ +--- +source: crates/turborepo-lib/src/opts.rs +expression: cache_opts +--- +Err( + OverlappingCacheOptions, +) diff --git a/crates/turborepo/tests/common/snapshots/query__common__check_query.snap b/crates/turborepo/tests/common/snapshots/query__common__check_query.snap deleted file mode 100644 index 890664e035247..0000000000000 --- a/crates/turborepo/tests/common/snapshots/query__common__check_query.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/turborepo-lib/tests/common/mod.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "index.js", - "dependencies": { - "files": { - "items": [ - { - "path": "nm/index.js" - } - ] - }, - "errors": { - "items": [] - } - } - } - } -} diff --git a/crates/turborepo/tests/snapshots/query__check_query.snap b/crates/turborepo/tests/snapshots/query__check_query.snap deleted file mode 100644 index 4fbff3206ef3c..0000000000000 --- a/crates/turborepo/tests/snapshots/query__check_query.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/turborepo-lib/tests/query.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "index.js", - "dependencies": { - "files": { - "items": [ - { - "path": "nm/index.js" - } - ] - }, - "errors": { - "items": [] - } - } - } - } -} diff --git a/crates/turborepo/tests/snapshots/query__oxc_repro_get_dependencies_(npm@10.5.0)_err.snap b/crates/turborepo/tests/snapshots/query__oxc_repro_get_dependencies_(npm@10.5.0)_err.snap deleted file mode 100644 index cd71b526b9bce..0000000000000 --- a/crates/turborepo/tests/snapshots/query__oxc_repro_get_dependencies_(npm@10.5.0)_err.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: stderr ---- - WARNING No locally installed `turbo` found. Using version: 2.2.4-canary.0. -turbo 2.2.4-canary.0 - - WARNING query command is experimental and may change in the future diff --git a/crates/turborepo/tests/snapshots/query__query_oxc_repro_get_dependencies_(npm@10.5.0).snap b/crates/turborepo/tests/snapshots/query__query_oxc_repro_get_dependencies_(npm@10.5.0).snap deleted file mode 100644 index 2e879b27d8d8f..0000000000000 --- a/crates/turborepo/tests/snapshots/query__query_oxc_repro_get_dependencies_(npm@10.5.0).snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "index.js", - "dependencies": { - "files": { - "items": [ - { - "path": "nm/index.js" - } - ] - }, - "errors": { - "items": [] - } - } - } - } -} diff --git a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`button.tsx`_with_dependencies_(npm@10.5.0).snap b/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`button.tsx`_with_dependencies_(npm@10.5.0).snap deleted file mode 100644 index 183e2c8e75dd7..0000000000000 --- a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`button.tsx`_with_dependencies_(npm@10.5.0).snap +++ /dev/null @@ -1,16 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "button.tsx", - "dependencies": { - "files": { - "items": [] - } - } - } - } -} diff --git a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`circular.ts`_with_dependencies_(npm@10.5.0).snap b/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`circular.ts`_with_dependencies_(npm@10.5.0).snap deleted file mode 100644 index e4950e2aa3f9d..0000000000000 --- a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`circular.ts`_with_dependencies_(npm@10.5.0).snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "circular.ts", - "dependencies": { - "files": { - "items": [ - { - "path": "circular2.ts" - } - ] - } - } - } - } -} diff --git a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`invalid.ts`_with_dependencies_(npm@10.5.0).snap b/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`invalid.ts`_with_dependencies_(npm@10.5.0).snap deleted file mode 100644 index 8354df28773f7..0000000000000 --- a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`invalid.ts`_with_dependencies_(npm@10.5.0).snap +++ /dev/null @@ -1,27 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "invalid.ts", - "dependencies": { - "files": { - "items": [ - { - "path": "button.tsx" - } - ] - }, - "errors": { - "items": [ - { - "message": "failed to resolve import" - } - ] - } - } - } - } -} diff --git a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_(npm@10.5.0).snap b/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_(npm@10.5.0).snap deleted file mode 100644 index 41503d9c76a44..0000000000000 --- a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_(npm@10.5.0).snap +++ /dev/null @@ -1,11 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "main.ts" - } - } -} diff --git a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_with_ast_(npm@10.5.0).snap b/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_with_ast_(npm@10.5.0).snap deleted file mode 100644 index 863d9791335e2..0000000000000 --- a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_with_ast_(npm@10.5.0).snap +++ /dev/null @@ -1,309 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "main.ts", - "ast": { - "type": "Module", - "span": { - "start": 1, - "end": 169 - }, - "body": [ - { - "type": "ImportDeclaration", - "span": { - "start": 1, - "end": 35 - }, - "specifiers": [ - { - "type": "ImportSpecifier", - "span": { - "start": 10, - "end": 16 - }, - "local": { - "type": "Identifier", - "span": { - "start": 10, - "end": 16 - }, - "ctxt": 0, - "value": "Button", - "optional": false - }, - "imported": null, - "isTypeOnly": false - } - ], - "source": { - "type": "StringLiteral", - "span": { - "start": 24, - "end": 34 - }, - "value": "./button", - "raw": "\"./button\"" - }, - "typeOnly": false, - "with": null, - "phase": "evaluation" - }, - { - "type": "ImportDeclaration", - "span": { - "start": 36, - "end": 60 - }, - "specifiers": [ - { - "type": "ImportDefaultSpecifier", - "span": { - "start": 43, - "end": 46 - }, - "local": { - "type": "Identifier", - "span": { - "start": 43, - "end": 46 - }, - "ctxt": 0, - "value": "foo", - "optional": false - } - } - ], - "source": { - "type": "StringLiteral", - "span": { - "start": 52, - "end": 59 - }, - "value": "./foo", - "raw": "\"./foo\"" - }, - "typeOnly": false, - "with": null, - "phase": "evaluation" - }, - { - "type": "ImportDeclaration", - "span": { - "start": 61, - "end": 96 - }, - "specifiers": [ - { - "type": "ImportDefaultSpecifier", - "span": { - "start": 68, - "end": 74 - }, - "local": { - "type": "Identifier", - "span": { - "start": 68, - "end": 74 - }, - "ctxt": 0, - "value": "repeat", - "optional": false - } - } - ], - "source": { - "type": "StringLiteral", - "span": { - "start": 80, - "end": 95 - }, - "value": "repeat-string", - "raw": "\"repeat-string\"" - }, - "typeOnly": false, - "with": null, - "phase": "evaluation" - }, - { - "type": "VariableDeclaration", - "span": { - "start": 98, - "end": 126 - }, - "ctxt": 0, - "kind": "const", - "declare": false, - "declarations": [ - { - "type": "VariableDeclarator", - "span": { - "start": 104, - "end": 125 - }, - "id": { - "type": "Identifier", - "span": { - "start": 104, - "end": 110 - }, - "ctxt": 0, - "value": "button", - "optional": false, - "typeAnnotation": null - }, - "init": { - "type": "NewExpression", - "span": { - "start": 113, - "end": 125 - }, - "ctxt": 0, - "callee": { - "type": "Identifier", - "span": { - "start": 117, - "end": 123 - }, - "ctxt": 0, - "value": "Button", - "optional": false - }, - "arguments": [], - "typeArguments": null - }, - "definite": false - } - ] - }, - { - "type": "ExpressionStatement", - "span": { - "start": 128, - "end": 144 - }, - "expression": { - "type": "CallExpression", - "span": { - "start": 128, - "end": 143 - }, - "ctxt": 0, - "callee": { - "type": "MemberExpression", - "span": { - "start": 128, - "end": 141 - }, - "object": { - "type": "Identifier", - "span": { - "start": 128, - "end": 134 - }, - "ctxt": 0, - "value": "button", - "optional": false - }, - "property": { - "type": "Identifier", - "span": { - "start": 135, - "end": 141 - }, - "value": "render" - } - }, - "arguments": [], - "typeArguments": null - } - }, - { - "type": "ExpressionStatement", - "span": { - "start": 145, - "end": 162 - }, - "expression": { - "type": "CallExpression", - "span": { - "start": 145, - "end": 161 - }, - "ctxt": 0, - "callee": { - "type": "Identifier", - "span": { - "start": 145, - "end": 151 - }, - "ctxt": 0, - "value": "repeat", - "optional": false - }, - "arguments": [ - { - "spread": null, - "expression": { - "type": "StringLiteral", - "span": { - "start": 152, - "end": 157 - }, - "value": "foo", - "raw": "\"foo\"" - } - }, - { - "spread": null, - "expression": { - "type": "NumericLiteral", - "span": { - "start": 159, - "end": 160 - }, - "value": 5.0, - "raw": "5" - } - } - ], - "typeArguments": null - } - }, - { - "type": "ExpressionStatement", - "span": { - "start": 163, - "end": 169 - }, - "expression": { - "type": "CallExpression", - "span": { - "start": 163, - "end": 168 - }, - "ctxt": 0, - "callee": { - "type": "Identifier", - "span": { - "start": 163, - "end": 166 - }, - "ctxt": 0, - "value": "foo", - "optional": false - }, - "arguments": [], - "typeArguments": null - } - } - ], - "interpreter": null - } - } - } -} diff --git a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_with_dependencies_(npm@10.5.0).snap b/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_with_dependencies_(npm@10.5.0).snap deleted file mode 100644 index d4762d093daf4..0000000000000 --- a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_with_dependencies_(npm@10.5.0).snap +++ /dev/null @@ -1,29 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "main.ts", - "dependencies": { - "files": { - "items": [ - { - "path": "bar.js" - }, - { - "path": "button.tsx" - }, - { - "path": "foo.js" - }, - { - "path": "node_modules/repeat-string/index.js" - } - ] - } - } - } - } -} diff --git a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_with_depth_=_0_(npm@10.5.0).snap b/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_with_depth_=_0_(npm@10.5.0).snap deleted file mode 100644 index d555c7a390292..0000000000000 --- a/crates/turborepo/tests/snapshots/query__query_turbo_trace_get_`main.ts`_with_depth_=_0_(npm@10.5.0).snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "main.ts", - "dependencies": { - "files": { - "items": [ - { - "path": "button.tsx" - }, - { - "path": "foo.js" - }, - { - "path": "node_modules/repeat-string/index.js" - } - ] - } - } - } - } -} diff --git a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`button.tsx`_with_dependencies_(npm@10.5.0)_err.snap b/crates/turborepo/tests/snapshots/query__turbo_trace_get_`button.tsx`_with_dependencies_(npm@10.5.0)_err.snap deleted file mode 100644 index cd71b526b9bce..0000000000000 --- a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`button.tsx`_with_dependencies_(npm@10.5.0)_err.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: stderr ---- - WARNING No locally installed `turbo` found. Using version: 2.2.4-canary.0. -turbo 2.2.4-canary.0 - - WARNING query command is experimental and may change in the future diff --git a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`circular.ts`_with_dependencies_(npm@10.5.0)_err.snap b/crates/turborepo/tests/snapshots/query__turbo_trace_get_`circular.ts`_with_dependencies_(npm@10.5.0)_err.snap deleted file mode 100644 index cd71b526b9bce..0000000000000 --- a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`circular.ts`_with_dependencies_(npm@10.5.0)_err.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: stderr ---- - WARNING No locally installed `turbo` found. Using version: 2.2.4-canary.0. -turbo 2.2.4-canary.0 - - WARNING query command is experimental and may change in the future diff --git a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`invalid.ts`_with_dependencies_(npm@10.5.0)_err.snap b/crates/turborepo/tests/snapshots/query__turbo_trace_get_`invalid.ts`_with_dependencies_(npm@10.5.0)_err.snap deleted file mode 100644 index cd71b526b9bce..0000000000000 --- a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`invalid.ts`_with_dependencies_(npm@10.5.0)_err.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: stderr ---- - WARNING No locally installed `turbo` found. Using version: 2.2.4-canary.0. -turbo 2.2.4-canary.0 - - WARNING query command is experimental and may change in the future diff --git a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_(npm@10.5.0)_err.snap b/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_(npm@10.5.0)_err.snap deleted file mode 100644 index cd71b526b9bce..0000000000000 --- a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_(npm@10.5.0)_err.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: stderr ---- - WARNING No locally installed `turbo` found. Using version: 2.2.4-canary.0. -turbo 2.2.4-canary.0 - - WARNING query command is experimental and may change in the future diff --git a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_with_ast_(npm@10.5.0)_err.snap b/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_with_ast_(npm@10.5.0)_err.snap deleted file mode 100644 index cd71b526b9bce..0000000000000 --- a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_with_ast_(npm@10.5.0)_err.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: stderr ---- - WARNING No locally installed `turbo` found. Using version: 2.2.4-canary.0. -turbo 2.2.4-canary.0 - - WARNING query command is experimental and may change in the future diff --git a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_with_dependencies_(npm@10.5.0)_err.snap b/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_with_dependencies_(npm@10.5.0)_err.snap deleted file mode 100644 index cd71b526b9bce..0000000000000 --- a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_with_dependencies_(npm@10.5.0)_err.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: stderr ---- - WARNING No locally installed `turbo` found. Using version: 2.2.4-canary.0. -turbo 2.2.4-canary.0 - - WARNING query command is experimental and may change in the future diff --git a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_with_depth_=_0_(npm@10.5.0)_err.snap b/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_with_depth_=_0_(npm@10.5.0)_err.snap deleted file mode 100644 index cd71b526b9bce..0000000000000 --- a/crates/turborepo/tests/snapshots/query__turbo_trace_get_`main.ts`_with_depth_=_0_(npm@10.5.0)_err.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: stderr ---- - WARNING No locally installed `turbo` found. Using version: 2.2.4-canary.0. -turbo 2.2.4-canary.0 - - WARNING query command is experimental and may change in the future diff --git a/crates/turborepo/tests/snapshots/query__turbo_trace_monorepo_get_`packages__utils__index`_with_dependents_(npm@10.5.0).snap b/crates/turborepo/tests/snapshots/query__turbo_trace_monorepo_get_`packages__utils__index`_with_dependents_(npm@10.5.0).snap deleted file mode 100644 index 4e6e7ed0e3329..0000000000000 --- a/crates/turborepo/tests/snapshots/query__turbo_trace_monorepo_get_`packages__utils__index`_with_dependents_(npm@10.5.0).snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: crates/turborepo/tests/query.rs -expression: query_output ---- -{ - "data": { - "file": { - "path": "packages/utils/index.ts", - "dependents": { - "files": { - "items": [ - { - "path": "apps/my-app/index.ts" - }, - { - "path": "packages/another/index.js" - } - ] - }, - "errors": { - "items": [] - } - } - } - } -} diff --git a/examples/with-nestjs/apps/web/test/__snapshots__/page.spec.tsx.snap b/examples/with-nestjs/apps/web/test/__snapshots__/page.spec.tsx.snap deleted file mode 100644 index 0a564fcf428b6..0000000000000 --- a/examples/with-nestjs/apps/web/test/__snapshots__/page.spec.tsx.snap +++ /dev/null @@ -1,160 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Root page should match the snapshot 1`] = ` -
-
-
-

- examples/ - - with-nestjs - -

- -
- -
-
-
-
- -
-
- -
- -
- -
- - - Turborepo logo - - - - - - - - - - - -
-
-
- -
-
-`; diff --git a/turborepo-tests/integration/tests/no-args.t b/turborepo-tests/integration/tests/no-args.t index 72848871ce25c..7236297d88e32 100644 --- a/turborepo-tests/integration/tests/no-args.t +++ b/turborepo-tests/integration/tests/no-args.t @@ -65,14 +65,22 @@ Make sure exit code is 2 when no args are passed Print help (see more with '--help') Run Arguments: + --cache + Set the cache behavior for this run. Pass a list of comma-separated key, value pairs to enable reading and writing to either the local or remote cache + --force [] + Ignore the existing cache (to force execution). Equivalent to `--cache=local:w,remote:w` [possible values: true, false] + --remote-only [] + Ignore the local filesystem cache for all tasks. Only allow reading and caching artifacts using the remote cache. Equivalent to `--cache=remote:rw` [possible values: true, false] + --remote-cache-read-only [] + Treat remote cache as read only. Equivalent to `--cache=remote:r;local:rw` [possible values: true, false] + --no-cache + Avoid saving task results to the cache. Useful for development/watch tasks. Equivalent to `--cache=local:r,remote:r` --cache-workers Set the number of concurrent cache operations (default 10) [default: 10] --dry-run [] [possible values: text, json] --graph [] Generate a graph of the task execution and output to a file when a filename is specified (.svg, .png, .jpg, .pdf, .json, .html, .mermaid, .dot). Outputs dot graph to stdout when if no filename is provided - --no-cache - Avoid saving task results to the cache. Useful for development/watch tasks --daemon Force turbo to use the local daemon. If unset turbo will use the default detection logic --no-daemon @@ -81,8 +89,6 @@ Make sure exit code is 2 when no args are passed File to write turbo's performance profile output into. You can load the file up in chrome://tracing to see which parts of your build were slow --anon-profile File to write turbo's performance profile output into. All identifying data omitted from the profile - --remote-cache-read-only [] - Treat remote cache as read only [possible values: true, false] --summarize [] Generate a summary of the turbo run [possible values: true, false] --parallel @@ -95,8 +101,6 @@ Make sure exit code is 2 when no args are passed Continue execution even if a task exits with an error or non-zero exit code. The default behavior is to bail --single-package Run turbo in single-package mode - --force [] - Ignore the existing cache (to force execution) [possible values: true, false] --framework-inference [] Specify whether or not to do framework inference for tasks [default: true] [possible values: true, false] --global-deps @@ -113,8 +117,6 @@ Make sure exit code is 2 when no args are passed Set type of task output order. Use "stream" to show output as soon as it is available. Use "grouped" to show output when a command has finished execution. Use "auto" to let turbo decide based on its own heuristics. (default auto) [possible values: auto, stream, grouped] --only Only executes the tasks specified, does not execute parent tasks - --remote-only [] - Ignore the local filesystem cache for all tasks. Only allow reading and caching artifacts using the remote cache [possible values: true, false] --log-prefix Use "none" to remove prefixes from task logs. Use "task" to get task id prefixing. Use "auto" to let turbo decide how to prefix the logs based on the execution environment. In most cases this will be the same as "task". Note that tasks running in parallel interleave their logs, so removing prefixes can make it difficult to associate logs with tasks. Use --log-order=grouped to prevent interleaving. (default auto) [default: auto] [possible values: auto, none, task] [1] diff --git a/turborepo-tests/integration/tests/turbo-help.t b/turborepo-tests/integration/tests/turbo-help.t index 0fd66d1c3d7be..78f2ce5208156 100644 --- a/turborepo-tests/integration/tests/turbo-help.t +++ b/turborepo-tests/integration/tests/turbo-help.t @@ -65,14 +65,22 @@ Test help flag Print help (see more with '--help') Run Arguments: + --cache + Set the cache behavior for this run. Pass a list of comma-separated key, value pairs to enable reading and writing to either the local or remote cache + --force [] + Ignore the existing cache (to force execution). Equivalent to `--cache=local:w,remote:w` [possible values: true, false] + --remote-only [] + Ignore the local filesystem cache for all tasks. Only allow reading and caching artifacts using the remote cache. Equivalent to `--cache=remote:rw` [possible values: true, false] + --remote-cache-read-only [] + Treat remote cache as read only. Equivalent to `--cache=remote:r;local:rw` [possible values: true, false] + --no-cache + Avoid saving task results to the cache. Useful for development/watch tasks. Equivalent to `--cache=local:r,remote:r` --cache-workers Set the number of concurrent cache operations (default 10) [default: 10] --dry-run [] [possible values: text, json] --graph [] Generate a graph of the task execution and output to a file when a filename is specified (.svg, .png, .jpg, .pdf, .json, .html, .mermaid, .dot). Outputs dot graph to stdout when if no filename is provided - --no-cache - Avoid saving task results to the cache. Useful for development/watch tasks --daemon Force turbo to use the local daemon. If unset turbo will use the default detection logic --no-daemon @@ -81,8 +89,6 @@ Test help flag File to write turbo's performance profile output into. You can load the file up in chrome://tracing to see which parts of your build were slow --anon-profile File to write turbo's performance profile output into. All identifying data omitted from the profile - --remote-cache-read-only [] - Treat remote cache as read only [possible values: true, false] --summarize [] Generate a summary of the turbo run [possible values: true, false] --parallel @@ -95,8 +101,6 @@ Test help flag Continue execution even if a task exits with an error or non-zero exit code. The default behavior is to bail --single-package Run turbo in single-package mode - --force [] - Ignore the existing cache (to force execution) [possible values: true, false] --framework-inference [] Specify whether or not to do framework inference for tasks [default: true] [possible values: true, false] --global-deps @@ -113,8 +117,6 @@ Test help flag Set type of task output order. Use "stream" to show output as soon as it is available. Use "grouped" to show output when a command has finished execution. Use "auto" to let turbo decide based on its own heuristics. (default auto) [possible values: auto, stream, grouped] --only Only executes the tasks specified, does not execute parent tasks - --remote-only [] - Ignore the local filesystem cache for all tasks. Only allow reading and caching artifacts using the remote cache [possible values: true, false] --log-prefix Use "none" to remove prefixes from task logs. Use "task" to get task id prefixing. Use "auto" to let turbo decide how to prefix the logs based on the execution environment. In most cases this will be the same as "task". Note that tasks running in parallel interleave their logs, so removing prefixes can make it difficult to associate logs with tasks. Use --log-order=grouped to prevent interleaving. (default auto) [default: auto] [possible values: auto, none, task] @@ -211,6 +213,27 @@ Test help flag Print help (see a summary with '-h') Run Arguments: + --cache + Set the cache behavior for this run. Pass a list of comma-separated key, value pairs to enable reading and writing to either the local or remote cache + + --force [] + Ignore the existing cache (to force execution). Equivalent to `--cache=local:w,remote:w` + + [possible values: true, false] + + --remote-only [] + Ignore the local filesystem cache for all tasks. Only allow reading and caching artifacts using the remote cache. Equivalent to `--cache=remote:rw` + + [possible values: true, false] + + --remote-cache-read-only [] + Treat remote cache as read only. Equivalent to `--cache=remote:r;local:rw` + + [possible values: true, false] + + --no-cache + Avoid saving task results to the cache. Useful for development/watch tasks. Equivalent to `--cache=local:r,remote:r` + --cache-workers Set the number of concurrent cache operations (default 10) @@ -222,9 +245,6 @@ Test help flag --graph [] Generate a graph of the task execution and output to a file when a filename is specified (.svg, .png, .jpg, .pdf, .json, .html, .mermaid, .dot). Outputs dot graph to stdout when if no filename is provided - --no-cache - Avoid saving task results to the cache. Useful for development/watch tasks - --daemon Force turbo to use the local daemon. If unset turbo will use the default detection logic @@ -237,11 +257,6 @@ Test help flag --anon-profile File to write turbo's performance profile output into. All identifying data omitted from the profile - --remote-cache-read-only [] - Treat remote cache as read only - - [possible values: true, false] - --summarize [] Generate a summary of the turbo run @@ -262,11 +277,6 @@ Test help flag --single-package Run turbo in single-package mode - --force [] - Ignore the existing cache (to force execution) - - [possible values: true, false] - --framework-inference [] Specify whether or not to do framework inference for tasks @@ -300,11 +310,6 @@ Test help flag --only Only executes the tasks specified, does not execute parent tasks - --remote-only [] - Ignore the local filesystem cache for all tasks. Only allow reading and caching artifacts using the remote cache - - [possible values: true, false] - --log-prefix Use "none" to remove prefixes from task logs. Use "task" to get task id prefixing. Use "auto" to let turbo decide how to prefix the logs based on the execution environment. In most cases this will be the same as "task". Note that tasks running in parallel interleave their logs, so removing prefixes can make it difficult to associate logs with tasks. Use --log-order=grouped to prevent interleaving. (default auto)