diff --git a/config/config.go b/config/config.go index b90333a05..d294c3339 100644 --- a/config/config.go +++ b/config/config.go @@ -106,6 +106,7 @@ type Archive struct { Format string `yaml:",omitempty"` FormatOverrides []FormatOverride `yaml:"format_overrides,omitempty"` NameTemplate string `yaml:"name_template,omitempty"` + WrapInDirectory bool `yaml:"wrap_in_directory,omitempty"` Replacements map[string]string `yaml:",omitempty"` Files []string `yaml:",omitempty"` diff --git a/docs/060-archive.md b/docs/060-archive.md index 0221306f7..b53212d88 100644 --- a/docs/060-archive.md +++ b/docs/060-archive.md @@ -23,6 +23,13 @@ archive: # Default is `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}`. name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + # Set to true, if you want all files in the archive to be in a single directory. + # If set to true and you extract the archive 'goreleaser_Linux_arm64.tar.gz', + # you get a folder 'goreleaser_Linux_arm64'. + # If set to false, all files are extracted separately. + # Default is false. + wrap_in_directory: true + # Archive format. Valid options are `tar.gz`, `zip` and `binary`. # If format is `binary`, no archives are created and the binaries are instead uploaded directly. # In that case name_template and the below specified files are ignored. diff --git a/pipeline/archive/archive.go b/pipeline/archive/archive.go index b9a3fba7a..e6a3b0469 100644 --- a/pipeline/archive/archive.go +++ b/pipeline/archive/archive.go @@ -43,31 +43,39 @@ func (Pipe) Run(ctx *context.Context) error { func create(ctx *context.Context, platform string, groups map[string][]context.Binary) error { for folder, binaries := range groups { var format = archiveformat.For(ctx, platform) - targetFolder := filepath.Join(ctx.Config.Dist, folder+"."+format) - file, err := os.Create(targetFolder) + archivePath := filepath.Join(ctx.Config.Dist, folder+"."+format) + archiveFile, err := os.Create(archivePath) if err != nil { - return fmt.Errorf("failed to create directory %s: %s", targetFolder, err.Error()) + return fmt.Errorf("failed to create directory %s: %s", archivePath, err.Error()) } - defer func() { _ = file.Close() }() - log.WithField("archive", file.Name()).Info("creating") - var archive = archive.New(file) - defer func() { _ = archive.Close() }() + defer func() { + if e := archiveFile.Close(); e != nil { + log.WithField("archive", archivePath).Errorf("failed to close file: %v", e) + } + }() + log.WithField("archive", archivePath).Info("creating") + var a = archive.New(archiveFile) + defer func() { + if e := a.Close(); e != nil { + log.WithField("archive", archivePath).Errorf("failed to close archive: %v", e) + } + }() files, err := findFiles(ctx) if err != nil { return fmt.Errorf("failed to find files to archive: %s", err.Error()) } for _, f := range files { - if err = archive.Add(f, f); err != nil { + if err = a.Add(wrap(ctx, f, folder), f); err != nil { return fmt.Errorf("failed to add %s to the archive: %s", f, err.Error()) } } for _, binary := range binaries { - if err := archive.Add(binary.Name, binary.Path); err != nil { + if err := a.Add(wrap(ctx, binary.Name, folder), binary.Path); err != nil { return fmt.Errorf("failed to add %s -> %s to the archive: %s", binary.Path, binary.Name, err.Error()) } } - ctx.AddArtifact(file.Name()) + ctx.AddArtifact(archivePath) } return nil } @@ -92,3 +100,11 @@ func findFiles(ctx *context.Context) (result []string, err error) { } return } + +// Wrap archive files with folder if set in config. +func wrap(ctx *context.Context, name, folder string) string { + if ctx.Config.Archive.WrapInDirectory { + return filepath.Join(folder, name) + } + return name +} diff --git a/pipeline/archive/archive_test.go b/pipeline/archive/archive_test.go index d78dc2d4e..1a6acad3e 100644 --- a/pipeline/archive/archive_test.go +++ b/pipeline/archive/archive_test.go @@ -1,6 +1,9 @@ package archive import ( + "archive/tar" + "compress/gzip" + "io" "os" "path/filepath" "testing" @@ -52,6 +55,23 @@ func TestRunPipe(t *testing.T) { assert.NoError(t, Pipe{}.Run(ctx)) }) } + + // Check archive contents + f, err := os.Open(filepath.Join(dist, "mybin_darwin_amd64.tar.gz")) + assert.NoError(t, err) + defer func() { assert.NoError(t, f.Close()) }() + gr, err := gzip.NewReader(f) + assert.NoError(t, err) + defer func() { assert.NoError(t, gr.Close()) }() + r := tar.NewReader(gr) + for _, n := range []string{"README.md", "mybin"} { + h, err := r.Next() + if err == io.EOF { + break + } + assert.NoError(t, err) + assert.Equal(t, n, h.Name) + } } func TestRunPipeBinary(t *testing.T) { @@ -132,3 +152,46 @@ func TestRunPipeGlobFailsToAdd(t *testing.T) { ctx.AddBinary("windows386", "mybin", "mybin", "dist/mybin") assert.Error(t, Pipe{}.Run(ctx)) } + +func TestRunPipeWrap(t *testing.T) { + folder, back := testlib.Mktmp(t) + defer back() + var dist = filepath.Join(folder, "dist") + assert.NoError(t, os.Mkdir(dist, 0755)) + assert.NoError(t, os.Mkdir(filepath.Join(dist, "mybin_darwin_amd64"), 0755)) + _, err := os.Create(filepath.Join(dist, "mybin_darwin_amd64", "mybin")) + assert.NoError(t, err) + _, err = os.Create(filepath.Join(folder, "README.md")) + assert.NoError(t, err) + var ctx = &context.Context{ + Config: config.Project{ + Dist: dist, + Archive: config.Archive{ + WrapInDirectory: true, + Format: "tar.gz", + Files: []string{ + "README.*", + }, + }, + }, + } + ctx.AddBinary("darwinamd64", "mybin_darwin_amd64", "mybin", filepath.Join(dist, "mybin_darwin_amd64", "mybin")) + assert.NoError(t, Pipe{}.Run(ctx)) + + // Check archive contents + f, err := os.Open(filepath.Join(dist, "mybin_darwin_amd64.tar.gz")) + assert.NoError(t, err) + defer func() { assert.NoError(t, f.Close()) }() + gr, err := gzip.NewReader(f) + assert.NoError(t, err) + defer func() { assert.NoError(t, gr.Close()) }() + r := tar.NewReader(gr) + for _, n := range []string{"README.md", "mybin"} { + h, err := r.Next() + if err == io.EOF { + break + } + assert.NoError(t, err) + assert.Equal(t, filepath.Join("mybin_darwin_amd64", n), h.Name) + } +}