@@ -226,6 +226,11 @@ impl CircleCIGenerator {
226226 circleci_config. parameters = Some ( self . convert_parameters ( parameters) ?) ;
227227 }
228228
229+ // Add orbs if specified (required for commands like slack/notify)
230+ if let Some ( orbs) = & config. orbs {
231+ circleci_config. orbs = Some ( orbs. clone ( ) ) ;
232+ }
233+
229234 // Scan for template command usage and add them to commands section
230235 let used_template_commands = self . scan_for_template_commands ( & circleci_config) ;
231236 let used_user_commands = self . scan_for_user_commands ( & circleci_config, commands) ;
@@ -396,6 +401,158 @@ impl CircleCIGenerator {
396401 source_files,
397402 architecture,
398403 ) ?;
404+
405+ // Decide backend for job-status cache
406+ let backend = config
407+ . caches
408+ . as_ref ( )
409+ . and_then ( |c| c. job_status . as_ref ( ) )
410+ . map ( |b| b. backend . as_str ( ) )
411+ . unwrap_or ( "native" ) ;
412+
413+ match backend {
414+ "redis" => {
415+ // Prepare key env
416+ let mut key_step = serde_yaml:: Mapping :: new ( ) ;
417+ let mut key_run = serde_yaml:: Mapping :: new ( ) ;
418+ key_run. insert (
419+ serde_yaml:: Value :: String ( "name" . to_string ( ) ) ,
420+ serde_yaml:: Value :: String ( "Prepare job status key" . to_string ( ) ) ,
421+ ) ;
422+ key_run. insert (
423+ serde_yaml:: Value :: String ( "command" . to_string ( ) ) ,
424+ serde_yaml:: Value :: String (
425+ "export JOB_STATUS_KEY=\" job_status:${CIRCLE_JOB}-${DOCKER_ARCH}-${JOB_HASH}\" " . to_string ( ) ,
426+ ) ,
427+ ) ;
428+ key_step. insert (
429+ serde_yaml:: Value :: String ( "run" . to_string ( ) ) ,
430+ serde_yaml:: Value :: Mapping ( key_run) ,
431+ ) ;
432+ circleci_job
433+ . steps
434+ . push ( CircleCIStep :: new ( serde_yaml:: Value :: Mapping ( key_step) ) ) ;
435+
436+ // Read redis url/ttl from config if provided
437+ let ( redis_url, ttl) = if let Some ( cfg) = config
438+ . caches
439+ . as_ref ( )
440+ . and_then ( |c| c. job_status . as_ref ( ) )
441+ . and_then ( |b| b. config . as_ref ( ) )
442+ {
443+ let url = cfg
444+ . get ( "url" )
445+ . and_then ( |v| v. as_str ( ) )
446+ . unwrap_or ( "redis://127.0.0.1:6379" ) ;
447+ let ttl = cfg. get ( "ttl_seconds" ) . and_then ( |v| v. as_u64 ( ) ) ;
448+ ( url. to_string ( ) , ttl)
449+ } else {
450+ ( "redis://127.0.0.1:6379" . to_string ( ) , None )
451+ } ;
452+
453+ // Skip if key exists
454+ let mut check_step = serde_yaml:: Mapping :: new ( ) ;
455+ let mut check_run = serde_yaml:: Mapping :: new ( ) ;
456+ check_run. insert (
457+ serde_yaml:: Value :: String ( "name" . to_string ( ) ) ,
458+ serde_yaml:: Value :: String ( "Check job status (redis)" . to_string ( ) ) ,
459+ ) ;
460+ let check_cmd = format ! (
461+ "VAL=$(redis-cli -u {} GET \" $JOB_STATUS_KEY\" 2>/dev/null || true)\n if [ \" $VAL\" = \" done\" ]; then\n echo 'Job already completed successfully for this file hash. Skipping...';\n circleci step halt;\n fi" ,
462+ redis_url
463+ ) ;
464+ check_run. insert (
465+ serde_yaml:: Value :: String ( "command" . to_string ( ) ) ,
466+ serde_yaml:: Value :: String ( check_cmd) ,
467+ ) ;
468+ check_step. insert (
469+ serde_yaml:: Value :: String ( "run" . to_string ( ) ) ,
470+ serde_yaml:: Value :: Mapping ( check_run) ,
471+ ) ;
472+ circleci_job
473+ . steps
474+ . push ( CircleCIStep :: new ( serde_yaml:: Value :: Mapping ( check_step) ) ) ;
475+
476+ // After steps complete, record completion and optionally set TTL
477+ let mut record_step = serde_yaml:: Mapping :: new ( ) ;
478+ let mut record_run = serde_yaml:: Mapping :: new ( ) ;
479+ record_run. insert (
480+ serde_yaml:: Value :: String ( "name" . to_string ( ) ) ,
481+ serde_yaml:: Value :: String ( "Record job completion (redis)" . to_string ( ) ) ,
482+ ) ;
483+ let set_cmd = if let Some ( ttl_secs) = ttl {
484+ format ! (
485+ "redis-cli -u {} SET \" $JOB_STATUS_KEY\" done >/dev/null 2>&1 || true\n redis-cli -u {} EXPIRE \" $JOB_STATUS_KEY\" {} >/dev/null 2>&1 || true" ,
486+ redis_url, redis_url, ttl_secs
487+ )
488+ } else {
489+ format ! (
490+ "redis-cli -u {} SET \" $JOB_STATUS_KEY\" done >/dev/null 2>&1 || true" ,
491+ redis_url
492+ )
493+ } ;
494+ record_run. insert (
495+ serde_yaml:: Value :: String ( "command" . to_string ( ) ) ,
496+ serde_yaml:: Value :: String ( set_cmd) ,
497+ ) ;
498+ record_step. insert (
499+ serde_yaml:: Value :: String ( "run" . to_string ( ) ) ,
500+ serde_yaml:: Value :: Mapping ( record_run) ,
501+ ) ;
502+ circleci_job
503+ . steps
504+ . push ( CircleCIStep :: new ( serde_yaml:: Value :: Mapping ( record_step) ) ) ;
505+ }
506+ _ => {
507+ // Native CircleCI cache
508+ // 1) Write job-key file (CIRCLE_JOB + arch + hash)
509+ let mut key_step = serde_yaml:: Mapping :: new ( ) ;
510+ let mut key_run = serde_yaml:: Mapping :: new ( ) ;
511+ key_run. insert (
512+ serde_yaml:: Value :: String ( "name" . to_string ( ) ) ,
513+ serde_yaml:: Value :: String ( "Prepare job status key" . to_string ( ) ) ,
514+ ) ;
515+ key_run. insert (
516+ serde_yaml:: Value :: String ( "command" . to_string ( ) ) ,
517+ serde_yaml:: Value :: String (
518+ "mkdir -p /tmp/cigen_job_status && echo \" ${CIRCLE_JOB}-$(echo $DOCKER_ARCH)-${JOB_HASH}\" > /tmp/cigen_job_status/job-key" . to_string ( ) ,
519+ ) ,
520+ ) ;
521+ key_step. insert (
522+ serde_yaml:: Value :: String ( "run" . to_string ( ) ) ,
523+ serde_yaml:: Value :: Mapping ( key_run) ,
524+ ) ;
525+ circleci_job
526+ . steps
527+ . push ( CircleCIStep :: new ( serde_yaml:: Value :: Mapping ( key_step) ) ) ;
528+
529+ // 2) Restore job status cache
530+ let mut restore_cfg = serde_yaml:: Mapping :: new ( ) ;
531+ restore_cfg. insert (
532+ serde_yaml:: Value :: String ( "keys" . to_string ( ) ) ,
533+ serde_yaml:: Value :: Sequence ( vec ! [
534+ serde_yaml:: Value :: String (
535+ "job_status-v1-{{ checksum \" /tmp/cigen_job_status/job-key\" }}"
536+ . to_string( ) ,
537+ ) ,
538+ serde_yaml:: Value :: String ( "job_status-v1-" . to_string( ) ) ,
539+ ] ) ,
540+ ) ;
541+ let mut restore_step = serde_yaml:: Mapping :: new ( ) ;
542+ restore_step. insert (
543+ serde_yaml:: Value :: String ( "restore_cache" . to_string ( ) ) ,
544+ serde_yaml:: Value :: Mapping ( restore_cfg) ,
545+ ) ;
546+ circleci_job
547+ . steps
548+ . push ( CircleCIStep :: new ( serde_yaml:: Value :: Mapping ( restore_step) ) ) ;
549+
550+ // 3) Check and early exit if restored
551+ let skip_check_step = self . build_skip_check_step ( config, architecture) ?;
552+ circleci_job. steps . push ( CircleCIStep :: new ( skip_check_step) ) ;
553+ }
554+ }
555+
399556 true
400557 } else {
401558 false
@@ -1163,20 +1320,17 @@ fi
11631320 fn build_skip_check_step (
11641321 & self ,
11651322 _config : & Config ,
1166- architecture : & str ,
1323+ _architecture : & str ,
11671324 ) -> Result < serde_yaml:: Value > {
1168- let command = format ! (
1169- r#"
1170- if [ -f "/tmp/cigen_skip_cache/job_${{JOB_HASH}}_{}" ]; then
1325+ let command = r#"
1326+ if [ -f "/tmp/cigen_job_status/done_${JOB_HASH}" ]; then
11711327 echo "Job already completed successfully for this file hash. Skipping..."
11721328 circleci step halt
11731329else
11741330 echo "No previous successful run found. Proceeding with job..."
1175- mkdir -p /tmp/cigen_skip_cache
1331+ mkdir -p /tmp/cigen_job_status
11761332fi
1177- "# ,
1178- architecture
1179- )
1333+ "#
11801334 . trim ( )
11811335 . to_string ( ) ;
11821336
@@ -1202,15 +1356,12 @@ fi
12021356 fn add_record_completion_step (
12031357 & self ,
12041358 circleci_job : & mut CircleCIJob ,
1205- architecture : & str ,
1359+ _architecture : & str ,
12061360 ) -> Result < ( ) > {
1207- let command = format ! (
1208- r#"
1209- echo "Recording successful completion for hash ${{JOB_HASH}}"
1210- echo "$(date): Job completed successfully" > "/tmp/cigen_skip_cache/job_${{JOB_HASH}}_{}"
1211- "# ,
1212- architecture
1213- )
1361+ let command = r#"
1362+ echo "Recording successful completion for hash ${JOB_HASH}"
1363+ touch "/tmp/cigen_job_status/done_${JOB_HASH}"
1364+ "#
12141365 . trim ( )
12151366 . to_string ( ) ;
12161367
@@ -1234,6 +1385,29 @@ echo "$(date): Job completed successfully" > "/tmp/cigen_skip_cache/job_${{JOB_H
12341385 . steps
12351386 . push ( CircleCIStep :: new ( serde_yaml:: Value :: Mapping ( step) ) ) ;
12361387
1388+ // Save job status cache
1389+ let mut save_cfg = serde_yaml:: Mapping :: new ( ) ;
1390+ save_cfg. insert (
1391+ serde_yaml:: Value :: String ( "key" . to_string ( ) ) ,
1392+ serde_yaml:: Value :: String (
1393+ "job_status-v1-{{ checksum \" /tmp/cigen_job_status/job-key\" }}" . to_string ( ) ,
1394+ ) ,
1395+ ) ;
1396+ save_cfg. insert (
1397+ serde_yaml:: Value :: String ( "paths" . to_string ( ) ) ,
1398+ serde_yaml:: Value :: Sequence ( vec ! [ serde_yaml:: Value :: String (
1399+ "/tmp/cigen_job_status" . to_string( ) ,
1400+ ) ] ) ,
1401+ ) ;
1402+ let mut save_step = serde_yaml:: Mapping :: new ( ) ;
1403+ save_step. insert (
1404+ serde_yaml:: Value :: String ( "save_cache" . to_string ( ) ) ,
1405+ serde_yaml:: Value :: Mapping ( save_cfg) ,
1406+ ) ;
1407+ circleci_job
1408+ . steps
1409+ . push ( CircleCIStep :: new ( serde_yaml:: Value :: Mapping ( save_step) ) ) ;
1410+
12371411 Ok ( ( ) )
12381412 }
12391413
0 commit comments