diff --git a/crates/turborepo-lib/src/commands/config.rs b/crates/turborepo-lib/src/commands/config.rs index 76b7b6cb5ffd6..b8b617465a081 100644 --- a/crates/turborepo-lib/src/commands/config.rs +++ b/crates/turborepo-lib/src/commands/config.rs @@ -1,7 +1,7 @@ use camino::Utf8Path; use serde::Serialize; use turbopath::AbsoluteSystemPathBuf; -use turborepo_repository::{cargo::CargoContributor, package_graph::PackageGraph}; +use turborepo_repository::package_graph::PackageGraph; use turborepo_types::{EnvMode, UIMode}; use crate::{ @@ -46,7 +46,7 @@ pub async fn run(repo_root: AbsoluteSystemPathBuf, args: Args) -> Result<(), cli let mut builder = PackageGraph::builder_optional(&repo_root, root_package_json) .with_allow_no_package_manager(config.allow_no_package_manager()); if cargo_enabled { - builder = builder.with_contributor(CargoContributor::new(repo_root.clone())); + builder = builder.with_cargo(); } let package_graph = builder.build().await?; diff --git a/crates/turborepo-lib/src/commands/daemon.rs b/crates/turborepo-lib/src/commands/daemon.rs index 47624b61419bc..7401865f4dbcd 100644 --- a/crates/turborepo-lib/src/commands/daemon.rs +++ b/crates/turborepo-lib/src/commands/daemon.rs @@ -346,16 +346,6 @@ pub async fn daemon_server( { let cargo_enabled = crate::run::builder::cargo_enabled(&base.opts().future_flags); move |args| { - // Mirror the run builder: the daemon-side watcher must see - // the same package set a run would. - let mut extra_contributors: Vec< - std::sync::Arc, - > = Vec::new(); - if cargo_enabled { - extra_contributors.push(turborepo_repository::cargo::CargoContributor::new( - args.repo_root.clone(), - )); - } PackageChangesWatcher::new( args.repo_root, args.file_events, @@ -363,7 +353,7 @@ pub async fn daemon_server( args.custom_turbo_json_path, false, args.allow_no_package_manager, - extra_contributors, + cargo_enabled, ) } }, diff --git a/crates/turborepo-lib/src/commands/get_mfe_port.rs b/crates/turborepo-lib/src/commands/get_mfe_port.rs index fbdf796fd5395..82cd974dfb620 100644 --- a/crates/turborepo-lib/src/commands/get_mfe_port.rs +++ b/crates/turborepo-lib/src/commands/get_mfe_port.rs @@ -4,7 +4,7 @@ use thiserror::Error; use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf}; use turborepo_microfrontends::TurborepoMfeConfig; use turborepo_repository::{ - cargo::{CargoContributor, CARGO_TOML}, + cargo::CARGO_TOML, package_graph::{PackageGraph, PackageGraphNodeKind, PackageName, PackageNode}, package_json::PackageJson, toolchain::ToolchainId, @@ -71,7 +71,7 @@ async fn build_package_graph(base: &CommandBase) -> Result .with_single_package_mode(base.opts().run_opts.single_package) .with_allow_no_package_manager(base.opts().repo_opts.allow_no_package_manager); if cargo_enabled { - builder = builder.with_contributor(CargoContributor::new(repo_root.to_owned())); + builder = builder.with_cargo(); } Ok(builder.build().await?) diff --git a/crates/turborepo-lib/src/commands/prune.rs b/crates/turborepo-lib/src/commands/prune.rs index 6a97230a8f297..9fb263134a51d 100644 --- a/crates/turborepo-lib/src/commands/prune.rs +++ b/crates/turborepo-lib/src/commands/prune.rs @@ -532,9 +532,7 @@ impl<'a> Prune<'a> { let mut graph_builder = PackageGraph::builder_optional(&base.repo_root, root_package_json) .with_allow_no_package_manager(allow_missing_package_manager); if cargo_enabled { - graph_builder = graph_builder.with_contributor( - turborepo_repository::cargo::CargoContributor::new(base.repo_root.clone()), - ); + graph_builder = graph_builder.with_cargo(); } let package_graph = graph_builder.build().await?; diff --git a/crates/turborepo-lib/src/devtools.rs b/crates/turborepo-lib/src/devtools.rs index 0364ad7f0200e..bd4fb92687889 100644 --- a/crates/turborepo-lib/src/devtools.rs +++ b/crates/turborepo-lib/src/devtools.rs @@ -11,10 +11,7 @@ use turborepo_devtools::{ package_graph_to_data, GraphData, GraphEdge, RepositoryGraphBuilder, TaskGraphData, TaskGraphError, TaskNode, }; -use turborepo_repository::{ - cargo::CargoContributor, - package_graph::{PackageGraph, PackageGraphBuilder, PackageName}, -}; +use turborepo_repository::package_graph::{PackageGraph, PackageGraphBuilder, PackageName}; use turborepo_task_id::TaskName; use crate::{ @@ -69,7 +66,7 @@ impl ProperTaskGraphBuilder { .with_single_package_mode(opts.run_opts.single_package) .with_allow_no_package_manager(opts.repo_opts.allow_no_package_manager); if cargo_enabled(&opts.future_flags) { - builder = builder.with_contributor(CargoContributor::new(self.repo_root.clone())); + builder = builder.with_cargo(); } builder diff --git a/crates/turborepo-lib/src/engine/mod.rs b/crates/turborepo-lib/src/engine/mod.rs index d1a256177c01c..5d2a8243f3ccf 100644 --- a/crates/turborepo-lib/src/engine/mod.rs +++ b/crates/turborepo-lib/src/engine/mod.rs @@ -380,9 +380,7 @@ mod test { .with_package_discovery(DummyDiscovery( turbopath::AbsoluteSystemPathBuf::try_from(tmp.path()).unwrap(), )) - .with_contributor(turborepo_repository::cargo::CargoContributor::new( - root.to_owned(), - )) + .with_cargo() .build() .await .unwrap(); diff --git a/crates/turborepo-lib/src/microfrontends.rs b/crates/turborepo-lib/src/microfrontends.rs index 14a098de861d8..bb7a6c8a49675 100644 --- a/crates/turborepo-lib/src/microfrontends.rs +++ b/crates/turborepo-lib/src/microfrontends.rs @@ -583,8 +583,7 @@ mod test { use turbopath::AbsoluteSystemPathBuf; use turborepo_microfrontends::MICROFRONTENDS_PACKAGE; use turborepo_repository::{ - cargo::CargoContributor, package_graph::PackageGraph, package_json::PackageJson, - package_manager::PackageManager, + package_graph::PackageGraph, package_json::PackageJson, package_manager::PackageManager, }; use super::*; @@ -657,7 +656,7 @@ mod test { write_mfe_config(&root, "aggregate"); let graph = PackageGraph::builder_optional(&root, None) - .with_contributor(CargoContributor::new(root.clone())) + .with_cargo() .build() .await .unwrap(); diff --git a/crates/turborepo-lib/src/package_changes_watcher.rs b/crates/turborepo-lib/src/package_changes_watcher.rs index f2f1929199efd..a346dd2862710 100644 --- a/crates/turborepo-lib/src/package_changes_watcher.rs +++ b/crates/turborepo-lib/src/package_changes_watcher.rs @@ -24,7 +24,7 @@ use turborepo_repository::{ }, package_graph::{PackageGraph, PackageName, WorkspacePackage}, package_json::{self, PackageJson}, - toolchain::{RepositoryContributor, WatchSpec}, + toolchain::WatchSpec, }; use turborepo_scm::GitHashes; @@ -70,7 +70,7 @@ impl PackageChangesWatcher { custom_turbo_json_path: Option, single_package: bool, allow_no_package_manager: bool, - extra_contributors: Vec>, + cargo_enabled: bool, ) -> Self { let (exit_tx, exit_rx) = oneshot::channel(); let (package_change_events_tx, package_change_events_rx) = @@ -83,7 +83,7 @@ impl PackageChangesWatcher { custom_turbo_json_path, single_package, allow_no_package_manager, - extra_contributors, + cargo_enabled, ); let _handle = tokio::spawn(subscriber.watch(exit_rx)); @@ -134,10 +134,8 @@ struct Subscriber { custom_turbo_json_path: Option, single_package: bool, allow_no_package_manager: bool, - /// Toolchains registered in addition to JavaScript (e.g. Cargo when - /// futureFlags.experimentalCargoWorkspaces is enabled), mirroring the - /// run builder so the watcher sees the same package graph a run would. - extra_contributors: Vec>, + /// Mirrors the run builder so the watcher observes the same Cargo scopes. + cargo_enabled: bool, } fn is_in_git_folder(path: &AnchoredSystemPath) -> bool { @@ -372,7 +370,7 @@ impl Subscriber { custom_turbo_json_path: Option, single_package: bool, allow_no_package_manager: bool, - extra_contributors: Vec>, + cargo_enabled: bool, ) -> Self { // Try to canonicalize the custom path to match what the file watcher reports let normalized_custom_path = custom_turbo_json_path.map(|path| { @@ -424,16 +422,12 @@ impl Subscriber { custom_turbo_json_path: normalized_custom_path, single_package, allow_no_package_manager, - extra_contributors, + cargo_enabled, } } async fn initialize_repo_state(&self) -> Option { - let cargo_registered = self - .extra_contributors - .iter() - .any(|toolchain| toolchain.id() == turborepo_repository::toolchain::ToolchainId::RUST); - let allow_missing_for_cargo = cargo_registered + let allow_missing_for_cargo = self.cargo_enabled && self .repo_root .join_component(turborepo_repository::cargo::CARGO_TOML) @@ -453,8 +447,8 @@ impl Subscriber { PackageGraph::builder_optional(&self.repo_root, root_package_json.clone()) .with_single_package_mode(self.single_package) .with_allow_no_package_manager(self.allow_no_package_manager); - for toolchain in &self.extra_contributors { - builder = builder.with_contributor(toolchain.clone()); + if self.cargo_enabled { + builder = builder.with_cargo(); } let Ok(pkg_dep_graph) = builder.build().await else { tracing::debug!("package graph not available, package watcher not available"); @@ -886,11 +880,10 @@ mod test { hash_watcher::HashWatcher, NotifyError, OptionalWatch, WatchEventSender, WatchSource, }; use turborepo_repository::{ - cargo::CargoContributor, change_mapper::{ChangeMapper, GlobalDepsPackageChangeMapper, PackageChanges}, package_graph::{PackageGraph, PackageGraphBuilder, PackageName, PackageTaskContextKind}, package_json::PackageJson, - toolchain::{RepositoryContributor, WatchSpec}, + toolchain::WatchSpec, }; use turborepo_scm::{GitHashes, SCM}; @@ -982,7 +975,7 @@ mod test { fn test_subscriber( repo_root: &AbsoluteSystemPathBuf, single_package: bool, - extra_contributors: Vec>, + cargo_enabled: bool, ) -> Subscriber { let (_file_events_tx, file_events) = WatchSource::channel_for_root(repo_root.as_std_path()); let (_discovery_tx, discovery_rx) = watch::channel(None); @@ -1000,15 +993,15 @@ mod test { None, single_package, false, - extra_contributors, + cargo_enabled, ) } async fn initialize_test_state( repo_root: &AbsoluteSystemPathBuf, - extra_contributors: Vec>, + cargo_enabled: bool, ) -> Option { - test_subscriber(repo_root, false, extra_contributors) + test_subscriber(repo_root, false, cargo_enabled) .initialize_repo_state() .await } @@ -1031,10 +1024,9 @@ mod test { .create_with_contents(b"{\"tasks\":{\"build\":{}}}") .unwrap(); - let state = - initialize_test_state(&repo_root, vec![CargoContributor::new(repo_root.clone())]) - .await - .expect("native toolchain permits an absent root package.json"); + let state = initialize_test_state(&repo_root, true) + .await + .expect("Cargo permits an absent root package.json"); assert!(!state.pkg_dep_graph.has_root_javascript_scope()); assert!(state.root_turbo_json.is_some()); assert_eq!(state.pkg_dep_graph.active_watch_spec(), cargo_watch_spec()); @@ -1076,10 +1068,9 @@ mod test { .create_with_contents(br#"{"name":"web"}"#) .unwrap(); - let state = - initialize_test_state(&repo_root, vec![CargoContributor::new(repo_root.clone())]) - .await - .expect("mixed graph initializes"); + let state = initialize_test_state(&repo_root, true) + .await + .expect("mixed graph initializes"); let names: HashSet<_> = hash_scopes(&state.pkg_dep_graph) .map(|scope| scope.name) .collect(); @@ -1097,18 +1088,16 @@ mod test { let tmp = tempfile::tempdir().unwrap(); let repo_root = canonical_temp_root(&tmp); assert!( - initialize_test_state(&repo_root, vec![CargoContributor::new(repo_root.clone())]) - .await - .is_none(), + initialize_test_state(&repo_root, true).await.is_none(), "registered Cargo without a root Cargo.toml must not permit a missing package.json" ); write_cargo_workspace(&repo_root); - let subscriber = test_subscriber( - &repo_root, - true, - vec![CargoContributor::new(repo_root.clone())], + assert!( + initialize_test_state(&repo_root, false).await.is_none(), + "a Cargo manifest must not bypass the disabled feature" ); + let subscriber = test_subscriber(&repo_root, true, true); let state = subscriber .initialize_repo_state() .await @@ -1142,22 +1131,14 @@ mod test { .create_with_contents(b"{") .unwrap(); assert!(load_root_package_json(&repo_root, true).is_err()); - assert!( - initialize_test_state(&repo_root, vec![CargoContributor::new(repo_root.clone())]) - .await - .is_none() - ); + assert!(initialize_test_state(&repo_root, true).await.is_none()); repo_root.join_component("package.json").remove().unwrap(); repo_root .join_component("Cargo.toml") .create_with_contents(b"[workspace") .unwrap(); - assert!( - initialize_test_state(&repo_root, vec![CargoContributor::new(repo_root.clone())]) - .await - .is_none() - ); + assert!(initialize_test_state(&repo_root, true).await.is_none()); } #[test] @@ -1200,11 +1181,7 @@ mod test { .unwrap(); write_cargo_workspace(&repo_root); - let subscriber = test_subscriber( - &repo_root, - true, - vec![CargoContributor::new(repo_root.clone())], - ); + let subscriber = test_subscriber(&repo_root, true, true); assert_eq!( *subscriber .watch_spec @@ -1839,7 +1816,7 @@ mod test { None, single_package, allow_no_package_manager, - Vec::new(), + false, ); TestWatcherHandle { diff --git a/crates/turborepo-lib/src/run/builder.rs b/crates/turborepo-lib/src/run/builder.rs index 3be6c335b72dc..08b6343b14dec 100644 --- a/crates/turborepo-lib/src/run/builder.rs +++ b/crates/turborepo-lib/src/run/builder.rs @@ -571,9 +571,7 @@ impl RunBuilder { turborepo_task_hash::hash_sorted_closures, )); if cargo_enabled(&self.opts.future_flags) { - builder = builder.with_contributor( - turborepo_repository::cargo::CargoContributor::new(self.repo_root.to_owned()), - ); + builder = builder.with_cargo(); } let graph = builder diff --git a/crates/turborepo-lib/src/run/watch.rs b/crates/turborepo-lib/src/run/watch.rs index 2a3a87ae67a49..7f1011f18167d 100644 --- a/crates/turborepo-lib/src/run/watch.rs +++ b/crates/turborepo-lib/src/run/watch.rs @@ -300,16 +300,8 @@ impl WatchClient { source, scm, )); - // The watcher builds its own package graph; register the same - // toolchains a run would so it watches the same package set. - let mut extra_contributors: Vec< - std::sync::Arc, - > = Vec::new(); - if crate::run::builder::cargo_enabled(&base.opts().future_flags) { - extra_contributors.push(turborepo_repository::cargo::CargoContributor::new( - base.repo_root.clone(), - )); - } + // The watcher builds its own graph and must enable the same ecosystems. + let cargo_enabled = crate::run::builder::cargo_enabled(&base.opts().future_flags); let package_changes_watcher = PackageChangesWatcher::new( base.repo_root.clone(), watcher.source(), @@ -317,7 +309,7 @@ impl WatchClient { custom_turbo_json_path, base.opts().run_opts.single_package, base.opts().repo_opts.allow_no_package_manager, - extra_contributors, + cargo_enabled, ); // Subscribe before building the Run so we don't miss the initial diff --git a/crates/turborepo-repository/src/cargo.rs b/crates/turborepo-repository/src/cargo.rs index fa0075992aa9b..78e54a9edd0ff 100644 --- a/crates/turborepo-repository/src/cargo.rs +++ b/crates/turborepo-repository/src/cargo.rs @@ -1297,12 +1297,12 @@ impl PruneDomain for CargoPruneKnowledge { /// The Cargo repository contributor. Registered during graph construction when /// `futureFlags.experimentalCargoWorkspaces` is enabled and the repository /// root contains a `Cargo.toml`. -pub struct CargoContributor { +pub(crate) struct CargoContributor { repo_root: AbsoluteSystemPathBuf, } impl CargoContributor { - pub fn new(repo_root: AbsoluteSystemPathBuf) -> Arc { + pub(crate) fn new(repo_root: AbsoluteSystemPathBuf) -> Arc { Arc::new(Self { repo_root }) } } diff --git a/crates/turborepo-repository/src/package_graph/builder.rs b/crates/turborepo-repository/src/package_graph/builder.rs index 65f48409d941e..31f0a6c03dbda 100644 --- a/crates/turborepo-repository/src/package_graph/builder.rs +++ b/crates/turborepo-repository/src/package_graph/builder.rs @@ -300,6 +300,12 @@ impl<'a, P> PackageGraphBuilder<'a, P> { self } + /// Enable Cargo repository contribution for this graph generation. + pub fn with_cargo(self) -> Self { + let repo_root = self.repo_root.to_owned(); + self.with_contributor(crate::cargo::CargoContributor::new(repo_root)) + } + /// Set the package discovery strategy to use. Note that whatever strategy /// selected here will be wrapped in a `CachingPackageDiscovery` to /// prevent unnecessary work during building. diff --git a/crates/turborepo-repository/src/package_graph/mod.rs b/crates/turborepo-repository/src/package_graph/mod.rs index 2a5daf66be2a2..7eab50f47cc72 100644 --- a/crates/turborepo-repository/src/package_graph/mod.rs +++ b/crates/turborepo-repository/src/package_graph/mod.rs @@ -2840,7 +2840,7 @@ version = "0.1.0" map })) .with_lockfile(Some(Box::new(MockLockfile {}))) - .with_contributor(crate::cargo::CargoContributor::new(root.clone())) + .with_cargo() .build() .await .unwrap(); @@ -3044,7 +3044,7 @@ version = "0.1.0" write_cargo_workspace_fixture(&root); let mut pkg_graph = PackageGraph::builder_optional(&root, None) - .with_contributor(crate::cargo::CargoContributor::new(root.clone())) + .with_cargo() .build() .await .unwrap(); @@ -3287,7 +3287,7 @@ version = "0.1.0" map })) .with_lockfile(Some(Box::new(MockLockfile {}))) - .with_contributor(crate::cargo::CargoContributor::new(root.clone())) + .with_cargo() .build() .await; diff --git a/crates/turborepo-run-cache/src/lib.rs b/crates/turborepo-run-cache/src/lib.rs index 089c8e32db2c2..d80550303d7cf 100644 --- a/crates/turborepo-run-cache/src/lib.rs +++ b/crates/turborepo-run-cache/src/lib.rs @@ -957,7 +957,6 @@ mod test { use turborepo_cache::{AsyncCache, CacheActions, CacheConfig, CacheOpts, LazyScmState}; use turborepo_log::{LogSink, Logger, OutputChannel, grouping::GroupingLayer}; use turborepo_repository::{ - cargo::CargoContributor, package_graph::{PackageGraph, PackageName}, package_json::PackageJson, }; @@ -1038,7 +1037,7 @@ mod test { ) .unwrap(); PackageGraph::builder_optional(repo_root, None) - .with_contributor(CargoContributor::new(repo_root.clone())) + .with_cargo() .build() .await .unwrap() diff --git a/crates/turborepo-task-hash/src/lib.rs b/crates/turborepo-task-hash/src/lib.rs index 3e3c87d5c25ca..6739be138b4d8 100644 --- a/crates/turborepo-task-hash/src/lib.rs +++ b/crates/turborepo-task-hash/src/lib.rs @@ -1078,7 +1078,6 @@ mod test { use tempfile::tempdir; use turbopath::AbsoluteSystemPathBuf; use turborepo_repository::{ - cargo::CargoContributor, package_graph::{PackageGraph, PackageTaskContextKind}, package_json::PackageJson, }; @@ -1173,7 +1172,7 @@ mod test { .unwrap(); PackageGraph::builder_optional(repo_root, None) - .with_contributor(CargoContributor::new(repo_root.clone())) + .with_cargo() .with_closure_hasher(Arc::new(hash_sorted_closures)) .build() .await diff --git a/crates/turborepo/ARCHITECTURE.md b/crates/turborepo/ARCHITECTURE.md index ef4654aabeddc..13cf34998f983 100644 --- a/crates/turborepo/ARCHITECTURE.md +++ b/crates/turborepo/ARCHITECTURE.md @@ -304,6 +304,11 @@ JavaScript is the first production producer. Machinery that predates the abstraction (package-manager resolution for dependency splitting and the JS lockfile closure phase) remains documented debt. +Production callers enable Cargo through `PackageGraphBuilder::with_cargo` +rather than constructing or retaining contributor objects. Run, watch, daemon, +prune, query, hashing, and cache paths carry only the feature decision; each +graph generation creates and drops its own Cargo contributor. + Contract-derived I/O receives the same task-scoped arguments as execution plus a narrow, platform-aware startup-environment projection keyed by toolchain. Dependency tasks do not inherit arguments for a different requested task, each