Repository: goreleaser/goreleaser
Pull request #959 (2019-02-07 13:22:49)

feat: support gzip format
refs  #958

codecov-io: # [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/959?src=pr&el=h1) Report
> Merging [#959](https://codecov.io/gh/goreleaser/goreleaser/pull/959?src=pr&el=desc) into [master](https://codecov.io/gh/goreleaser/goreleaser/commit/adcc4524da5e5223faabdc1b3fd78251a6ddae38?src=pr&el=desc) will **decrease** coverage by `0.2%`.
> The diff coverage is `78.57%`.

[![Impacted file tree graph](https://codecov.io/gh/goreleaser/goreleaser/pull/959/graphs/tree.svg?width=650&token=3bJvv6b7hN&height=150&src=pr)](https://codecov.io/gh/goreleaser/goreleaser/pull/959?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #959      +/-   ##
==========================================
- Coverage   88.43%   88.23%   -0.21%     
==========================================
  Files          51       52       +1     
  Lines        2465     2491      +26     
==========================================
+ Hits         2180     2198      +18     
- Misses        229      233       +4     
- Partials       56       60       +4
```


| [Impacted Files](https://codecov.io/gh/goreleaser/goreleaser/pull/959?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/archive/targz/targz.go](https://codecov.io/gh/goreleaser/goreleaser/pull/959/diff?src=pr&el=tree#diff-cGtnL2FyY2hpdmUvdGFyZ3ovdGFyZ3ouZ28=) | `71.42% <ø> (ø)` | |
| [pkg/archive/archive.go](https://codecov.io/gh/goreleaser/goreleaser/pull/959/diff?src=pr&el=tree#diff-cGtnL2FyY2hpdmUvYXJjaGl2ZS5nbw==) | `100% <100%> (ø)` | :arrow_up: |
| [pkg/archive/gzip/gzip.go](https://codecov.io/gh/goreleaser/goreleaser/pull/959/diff?src=pr&el=tree#diff-cGtnL2FyY2hpdmUvZ3ppcC9nemlwLmdv) | `72.72% <72.72%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/959?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/959?src=pr&el=footer). Last update [adcc452...38f1c08](https://codecov.io/gh/goreleaser/goreleaser/pull/959?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

frapposelli: wow, thanks for the super quick turnaround.

The patch works great when there's one single build, but when the builds are > 1 it tries to add the second binary to the same archive (instead of creating a new one) and fails:

```yaml
project_name: govmomi
builds:
- binary: govc
  main: ./govc/
[...]
- binary: vcsim
  main: ./vcsim/
[...]
archive:
  name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}'
  format: gz
  format_overrides:
    - goos: windows
      format: zip
  files:
  - none*
[...]
```

```console
govmomi · goreleaser-rehaul± ⟩ goreleaser --rm-dist --snapshot --skip-publish -p 24

   • releasing using goreleaser dev...
   • loading config file       file=.goreleaser.yml
   • RUNNING BEFORE HOOKS
   • GETTING AND VALIDATING GIT STATE
      • releasing v0.20.0, commit 2cce146648c5c7d1b85b76e48e74bc6068532f13
      • pipe skipped              error=disabled during snapshot mode
   • PARSING TAG
   • SETTING DEFAULTS
      • LOADING ENVIRONMENT VARIABLES
      • SNAPSHOTING
      • GITHUB RELEASES
      • PROJECT NAME
      • ARCHIVES
      • BUILDING BINARIES
         • you have more than 1 build setup: please make sure it is a not a typo on your config
      • LINUX PACKAGES WITH NFPM
      • SNAPCRAFT PACKAGES
      • CALCULATING CHECKSUMS
      • SIGNING ARTIFACTS
      • DOCKER IMAGES
      • ARTIFACTORY
      • S3
      • HOMEBREW TAP FORMULA
      • SCOOP MANIFEST
   • SNAPSHOTING
   • CHECKING ./DIST
      • --rm-dist is set, cleaning it up
   • WRITING EFFECTIVE CONFIG FILE
      • writing                   config=dist/config.yaml
   • GENERATING CHANGELOG
      • pipe skipped              error=not available for snapshots
   • LOADING ENVIRONMENT VARIABLES
      • pipe skipped              error=publishing is disabled
   • BUILDING BINARIES
      • building                  binary=dist/freebsd_arm_6/govc
      • building                  binary=dist/linux_amd64/govc
      • building                  binary=dist/linux_386/govc
      • building                  binary=dist/darwin_386/govc
      • building                  binary=dist/linux_arm64/govc
      • building                  binary=dist/darwin_amd64/govc
      • building                  binary=dist/linux_arm_6/govc
      • building                  binary=dist/windows_386/govc.exe
      • building                  binary=dist/windows_amd64/govc.exe
      • building                  binary=dist/freebsd_amd64/govc
      • building                  binary=dist/freebsd_386/govc
      • building                  binary=dist/freebsd_arm_6/vcsim
      • building                  binary=dist/linux_arm64/vcsim
      • building                  binary=dist/freebsd_386/vcsim
      • building                  binary=dist/windows_386/vcsim.exe
      • building                  binary=dist/freebsd_amd64/vcsim
      • building                  binary=dist/windows_amd64/vcsim.exe
      • building                  binary=dist/linux_amd64/vcsim
      • building                  binary=dist/linux_386/vcsim
      • building                  binary=dist/darwin_amd64/vcsim
      • building                  binary=dist/darwin_386/vcsim
      • building                  binary=dist/linux_arm_6/vcsim
   • ARCHIVES
      • creating                  archive=dist/govc_linux_arm64.gz
      • creating                  archive=dist/govc_darwin_386.gz
      • creating                  archive=dist/govc_freebsd_386.gz
      • creating                  archive=dist/govc_linux_386.gz
      • creating                  archive=dist/govc_darwin_amd64.gz
      • creating                  archive=dist/govc_freebsd_arm.gz
      • creating                  archive=dist/govc_windows_386.zip
      • creating                  archive=dist/govc_linux_amd64.gz
      • creating                  archive=dist/govc_windows_amd64.zip
      • creating                  archive=dist/govc_linux_arm.gz
      • creating                  archive=dist/govc_freebsd_amd64.gz
   ⨯ release failed after 10.60s error=failed to add dist/freebsd_arm_6/vcsim -> vcsim to the archive: gzip: failed to add vcsim, only one file can be archived in gz format
```
caarlos0: yeah, this will not be possible right now... the fix is big and will land on #705 

will add a note to the docs though
caarlos0: you may workaround it with multiple .goreleaser files... one for each build :) 
frapposelli: sounds good, thanks a ton!

Patch:
diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go
index 40a81693..2531a472 100644
--- a/pkg/archive/archive.go
+++ b/pkg/archive/archive.go
@@ -3,10 +3,10 @@ package archive
 
 import (
 	"os"
+	"strings"
 
-	"path/filepath"
-
-	"github.com/goreleaser/goreleaser/pkg/archive/tar"
+	"github.com/goreleaser/goreleaser/pkg/archive/gzip"
+	"github.com/goreleaser/goreleaser/pkg/archive/targz"
 	"github.com/goreleaser/goreleaser/pkg/archive/zip"
 )
 
@@ -16,12 +16,16 @@ type Archive interface {
 	Add(name, path string) error
 }
 
-// New archive
-// If the exentions of the target file is .zip, the archive will be in the zip
-// format, otherwise, it will be a tar.gz archive.
+// New archive.
 func New(file *os.File) Archive {
-	if filepath.Ext(file.Name()) == ".zip" {
+	if strings.HasSuffix(file.Name(), ".tar.gz") {
+		return targz.New(file)
+	}
+	if strings.HasSuffix(file.Name(), ".gz") {
+		return gzip.New(file)
+	}
+	if strings.HasSuffix(file.Name(), ".zip") {
 		return zip.New(file)
 	}
-	return tar.New(file)
+	return targz.New(file)
 }
diff --git a/pkg/archive/gzip/gzip.go b/pkg/archive/gzip/gzip.go
new file mode 100644
index 00000000..730df290
--- /dev/null
+++ b/pkg/archive/gzip/gzip.go
@@ -0,0 +1,51 @@
+// Package gzip implements the Archive interface providing gz archiving
+// and compression.
+package gzip
+
+import (
+	"compress/gzip"
+	"fmt"
+	"io"
+	"os"
+)
+
+// Archive as gz
+type Archive struct {
+	gw *gzip.Writer
+}
+
+// Close all closeables
+func (a Archive) Close() error {
+	return a.gw.Close()
+}
+
+// New gz archive
+func New(target io.Writer) Archive {
+	gw := gzip.NewWriter(target)
+	return Archive{
+		gw: gw,
+	}
+}
+
+// Add file to the archive
+func (a Archive) Add(name, path string) error {
+	if a.gw.Header.Name != "" {
+		return fmt.Errorf("gzip: failed to add %s, only one file can be archived in gz format", name)
+	}
+	file, err := os.Open(path) // #nosec
+	if err != nil {
+		return err
+	}
+	defer file.Close() // nolint: errcheck
+	info, err := file.Stat()
+	if err != nil {
+		return err
+	}
+	if info.IsDir() {
+		return nil
+	}
+	a.gw.Header.Name = name
+	a.gw.Header.ModTime = info.ModTime()
+	_, err = io.Copy(a.gw, file)
+	return err
+}
diff --git a/pkg/archive/tar/tar.go b/pkg/archive/targz/targz.go
similarity index 91%
rename from pkg/archive/tar/tar.go
rename to pkg/archive/targz/targz.go
index 2d2244a9..afc837be 100644
--- a/pkg/archive/tar/tar.go
+++ b/pkg/archive/targz/targz.go
@@ -1,6 +1,6 @@
-// Package tar implements the Archive interface providing tar.gz archiving
+// Package targz implements the Archive interface providing tar.gz archiving
 // and compression.
-package tar
+package targz
 
 import (
 	"archive/tar"
diff --git a/www/content/archive.md b/www/content/archive.md
index 87cd4ae0..72ff18e9 100644
--- a/www/content/archive.md
+++ b/www/content/archive.md
@@ -16,7 +16,7 @@ Here is a commented `archive` section with all fields specified:
 archive:
   # Archive name template.
   # Defaults:
-  # - if format is `tar.gz` or `zip`:
+  # - if format is `tar.gz`, `gz` or `zip`:
   #   - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}`
   # - if format is `binary`:
   #   - `{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}`
@@ -40,7 +40,7 @@ archive:
   # Default is false.
   wrap_in_directory: true
 
-  # Archive format. Valid options are `tar.gz`, `zip` and `binary`.
+  # Archive format. Valid options are `tar.gz`, `gz`, `zip` and `binary`.
   # If format is `binary`, no archives are created and the binaries are instead
   # uploaded directly.
   # Default is `tar.gz`.
@@ -90,3 +90,25 @@ have any files matching that glob, only the binary will be added to the
 archive.
 
 For more information, check [#602](https://github.com/goreleaser/goreleaser/issues/602)
+
+## A note about Gzip
+
+Gzip is a compression-only format, therefore, it couldn't have more than one
+file inside.
+
+Presumably, you'll want that file to be the binary, so, your archive section
+will probably look like this:
+
+```yaml
+# goreleaser.yml
+archive:
+  format: gz
+  files:
+  - none*
+```
+
+This should create `.gz` files with the binaries only, which should be
+extracted with something like `gzip -d file.gz`.
+
+Multiple builds will also not work in this case and will be handled on
+[#705](https://github.com/goreleaser/goreleaser/issues/705).


Test patch:
diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go
index 181ba404..342c9f7c 100644
--- a/pkg/archive/archive_test.go
+++ b/pkg/archive/archive_test.go
@@ -16,14 +16,14 @@ func TestArchive(t *testing.T) {
 	assert.NoError(err)
 	assert.NoError(os.Mkdir(folder+"/folder-inside", 0755))
 
-	for _, archive := range []Archive{
-		newArchive(folder, "tar.gz", t),
-		newArchive(folder, "zip", t),
-	} {
-		assert.NoError(archive.Add("empty.txt", empty.Name()))
-		assert.NoError(archive.Add("empty.txt", folder+"/folder-inside"))
-		assert.Error(archive.Add("dont.txt", empty.Name()+"_nope"))
-		assert.NoError(archive.Close())
+	for _, format := range []string{"tar.gz", "zip", "gz", "willbeatargzanyway"} {
+		format := format
+		t.Run(format, func(t *testing.T) {
+			var archive = newArchive(folder, format, t)
+			assert.NoError(archive.Add("empty.txt", empty.Name()))
+			assert.Error(archive.Add("dont.txt", empty.Name()+"_nope"))
+			assert.NoError(archive.Close())
+		})
 	}
 }
 
diff --git a/pkg/archive/gzip/gzip_test.go b/pkg/archive/gzip/gzip_test.go
new file mode 100644
index 00000000..ca3a4e69
--- /dev/null
+++ b/pkg/archive/gzip/gzip_test.go
@@ -0,0 +1,46 @@
+package gzip
+
+import (
+	"compress/gzip"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGzFile(t *testing.T) {
+	var assert = assert.New(t)
+	tmp, err := ioutil.TempDir("", "")
+	assert.NoError(err)
+	f, err := os.Create(filepath.Join(tmp, "test.gz"))
+	assert.NoError(err)
+	defer f.Close() // nolint: errcheck
+	archive := New(f)
+
+	assert.NoError(archive.Add("sub1/sub2/subfoo.txt", "../testdata/sub1/sub2/subfoo.txt"))
+	assert.EqualError(archive.Add("foo.txt", "../testdata/foo.txt"), "gzip: failed to add foo.txt, only one file can be archived in gz format")
+	assert.NoError(archive.Close())
+
+	assert.NoError(f.Close())
+
+	t.Log(f.Name())
+	f, err = os.Open(f.Name())
+	assert.NoError(err)
+	defer f.Close() // nolint: errcheck
+
+	info, err := f.Stat()
+	assert.NoError(err)
+	assert.Truef(info.Size() < 500, "archived file should be smaller than %d", info.Size())
+
+	gzf, err := gzip.NewReader(f)
+	assert.NoError(err)
+	defer gzf.Close() // nolint: errcheck
+
+	assert.Equal("sub1/sub2/subfoo.txt", gzf.Name)
+
+	bts, err := ioutil.ReadAll(gzf)
+	assert.NoError(err)
+	assert.Equal("sub\n", string(bts))
+}
diff --git a/pkg/archive/tar/tar_test.go b/pkg/archive/targz/targz_test.go
similarity index 92%
rename from pkg/archive/tar/tar_test.go
rename to pkg/archive/targz/targz_test.go
index 094c2982..4f2b9fe4 100644
--- a/pkg/archive/tar/tar_test.go
+++ b/pkg/archive/targz/targz_test.go
@@ -1,4 +1,4 @@
-package tar
+package targz
 
 import (
 	"archive/tar"
@@ -36,11 +36,16 @@ func TestTarGzFile(t *testing.T) {
 	t.Log(f.Name())
 	f, err = os.Open(f.Name())
 	assert.NoError(err)
+	defer f.Close() // nolint: errcheck
+
 	info, err := f.Stat()
 	assert.NoError(err)
 	assert.Truef(info.Size() < 500, "archived file should be smaller than %d", info.Size())
+
 	gzf, err := gzip.NewReader(f)
 	assert.NoError(err)
+	defer gzf.Close() // nolint: errcheck
+
 	var paths []string
 	r := tar.NewReader(gzf)
 	for {
@@ -52,7 +57,7 @@ func TestTarGzFile(t *testing.T) {
 		paths = append(paths, next.Name)
 		t.Logf("%s: %v", next.Name, next.FileInfo().Mode())
 		if next.Name == "sub1/executable" {
-			var ex os.FileMode = next.FileInfo().Mode() | 0111
+			var ex = next.FileInfo().Mode() | 0111
 			assert.Equal(next.FileInfo().Mode().String(), ex.String())
 		}
 	}
diff --git a/pkg/archive/zip/zip_test.go b/pkg/archive/zip/zip_test.go
index f1bb9c18..7bf41ea3 100644
--- a/pkg/archive/zip/zip_test.go
+++ b/pkg/archive/zip/zip_test.go
@@ -36,17 +36,21 @@ func TestZipFile(t *testing.T) {
 	t.Log(f.Name())
 	f, err = os.Open(f.Name())
 	assert.NoError(err)
+	defer f.Close() // nolint: errcheck
+
 	info, err := f.Stat()
 	assert.NoError(err)
 	assert.Truef(info.Size() < 900, "archived file should be smaller than %d", info.Size())
+
 	r, err := zip.NewReader(f, info.Size())
 	assert.NoError(err)
+
 	var paths = make([]string, len(r.File))
 	for i, zf := range r.File {
 		paths[i] = zf.Name
 		t.Logf("%s: %v", zf.Name, zf.Mode())
 		if zf.Name == "sub1/executable" {
-			var ex os.FileMode = zf.Mode() | 0111
+			var ex = zf.Mode() | 0111
 			assert.Equal(zf.Mode().String(), ex.String())
 		}
 	}