diff --git a/.travis.yml b/.travis.yml index fa364df..545c079 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ -dist: trusty addons: apt: packages: @@ -9,6 +8,8 @@ go: '1.10' # - docker install: - make setup +before_script: + - rpmbuild --version script: - make ci after_success: diff --git a/cmd/nfpm/nfpm.yaml.example b/cmd/nfpm/nfpm.yaml.example index d77cd93..8b4132f 100644 --- a/cmd/nfpm/nfpm.yaml.example +++ b/cmd/nfpm/nfpm.yaml.example @@ -11,6 +11,8 @@ provides: depends: - foo - bar +recommends: +- whatever conflicts: - not-foo - not-bar diff --git a/deb/deb.go b/deb/deb.go index 87b4b10..a600b29 100644 --- a/deb/deb.go +++ b/deb/deb.go @@ -152,6 +152,8 @@ Installed-Size: {{.InstalledSize}} Replaces: {{join .Info.Replaces}} Provides: {{join .Info.Provides}} Depends: {{join .Info.Depends}} +Recommends: {{join .Info.Recommends}} +Recommends: {{join .Info.Suggests}} Conflicts: {{join .Info.Conflicts}} Homepage: {{.Info.Homepage}} Description: {{.Info.Description}} @@ -172,13 +174,7 @@ func createControl(now time.Time, instSize int64, md5sums []byte, info nfpm.Info defer compress.Close() // nolint: errcheck var body bytes.Buffer - var tmpl = template.New("control") - tmpl.Funcs(template.FuncMap{ - "join": func(strs []string) string { - return strings.Trim(strings.Join(strs, ", "), " ") - }, - }) - if err := template.Must(tmpl.Parse(controlTemplate)).Execute(&body, controlData{ + if err := writeControl(&body, controlData{ Info: info, InstalledSize: instSize / 1024, }); err != nil { @@ -204,6 +200,16 @@ func createControl(now time.Time, instSize int64, md5sums []byte, info nfpm.Info return buf.Bytes(), nil } +func writeControl(w io.Writer, data controlData) error { + var tmpl = template.New("control") + tmpl.Funcs(template.FuncMap{ + "join": func(strs []string) string { + return strings.Trim(strings.Join(strs, ", "), " ") + }, + }) + return template.Must(tmpl.Parse(controlTemplate)).Execute(w, data) +} + func newFileInsideTarGz(out *tar.Writer, name string, content []byte, now time.Time) error { var header = tar.Header{ Name: name, diff --git a/deb/deb_test.go b/deb/deb_test.go index 5b23c23..e93280a 100644 --- a/deb/deb_test.go +++ b/deb/deb_test.go @@ -1,6 +1,8 @@ package deb import ( + "bytes" + "flag" "io/ioutil" "testing" @@ -8,33 +10,64 @@ import ( "github.com/stretchr/testify/assert" ) +var update = flag.Bool("update", false, "update .golden files") + +var info = nfpm.WithDefaults(nfpm.Info{ + Name: "foo", + Arch: "amd64", + Depends: []string{ + "bash", + }, + Recommends: []string{ + "git", + }, + Suggests: []string{ + "bash", + }, + Replaces: []string{ + "svn", + }, + Provides: []string{ + "bzr", + }, + Conflicts: []string{ + "zsh", + }, + Description: "Foo does things", + Priority: "extra", + Maintainer: "Carlos A Becker ", + Version: "1.0.0", + Section: "default", + Homepage: "http://carlosbecker.com", + Vendor: "nope", + Files: map[string]string{ + "../testdata/fake": "/usr/local/bin/fake", + }, + ConfigFiles: map[string]string{ + "../testdata/whatever.conf": "/etc/fake/fake.conf", + }, +}) + func TestDeb(t *testing.T) { - var err = Default.Package( - nfpm.WithDefaults(nfpm.Info{ - Name: "foo", - Arch: "amd64", - Depends: []string{ - "bash", - }, - Description: "Foo does things", - Priority: "extra", - Maintainer: "Carlos A Becker ", - Version: "1.0.0", - Section: "default", - Homepage: "http://carlosbecker.com", - Vendor: "nope", - Files: map[string]string{ - "../testdata/fake": "/usr/local/bin/fake", - }, - ConfigFiles: map[string]string{ - "../testdata/whatever.conf": "/etc/fake/fake.conf", - }, - }), - ioutil.Discard, - ) + var err = Default.Package(info, ioutil.Discard) assert.NoError(t, err) } +func TestControl(t *testing.T) { + var w bytes.Buffer + assert.NoError(t, writeControl(&w, controlData{ + Info: info, + InstalledSize: 10, + })) + var golden = "testdata/control.golden" + if *update { + ioutil.WriteFile(golden, w.Bytes(), 0655) + } + bts, err := ioutil.ReadFile(golden) + assert.NoError(t, err) + assert.Equal(t, string(bts), w.String()) +} + func TestDebFileDoesNotExist(t *testing.T) { var err = Default.Package( nfpm.WithDefaults(nfpm.Info{ diff --git a/deb/testdata/control.golden b/deb/testdata/control.golden new file mode 100644 index 0000000..5aae1ff --- /dev/null +++ b/deb/testdata/control.golden @@ -0,0 +1,16 @@ +Package: foo +Version: 1.0.0 +Section: default +Priority: extra +Architecture: amd64 +Maintainer: Carlos A Becker +Vendor: nope +Installed-Size: 10 +Replaces: svn +Provides: bzr +Depends: bash +Recommends: git +Recommends: bash +Conflicts: zsh +Homepage: http://carlosbecker.com +Description: Foo does things diff --git a/nfpm.go b/nfpm.go index ba8bd8c..589b586 100644 --- a/nfpm.go +++ b/nfpm.go @@ -45,6 +45,8 @@ type Info struct { Replaces []string `yaml:"replaces,omitempty"` Provides []string `yaml:"provides,omitempty"` Depends []string `yaml:"depends,omitempty"` + Recommends []string `yaml:"recommends,omitempty"` + Suggests []string `yaml:"suggests,omitempty"` Conflicts []string `yaml:"conflicts,omitempty"` Maintainer string `yaml:"maintainer,omitempty"` Description string `yaml:"description,omitempty"` diff --git a/rpm/rpm.go b/rpm/rpm.go index abad4e0..f2c8ce2 100644 --- a/rpm/rpm.go +++ b/rpm/rpm.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "text/template" @@ -32,6 +33,10 @@ func (*RPM) Package(info nfpm.Info, w io.Writer) error { if info.Arch == "amd64" { info.Arch = "x86_64" } + _, err := exec.LookPath("rpmbuild") + if err != nil { + return fmt.Errorf("rpmbuild not present in $PATH") + } temps, err := setupTempFiles(info) if err != nil { return err @@ -70,21 +75,62 @@ func (*RPM) Package(info nfpm.Info, w io.Writer) error { return errors.Wrap(err, "failed to copy rpm file to writer") } +type rpmbuildVersion struct { + Major, Minor, Path int +} + +func getRpmbuildVersion() (rpmbuildVersion, error) { + // #nosec + bts, err := exec.Command("rpmbuild", "--version").CombinedOutput() + if err != nil { + return rpmbuildVersion{}, errors.Wrap(err, "failed to get rpmbuild version") + } + var v = make([]int, 3) + vs := strings.TrimSuffix(strings.TrimPrefix(string(bts), "RPM version "), "\n") + for i, part := range strings.Split(vs, ".")[:3] { + pi, err := strconv.Atoi(part) + if err != nil { + return rpmbuildVersion{}, errors.Wrapf(err, "could not parse version %s", vs) + } + v[i] = pi + } + return rpmbuildVersion{ + Major: v[0], + Minor: v[1], + Path: v[2], + }, nil +} + func createSpec(info nfpm.Info, path string) error { - var body bytes.Buffer + file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600) + if err != nil { + return errors.Wrap(err, "failed to create spec") + } + vs, err := getRpmbuildVersion() + if err != nil { + return err + } + return writeSpec(file, info, vs) +} + +func writeSpec(w io.Writer, info nfpm.Info, vs rpmbuildVersion) error { var tmpl = template.New("spec") tmpl.Funcs(template.FuncMap{ - "join": func(strs []string) string { - return strings.Trim(strings.Join(strs, ", "), " ") - }, "first_line": func(str string) string { return strings.Split(str, "\n")[0] }, }) - if err := template.Must(tmpl.Parse(specTemplate)).Execute(&body, info); err != nil { + type data struct { + Info nfpm.Info + RPM413 bool + } + if err := template.Must(tmpl.Parse(specTemplate)).Execute(w, data{ + Info: info, + RPM413: vs.Major >= 4 && vs.Minor >= 13, + }); err != nil { return errors.Wrap(err, "failed to parse spec template") } - return errors.Wrap(ioutil.WriteFile(path, body.Bytes(), 0644), "failed to write spec file") + return nil } type tempFiles struct { @@ -196,37 +242,47 @@ const specTemplate = ` %define __spec_install_post %{nil} %define debug_package %{nil} %define __os_install_post %{_dbpath}/brp-compress -%define _arch {{.Arch}} -%define _bindir {{.Bindir}} +%define _arch {{ .Info.Arch }} +%define _bindir {{ .Info.Bindir }} -Name: {{ .Name }} -Summary: {{ first_line .Description }} -Version: {{ .Version }} +Name: {{ .Info.Name }} +Summary: {{ first_line .Info.Description }} +Version: {{ .Info.Version }} Release: 1 -License: {{ .License }} +License: {{ .Info.License }} Group: Development/Tools SOURCE0 : %{name}-%{version}.tar.gz -URL: {{ .Homepage }} +URL: {{ .Info.Homepage }} BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root -{{ range $index, $element := .Replaces }} -Obsolotes: {{ . }} +{{ range $index, $element := .Info.Replaces }} +Obsoletes: {{ . }} {{ end }} -{{ range $index, $element := .Conflicts }} +{{ range $index, $element := .Info.Conflicts }} Conflicts: {{ . }} {{ end }} -{{ range $index, $element := .Provides }} +{{ range $index, $element := .Info.Provides }} Provides: {{ . }} {{ end }} -{{ range $index, $element := .Depends }} +{{ range $index, $element := .Info.Depends }} Requires: {{ . }} {{ end }} +{{ if .RPM413 }} +{{ range $index, $element := .Info.Recommends }} +Recommends: {{ . }} +{{ end }} + +{{ range $index, $element := .Info.Suggests }} +Suggests: {{ . }} +{{ end }} +{{ end }} + %description -{{ .Description }} +{{ .Info.Description }} %prep %setup -q @@ -246,14 +302,14 @@ rm -rf %{buildroot} %files %defattr(-,root,root,-) -{{ range $index, $element := .Files }} +{{ range $index, $element := .Info.Files }} {{ . }} {{ end }} %{_bindir}/* -{{ range $index, $element := .ConfigFiles }} +{{ range $index, $element := .Info.ConfigFiles }} {{ . }} {{ end }} -{{ range $index, $element := .ConfigFiles }} +{{ range $index, $element := .Info.ConfigFiles }} %config(noreplace) {{ . }} {{ end }} diff --git a/rpm/rpm_test.go b/rpm/rpm_test.go index ab76ee5..e25a44a 100644 --- a/rpm/rpm_test.go +++ b/rpm/rpm_test.go @@ -1,6 +1,8 @@ package rpm import ( + "bytes" + "flag" "io/ioutil" "os" "testing" @@ -9,32 +11,67 @@ import ( "github.com/stretchr/testify/assert" ) +var update = flag.Bool("update", false, "update .golden files") + +var info = nfpm.WithDefaults(nfpm.Info{ + Name: "foo", + Arch: "amd64", + Depends: []string{ + "bash", + }, + Recommends: []string{ + "git", + }, + Suggests: []string{ + "bash", + }, + Replaces: []string{ + "svn", + }, + Provides: []string{ + "bzr", + }, + Conflicts: []string{ + "zsh", + }, + Description: "Foo does things", + Priority: "extra", + Maintainer: "Carlos A Becker ", + Version: "1.0.0", + Section: "default", + Homepage: "http://carlosbecker.com", + Vendor: "nope", + License: "MIT", + Bindir: "/usr/local/bin", + Files: map[string]string{ + "../testdata/fake": "/usr/local/bin/fake", + }, + ConfigFiles: map[string]string{ + "../testdata/whatever.conf": "/etc/fake/fake.conf", + }, +}) + +func TestSpec(t *testing.T) { + for golden, vs := range map[string]rpmbuildVersion{ + "testdata/spec_4.14.x.golden": rpmbuildVersion{4, 14, 2}, + "testdata/spec_4.13.x.golden": rpmbuildVersion{4, 13, 1}, + "testdata/spec_4.12.x.golden": rpmbuildVersion{4, 12, 9}, + } { + t.Run(golden, func(tt *testing.T) { + var w bytes.Buffer + assert.NoError(tt, writeSpec(&w, info, vs)) + if *update { + ioutil.WriteFile(golden, w.Bytes(), 0655) + } + bts, err := ioutil.ReadFile(golden) + assert.NoError(tt, err) + assert.Equal(tt, string(bts), w.String()) + }) + } +} + func TestRPM(t *testing.T) { - var err = Default.Package( - nfpm.WithDefaults(nfpm.Info{ - Name: "foo", - Arch: "amd64", - Depends: []string{ - "bash", - }, - Description: "Foo does things", - Priority: "extra", - Maintainer: "Carlos A Becker ", - Version: "1.0.0", - Section: "default", - Homepage: "http://carlosbecker.com", - Vendor: "nope", - License: "MIT", - Bindir: "/usr/local/bin", - Files: map[string]string{ - "../testdata/fake": "/usr/local/bin/fake", - }, - ConfigFiles: map[string]string{ - "../testdata/whatever.conf": "/etc/fake/fake.conf", - }, - }), - ioutil.Discard, - ) + var err = Default.Package(info, ioutil.Discard) assert.NoError(t, err) } @@ -62,10 +99,20 @@ func TestNoFiles(t *testing.T) { } func TestRPMBuildNotInPath(t *testing.T) { + path := os.Getenv("PATH") + defer os.Setenv("PATH", path) assert.NoError(t, os.Setenv("PATH", "")) var err = Default.Package( nfpm.WithDefaults(nfpm.Info{}), ioutil.Discard, ) - assert.EqualError(t, err, `rpmbuild failed: exec: "rpmbuild": executable file not found in $PATH`) + assert.EqualError(t, err, `rpmbuild not present in $PATH`) +} + +func TestRpmBuildVersion(t *testing.T) { + v, err := getRpmbuildVersion() + assert.NoError(t, err) + assert.Equal(t, 4, v.Major) + assert.True(t, v.Minor >= 11) + assert.True(t, v.Path >= 0) } diff --git a/rpm/testdata/spec_4.12.x.golden b/rpm/testdata/spec_4.12.x.golden new file mode 100644 index 0000000..457e8f0 --- /dev/null +++ b/rpm/testdata/spec_4.12.x.golden @@ -0,0 +1,69 @@ + +%define __spec_install_post %{nil} +%define debug_package %{nil} +%define __os_install_post %{_dbpath}/brp-compress +%define _arch amd64 +%define _bindir /usr/local/bin + +Name: foo +Summary: Foo does things +Version: 1.0.0 +Release: 1 +License: MIT +Group: Development/Tools +SOURCE0 : %{name}-%{version}.tar.gz +URL: http://carlosbecker.com +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root + + +Obsoletes: svn + + + +Conflicts: zsh + + + +Provides: bzr + + + +Requires: bash + + + + +%description +Foo does things + +%prep +%setup -q + +%build +# Empty section. + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot} + +# in builddir +cp -a * %{buildroot} + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) + +/usr/local/bin/fake + +%{_bindir}/* + +/etc/fake/fake.conf + + +%config(noreplace) /etc/fake/fake.conf + + +%changelog +# noop diff --git a/rpm/testdata/spec_4.13.x.golden b/rpm/testdata/spec_4.13.x.golden new file mode 100644 index 0000000..4d6ca3d --- /dev/null +++ b/rpm/testdata/spec_4.13.x.golden @@ -0,0 +1,77 @@ + +%define __spec_install_post %{nil} +%define debug_package %{nil} +%define __os_install_post %{_dbpath}/brp-compress +%define _arch amd64 +%define _bindir /usr/local/bin + +Name: foo +Summary: Foo does things +Version: 1.0.0 +Release: 1 +License: MIT +Group: Development/Tools +SOURCE0 : %{name}-%{version}.tar.gz +URL: http://carlosbecker.com +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root + + +Obsoletes: svn + + + +Conflicts: zsh + + + +Provides: bzr + + + +Requires: bash + + + + +Recommends: git + + + +Suggests: bash + + + +%description +Foo does things + +%prep +%setup -q + +%build +# Empty section. + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot} + +# in builddir +cp -a * %{buildroot} + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) + +/usr/local/bin/fake + +%{_bindir}/* + +/etc/fake/fake.conf + + +%config(noreplace) /etc/fake/fake.conf + + +%changelog +# noop diff --git a/rpm/testdata/spec_4.14.x.golden b/rpm/testdata/spec_4.14.x.golden new file mode 100644 index 0000000..4d6ca3d --- /dev/null +++ b/rpm/testdata/spec_4.14.x.golden @@ -0,0 +1,77 @@ + +%define __spec_install_post %{nil} +%define debug_package %{nil} +%define __os_install_post %{_dbpath}/brp-compress +%define _arch amd64 +%define _bindir /usr/local/bin + +Name: foo +Summary: Foo does things +Version: 1.0.0 +Release: 1 +License: MIT +Group: Development/Tools +SOURCE0 : %{name}-%{version}.tar.gz +URL: http://carlosbecker.com +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root + + +Obsoletes: svn + + + +Conflicts: zsh + + + +Provides: bzr + + + +Requires: bash + + + + +Recommends: git + + + +Suggests: bash + + + +%description +Foo does things + +%prep +%setup -q + +%build +# Empty section. + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot} + +# in builddir +cp -a * %{buildroot} + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) + +/usr/local/bin/fake + +%{_bindir}/* + +/etc/fake/fake.conf + + +%config(noreplace) /etc/fake/fake.conf + + +%changelog +# noop