1
1
Fork 0
mirror of https://github.com/goreleaser/nfpm synced 2024-06-18 17:50:02 +02:00
nfpm/rpm/rpm.go

352 lines
8.3 KiB
Go
Raw Normal View History

2018-02-19 14:37:30 +01:00
// Package rpm implements nfpm.Packager providing .rpm bindings through rpmbuild.
2018-02-03 20:42:56 +01:00
package rpm
import (
2018-02-06 03:28:30 +01:00
"archive/tar"
2018-02-05 00:00:20 +01:00
"bytes"
2018-02-06 03:28:30 +01:00
"compress/gzip"
2018-02-03 20:42:56 +01:00
"fmt"
"io"
"io/ioutil"
"os"
2018-02-05 00:00:20 +01:00
"os/exec"
2018-02-03 20:42:56 +01:00
"path/filepath"
"regexp"
"strconv"
2018-02-05 00:00:20 +01:00
"strings"
2018-02-05 03:54:03 +01:00
"text/template"
2018-02-03 20:42:56 +01:00
2018-02-05 02:53:22 +01:00
"github.com/goreleaser/nfpm"
"github.com/goreleaser/nfpm/glob"
2018-02-12 17:09:59 +01:00
"github.com/pkg/errors"
2018-02-03 20:42:56 +01:00
)
2018-02-12 16:50:25 +01:00
func init() {
nfpm.Register("rpm", Default)
}
2018-02-03 20:42:56 +01:00
2018-03-23 18:04:38 +01:00
var goarchToRPM = map[string]string{
"amd64": "x86_64",
2018-03-23 18:14:16 +01:00
"386": "i386",
2018-03-23 18:04:38 +01:00
}
2018-02-03 20:42:56 +01:00
// Default deb packager
var Default = &RPM{}
// RPM is a RPM packager implementation
type RPM struct{}
// Package writes a new RPM package to the given writer using the given info
2018-02-05 02:53:22 +01:00
func (*RPM) Package(info nfpm.Info, w io.Writer) error {
2018-03-23 18:04:38 +01:00
arch, ok := goarchToRPM[info.Arch]
if ok {
info.Arch = arch
2018-02-05 00:19:00 +01:00
}
2018-03-28 19:41:25 +02:00
info.Version = strings.Replace(info.Version, "-", "_", -1)
_, err := exec.LookPath("rpmbuild")
if err != nil {
return fmt.Errorf("rpmbuild not present in $PATH")
}
2018-02-16 02:58:37 +01:00
temps, err := setupTempFiles(info)
2018-02-03 20:42:56 +01:00
if err != nil {
2018-02-16 02:58:37 +01:00
return err
2018-02-06 03:28:30 +01:00
}
2018-02-16 03:15:41 +01:00
if err = createTarGz(info, temps.Folder, temps.Source); err != nil {
return err
2018-02-03 20:42:56 +01:00
}
2018-02-16 03:15:41 +01:00
if err = createSpec(info, temps.Spec); err != nil {
2018-02-12 17:09:59 +01:00
return errors.Wrap(err, "failed to create rpm spec file")
2018-02-05 00:00:20 +01:00
}
2018-02-03 20:42:56 +01:00
var args = []string{
2018-02-16 02:58:37 +01:00
"--define", fmt.Sprintf("_topdir %s", temps.Root),
"--define", fmt.Sprintf("_tmppath %s/tmp", temps.Root),
2018-02-05 02:28:36 +01:00
"--target", fmt.Sprintf("%s-unknown-%s", info.Arch, info.Platform),
2018-02-05 00:00:20 +01:00
"-ba",
"SPECS/" + info.Name + ".spec",
2018-02-03 20:42:56 +01:00
}
2018-02-16 03:15:41 +01:00
// #nosec
2018-02-05 00:00:20 +01:00
cmd := exec.Command("rpmbuild", args...)
2018-02-16 02:58:37 +01:00
cmd.Dir = temps.Root
2018-02-05 00:00:20 +01:00
out, err := cmd.CombinedOutput()
2018-02-06 03:28:30 +01:00
if err != nil {
var msg = "rpmbuild failed"
if string(out) != "" {
msg += ": " + string(out)
}
return errors.Wrap(err, msg)
2018-02-06 03:28:30 +01:00
}
2018-02-16 02:58:37 +01:00
rpm, err := os.Open(temps.RPM)
2018-02-05 00:19:00 +01:00
if err != nil {
2018-02-12 17:09:59 +01:00
return errors.Wrap(err, "failed open rpm file")
2018-02-05 00:19:00 +01:00
}
_, err = io.Copy(w, rpm)
2018-02-12 17:09:59 +01:00
return errors.Wrap(err, "failed to copy rpm file to writer")
2018-02-03 20:42:56 +01:00
}
2018-02-18 22:54:15 +01:00
type rpmbuildVersion struct {
Major, Minor, Path int
}
2018-02-18 22:54:15 +01:00
func getRpmbuildVersion() (rpmbuildVersion, error) {
// #nosec
bts, err := exec.Command("rpmbuild", "--version").CombinedOutput()
if err != nil {
2018-02-18 22:54:15 +01:00
return rpmbuildVersion{}, errors.Wrap(err, "failed to get rpmbuild version")
}
return parseRPMbuildVersion(strings.TrimSuffix(string(bts), "\n"))
2018-03-06 01:02:26 +01:00
}
var versionExp = regexp.MustCompile(`RPM.* (\d+)\.(\d+)\.(\d+)`)
2018-03-06 01:02:26 +01:00
func parseRPMbuildVersion(out string) (rpmbuildVersion, error) {
matches := versionExp.FindAllStringSubmatch(out, -1)
if len(matches) == 0 {
return rpmbuildVersion{}, errors.New("unexpected rpmbuild --version output")
}
version := matches[0][1:]
if len(version) != 3 {
return rpmbuildVersion{}, errors.New("unexpected rpmbuild --version output")
}
var v = make([]int, 3)
for i, part := range version {
pi, err := strconv.Atoi(part)
if err != nil {
return rpmbuildVersion{}, errors.Wrapf(err, "could not parse version %s", out)
}
v[i] = pi
}
2018-02-18 22:54:15 +01:00
return rpmbuildVersion{
Major: v[0],
Minor: v[1],
Path: v[2],
}, nil
}
2018-02-06 03:28:30 +01:00
func createSpec(info nfpm.Info, path string) error {
2018-02-18 22:54:15 +01:00
file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600)
2018-02-18 21:13:47 +01:00
if err != nil {
return errors.Wrap(err, "failed to create spec")
}
2018-02-18 22:54:15 +01:00
vs, err := getRpmbuildVersion()
if err != nil {
return err
}
2018-02-18 22:54:15 +01:00
return writeSpec(file, info, vs)
2018-02-18 21:13:47 +01:00
}
2018-02-18 22:54:15 +01:00
func writeSpec(w io.Writer, info nfpm.Info, vs rpmbuildVersion) error {
2018-02-06 03:28:30 +01:00
var tmpl = template.New("spec")
tmpl.Funcs(template.FuncMap{
"first_line": func(str string) string {
return strings.Split(str, "\n")[0]
},
})
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 {
2018-02-12 17:09:59 +01:00
return errors.Wrap(err, "failed to parse spec template")
2018-02-06 03:28:30 +01:00
}
2018-02-18 21:13:47 +01:00
return nil
2018-02-06 03:28:30 +01:00
}
2018-02-16 02:58:37 +01:00
type tempFiles struct {
// Root folder - topdir on rpm's slang
Root string
// Folder is the name of subfolders and etc, in the `name-version` format
Folder string
// Source is the path the .tar.gz file should be in
Source string
// Spec is the path the .spec file should be in
Spec string
// RPM is the path where the .rpm file should be generated
RPM string
}
func setupTempFiles(info nfpm.Info) (tempFiles, error) {
root, err := ioutil.TempDir("", info.Name)
if err != nil {
return tempFiles{}, errors.Wrap(err, "failed to create temp dir")
}
if err := createDirs(root); err != nil {
return tempFiles{}, errors.Wrap(err, "failed to rpm dir structure")
}
folder := fmt.Sprintf("%s-%s", info.Name, info.Version)
return tempFiles{
Root: root,
Folder: folder,
Source: filepath.Join(root, "SOURCES", folder+".tar.gz"),
Spec: filepath.Join(root, "SPECS", info.Name+".spec"),
RPM: filepath.Join(root, "RPMS", info.Arch, fmt.Sprintf("%s-1.%s.rpm", folder, info.Arch)),
}, nil
}
2018-02-06 03:28:30 +01:00
func createDirs(root string) error {
for _, folder := range []string{
"RPMS",
"SRPMS",
"BUILD",
"SOURCES",
"SPECS",
"tmp",
} {
path := filepath.Join(root, folder)
2018-02-16 03:15:41 +01:00
if err := os.Mkdir(path, 0700); err != nil {
2018-02-12 17:09:59 +01:00
return errors.Wrapf(err, "failed to create %s", path)
2018-02-06 03:28:30 +01:00
}
}
return nil
}
2018-02-16 02:58:37 +01:00
func createTarGz(info nfpm.Info, root, file string) error {
2018-02-06 03:28:30 +01:00
var buf bytes.Buffer
var compress = gzip.NewWriter(&buf)
var out = tar.NewWriter(compress)
2018-02-16 02:58:37 +01:00
// the writers are properly closed later, this is just in case that we have
// an error in another part of the code.
defer out.Close() // nolint: errcheck
defer compress.Close() // nolint: errcheck
2018-02-06 03:28:30 +01:00
for _, files := range []map[string]string{info.Files, info.ConfigFiles} {
for srcglob, dstroot := range files {
globbed, err := glob.Glob(srcglob, dstroot)
if err != nil {
2018-02-16 03:12:54 +01:00
return err
2018-02-06 03:28:30 +01:00
}
for src, dst := range globbed {
if err := copyToTarGz(out, root, src, dst); err != nil {
return err
}
}
2018-02-06 03:28:30 +01:00
}
}
if err := out.Close(); err != nil {
2018-02-16 02:58:37 +01:00
return errors.Wrap(err, "failed to close data.tar.gz writer")
2018-02-06 03:28:30 +01:00
}
if err := compress.Close(); err != nil {
2018-02-16 02:58:37 +01:00
return errors.Wrap(err, "failed to close data.tar.gz gzip writer")
2018-02-06 03:28:30 +01:00
}
2018-02-16 02:58:37 +01:00
if err := ioutil.WriteFile(file, buf.Bytes(), 0666); err != nil {
return errors.Wrap(err, "could not write to .tar.gz file")
}
return nil
2018-02-06 03:28:30 +01:00
}
2018-02-16 03:12:54 +01:00
func copyToTarGz(out *tar.Writer, root, src, dst string) error {
file, err := os.OpenFile(src, os.O_RDONLY, 0600)
if err != nil {
return errors.Wrap(err, "could not add file to the archive")
}
// don't really care if Close() errs
defer file.Close() // nolint: errcheck
info, err := file.Stat()
if err != nil {
return err
}
if info.IsDir() {
return nil
}
var header = tar.Header{
Name: filepath.Join(root, dst),
Size: info.Size(),
Mode: int64(info.Mode()),
ModTime: info.ModTime(),
}
if err := out.WriteHeader(&header); err != nil {
return errors.Wrapf(err, "cannot write header of %s to data.tar.gz", header.Name)
}
if _, err := io.Copy(out, file); err != nil {
return errors.Wrapf(err, "cannot write %s to data.tar.gz", header.Name)
}
return nil
}
2018-02-03 20:42:56 +01:00
const specTemplate = `
2018-02-05 02:28:36 +01:00
%define __spec_install_post %{nil}
%define debug_package %{nil}
%define __os_install_post %{_dbpath}/brp-compress
%define _arch {{ .Info.Arch }}
%define _bindir {{ .Info.Bindir }}
2018-02-03 20:42:56 +01:00
Name: {{ .Info.Name }}
Summary: {{ first_line .Info.Description }}
Version: {{ .Info.Version }}
2018-02-03 20:42:56 +01:00
Release: 1
2018-04-05 04:13:47 +02:00
{{- with .Info.License }}
License: {{ . }}
{{- end }}
2018-02-03 20:42:56 +01:00
Group: Development/Tools
2018-02-05 00:00:20 +01:00
SOURCE0 : %{name}-%{version}.tar.gz
2018-04-05 04:13:47 +02:00
{{- with .Info.Homepage }}
URL: {{ . }}
{{- end }}
2018-02-03 20:42:56 +01:00
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
{{ range $index, $element := .Info.Replaces }}
2018-02-18 21:13:47 +01:00
Obsoletes: {{ . }}
2018-02-05 02:42:03 +01:00
{{ end }}
{{ range $index, $element := .Info.Conflicts }}
2018-02-05 02:42:03 +01:00
Conflicts: {{ . }}
{{ end }}
{{ range $index, $element := .Info.Provides }}
2018-02-05 02:42:03 +01:00
Provides: {{ . }}
{{ end }}
{{ range $index, $element := .Info.Depends }}
2018-02-05 02:42:03 +01:00
Requires: {{ . }}
{{ end }}
{{ if .RPM413 }}
{{ range $index, $element := .Info.Recommends }}
Recommends: {{ . }}
2018-02-18 21:13:47 +01:00
{{ end }}
{{ range $index, $element := .Info.Suggests }}
Suggests: {{ . }}
{{ end }}
{{ end }}
2018-02-03 20:42:56 +01:00
%description
{{ .Info.Description }}
2018-02-03 20:42:56 +01:00
%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,-)
{{ range $index, $element := .Info.Files }}
2018-02-05 02:28:36 +01:00
{{ . }}
{{ end }}
2018-02-12 19:29:44 +01:00
%{_bindir}/*
{{ range $index, $element := .Info.ConfigFiles }}
2018-02-06 03:28:30 +01:00
{{ . }}
{{ end }}
{{ range $index, $element := .Info.ConfigFiles }}
2018-02-12 19:29:44 +01:00
%config(noreplace) {{ . }}
2018-02-06 03:28:30 +01:00
{{ end }}
2018-02-03 20:42:56 +01:00
2018-02-05 00:00:20 +01:00
%changelog
# noop
2018-02-03 20:42:56 +01:00
`