2017-04-14 20:39:32 +02: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 23:47:15 +02:00
package archive
2016-12-29 02:23:39 +02:00
2016-12-29 02:53:56 +02:00
import (
2020-10-10 13:46:16 +02:00
"errors"
2017-09-09 11:10:08 +02:00
"fmt"
2016-12-29 22:10:11 +02:00
"os"
2017-07-14 01:22:10 +02:00
"path/filepath"
2017-12-17 22:01:58 +02:00
"strings"
2018-11-11 16:11:03 +02:00
"sync"
2016-12-29 22:10:11 +02:00
2017-06-22 05:09:14 +02:00
"github.com/apex/log"
2018-01-21 16:59:15 +02:00
"github.com/campoy/unique"
2020-11-10 16:20:55 +02:00
"github.com/goreleaser/fileglob"
2019-12-29 20:02:15 +02:00
2017-12-17 19:50:09 +02:00
"github.com/goreleaser/goreleaser/internal/artifact"
2019-05-07 12:18:35 +02:00
"github.com/goreleaser/goreleaser/internal/ids"
2019-04-16 15:19:15 +02:00
"github.com/goreleaser/goreleaser/internal/semerrgroup"
2018-07-09 05:47:30 +02:00
"github.com/goreleaser/goreleaser/internal/tmpl"
2018-08-05 15:23:39 +02:00
"github.com/goreleaser/goreleaser/pkg/archive"
2018-11-13 17:48:16 +02:00
"github.com/goreleaser/goreleaser/pkg/config"
2018-08-15 04:50:20 +02:00
"github.com/goreleaser/goreleaser/pkg/context"
2016-12-29 02:53:56 +02:00
)
2016-12-29 02:23:39 +02:00
2017-12-27 01:19:58 +02:00
const (
2020-02-06 03:08:18 +02: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-27 01:19:58 +02:00
)
2017-12-19 01:32:31 +02:00
2020-10-10 13:46:16 +02: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 22:17:44 +02:00
// nolint: gochecknoglobals
var lock sync . Mutex
2020-05-26 05:48:10 +02:00
// Pipe for archive.
2018-11-11 22:17:44 +02:00
type Pipe struct { }
2016-12-30 13:27:35 +02:00
2017-12-02 23:53:19 +02:00
func ( Pipe ) String ( ) string {
2018-10-26 22:27:41 +02:00
return "archives"
2016-12-30 13:27:35 +02:00
}
2020-05-26 05:48:10 +02:00
// Default sets the pipe defaults.
2017-12-02 23:53:19 +02:00
func ( Pipe ) Default ( ctx * context . Context ) error {
2019-07-27 15:55:56 +02:00
var ids = ids . New ( "archives" )
2019-04-16 15:19:15 +02:00
if len ( ctx . Config . Archives ) == 0 {
2019-12-29 20:02:15 +02:00
ctx . Config . Archives = append ( ctx . Config . Archives , config . Archive { } )
2017-12-02 23:53:19 +02:00
}
2019-04-16 15:19:15 +02:00
for i := range ctx . Config . Archives {
var archive = & ctx . Config . Archives [ i ]
if archive . Format == "" {
archive . Format = "tar.gz"
}
if archive . ID == "" {
archive . ID = "default"
}
if len ( archive . Files ) == 0 {
archive . Files = [ ] string {
"licence*" ,
"LICENCE*" ,
"license*" ,
"LICENSE*" ,
"readme*" ,
"README*" ,
"changelog*" ,
"CHANGELOG*" ,
}
}
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 12:18:35 +02:00
ids . Inc ( archive . ID )
2017-12-27 01:19:58 +02:00
}
2019-05-07 12:18:35 +02:00
return ids . Validate ( )
2017-12-02 23:53:19 +02:00
}
2020-05-26 05:48:10 +02:00
// Run the pipe.
2018-11-11 22:17:44 +02:00
func ( Pipe ) Run ( ctx * context . Context ) error {
2019-04-16 15:19:15 +02:00
var g = semerrgroup . New ( ctx . Parallelism )
2020-10-10 13:46:16 +02:00
for i , archive := range ctx . Config . Archives {
2019-04-16 15:19:15 +02:00
archive := archive
2020-10-10 13:46:16 +02:00
var artifacts = ctx . Artifacts . Filter (
2019-04-16 15:19:15 +02:00
artifact . And (
artifact . ByType ( artifact . Binary ) ,
artifact . ByIDs ( archive . Builds ... ) ,
) ,
2020-10-10 13:46:16 +02:00
) . GroupByPlatform ( )
2020-10-13 22:57:08 +02:00
if err := checkArtifacts ( artifacts ) ; err != nil && ! archive . AllowDifferentBinaryCount {
2020-10-10 13:46:16 +02:00
return fmt . Errorf ( "invalid archive: %d: %w" , i , ErrArchiveDifferentBinaryCount )
}
for group , artifacts := range artifacts {
2019-04-16 15:19:15 +02: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-27 01:19:58 +02:00
}
return g . Wait ( )
}
2020-10-10 13:46:16 +02:00
func checkArtifacts ( artifacts map [ string ] [ ] * artifact . Artifact ) error {
var lens = map [ int ] bool { }
for _ , v := range artifacts {
lens [ len ( v ) ] = true
}
2020-10-11 18:00:42 +02:00
if len ( lens ) <= 1 {
2020-10-10 13:46:16 +02:00
return nil
}
return ErrArchiveDifferentBinaryCount
}
2020-05-26 05:48:10 +02:00
func create ( ctx * context . Context , arch config . Archive , binaries [ ] * artifact . Artifact ) error {
var format = packageFormat ( arch , binaries [ 0 ] . Goos )
2018-07-09 05:47:30 +02:00
folder , err := tmpl . New ( ctx ) .
2020-05-26 05:48:10 +02:00
WithArtifact ( binaries [ 0 ] , arch . Replacements ) .
Apply ( arch . NameTemplate )
2017-12-17 19:50:09 +02:00
if err != nil {
return err
}
archivePath := filepath . Join ( ctx . Config . Dist , folder + "." + format )
2018-11-11 22:17:44 +02:00
lock . Lock ( )
2020-03-28 20:40:16 +02:00
if err := os . MkdirAll ( filepath . Dir ( archivePath ) , 0755 | os . ModeDir ) ; err != nil {
lock . Unlock ( )
return err
}
2018-11-13 17:48:16 +02:00
if _ , err = os . Stat ( archivePath ) ; ! os . IsNotExist ( err ) {
2018-11-11 22:17:44 +02:00
lock . Unlock ( )
2018-11-11 16:11:03 +02:00
return fmt . Errorf ( "archive named %s already exists. Check your archive name template" , archivePath )
}
2017-12-17 19:50:09 +02:00
archiveFile , err := os . Create ( archivePath )
if err != nil {
2018-11-11 22:17:44 +02:00
lock . Unlock ( )
2020-11-10 16:15:03 +02:00
return fmt . Errorf ( "failed to create directory %s: %w" , archivePath , err )
2017-12-17 19:50:09 +02:00
}
2018-11-11 22:17:44 +02:00
lock . Unlock ( )
2020-05-26 05:48:10 +02:00
defer archiveFile . Close ( )
2018-11-13 17:48:16 +02:00
2018-10-10 17:49:50 +02:00
var log = log . WithField ( "archive" , archivePath )
2018-10-27 00:31:06 +02:00
log . Info ( "creating" )
2018-11-13 17:48:16 +02:00
2020-03-04 05:52:43 +02:00
template := tmpl . New ( ctx ) .
2020-05-26 05:48:10 +02:00
WithArtifact ( binaries [ 0 ] , arch . Replacements )
wrap , err := template . Apply ( wrapFolder ( arch ) )
2018-11-13 17:48:16 +02:00
if err != nil {
return err
2018-11-07 18:15:51 +02:00
}
2018-11-13 17:48:16 +02:00
2020-05-26 05:48:10 +02:00
var a = NewEnhancedArchive ( archive . New ( archiveFile ) , wrap )
defer a . Close ( )
2017-12-17 19:50:09 +02:00
2020-05-26 05:48:10 +02:00
files , err := findFiles ( template , arch )
2017-12-17 19:50:09 +02:00
if err != nil {
2020-11-10 16:15:03 +02:00
return fmt . Errorf ( "failed to find files to archive: %w" , err )
2017-12-17 19:50:09 +02:00
}
for _ , f := range files {
2018-11-07 18:15:51 +02:00
if err = a . Add ( f , f ) ; err != nil {
2020-11-10 16:15:03 +02:00
return fmt . Errorf ( "failed to add %s to the archive: %w" , f , err )
2017-07-14 00:46:48 +02:00
}
2017-12-17 19:50:09 +02:00
}
2017-12-29 15:47:13 +02:00
for _ , binary := range binaries {
2018-11-07 18:15:51 +02:00
if err := a . Add ( binary . Name , binary . Path ) ; err != nil {
2020-11-10 16:15:03 +02:00
return fmt . Errorf ( "failed to add %s -> %s to the archive: %w" , binary . Path , binary . Name , err )
2017-07-14 00:46:48 +02:00
}
2017-05-11 05:05:51 +02:00
}
2019-08-12 22:44:48 +02:00
ctx . Artifacts . Add ( & artifact . Artifact {
2017-12-17 20:46:45 +02:00
Type : artifact . UploadableArchive ,
2017-12-17 19:50:09 +02:00
Name : folder + "." + format ,
Path : archivePath ,
2017-12-29 15:47:13 +02:00
Goos : binaries [ 0 ] . Goos ,
Goarch : binaries [ 0 ] . Goarch ,
Goarm : binaries [ 0 ] . Goarm ,
2020-02-06 03:08:18 +02:00
Gomips : binaries [ 0 ] . Gomips ,
2019-01-01 18:40:17 +02:00
Extra : map [ string ] interface { } {
2020-04-21 21:11:18 +02:00
"Builds" : binaries ,
2020-05-26 05:48:10 +02:00
"ID" : arch . ID ,
"Format" : arch . Format ,
2020-04-21 21:11:18 +02:00
"WrappedIn" : wrap ,
2019-01-01 18:40:17 +02:00
} ,
2017-12-17 19:50:09 +02:00
} )
2017-01-14 16:51:09 +02:00
return nil
2016-12-29 02:53:56 +02:00
}
2018-11-13 17: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 22:44:48 +02:00
func skip ( ctx * context . Context , archive config . Archive , binaries [ ] * artifact . Artifact ) error {
2017-12-29 15:47:13 +02:00
for _ , binary := range binaries {
log . WithField ( "binary" , binary . Name ) . Info ( "skip archiving" )
2018-07-09 05:47:30 +02:00
name , err := tmpl . New ( ctx ) .
2019-04-16 15:19:15 +02:00
WithArtifact ( binary , archive . Replacements ) .
Apply ( archive . NameTemplate )
2017-12-17 22:01:58 +02:00
if err != nil {
return err
}
2019-08-12 22:44:48 +02:00
ctx . Artifacts . Add ( & artifact . Artifact {
2019-01-01 18:40:17 +02:00
Type : artifact . UploadableBinary ,
Name : name + binary . ExtraOr ( "Ext" , "" ) . ( string ) ,
Path : binary . Path ,
Goos : binary . Goos ,
Goarch : binary . Goarch ,
Goarm : binary . Goarm ,
2020-02-06 03:08:18 +02:00
Gomips : binary . Gomips ,
2019-01-01 18:40:17 +02:00
Extra : map [ string ] interface { } {
2019-08-12 22:44:48 +02:00
"Builds" : [ ] * artifact . Artifact { binary } ,
2019-04-16 15:19:15 +02:00
"ID" : archive . ID ,
2019-06-10 15:35:19 +02:00
"Format" : archive . Format ,
2019-01-01 18:40:17 +02:00
} ,
} )
2017-07-03 05:57:39 +02:00
}
2017-06-05 16:38:59 +02:00
return nil
}
2020-03-04 05:52:43 +02:00
func findFiles ( template * tmpl . Template , archive config . Archive ) ( result [ ] string , err error ) {
2019-04-16 15:19:15 +02:00
for _ , glob := range archive . Files {
2020-03-04 05:52:43 +02:00
replaced , err := template . Apply ( glob )
if err != nil {
2020-09-21 19:47:51 +02:00
return result , fmt . Errorf ( "failed to apply template %s: %w" , glob , err )
2020-03-04 05:52:43 +02:00
}
2020-11-10 16:20:55 +02:00
files , err := fileglob . Glob ( replaced )
2017-05-11 05:05:51 +02:00
if err != nil {
2020-09-21 19:47:51 +02:00
return result , fmt . Errorf ( "globbing failed for pattern %s: %w" , glob , err )
2017-05-11 05:05:51 +02:00
}
result = append ( result , files ... )
}
2018-01-21 16:59:15 +02:00
// remove duplicates
unique . Slice ( & result , func ( i , j int ) bool {
return strings . Compare ( result [ i ] , result [ j ] ) < 0
} )
2017-05-11 05:05:51 +02:00
return
}
2017-10-02 18:43:03 +02:00
2019-04-16 15:19:15 +02:00
func packageFormat ( archive config . Archive , platform string ) string {
for _ , override := range archive . FormatOverrides {
2017-12-19 01:15:32 +02:00
if strings . HasPrefix ( platform , override . Goos ) {
return override . Format
}
}
2019-04-16 15:19:15 +02:00
return archive . Format
2017-12-19 01:15:32 +02:00
}
2018-11-07 18:15:51 +02:00
2018-11-11 23: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 18: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 23:02:45 +02:00
a archive . Archive
wrap string
files map [ string ] string
2018-11-07 18:15:51 +02:00
}
2020-05-26 05:48:10 +02:00
// Add adds a file.
2018-11-07 18:15:51 +02:00
func ( d EnhancedArchive ) Add ( name , path string ) error {
2020-09-21 15:13:03 +02:00
name = strings . ReplaceAll ( filepath . Join ( d . wrap , name ) , "\\" , "/" )
2018-11-07 18:15:51 +02:00
log . Debugf ( "adding file: %s as %s" , path , name )
2018-11-11 23:02:45 +02:00
if _ , ok := d . files [ name ] ; ok {
return fmt . Errorf ( "file %s already exists in the archive" , name )
}
d . files [ name ] = path
2018-11-07 18:15:51 +02:00
return d . a . Add ( name , path )
}
2020-05-26 05:48:10 +02:00
// Close closes the underlying archive.
2018-11-07 18:15:51 +02:00
func ( d EnhancedArchive ) Close ( ) error {
return d . a . Close ( )
}