2017-04-14 15:39:32 -03:00
// Package archive implements the pipe interface with the intent of
// archiving and compressing the binaries, readme, and other artifacts. It
// also provides an Archive interface which represents an archiving format.
2017-01-14 19:47:15 -02:00
package archive
2016-12-28 22:23:39 -02:00
2016-12-28 22:53:56 -02:00
import (
2020-10-10 08:46:16 -03:00
"errors"
2017-09-09 11:10:08 +02:00
"fmt"
2016-12-29 18:10:11 -02:00
"os"
2017-07-13 20:22:10 -03:00
"path/filepath"
2021-07-21 22:09:02 -03:00
"sort"
2017-12-17 18:01:58 -02:00
"strings"
2018-11-11 12:11:03 -02:00
"sync"
2016-12-29 18:10:11 -02:00
2017-06-22 00:09:14 -03:00
"github.com/apex/log"
2020-11-10 11:20:55 -03:00
"github.com/goreleaser/fileglob"
2019-12-29 15:02:15 -03:00
2017-12-17 15:50:09 -02:00
"github.com/goreleaser/goreleaser/internal/artifact"
2019-05-07 07:18:35 -03:00
"github.com/goreleaser/goreleaser/internal/ids"
2019-04-16 10:19:15 -03:00
"github.com/goreleaser/goreleaser/internal/semerrgroup"
2018-07-08 20:47:30 -07:00
"github.com/goreleaser/goreleaser/internal/tmpl"
2018-08-05 10:23:39 -03:00
"github.com/goreleaser/goreleaser/pkg/archive"
2018-11-13 13:48:16 -02:00
"github.com/goreleaser/goreleaser/pkg/config"
2018-08-14 23:50:20 -03:00
"github.com/goreleaser/goreleaser/pkg/context"
2016-12-28 22:53:56 -02:00
)
2016-12-28 22:23:39 -02:00
2017-12-26 21:19:58 -02:00
const (
2020-02-05 22:08:18 -03:00
defaultNameTemplate = "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
defaultBinaryNameTemplate = "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}"
2017-12-26 21:19:58 -02:00
)
2017-12-18 21:32:31 -02:00
2020-10-10 08:46:16 -03:00
// ErrArchiveDifferentBinaryCount happens when an archive uses several builds which have different goos/goarch/etc sets,
// causing the archives for some platforms to have more binaries than others.
// GoReleaser breaks in these cases as it will only cause confusion to other users.
var ErrArchiveDifferentBinaryCount = errors . New ( "archive has different count of built binaries for each platform, which may cause your users confusion. Please make sure all builds used have the same set of goos/goarch/etc or split it into multiple archives" )
2018-11-11 18:17:44 -02:00
// nolint: gochecknoglobals
var lock sync . Mutex
2020-05-26 00:48:10 -03:00
// Pipe for archive.
2018-11-11 18:17:44 -02:00
type Pipe struct { }
2016-12-30 09:27:35 -02:00
2017-12-02 19:53:19 -02:00
func ( Pipe ) String ( ) string {
2018-10-26 14:27:41 -06:00
return "archives"
2016-12-30 09:27:35 -02:00
}
2020-05-26 00:48:10 -03:00
// Default sets the pipe defaults.
2017-12-02 19:53:19 -02:00
func ( Pipe ) Default ( ctx * context . Context ) error {
2021-04-25 14:20:49 -03:00
ids := ids . New ( "archives" )
2019-04-16 10:19:15 -03:00
if len ( ctx . Config . Archives ) == 0 {
2019-12-29 15:02:15 -03:00
ctx . Config . Archives = append ( ctx . Config . Archives , config . Archive { } )
2017-12-02 19:53:19 -02:00
}
2019-04-16 10:19:15 -03:00
for i := range ctx . Config . Archives {
2021-04-25 14:20:49 -03:00
archive := & ctx . Config . Archives [ i ]
2019-04-16 10:19:15 -03:00
if archive . Format == "" {
archive . Format = "tar.gz"
}
if archive . ID == "" {
archive . ID = "default"
}
if len ( archive . Files ) == 0 {
2021-07-21 22:09:02 -03:00
archive . Files = [ ] config . File {
{ Source : "license*" } ,
{ Source : "LICENSE*" } ,
{ Source : "readme*" } ,
{ Source : "README*" } ,
{ Source : "changelog*" } ,
{ Source : "CHANGELOG*" } ,
2019-04-16 10:19:15 -03:00
}
}
if archive . NameTemplate == "" {
archive . NameTemplate = defaultNameTemplate
if archive . Format == "binary" {
archive . NameTemplate = defaultBinaryNameTemplate
}
}
if len ( archive . Builds ) == 0 {
for _ , build := range ctx . Config . Builds {
archive . Builds = append ( archive . Builds , build . ID )
}
}
2019-05-07 07:18:35 -03:00
ids . Inc ( archive . ID )
2017-12-26 21:19:58 -02:00
}
2019-05-07 07:18:35 -03:00
return ids . Validate ( )
2017-12-02 19:53:19 -02:00
}
2020-05-26 00:48:10 -03:00
// Run the pipe.
2018-11-11 18:17:44 -02:00
func ( Pipe ) Run ( ctx * context . Context ) error {
2021-04-25 14:20:49 -03:00
g := semerrgroup . New ( ctx . Parallelism )
2020-10-10 08:46:16 -03:00
for i , archive := range ctx . Config . Archives {
2019-04-16 10:19:15 -03:00
archive := archive
2021-04-25 14:20:49 -03:00
artifacts := ctx . Artifacts . Filter (
2019-04-16 10:19:15 -03:00
artifact . And (
2021-10-12 14:55:43 -03:00
artifact . Or (
artifact . ByType ( artifact . Binary ) ,
artifact . ByType ( artifact . UniversalBinary ) ,
) ,
2019-04-16 10:19:15 -03:00
artifact . ByIDs ( archive . Builds ... ) ,
) ,
2020-10-10 08:46:16 -03:00
) . GroupByPlatform ( )
2020-10-13 17:57:08 -03:00
if err := checkArtifacts ( artifacts ) ; err != nil && ! archive . AllowDifferentBinaryCount {
2020-10-10 08:46:16 -03:00
return fmt . Errorf ( "invalid archive: %d: %w" , i , ErrArchiveDifferentBinaryCount )
}
for group , artifacts := range artifacts {
2019-04-16 10:19:15 -03:00
log . Debugf ( "group %s has %d binaries" , group , len ( artifacts ) )
artifacts := artifacts
g . Go ( func ( ) error {
if packageFormat ( archive , artifacts [ 0 ] . Goos ) == "binary" {
return skip ( ctx , archive , artifacts )
}
return create ( ctx , archive , artifacts )
} )
}
2017-12-26 21:19:58 -02:00
}
return g . Wait ( )
}
2020-10-10 08:46:16 -03:00
func checkArtifacts ( artifacts map [ string ] [ ] * artifact . Artifact ) error {
2021-04-25 14:20:49 -03:00
lens := map [ int ] bool { }
2020-10-10 08:46:16 -03:00
for _ , v := range artifacts {
lens [ len ( v ) ] = true
}
2020-10-11 13:00:42 -03:00
if len ( lens ) <= 1 {
2020-10-10 08:46:16 -03:00
return nil
}
return ErrArchiveDifferentBinaryCount
}
2020-05-26 00:48:10 -03:00
func create ( ctx * context . Context , arch config . Archive , binaries [ ] * artifact . Artifact ) error {
2021-04-25 14:20:49 -03:00
format := packageFormat ( arch , binaries [ 0 ] . Goos )
2018-07-08 20:47:30 -07:00
folder , err := tmpl . New ( ctx ) .
2020-05-26 00:48:10 -03:00
WithArtifact ( binaries [ 0 ] , arch . Replacements ) .
Apply ( arch . NameTemplate )
2017-12-17 15:50:09 -02:00
if err != nil {
return err
}
archivePath := filepath . Join ( ctx . Config . Dist , folder + "." + format )
2018-11-11 18:17:44 -02:00
lock . Lock ( )
2021-04-25 14:20:49 -03:00
if err := os . MkdirAll ( filepath . Dir ( archivePath ) , 0 o755 | os . ModeDir ) ; err != nil {
2020-03-28 15:40:16 -03:00
lock . Unlock ( )
return err
}
2018-11-13 13:48:16 -02:00
if _ , err = os . Stat ( archivePath ) ; ! os . IsNotExist ( err ) {
2018-11-11 18:17:44 -02:00
lock . Unlock ( )
2018-11-11 12:11:03 -02:00
return fmt . Errorf ( "archive named %s already exists. Check your archive name template" , archivePath )
}
2017-12-17 15:50:09 -02:00
archiveFile , err := os . Create ( archivePath )
if err != nil {
2018-11-11 18:17:44 -02:00
lock . Unlock ( )
2020-11-10 11:15:03 -03:00
return fmt . Errorf ( "failed to create directory %s: %w" , archivePath , err )
2017-12-17 15:50:09 -02:00
}
2018-11-11 18:17:44 -02:00
lock . Unlock ( )
2020-05-26 00:48:10 -03:00
defer archiveFile . Close ( )
2018-11-13 13:48:16 -02:00
2021-04-25 14:20:49 -03:00
log := log . WithField ( "archive" , archivePath )
2018-10-26 16:31:06 -06:00
log . Info ( "creating" )
2018-11-13 13:48:16 -02:00
2020-03-04 00:52:43 -03:00
template := tmpl . New ( ctx ) .
2020-05-26 00:48:10 -03:00
WithArtifact ( binaries [ 0 ] , arch . Replacements )
wrap , err := template . Apply ( wrapFolder ( arch ) )
2018-11-13 13:48:16 -02:00
if err != nil {
return err
2018-11-07 14:15:51 -02:00
}
2018-11-13 13:48:16 -02:00
2021-04-25 14:20:49 -03:00
a := NewEnhancedArchive ( archive . New ( archiveFile ) , wrap )
2020-05-26 00:48:10 -03:00
defer a . Close ( )
2017-12-17 15:50:09 -02:00
2021-07-21 22:09:02 -03:00
files , err := findFiles ( template , arch . Files )
2017-12-17 15:50:09 -02:00
if err != nil {
2020-11-10 11:15:03 -03:00
return fmt . Errorf ( "failed to find files to archive: %w" , err )
2017-12-17 15:50:09 -02:00
}
for _ , f := range files {
2021-07-21 22:09:02 -03:00
if err = a . Add ( f ) ; err != nil {
return fmt . Errorf ( "failed to add: '%s' -> '%s': %w" , f . Source , f . Destination , err )
2017-07-13 19:46:48 -03:00
}
2017-12-17 15:50:09 -02:00
}
2021-10-08 21:25:53 -03:00
bins := [ ] string { }
2017-12-29 11:47:13 -02:00
for _ , binary := range binaries {
2021-07-21 22:09:02 -03:00
if err := a . Add ( config . File {
Source : binary . Path ,
Destination : binary . Name ,
} ) ; err != nil {
return fmt . Errorf ( "failed to add: '%s' -> '%s': %w" , binary . Path , binary . Name , err )
2017-07-13 19:46:48 -03:00
}
2021-10-08 21:25:53 -03:00
bins = append ( bins , binary . Name )
2017-05-11 00:05:51 -03:00
}
2019-08-12 17:44:48 -03:00
ctx . Artifacts . Add ( & artifact . Artifact {
2017-12-17 16:46:45 -02:00
Type : artifact . UploadableArchive ,
2017-12-17 15:50:09 -02:00
Name : folder + "." + format ,
Path : archivePath ,
2017-12-29 11:47:13 -02:00
Goos : binaries [ 0 ] . Goos ,
Goarch : binaries [ 0 ] . Goarch ,
Goarm : binaries [ 0 ] . Goarm ,
2020-02-05 22:08:18 -03:00
Gomips : binaries [ 0 ] . Gomips ,
2019-01-01 14:40:17 -02:00
Extra : map [ string ] interface { } {
2021-10-16 23:20:14 -03:00
artifact . ExtraBuilds : binaries ,
artifact . ExtraID : arch . ID ,
artifact . ExtraFormat : arch . Format ,
artifact . ExtraWrappedIn : wrap ,
artifact . ExtraBinaries : bins ,
2019-01-01 14:40:17 -02:00
} ,
2017-12-17 15:50:09 -02:00
} )
2017-01-14 12:51:09 -02:00
return nil
2016-12-28 22:53:56 -02:00
}
2018-11-13 13:48:16 -02:00
func wrapFolder ( a config . Archive ) string {
switch a . WrapInDirectory {
case "true" :
return a . NameTemplate
case "false" :
return ""
default :
return a . WrapInDirectory
}
}
2019-08-12 17:44:48 -03:00
func skip ( ctx * context . Context , archive config . Archive , binaries [ ] * artifact . Artifact ) error {
2017-12-29 11:47:13 -02:00
for _ , binary := range binaries {
2018-07-08 20:47:30 -07:00
name , err := tmpl . New ( ctx ) .
2019-04-16 10:19:15 -03:00
WithArtifact ( binary , archive . Replacements ) .
Apply ( archive . NameTemplate )
2017-12-17 18:01:58 -02:00
if err != nil {
return err
}
2021-10-16 22:46:11 -03:00
finalName := name + binary . ExtraOr ( artifact . ExtraExt , "" ) . ( string )
2021-10-16 23:03:06 -03:00
log . WithField ( "binary" , binary . Name ) .
2021-10-13 22:34:55 -03:00
WithField ( "name" , finalName ) .
Info ( "skip archiving" )
2019-08-12 17:44:48 -03:00
ctx . Artifacts . Add ( & artifact . Artifact {
2019-01-01 14:40:17 -02:00
Type : artifact . UploadableBinary ,
2021-10-13 22:34:55 -03:00
Name : finalName ,
2019-01-01 14:40:17 -02:00
Path : binary . Path ,
Goos : binary . Goos ,
Goarch : binary . Goarch ,
Goarm : binary . Goarm ,
2020-02-05 22:08:18 -03:00
Gomips : binary . Gomips ,
2019-01-01 14:40:17 -02:00
Extra : map [ string ] interface { } {
2021-10-16 23:20:14 -03:00
artifact . ExtraBuilds : [ ] * artifact . Artifact { binary } ,
artifact . ExtraID : archive . ID ,
artifact . ExtraFormat : archive . Format ,
artifact . ExtraBinary : binary . Name ,
2019-01-01 14:40:17 -02:00
} ,
} )
2017-07-03 00:57:39 -03:00
}
2017-06-05 16:38:59 +02:00
return nil
}
2021-07-21 22:09:02 -03:00
func findFiles ( template * tmpl . Template , files [ ] config . File ) ( [ ] config . File , error ) {
var result [ ] config . File
for _ , f := range files {
replaced , err := template . Apply ( f . Source )
2020-03-04 00:52:43 -03:00
if err != nil {
2021-07-21 22:09:02 -03:00
return result , fmt . Errorf ( "failed to apply template %s: %w" , f . Source , err )
2020-03-04 00:52:43 -03:00
}
2021-07-21 22:09:02 -03:00
2020-11-10 11:20:55 -03:00
files , err := fileglob . Glob ( replaced )
2017-05-11 00:05:51 -03:00
if err != nil {
2021-07-21 22:09:02 -03:00
return result , fmt . Errorf ( "globbing failed for pattern %s: %w" , f . Source , err )
}
for _ , file := range files {
result = append ( result , config . File {
Source : file ,
Destination : destinationFor ( f , file ) ,
Info : f . Info ,
} )
2017-05-11 00:05:51 -03:00
}
}
2021-07-21 22:09:02 -03:00
sort . Slice ( result , func ( i , j int ) bool {
return result [ i ] . Destination < result [ j ] . Destination
2018-01-21 12:59:15 -02:00
} )
2021-07-21 22:09:02 -03:00
return unique ( result ) , nil
}
// remove duplicates
func unique ( in [ ] config . File ) [ ] config . File {
var result [ ] config . File
exist := map [ string ] string { }
for _ , f := range in {
if current := exist [ f . Destination ] ; current != "" {
log . Warnf (
"file '%s' already exists in archive as '%s' - '%s' will be ignored" ,
f . Destination ,
current ,
f . Source ,
)
continue
}
exist [ f . Destination ] = f . Source
result = append ( result , f )
}
return result
}
func destinationFor ( f config . File , path string ) string {
if f . Destination == "" {
return path
}
if f . StripParent {
return filepath . Join ( f . Destination , filepath . Base ( path ) )
}
return filepath . Join ( f . Destination , path )
2017-05-11 00:05:51 -03:00
}
2017-10-02 18:43:03 +02:00
2019-04-16 10:19:15 -03:00
func packageFormat ( archive config . Archive , platform string ) string {
for _ , override := range archive . FormatOverrides {
2017-12-18 21:15:32 -02:00
if strings . HasPrefix ( platform , override . Goos ) {
return override . Format
}
}
2019-04-16 10:19:15 -03:00
return archive . Format
2017-12-18 21:15:32 -02:00
}
2018-11-07 14:15:51 -02:00
2018-11-11 19:02:45 -02:00
// NewEnhancedArchive enhances a pre-existing archive.Archive instance
// with this pipe specifics.
func NewEnhancedArchive ( a archive . Archive , wrap string ) archive . Archive {
return EnhancedArchive {
a : a ,
wrap : wrap ,
files : map [ string ] string { } ,
}
}
2018-11-07 14:15:51 -02:00
// EnhancedArchive is an archive.Archive implementation which decorates an
// archive.Archive adding wrap directory support, logging and windows
// backslash fixes.
type EnhancedArchive struct {
2018-11-11 19:02:45 -02:00
a archive . Archive
wrap string
files map [ string ] string
2018-11-07 14:15:51 -02:00
}
2020-05-26 00:48:10 -03:00
// Add adds a file.
2021-07-21 22:09:02 -03:00
func ( d EnhancedArchive ) Add ( f config . File ) error {
name := strings . ReplaceAll ( filepath . Join ( d . wrap , f . Destination ) , "\\" , "/" )
log . Debugf ( "adding file: %s as %s" , f . Source , name )
if _ , ok := d . files [ f . Destination ] ; ok {
return fmt . Errorf ( "file %s already exists in the archive" , f . Destination )
}
d . files [ f . Destination ] = name
ff := config . File {
Source : f . Source ,
Destination : name ,
Info : f . Info ,
2018-11-11 19:02:45 -02:00
}
2021-07-21 22:09:02 -03:00
return d . a . Add ( ff )
2018-11-07 14:15:51 -02:00
}
2020-05-26 00:48:10 -03:00
// Close closes the underlying archive.
2018-11-07 14:15:51 -02:00
func ( d EnhancedArchive ) Close ( ) error {
return d . a . Close ( )
}