@@ -435,14 +435,6 @@ async fn post_build_processing(
435435 return Err ( format ! ( "GHCR login failed: {}" , e) ) ;
436436 }
437437
438- // Collect files to push
439- let files: Vec < PathBuf > = std:: fs:: read_dir ( outdir)
440- . map_err ( |e| format ! ( "Failed to read output directory: {}" , e) ) ?
441- . filter_map ( |e| e. ok ( ) )
442- . map ( |e| e. path ( ) )
443- . filter ( |p| p. is_file ( ) )
444- . collect ( ) ;
445-
446438 // Read version from .version file
447439 let version = std:: fs:: read_dir ( outdir)
448440 . ok ( )
@@ -458,63 +450,233 @@ async fn post_build_processing(
458450
459451 // Get architecture
460452 let arch = format ! ( "{}-{}" , std:: env:: consts:: ARCH , std:: env:: consts:: OS ) ;
453+ let tag = format ! ( "{}-{}" , version, arch. to_lowercase( ) ) ;
461454
462- // Build GHCR repo path: base_repo/pkg_family/recipe_name
463- // e.g., pkgforge/bincache/hello/static or pkgforge/pkgcache/cat/appimage.cat.stable
464- let ( full_repo, pkg) = if let Some ( url) = recipe_url {
465- if let Some ( ( pkg_family, recipe_name) ) = parse_ghcr_path ( url) {
466- let full_path = format ! ( "{}/{}/{}" , base_repo, pkg_family, recipe_name) ;
467- info ! ( "GHCR path: {}" , full_path) ;
468- ( full_path, recipe_name)
469- } else {
470- warn ! ( "Could not parse GHCR path from recipe URL, using base repo" ) ;
471- ( base_repo. clone ( ) , pkg_name. unwrap_or ( "unknown" ) . to_string ( ) )
472- }
473- } else {
474- ( base_repo. clone ( ) , pkg_name. unwrap_or ( "unknown" ) . to_string ( ) )
475- } ;
455+ // Get pkg_family from recipe URL
456+ let pkg_family = recipe_url
457+ . and_then ( |url| parse_ghcr_path ( url) )
458+ . map ( |( family, _) | family)
459+ . unwrap_or_else ( || pkg_name. unwrap_or ( "unknown" ) . to_string ( ) ) ;
476460
477461 // Read metadata from SBUILD file
478462 let metadata = read_sbuild_metadata ( outdir) . unwrap_or_default ( ) ;
479463
480- let annotations = PackageAnnotations {
481- pkg : if metadata. pkg . is_empty ( ) || metadata. pkg == "unknown" {
482- pkg. clone ( )
483- } else {
484- metadata. pkg
485- } ,
486- pkg_id : metadata. pkg_id ,
487- pkg_type : metadata. pkg_type ,
488- version : version. clone ( ) ,
489- description : metadata. description ,
490- homepage : metadata. homepage ,
491- license : metadata. license ,
492- build_date : chrono:: Utc :: now ( ) . to_rfc3339 ( ) ,
493- build_id : env:: var ( "GITHUB_RUN_ID" ) . ok ( ) ,
494- build_gha : env:: var ( "GITHUB_RUN_ID" )
495- . ok ( )
496- . map ( |id| format ! ( "https://github.com/{}/actions/runs/{}" ,
497- env:: var( "GITHUB_REPOSITORY" ) . unwrap_or_default( ) , id) ) ,
498- build_script : recipe_url. map ( |s| s. to_string ( ) ) ,
499- } ;
464+ let mut push_success = true ;
465+ let mut pushed_urls = Vec :: new ( ) ;
466+
467+ // Check for soar-packages/ directory (explicit multi-package structure)
468+ let soar_packages_dir = outdir. join ( "soar-packages" ) ;
469+
470+ if soar_packages_dir. is_dir ( ) {
471+ // Explicit multi-package mode
472+ info ! ( "Found soar-packages/ directory, using explicit package structure" ) ;
473+
474+ // Collect shared files from root (everything except soar-packages/)
475+ let shared_files: Vec < PathBuf > = std:: fs:: read_dir ( outdir)
476+ . map_err ( |e| format ! ( "Failed to read output directory: {}" , e) ) ?
477+ . filter_map ( |e| e. ok ( ) )
478+ . map ( |e| e. path ( ) )
479+ . filter ( |p| p. is_file ( ) )
480+ . collect ( ) ;
481+
482+ // Each subdirectory in soar-packages/ is a package
483+ let package_dirs: Vec < PathBuf > = std:: fs:: read_dir ( & soar_packages_dir)
484+ . map_err ( |e| format ! ( "Failed to read soar-packages directory: {}" , e) ) ?
485+ . filter_map ( |e| e. ok ( ) )
486+ . map ( |e| e. path ( ) )
487+ . filter ( |p| p. is_dir ( ) )
488+ . collect ( ) ;
489+
490+ if package_dirs. is_empty ( ) {
491+ warn ! ( "soar-packages/ directory is empty" ) ;
492+ return Ok ( ( ) ) ;
493+ }
500494
501- let tag = format ! ( "{}-{}" , version, arch. to_lowercase( ) ) ;
495+ for pkg_dir in & package_dirs {
496+ let pkg_name_dir = pkg_dir
497+ . file_name ( )
498+ . and_then ( |n| n. to_str ( ) )
499+ . unwrap_or ( "unknown" ) ;
500+
501+ // Build GHCR repo path for this package
502+ let full_repo = format ! ( "{}/{}/{}" , base_repo, pkg_family, pkg_name_dir) ;
503+ info ! ( "Pushing package {} to {}" , pkg_name_dir, full_repo) ;
502504
503- match client. push ( & files, & full_repo, & tag, & annotations) {
504- Ok ( target) => {
505- info ! ( "Pushed to {}" , target) ;
506- if cli. ci {
507- write_github_env ( "GHCRPKG_URL" , & target) ;
508- write_github_env ( "PUSH_SUCCESSFUL" , "YES" ) ;
505+ // Collect files: package-specific files + shared files
506+ let mut files_to_push: Vec < PathBuf > = std:: fs:: read_dir ( pkg_dir)
507+ . map_err ( |e| format ! ( "Failed to read package directory: {}" , e) ) ?
508+ . filter_map ( |e| e. ok ( ) )
509+ . map ( |e| e. path ( ) )
510+ . filter ( |p| p. is_file ( ) )
511+ . collect ( ) ;
512+
513+ // Add shared files from root
514+ files_to_push. extend ( shared_files. clone ( ) ) ;
515+
516+ // Find the main binary (same name as directory)
517+ let main_binary = files_to_push. iter ( ) . find ( |f| {
518+ f. file_name ( ) . and_then ( |n| n. to_str ( ) ) == Some ( pkg_name_dir)
519+ } ) ;
520+
521+ // Compute checksums for the main binary
522+ let ( bsum, shasum) = if let Some ( binary_path) = main_binary {
523+ (
524+ checksum:: b3sum ( binary_path) . ok ( ) ,
525+ checksum:: sha256sum ( binary_path) . ok ( ) ,
526+ )
527+ } else {
528+ ( None , None )
529+ } ;
530+
531+ let annotations = PackageAnnotations {
532+ pkg : pkg_name_dir. to_string ( ) ,
533+ pkg_id : metadata. pkg_id . clone ( ) ,
534+ pkg_type : metadata. pkg_type . clone ( ) ,
535+ version : version. clone ( ) ,
536+ description : metadata. description . clone ( ) ,
537+ homepage : metadata. homepage . clone ( ) ,
538+ license : metadata. license . clone ( ) ,
539+ build_date : chrono:: Utc :: now ( ) . to_rfc3339 ( ) ,
540+ build_id : env:: var ( "GITHUB_RUN_ID" ) . ok ( ) ,
541+ build_gha : env:: var ( "GITHUB_RUN_ID" )
542+ . ok ( )
543+ . map ( |id| format ! ( "https://github.com/{}/actions/runs/{}" ,
544+ env:: var( "GITHUB_REPOSITORY" ) . unwrap_or_default( ) , id) ) ,
545+ build_script : recipe_url. map ( |s| s. to_string ( ) ) ,
546+ bsum,
547+ shasum,
548+ } ;
549+
550+ match client. push ( & files_to_push, & full_repo, & tag, & annotations) {
551+ Ok ( target) => {
552+ info ! ( "Pushed {} to {}" , pkg_name_dir, target) ;
553+ pushed_urls. push ( target) ;
554+ }
555+ Err ( e) => {
556+ error ! ( "Failed to push {}: {}" , pkg_name_dir, e) ;
557+ push_success = false ;
558+ }
509559 }
510560 }
511- Err ( e) => {
512- if cli. ci {
513- write_github_env ( "PUSH_SUCCESSFUL" , "NO" ) ;
561+ } else {
562+ // Fallback: auto-detect binaries from flat structure
563+ info ! ( "Using auto-detection for package structure" ) ;
564+
565+ // Collect all files
566+ let all_files: Vec < PathBuf > = std:: fs:: read_dir ( outdir)
567+ . map_err ( |e| format ! ( "Failed to read output directory: {}" , e) ) ?
568+ . filter_map ( |e| e. ok ( ) )
569+ . map ( |e| e. path ( ) )
570+ . filter ( |p| p. is_file ( ) )
571+ . collect ( ) ;
572+
573+ // Identify binary files (exclude metadata files)
574+ let binary_files: Vec < & PathBuf > = all_files
575+ . iter ( )
576+ . filter ( |p| {
577+ let ext = p. extension ( ) . and_then ( |e| e. to_str ( ) ) . unwrap_or ( "" ) ;
578+ // Exclude metadata files
579+ !matches ! ( ext, "json" | "log" | "version" | "sig" | "minisig" | "txt" | "yaml" | "yml" | "png" | "svg" )
580+ } )
581+ . collect ( ) ;
582+
583+ if binary_files. is_empty ( ) {
584+ warn ! ( "No binary files found to push" ) ;
585+ return Ok ( ( ) ) ;
586+ }
587+
588+ // Collect shared files (files that don't match any binary name pattern)
589+ let shared_files: Vec < & PathBuf > = all_files
590+ . iter ( )
591+ . filter ( |p| {
592+ let stem = p. file_stem ( ) . and_then ( |s| s. to_str ( ) ) . unwrap_or ( "" ) ;
593+ // Shared if no binary has this stem as its name
594+ !binary_files. iter ( ) . any ( |b| {
595+ b. file_name ( ) . and_then ( |n| n. to_str ( ) ) == Some ( stem)
596+ } )
597+ } )
598+ . filter ( |p| {
599+ let ext = p. extension ( ) . and_then ( |e| e. to_str ( ) ) . unwrap_or ( "" ) ;
600+ // Only include certain file types as shared
601+ matches ! ( ext, "log" | "version" | "txt" )
602+ } )
603+ . collect ( ) ;
604+
605+ // Push each binary separately to {base_repo}/{pkg_family}/{binary_name}
606+ for binary_path in & binary_files {
607+ let binary_name = binary_path
608+ . file_name ( )
609+ . and_then ( |n| n. to_str ( ) )
610+ . unwrap_or ( "unknown" ) ;
611+
612+ // Build GHCR repo path for this binary
613+ let full_repo = format ! ( "{}/{}/{}" , base_repo, pkg_family, binary_name) ;
614+ info ! ( "Pushing {} to {}" , binary_name, full_repo) ;
615+
616+ // Collect files for this binary: binary + associated metadata + shared files
617+ let mut files_to_push: Vec < PathBuf > = vec ! [ ( * binary_path) . clone( ) ] ;
618+
619+ // Add associated files (json, sig, png, svg, log)
620+ for ext in & [ "json" , "sig" , "minisig" , "png" , "svg" , "log" ] {
621+ let assoc_file = outdir. join ( format ! ( "{}.{}" , binary_name, ext) ) ;
622+ if assoc_file. exists ( ) {
623+ files_to_push. push ( assoc_file) ;
624+ }
625+ }
626+
627+ // Add shared files
628+ for shared in & shared_files {
629+ files_to_push. push ( ( * shared) . clone ( ) ) ;
630+ }
631+
632+ // Compute checksums for this binary
633+ let bsum = checksum:: b3sum ( binary_path) . ok ( ) ;
634+ let shasum = checksum:: sha256sum ( binary_path) . ok ( ) ;
635+
636+ let annotations = PackageAnnotations {
637+ pkg : binary_name. to_string ( ) ,
638+ pkg_id : metadata. pkg_id . clone ( ) ,
639+ pkg_type : metadata. pkg_type . clone ( ) ,
640+ version : version. clone ( ) ,
641+ description : metadata. description . clone ( ) ,
642+ homepage : metadata. homepage . clone ( ) ,
643+ license : metadata. license . clone ( ) ,
644+ build_date : chrono:: Utc :: now ( ) . to_rfc3339 ( ) ,
645+ build_id : env:: var ( "GITHUB_RUN_ID" ) . ok ( ) ,
646+ build_gha : env:: var ( "GITHUB_RUN_ID" )
647+ . ok ( )
648+ . map ( |id| format ! ( "https://github.com/{}/actions/runs/{}" ,
649+ env:: var( "GITHUB_REPOSITORY" ) . unwrap_or_default( ) , id) ) ,
650+ build_script : recipe_url. map ( |s| s. to_string ( ) ) ,
651+ bsum,
652+ shasum,
653+ } ;
654+
655+ match client. push ( & files_to_push, & full_repo, & tag, & annotations) {
656+ Ok ( target) => {
657+ info ! ( "Pushed {} to {}" , binary_name, target) ;
658+ pushed_urls. push ( target) ;
659+ }
660+ Err ( e) => {
661+ error ! ( "Failed to push {}: {}" , binary_name, e) ;
662+ push_success = false ;
663+ }
514664 }
515- return Err ( format ! ( "GHCR push failed: {}" , e) ) ;
516665 }
517666 }
667+
668+ if cli. ci {
669+ if push_success && !pushed_urls. is_empty ( ) {
670+ write_github_env ( "GHCRPKG_URL" , & pushed_urls. join ( "," ) ) ;
671+ write_github_env ( "PUSH_SUCCESSFUL" , "YES" ) ;
672+ } else {
673+ write_github_env ( "PUSH_SUCCESSFUL" , "NO" ) ;
674+ }
675+ }
676+
677+ if !push_success {
678+ return Err ( "One or more GHCR pushes failed" . to_string ( ) ) ;
679+ }
518680 } else {
519681 warn ! ( "--push specified but --ghcr-token or --ghcr-repo not provided" ) ;
520682 }
0 commit comments