@@ -373,6 +373,7 @@ impl CircleCIGenerator {
373373 architectures : None ,
374374 resource_class : None ,
375375 source_files : None ,
376+ source_submodules : None ,
376377 parallelism : None ,
377378 requires : None ,
378379 cache : None ,
@@ -462,7 +463,47 @@ fi"#,
462463 job_name. clone ( )
463464 } ;
464465 // Hash calculation (reuse existing function)
465- let hash_step = self . build_hash_calculation_step ( config, source_files) ?;
466+ let mut extra_patterns: Vec < String > = Vec :: new ( ) ;
467+ if let Some ( submodules) = & job_def. source_submodules {
468+ for submodule in submodules {
469+ let output_file =
470+ Self :: submodule_hash_output_path ( & variant, submodule) ;
471+
472+ let mut params = serde_yaml:: Mapping :: new ( ) ;
473+ params. insert (
474+ serde_yaml:: Value :: String ( "submodule" . to_string ( ) ) ,
475+ serde_yaml:: Value :: String ( submodule. clone ( ) ) ,
476+ ) ;
477+ params. insert (
478+ serde_yaml:: Value :: String ( "output_file" . to_string ( ) ) ,
479+ serde_yaml:: Value :: String ( output_file. clone ( ) ) ,
480+ ) ;
481+
482+ let mut step_map = serde_yaml:: Mapping :: new ( ) ;
483+ step_map. insert (
484+ serde_yaml:: Value :: String (
485+ "cigen_write_submodule_commit_hash" . to_string ( ) ,
486+ ) ,
487+ serde_yaml:: Value :: Mapping ( params) ,
488+ ) ;
489+
490+ setup_job. steps . push ( cc:: CircleCIStep :: new (
491+ serde_yaml:: Value :: Mapping ( step_map) ,
492+ ) ) ;
493+
494+ extra_patterns. push ( output_file) ;
495+ }
496+ }
497+
498+ let hash_step = self . build_hash_calculation_step (
499+ config,
500+ source_files,
501+ if extra_patterns. is_empty ( ) {
502+ None
503+ } else {
504+ Some ( & extra_patterns)
505+ } ,
506+ ) ?;
466507 setup_job. steps . push ( cc:: CircleCIStep :: new ( hash_step) ) ;
467508 // Restore exists cache with OS + variant + job hash
468509 let mut restore_cfg = serde_yaml:: Mapping :: new ( ) ;
@@ -933,6 +974,7 @@ cat /tmp/continuation.json
933974 architectures : Some ( archs. iter ( ) . cloned ( ) . collect ( ) ) ,
934975 resource_class : None ,
935976 source_files : None ,
977+ source_submodules : None ,
936978 parallelism : None ,
937979 requires : None ,
938980 cache : None ,
@@ -1588,6 +1630,8 @@ cat /tmp/continuation.json
15881630 & mut circleci_job,
15891631 config,
15901632 source_files,
1633+ job. source_submodules . as_ref ( ) ,
1634+ job_name,
15911635 architecture,
15921636 ) ?;
15931637
@@ -2423,10 +2467,50 @@ echo "export JOB_STATUS_KEY=\"${{OS_LABEL}}-job_status-v1-{job}-{arch}-${{JOB_HA
24232467 circleci_job : & mut CircleCIJob ,
24242468 config : & Config ,
24252469 source_files : & Vec < String > ,
2470+ submodules : Option < & Vec < String > > ,
2471+ job_name : & str ,
24262472 architecture : & str ,
24272473 ) -> Result < ( ) > {
2474+ let mut unfiltered_patterns: Vec < String > = Vec :: new ( ) ;
2475+
2476+ if let Some ( submodules) = submodules {
2477+ for submodule in submodules {
2478+ let output_file = Self :: submodule_hash_output_path ( job_name, submodule) ;
2479+
2480+ let mut params = serde_yaml:: Mapping :: new ( ) ;
2481+ params. insert (
2482+ serde_yaml:: Value :: String ( "submodule" . to_string ( ) ) ,
2483+ serde_yaml:: Value :: String ( submodule. clone ( ) ) ,
2484+ ) ;
2485+ params. insert (
2486+ serde_yaml:: Value :: String ( "output_file" . to_string ( ) ) ,
2487+ serde_yaml:: Value :: String ( output_file. clone ( ) ) ,
2488+ ) ;
2489+
2490+ let mut step_map = serde_yaml:: Mapping :: new ( ) ;
2491+ step_map. insert (
2492+ serde_yaml:: Value :: String ( "cigen_write_submodule_commit_hash" . to_string ( ) ) ,
2493+ serde_yaml:: Value :: Mapping ( params) ,
2494+ ) ;
2495+
2496+ circleci_job
2497+ . steps
2498+ . push ( CircleCIStep :: new ( serde_yaml:: Value :: Mapping ( step_map) ) ) ;
2499+
2500+ unfiltered_patterns. push ( output_file) ;
2501+ }
2502+ }
2503+
24282504 // Add hash calculation step
2429- let hash_step = self . build_hash_calculation_step ( config, source_files) ?;
2505+ let hash_step = self . build_hash_calculation_step (
2506+ config,
2507+ source_files,
2508+ if unfiltered_patterns. is_empty ( ) {
2509+ None
2510+ } else {
2511+ Some ( & unfiltered_patterns)
2512+ } ,
2513+ ) ?;
24302514 circleci_job. steps . push ( CircleCIStep :: new ( hash_step) ) ;
24312515
24322516 // Add skip check step
@@ -2440,6 +2524,7 @@ echo "export JOB_STATUS_KEY=\"${{OS_LABEL}}-job_status-v1-{job}-{arch}-${{JOB_HA
24402524 & self ,
24412525 config : & Config ,
24422526 source_files : & Vec < String > ,
2527+ unfiltered_patterns : Option < & Vec < String > > ,
24432528 ) -> Result < serde_yaml:: Value > {
24442529 let source_file_groups = config
24452530 . source_file_groups
@@ -2476,6 +2561,14 @@ echo "export JOB_STATUS_KEY=\"${{OS_LABEL}}-job_status-v1-{job}-{arch}-${{JOB_HA
24762561 serde_yaml:: Value :: String ( "patterns" . to_string ( ) ) ,
24772562 serde_yaml:: Value :: String ( pat_str) ,
24782563 ) ;
2564+ if let Some ( extra_patterns) = unfiltered_patterns
2565+ && !extra_patterns. is_empty ( )
2566+ {
2567+ params. insert (
2568+ serde_yaml:: Value :: String ( "unfiltered_patterns" . to_string ( ) ) ,
2569+ serde_yaml:: Value :: String ( extra_patterns. join ( " " ) ) ,
2570+ ) ;
2571+ }
24792572 let mut step_map = serde_yaml:: Mapping :: new ( ) ;
24802573 step_map. insert (
24812574 serde_yaml:: Value :: String ( "cigen_calculate_sha256" . to_string ( ) ) ,
@@ -2484,6 +2577,37 @@ echo "export JOB_STATUS_KEY=\"${{OS_LABEL}}-job_status-v1-{job}-{arch}-${{JOB_HA
24842577 Ok ( serde_yaml:: Value :: Mapping ( step_map) )
24852578 }
24862579
2580+ fn sanitize_identifier ( input : & str ) -> String {
2581+ let mut sanitized = String :: new ( ) ;
2582+ let mut last_was_dash = false ;
2583+
2584+ for ch in input. chars ( ) {
2585+ if ch. is_ascii_alphanumeric ( ) {
2586+ sanitized. push ( ch) ;
2587+ last_was_dash = false ;
2588+ } else if !last_was_dash {
2589+ sanitized. push ( '-' ) ;
2590+ last_was_dash = true ;
2591+ }
2592+ }
2593+
2594+ let trimmed = sanitized. trim_matches ( '-' ) ;
2595+ if trimmed. is_empty ( ) {
2596+ "entry" . to_string ( )
2597+ } else {
2598+ trimmed. to_string ( )
2599+ }
2600+ }
2601+
2602+ fn submodule_hash_output_path ( job_identifier : & str , submodule : & str ) -> String {
2603+ let job_part = Self :: sanitize_identifier ( job_identifier) ;
2604+ let module_part = Self :: sanitize_identifier ( submodule) ;
2605+ format ! (
2606+ "/tmp/cigen/submodule-hashes/{}-{}.commit" ,
2607+ job_part, module_part
2608+ )
2609+ }
2610+
24872611 fn build_skip_check_step (
24882612 & self ,
24892613 _config : & Config ,
0 commit comments