@@ -27,6 +27,7 @@ import (
2727 "github.com/minio/minio-go/v7/pkg/credentials"
2828 "github.com/sxwebdev/gcx/internal/helpers"
2929 "github.com/urfave/cli/v3"
30+ "golang.org/x/sync/errgroup"
3031 "gopkg.in/yaml.v3"
3132)
3233
@@ -407,6 +408,24 @@ func getGitCommitHash() string {
407408 return strings .TrimSpace (string (out ))
408409}
409410
411+ // getOutputFilename returns the output filename based on the configuration and platform.
412+ func getOutputFilename (
413+ usePlatformSuffix bool ,
414+ outDir , binaryBase , version , goos , goarch , goarm string ,
415+ ) string {
416+ var outputName string
417+ if usePlatformSuffix {
418+ if goarch == "arm" && goarm != "" {
419+ outputName = fmt .Sprintf ("%s/%s_%s_%s_%s_%s/%s" , outDir , binaryBase , version , goos , goarch , goarm , binaryBase )
420+ } else {
421+ outputName = fmt .Sprintf ("%s/%s_%s_%s_%s/%s" , outDir , binaryBase , version , goos , goarch , binaryBase )
422+ }
423+ } else {
424+ outputName = fmt .Sprintf ("%s/%s_%s/%s" , outDir , binaryBase , version , binaryBase )
425+ }
426+ return outputName
427+ }
428+
410429// buildBinaries performs cross-compilation of binaries according to the configuration.
411430func buildBinaries (cfg * Config ) error {
412431 // Execute hooks (e.g., "go mod tidy")
@@ -504,32 +523,57 @@ func buildBinaries(cfg *Config) error {
504523 processedLdflags = append (processedLdflags , buf .String ())
505524 }
506525
526+ eg := errgroup.Group {}
527+ eg .SetLimit (runtime .NumCPU ())
528+
529+ log .Printf ("Use %d CPU cores for building...\n " , runtime .NumCPU ())
530+
507531 // Iterate over all combinations of GOOS and GOARCH
508532 for _ , goos := range buildCfg .Goos {
509- for _ , goarch := range buildCfg .Goarch {
510- // If the architecture is arm and OS is not linux, skip build
511- if goarch == "arm" && goos != "linux" {
512- continue
513- }
514- // If architecture is arm and goarm parameters are provided, iterate over them
515- if goarch == "arm" && len (buildCfg .Goarm ) > 0 {
516- for _ , goarm := range buildCfg .Goarm {
533+ eg .Go (func () error {
534+ for _ , goarch := range buildCfg .Goarch {
535+ // If the architecture is arm and OS is not linux, skip build
536+ if goarch == "arm" && goos != "linux" {
537+ continue
538+ }
539+ // If architecture is arm and goarm parameters are provided, iterate over them
540+ if goarch == "arm" && len (buildCfg .Goarm ) > 0 {
541+ for _ , goarm := range buildCfg .Goarm {
542+ envs := os .Environ ()
543+ envs = append (envs , "GOOS=" + goos , "GOARCH=" + goarch , "GOARM=" + goarm )
544+ envs = append (envs , buildCfg .Env ... )
545+ outputName := getOutputFilename (
546+ usePlatformSuffix , outDir , binaryBase , currentTag , goos , goarch , goarm ,
547+ )
548+ args := []string {"build" }
549+ args = append (args , buildCfg .Flags ... )
550+ if len (processedLdflags ) > 0 {
551+ args = append (args , "-ldflags" , strings .Join (processedLdflags , " " ))
552+ }
553+ args = append (args , "-o" , outputName , buildCfg .Main )
554+ log .Printf ("Building %s for %s/%s arm%s..." , binaryBase , goos , goarch , goarm )
555+ cmd := exec .Command ("go" , args ... )
556+ cmd .Env = envs
557+ cmd .Stdout = os .Stdout
558+ cmd .Stderr = os .Stderr
559+ if err := cmd .Run (); err != nil {
560+ return fmt .Errorf ("build error: %w" , err )
561+ }
562+ }
563+ } else {
517564 envs := os .Environ ()
518- envs = append (envs , "GOOS=" + goos , "GOARCH=" + goarch , "GOARM=" + goarm )
565+ envs = append (envs , "GOOS=" + goos , "GOARCH=" + goarch )
519566 envs = append (envs , buildCfg .Env ... )
520- var outputName string
521- if usePlatformSuffix {
522- outputName = fmt .Sprintf ("%s/%s_%s_%s_%s" , outDir , binaryBase , goos , goarch , goarm )
523- } else {
524- outputName = fmt .Sprintf ("%s/%s" , outDir , binaryBase )
525- }
567+ outputName := getOutputFilename (
568+ usePlatformSuffix , outDir , binaryBase , currentTag , goos , goarch , "" ,
569+ )
526570 args := []string {"build" }
527571 args = append (args , buildCfg .Flags ... )
528572 if len (processedLdflags ) > 0 {
529573 args = append (args , "-ldflags" , strings .Join (processedLdflags , " " ))
530574 }
531575 args = append (args , "-o" , outputName , buildCfg .Main )
532- log .Printf ("Building %s for %s/%s arm%s ..." , binaryBase , goos , goarch , goarm )
576+ log .Printf ("Building %s for %s/%s..." , binaryBase , goos , goarch )
533577 cmd := exec .Command ("go" , args ... )
534578 cmd .Env = envs
535579 cmd .Stdout = os .Stdout
@@ -538,32 +582,13 @@ func buildBinaries(cfg *Config) error {
538582 return fmt .Errorf ("build error: %w" , err )
539583 }
540584 }
541- } else {
542- envs := os .Environ ()
543- envs = append (envs , "GOOS=" + goos , "GOARCH=" + goarch )
544- envs = append (envs , buildCfg .Env ... )
545- var outputName string
546- if usePlatformSuffix {
547- outputName = fmt .Sprintf ("%s/%s_%s_%s" , outDir , binaryBase , goos , goarch )
548- } else {
549- outputName = fmt .Sprintf ("%s/%s" , outDir , binaryBase )
550- }
551- args := []string {"build" }
552- args = append (args , buildCfg .Flags ... )
553- if len (processedLdflags ) > 0 {
554- args = append (args , "-ldflags" , strings .Join (processedLdflags , " " ))
555- }
556- args = append (args , "-o" , outputName , buildCfg .Main )
557- log .Printf ("Building %s for %s/%s..." , binaryBase , goos , goarch )
558- cmd := exec .Command ("go" , args ... )
559- cmd .Env = envs
560- cmd .Stdout = os .Stdout
561- cmd .Stderr = os .Stderr
562- if err := cmd .Run (); err != nil {
563- return fmt .Errorf ("build error: %w" , err )
564- }
565585 }
566- }
586+ return nil
587+ })
588+ }
589+
590+ if err := eg .Wait (); err != nil {
591+ return fmt .Errorf ("build error: %w" , err )
567592 }
568593 }
569594
@@ -814,38 +839,37 @@ func createArchives(cfg *Config, artifactsDir string) error {
814839 return nil
815840 }
816841
817- // Get current version
818- version := getGitTag ()
819-
820842 // Read all files in artifacts directory
821843 files , err := os .ReadDir (artifactsDir )
822844 if err != nil {
823845 return fmt .Errorf ("failed to read artifacts directory: %w" , err )
824846 }
825847
848+ eg := errgroup.Group {}
849+ eg .SetLimit (runtime .NumCPU ())
850+
851+ log .Printf ("Use %d CPU cores for creating archives...\n " , runtime .NumCPU ())
852+
826853 // Track which files were archived
827854 archivedFiles := make (map [string ]bool )
828855
829- // Create archives for each file according to configuration
856+ // Create archives for each file/directory according to configuration
830857 for _ , file := range files {
831- if file .IsDir () {
832- continue
833- }
834-
835- // Parse filename to get platform information
836858 fileName := file .Name ()
859+
837860 parts := strings .Split (fileName , "_" )
838- if len (parts ) < 3 {
861+ if len (parts ) < 4 {
839862 continue
840863 }
841864
842- binary := parts [0 ]
843- os := parts [1 ]
844- arch := parts [2 ]
865+ binaryName := parts [0 ]
866+ version := parts [1 ]
867+ os := parts [2 ]
868+ arch := parts [3 ]
845869
846870 // Template data
847871 tmplData := ArchiveTemplateData {
848- Binary : binary ,
872+ Binary : binaryName ,
849873 Version : version ,
850874 Os : os ,
851875 Arch : arch ,
@@ -854,55 +878,74 @@ func createArchives(cfg *Config, artifactsDir string) error {
854878 // For each archive configuration
855879 for _ , archive := range cfg .Archives {
856880 // Create archive name from template
857- tmpl , err := template .New ("archive" ).Parse (archive .NameTemplate )
858- if err != nil {
859- return fmt .Errorf ("failed to parse archive name template: %w" , err )
860- }
881+ archiveName := fileName
882+ if archive .NameTemplate != "" {
883+ tmpl , err := template .New ("archive" ).Parse (archive .NameTemplate )
884+ if err != nil {
885+ return fmt .Errorf ("failed to parse archive name template: %w" , err )
886+ }
887+
888+ var nameBuffer strings.Builder
889+ if err := tmpl .Execute (& nameBuffer , tmplData ); err != nil {
890+ return fmt .Errorf ("failed to execute archive name template: %w" , err )
891+ }
861892
862- var nameBuffer strings.Builder
863- if err := tmpl .Execute (& nameBuffer , tmplData ); err != nil {
864- return fmt .Errorf ("failed to execute archive name template: %w" , err )
893+ archiveName = nameBuffer .String ()
865894 }
866895
867896 // For each archive format
868897 for _ , format := range archive .Formats {
869- archiveName := nameBuffer .String () + "." + format
870- archivePath := filepath .Join (artifactsDir , archiveName )
898+ archiveFileName := archiveName + "." + format
899+ archivePath := filepath .Join (artifactsDir , archiveFileName )
900+ sourcePath := filepath .Join (artifactsDir , fileName )
871901
872902 switch format {
873903 case "tar.gz" :
874- if err := createTarGz (filepath .Join (artifactsDir , fileName ), archivePath ); err != nil {
875- return fmt .Errorf ("failed to create tar.gz archive: %w" , err )
876- }
877- // Mark the source file as archived
904+ // Mark the source file/directory as archived
878905 archivedFiles [fileName ] = true
879- // Here you can add support for other archive formats
906+
907+ eg .Go (func () error {
908+ if err := createTarGz (sourcePath , archivePath ); err != nil {
909+ return fmt .Errorf ("failed to create tar.gz archive: %w" , err )
910+ }
911+
912+ return nil
913+ })
880914 default :
881915 log .Printf ("Unsupported archive format: %s" , format )
882916 }
883917 }
884918 }
885919 }
886920
887- // Remove all source files that were archived
921+ if err := eg .Wait (); err != nil {
922+ return fmt .Errorf ("error creating archives: %w" , err )
923+ }
924+
925+ // Remove all source files/directories that were archived
888926 for _ , file := range files {
889- if file .IsDir () {
890- continue
891- }
892927 fileName := file .Name ()
893928 if archivedFiles [fileName ] {
894929 filePath := filepath .Join (artifactsDir , fileName )
895- if err := os .Remove (filePath ); err != nil {
896- log .Printf ("Warning: failed to remove source file %s: %v" , filePath , err )
930+ if file .IsDir () {
931+ if err := os .RemoveAll (filePath ); err != nil {
932+ log .Printf ("Warning: failed to remove source directory %s: %v" , filePath , err )
933+ }
934+ } else {
935+ if err := os .Remove (filePath ); err != nil {
936+ log .Printf ("Warning: failed to remove source file %s: %v" , filePath , err )
937+ }
897938 }
898939 }
899940 }
900941
942+ log .Printf ("All archives created successfully." )
943+
901944 return nil
902945}
903946
904- // createTarGz creates a tar.gz archive from a file
905- func createTarGz (srcFile , destFile string ) error {
947+ // createTarGz creates a tar.gz archive from a file or directory
948+ func createTarGz (srcPath , destFile string ) error {
906949 // Create archive file
907950 archive , err := os .Create (destFile )
908951 if err != nil {
@@ -918,8 +961,26 @@ func createTarGz(srcFile, destFile string) error {
918961 tw := tar .NewWriter (gw )
919962 defer tw .Close ()
920963
964+ // Check if source is file or directory
965+ srcInfo , err := os .Stat (srcPath )
966+ if err != nil {
967+ return fmt .Errorf ("failed to get source info: %w" , err )
968+ }
969+
970+ if srcInfo .IsDir () {
971+ // Archive directory - use directory name as base
972+ dirName := filepath .Base (srcPath )
973+ return addDirToTar (tw , srcPath , dirName )
974+ } else {
975+ // Archive single file
976+ return addFileToTar (tw , srcPath , filepath .Base (srcPath ))
977+ }
978+ }
979+
980+ // addFileToTar adds a single file to tar archive
981+ func addFileToTar (tw * tar.Writer , filePath , nameInTar string ) error {
921982 // Open source file
922- file , err := os .Open (srcFile )
983+ file , err := os .Open (filePath )
923984 if err != nil {
924985 return fmt .Errorf ("failed to open source file: %w" , err )
925986 }
@@ -933,7 +994,7 @@ func createTarGz(srcFile, destFile string) error {
933994
934995 // Create tar header
935996 header := & tar.Header {
936- Name : filepath . Base ( srcFile ) ,
997+ Name : nameInTar ,
937998 Size : stat .Size (),
938999 Mode : int64 (stat .Mode ()),
9391000 ModTime : stat .ModTime (),
@@ -952,6 +1013,44 @@ func createTarGz(srcFile, destFile string) error {
9521013 return nil
9531014}
9541015
1016+ // addDirToTar recursively adds directory contents to tar archive
1017+ func addDirToTar (tw * tar.Writer , dirPath , baseInTar string ) error {
1018+ return filepath .Walk (dirPath , func (path string , info os.FileInfo , err error ) error {
1019+ if err != nil {
1020+ return err
1021+ }
1022+
1023+ // Calculate relative path for tar
1024+ relPath , err := filepath .Rel (dirPath , path )
1025+ if err != nil {
1026+ return fmt .Errorf ("failed to get relative path: %w" , err )
1027+ }
1028+
1029+ var nameInTar string
1030+ if relPath == "." {
1031+ // This is the root directory itself
1032+ nameInTar = baseInTar
1033+ } else {
1034+ // Combine with base path in tar
1035+ nameInTar = filepath .Join (baseInTar , relPath )
1036+ }
1037+
1038+ // Handle directories
1039+ if info .IsDir () {
1040+ header := & tar.Header {
1041+ Name : nameInTar + "/" ,
1042+ Mode : int64 (info .Mode ()),
1043+ ModTime : info .ModTime (),
1044+ Typeflag : tar .TypeDir ,
1045+ }
1046+ return tw .WriteHeader (header )
1047+ }
1048+
1049+ // Handle regular files
1050+ return addFileToTar (tw , path , nameInTar )
1051+ })
1052+ }
1053+
9551054// deployArtifacts executes deployment according to the configuration
9561055func deployArtifacts (cfg * Config , deployName string ) error {
9571056 if len (cfg .Deploys ) == 0 {
0 commit comments