Repository: goreleaser/nfpm
Pull request #111 (2019-12-05 13:52:59)

feat: version release/prerelease options
problem to solve:
- rpm and deb have quite different versioning scheme for releases / prereleases:
  https://fedoraproject.org/wiki/Package_Versioning_Examples#Complex_versioning_examples
  https://www.debian.org/doc/manuals/maint-guide/first.en.html#namever
- it is impossible right now to use `release` version for nfpm deb package (because `release` part is actually used as prerelease)
 
changes:

- added `prerelease` option to specify it separately from `release`
- for deb: `prerelease` comes after tilde, `release` after dash (breaking!)
- for rpm: `prerelease` comes with default `0.1` release, `release` is not changed
- semver parsing now treats numeric `prerelease` data as release version
- semver parsing now treats non-numeric `prerelease` data as prerelease version


codecov-io: # [Codecov](https://codecov.io/gh/goreleaser/nfpm/pull/111?src=pr&el=h1) Report
> Merging [#111](https://codecov.io/gh/goreleaser/nfpm/pull/111?src=pr&el=desc) into [master](https://codecov.io/gh/goreleaser/nfpm/commit/6ef61041ebd2771f37a5ccd21202845876ffb8a9?src=pr&el=desc) will **increase** coverage by `0.47%`.
> The diff coverage is `100%`.

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

```diff
@@            Coverage Diff             @@
##           master     #111      +/-   ##
==========================================
+ Coverage   70.46%   70.94%   +0.47%     
==========================================
  Files           5        6       +1     
  Lines         491      499       +8     
==========================================
+ Hits          346      354       +8     
  Misses         91       91              
  Partials       54       54
```


| [Impacted Files](https://codecov.io/gh/goreleaser/nfpm/pull/111?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [deb/deb.go](https://codecov.io/gh/goreleaser/nfpm/pull/111/diff?src=pr&el=tree#diff-ZGViL2RlYi5nbw==) | `71.78% <ø> (ø)` | :arrow_up: |
| [nfpm.go](https://codecov.io/gh/goreleaser/nfpm/pull/111/diff?src=pr&el=tree#diff-bmZwbS5nbw==) | `90.62% <100%> (+0.3%)` | :arrow_up: |
| [internal/helpers/helpers.go](https://codecov.io/gh/goreleaser/nfpm/pull/111/diff?src=pr&el=tree#diff-aW50ZXJuYWwvaGVscGVycy9oZWxwZXJzLmdv) | `100% <100%> (ø)` | |
| [rpm/rpm.go](https://codecov.io/gh/goreleaser/nfpm/pull/111/diff?src=pr&el=tree#diff-cnBtL3JwbS5nbw==) | `71.69% <100%> (+0.54%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/nfpm/pull/111?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/nfpm/pull/111?src=pr&el=footer). Last update [6ef6104...2d13f2d](https://codecov.io/gh/goreleaser/nfpm/pull/111?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/deb/deb.go b/deb/deb.go
index dee3b4b..d9ab8e8 100644
--- a/deb/deb.go
+++ b/deb/deb.go
@@ -345,7 +345,8 @@ const controlTemplate = `
 {{- /* Mandatory fields */ -}}
 Package: {{.Info.Name}}
 Version: {{ if .Info.Epoch}}{{ .Info.Epoch }}:{{ end }}{{.Info.Version}}
-         {{- if .Info.Release}}~{{ .Info.Release }}{{- end }}
+         {{- if .Info.Release}}-{{ .Info.Release }}{{- end }}
+         {{- if .Info.Prerelease}}~{{ .Info.Prerelease }}{{- end }}
          {{- if .Info.Deb.VersionMetadata}}+{{ .Info.Deb.VersionMetadata }}{{- end }}
 Section: {{.Info.Section}}
 Priority: {{.Info.Priority}}
diff --git a/go.sum b/go.sum
index c99823d..a6d3090 100644
--- a/go.sum
+++ b/go.sum
@@ -22,6 +22,7 @@ github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
 github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -48,9 +49,12 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
 golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go
new file mode 100644
index 0000000..7444507
--- /dev/null
+++ b/internal/helpers/helpers.go
@@ -0,0 +1,10 @@
+// Package helpers provides common helper methods
+package helpers
+
+import "strconv"
+
+// IsInt returns true if s is int, false otherwise
+func IsInt(s string) bool {
+	_, err := strconv.Atoi(s)
+	return err == nil
+}
diff --git a/nfpm.go b/nfpm.go
index a3033a1..34f8922 100644
--- a/nfpm.go
+++ b/nfpm.go
@@ -9,6 +9,7 @@ import (
 	"sync"
 
 	"github.com/Masterminds/semver/v3"
+	"github.com/goreleaser/nfpm/internal/helpers"
 	"github.com/imdario/mergo"
 	"github.com/pkg/errors"
 
@@ -111,6 +112,7 @@ type Info struct {
 	Epoch        string `yaml:"epoch,omitempty"`
 	Version      string `yaml:"version,omitempty"`
 	Release      string `yaml:"release,omitempty"`
+	Prerelease   string `yaml:"prerelease,omitempty"`
 	Section      string `yaml:"section,omitempty"`
 	Priority     string `yaml:"priority,omitempty"`
 	Maintainer   string `yaml:"maintainer,omitempty"`
@@ -196,11 +198,14 @@ func WithDefaults(info *Info) *Info {
 	// and support proper ordering for both rpm and deb
 	if v, err := semver.NewVersion(info.Version); err == nil {
 		info.Version = fmt.Sprintf("%d.%d.%d", v.Major(), v.Minor(), v.Patch())
-		if info.Release == "" {
-			// maybe we should concat the previous info.Release with v.Prerelease?
+		if info.Release == "" && helpers.IsInt(v.Prerelease()) {
 			info.Release = v.Prerelease()
 		}
+		if info.Prerelease == "" && !helpers.IsInt(v.Prerelease()) {
+			info.Prerelease = v.Prerelease()
+		}
 		info.Deb.VersionMetadata = v.Metadata()
 	}
+
 	return info
 }
diff --git a/rpm/rpm.go b/rpm/rpm.go
index 11e9b85..4fd466e 100644
--- a/rpm/rpm.go
+++ b/rpm/rpm.go
@@ -114,6 +114,11 @@ func buildRPMMeta(info *nfpm.Info) (*rpmpack.RPMMetaData, error) {
 		return nil, err
 	}
 
+	var release = defaultTo(info.Release, "1")
+	if info.Prerelease != "" {
+		release = fmt.Sprintf("%s.%s", defaultTo(info.Release, "0.1"), info.Prerelease)
+	}
+
 	return &rpmpack.RPMMetaData{
 		Name:        info.Name,
 		Summary:     strings.Split(info.Description, "\n")[0],


Test patch:
diff --git a/acceptance/testdata/rpm.env-var-version.dockerfile b/acceptance/testdata/rpm.env-var-version.dockerfile
index bdf9341..c58d877 100644
--- a/acceptance/testdata/rpm.env-var-version.dockerfile
+++ b/acceptance/testdata/rpm.env-var-version.dockerfile
@@ -2,7 +2,7 @@ FROM fedora
 ARG package
 COPY ${package} /tmp/foo.rpm
 ENV EXPECTVER="Version : 1.0.0" \
-    EXPECTREL="Release : 0.1.b1"
+    EXPECTREL="Release : 0.1.0.1.b1"
 RUN rpm -qpi /tmp/foo.rpm | sed -e 's/ \+/ /g' | grep "Version" > found.ver
 RUN rpm -qpi /tmp/foo.rpm | sed -e 's/ \+/ /g' | grep "Release" > found.rel
 RUN export FOUND_VER="$(cat found.ver)" && \
diff --git a/deb/deb_test.go b/deb/deb_test.go
index 535bf1e..aa2c819 100644
--- a/deb/deb_test.go
+++ b/deb/deb_test.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"strconv"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -75,6 +76,15 @@ func TestDeb(t *testing.T) {
 	}
 }
 
+func extractDebVersion(deb *bytes.Buffer) string {
+	for _, s := range strings.Split(deb.String(), "\n") {
+		if strings.Contains(s, "Version: ") {
+			return strings.TrimPrefix(s, "Version: ")
+		}
+	}
+	return ""
+}
+
 func TestDebVersionWithDash(t *testing.T) {
 	info := exampleInfo()
 	info.Version = "1.0.0-beta"
@@ -82,6 +92,50 @@ func TestDebVersionWithDash(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+func TestDebVersion(t *testing.T) {
+	info := exampleInfo()
+	info.Version = "1.0.0" //nolint:golint,goconst
+	var buf bytes.Buffer
+	var err = writeControl(&buf, controlData{info, 0})
+	assert.NoError(t, err)
+	var v = extractDebVersion(&buf)
+	assert.Equal(t, "1.0.0", v)
+}
+
+func TestDebVersionWithRelease(t *testing.T) {
+	info := exampleInfo()
+	info.Version = "1.0.0" //nolint:golint,goconst
+	info.Release = "1"
+	var buf bytes.Buffer
+	var err = writeControl(&buf, controlData{info, 0})
+	assert.NoError(t, err)
+	var v = extractDebVersion(&buf)
+	assert.Equal(t, "1.0.0-1", v)
+}
+
+func TestDebVersionWithPrerelease(t *testing.T) {
+	info := exampleInfo()
+	info.Version = "1.0.0" //nolint:golint,goconst
+	info.Prerelease = "1"
+	var buf bytes.Buffer
+	var err = writeControl(&buf, controlData{info, 0})
+	assert.NoError(t, err)
+	var v = extractDebVersion(&buf)
+	assert.Equal(t, "1.0.0~1", v)
+}
+
+func TestDebVersionWithReleaseAndPrerelease(t *testing.T) {
+	info := exampleInfo()
+	info.Version = "1.0.0" //nolint:golint,goconst
+	info.Release = "2"
+	info.Prerelease = "rc1"
+	var buf bytes.Buffer
+	var err = writeControl(&buf, controlData{info, 0})
+	assert.NoError(t, err)
+	var v = extractDebVersion(&buf)
+	assert.Equal(t, "1.0.0-2~rc1", v)
+}
+
 func TestControl(t *testing.T) {
 	var w bytes.Buffer
 	assert.NoError(t, writeControl(&w, controlData{
diff --git a/nfpm_test.go b/nfpm_test.go
index f04604a..4bb51d5 100644
--- a/nfpm_test.go
+++ b/nfpm_test.go
@@ -35,25 +35,51 @@ func TestGet(t *testing.T) {
 
 func TestDefaultsVersion(t *testing.T) {
 	info := &Info{
-		Version: "v2.4.1-beta3",
+		Version: "v1.0.0",
 	}
 	info = WithDefaults(info)
 	assert.NotEmpty(t, info.Bindir)
 	assert.NotEmpty(t, info.Platform)
-	assert.Equal(t, "2.4.1", info.Version)
-	assert.Equal(t, "beta3", info.Release)
-}
+	assert.Equal(t, "1.0.0", info.Version)
+	assert.Equal(t, "", info.Release)
+	assert.Equal(t, "", info.Prerelease)
 
-func TestDefaultsVersionWithRelease(t *testing.T) {
-	info := &Info{
-		Version: "v2.4.1-beta3",
-		Release: "4",
+	info = &Info{
+		Version: "v1.0.0-rc1",
 	}
 	info = WithDefaults(info)
-	assert.NotEmpty(t, info.Bindir)
-	assert.NotEmpty(t, info.Platform)
-	assert.Equal(t, "2.4.1", info.Version)
-	assert.Equal(t, "4", info.Release)
+	assert.Equal(t, "1.0.0", info.Version)
+	assert.Equal(t, "", info.Release)
+	assert.Equal(t, "rc1", info.Prerelease)
+
+	info = &Info{
+		Version: "v1.0.0-1",
+	}
+	info = WithDefaults(info)
+	assert.Equal(t, "1.0.0", info.Version)
+	assert.Equal(t, "1", info.Release)
+	assert.Equal(t, "", info.Prerelease)
+
+	info = &Info{
+		Version:    "v1.0.0-1",
+		Release:    "2",
+		Prerelease: "beta1",
+	}
+	info = WithDefaults(info)
+	assert.Equal(t, "1.0.0", info.Version)
+	assert.Equal(t, "2", info.Release)
+	assert.Equal(t, "beta1", info.Prerelease)
+
+	info = &Info{
+		Version:    "v1.0.0-1",
+		Release:    "2",
+		Prerelease: "beta1",
+	}
+	info = WithDefaults(info)
+	assert.Equal(t, "1.0.0", info.Version)
+	assert.Equal(t, "2", info.Release)
+	assert.Equal(t, "beta1", info.Prerelease)
+
 }
 
 func TestDefaults(t *testing.T) {
diff --git a/rpm/rpm_test.go b/rpm/rpm_test.go
index 7846d80..65c96fa 100644
--- a/rpm/rpm_test.go
+++ b/rpm/rpm_test.go
@@ -171,6 +171,49 @@ func TestWithRPMTags(t *testing.T) {
 	assert.Equal(t, info.Description, description)
 }
 
+
+func TestRPMVersion(t *testing.T) {
+	info := exampleInfo()
+	info.Version = "1.0.0" //nolint:golint,goconst
+	meta, err := buildRPMMeta(info)
+	assert.NoError(t, err)
+	assert.Equal(t, "1.0.0", meta.Version)
+	assert.Equal(t, "1", meta.Release)
+}
+
+func TestRPMVersionWithRelease(t *testing.T) {
+	info := exampleInfo()
+	info.Version = "1.0.0" //nolint:golint,goconst
+	info.Release = "2"
+	meta, err := buildRPMMeta(info)
+	assert.NoError(t, err)
+	assert.Equal(t, "1.0.0", meta.Version)
+	assert.Equal(t, "2", meta.Release)
+}
+
+func TestRPMVersionWithPrerelease(t *testing.T) {
+	// https://fedoraproject.org/wiki/Package_Versioning_Examples#Complex_versioning_examples
+	info := exampleInfo()
+	info.Version = "1.0.0" //nolint:golint,goconst
+	info.Prerelease = "rc1"
+	meta, err := buildRPMMeta(info)
+	assert.NoError(t, err)
+	assert.Equal(t, "1.0.0", meta.Version)
+	assert.Equal(t, "0.1.rc1", meta.Release)
+}
+
+func TestRPMVersionWithReleaseAndPrerelease(t *testing.T) {
+	// https://fedoraproject.org/wiki/Package_Versioning_Examples#Complex_versioning_examples
+	info := exampleInfo()
+	info.Version = "1.0.0" //nolint:golint,goconst
+	info.Release = "0.2"
+	info.Prerelease = "rc1"
+	meta, err := buildRPMMeta(info)
+	assert.NoError(t, err)
+	assert.Equal(t, "1.0.0", meta.Version)
+	assert.Equal(t, "0.2.rc1", meta.Release)
+}
+
 func TestWithInvalidEpoch(t *testing.T) {
 	f, err := ioutil.TempFile("", "test.rpm")
 	defer func() {