@@ -256,13 +256,19 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
256256
257257 let mut visited = HashSet :: new ( ) ;
258258 let mut engine: Engine < Building , TaskDefinition > = Engine :: default ( ) ;
259+ let mut turbo_json_chain_cache: HashMap < PackageName , Vec < & TurboJson > > = HashMap :: new ( ) ;
259260
260261 while let Some ( task_id) = traversal_queue. pop_front ( ) {
261262 {
262263 let ( task_id, span) = task_id. clone ( ) . split ( ) ;
263264 engine. add_task_location ( task_id. into_owned ( ) , span) ;
264265 }
265266
267+ // Skip before doing expensive work if we've already processed this task.
268+ if visited. contains ( task_id. as_inner ( ) ) {
269+ continue ;
270+ }
271+
266272 // For root tasks, verify they are either explicitly enabled OR (when using
267273 // add_all_tasks mode like devtools) have a definition in root turbo.json.
268274 // Tasks defined without the //# prefix (like "transit") in root turbo.json
@@ -323,17 +329,13 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
323329 ) ) ) ;
324330 }
325331
326- let task_definition = self . task_definition (
332+ let task_definition = self . task_definition_cached (
327333 turbo_json_loader,
328334 & task_id,
329335 & task_id. as_non_workspace_task_name ( ) ,
336+ & mut turbo_json_chain_cache,
330337 ) ?;
331338
332- // Skip this iteration of the loop if we've already seen this taskID
333- if visited. contains ( task_id. as_inner ( ) ) {
334- continue ;
335- }
336-
337339 visited. insert ( task_id. as_inner ( ) . clone ( ) ) ;
338340
339341 // Note that the Go code has a whole if/else statement for putting stuff into
@@ -576,19 +578,51 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
576578 Ok ( TaskDefinitionResult :: not_found ( ) )
577579 }
578580
579- fn task_definition (
581+ /// Resolves the merged `TaskDefinition` for a task, caching the turbo.json
582+ /// chain per package. The chain only depends on the package name (not the
583+ /// task), so multiple tasks in the same package share the cached chain.
584+ fn task_definition_cached < ' b > (
580585 & self ,
581- turbo_json_loader : & L ,
586+ turbo_json_loader : & ' b L ,
582587 task_id : & Spanned < TaskId > ,
583588 task_name : & TaskName ,
589+ chain_cache : & mut HashMap < PackageName , Vec < & ' b TurboJson > > ,
584590 ) -> Result < TaskDefinition , BuilderError > {
585591 let processed_task_definition = ProcessedTaskDefinition :: from_iter (
586- self . task_definition_chain ( turbo_json_loader, task_id, task_name) ?,
592+ self . task_definition_chain_cached ( turbo_json_loader, task_id, task_name, chain_cache ) ?,
587593 ) ;
588594 let path_to_root = self . path_to_root ( task_id. as_inner ( ) ) ?;
589595 TaskDefinition :: from_processed ( processed_task_definition, & path_to_root)
590596 }
591597
598+ /// Like `task_definition_chain` but caches the turbo.json chain per
599+ /// package.
600+ fn task_definition_chain_cached < ' b > (
601+ & self ,
602+ turbo_json_loader : & ' b L ,
603+ task_id : & Spanned < TaskId > ,
604+ task_name : & TaskName ,
605+ chain_cache : & mut HashMap < PackageName , Vec < & ' b TurboJson > > ,
606+ ) -> Result < Vec < ProcessedTaskDefinition > , BuilderError > {
607+ let package_name = PackageName :: from ( task_id. package ( ) ) ;
608+ let turbo_json_chain = match chain_cache. get ( & package_name) {
609+ Some ( cached) => cached. clone ( ) ,
610+ None => {
611+ let chain = self . turbo_json_chain ( turbo_json_loader, & package_name) ?;
612+ chain_cache. insert ( package_name, chain. clone ( ) ) ;
613+ chain
614+ }
615+ } ;
616+
617+ Self :: resolve_task_definitions_from_chain (
618+ turbo_json_chain,
619+ task_id,
620+ task_name,
621+ self . is_single ,
622+ self . should_validate_engine ,
623+ )
624+ }
625+
592626 pub fn task_definition_chain (
593627 & self ,
594628 turbo_json_loader : & L ,
@@ -597,6 +631,25 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
597631 ) -> Result < Vec < ProcessedTaskDefinition > , BuilderError > {
598632 let package_name = PackageName :: from ( task_id. package ( ) ) ;
599633 let turbo_json_chain = self . turbo_json_chain ( turbo_json_loader, & package_name) ?;
634+ Self :: resolve_task_definitions_from_chain (
635+ turbo_json_chain,
636+ task_id,
637+ task_name,
638+ self . is_single ,
639+ self . should_validate_engine ,
640+ )
641+ }
642+
643+ /// Given a resolved turbo.json chain for a package, extract the task
644+ /// definitions for a specific task by walking the chain and handling
645+ /// `extends: false`.
646+ fn resolve_task_definitions_from_chain (
647+ turbo_json_chain : Vec < & TurboJson > ,
648+ task_id : & Spanned < TaskId > ,
649+ task_name : & TaskName ,
650+ is_single : bool ,
651+ should_validate_engine : bool ,
652+ ) -> Result < Vec < ProcessedTaskDefinition > , BuilderError > {
600653 let mut task_definitions = Vec :: new ( ) ;
601654
602655 // Find the first package in the chain (iterating in reverse from leaf to root)
@@ -645,7 +698,7 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
645698 task_definitions. push ( root_definition)
646699 }
647700
648- if self . is_single {
701+ if is_single {
649702 return match task_definitions. is_empty ( ) {
650703 true => {
651704 let ( span, text) = task_id. span_and_text ( "turbo.json" ) ;
@@ -667,7 +720,7 @@ impl<'a, L: TurboJsonLoader> EngineBuilder<'a, L> {
667720 }
668721 }
669722
670- if task_definitions. is_empty ( ) && self . should_validate_engine {
723+ if task_definitions. is_empty ( ) && should_validate_engine {
671724 let ( span, text) = task_id. span_and_text ( "turbo.json" ) ;
672725 return Err ( BuilderError :: MissingPackageTask ( Box :: new (
673726 MissingPackageTaskError {
0 commit comments