1
1
Fork 0
mirror of https://github.com/goreleaser/nfpm synced 2024-05-07 01:26:08 +02:00

feat: reproducible packages (#748)

* feat: allow to set a build date

defaults to $SOURCE_DATE_EPOCH

closes #744
closes #734

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: rename to mtime

* docs: fix systemd note

closes #739

* fix: improve arch packager

* fix: arch test

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: improve apk packager

* fix: improve deb special files

* fix: reuse keys func

* fix: deps

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
Carlos Alexandro Becker 2023-12-07 13:58:36 -03:00 committed by GitHub
parent c3142513c9
commit 9c4fc0e886
Signed by: GitHub
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 412 additions and 209 deletions

View File

@ -45,6 +45,7 @@ import (
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"github.com/goreleaser/nfpm/v2/internal/maps"
"github.com/goreleaser/nfpm/v2/internal/sign"
gzip "github.com/klauspost/pgzip"
)
@ -343,18 +344,21 @@ func createBuilderControl(info *nfpm.Info, size int64, dataDigest []byte) func(t
// bin/echo 'running preinstall.sh' // do stuff here
//
// exit 0
for script, dest := range map[string]string{
info.Scripts.PreInstall: ".pre-install",
info.APK.Scripts.PreUpgrade: ".pre-upgrade",
info.Scripts.PostInstall: ".post-install",
info.APK.Scripts.PostUpgrade: ".post-upgrade",
info.Scripts.PreRemove: ".pre-deinstall",
info.Scripts.PostRemove: ".post-deinstall",
} {
if script != "" {
if err := newScriptInsideTarGz(tw, script, dest); err != nil {
return err
}
scripts := map[string]string{
".pre-install": info.Scripts.PreInstall,
".pre-upgrade": info.APK.Scripts.PreUpgrade,
".post-install": info.Scripts.PostInstall,
".post-upgrade": info.APK.Scripts.PostUpgrade,
".pre-deinstall": info.Scripts.PreRemove,
".post-deinstall": info.Scripts.PostRemove,
}
for _, name := range maps.Keys(scripts) {
path := scripts[name]
if path == "" {
continue
}
if err := newScriptInsideTarGz(tw, path, name); err != nil {
return err
}
}

View File

@ -16,6 +16,7 @@ import (
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"github.com/goreleaser/nfpm/v2/internal/maps"
"github.com/klauspost/compress/zstd"
"github.com/klauspost/pgzip"
)
@ -151,7 +152,7 @@ func (ArchLinux) Package(info *nfpm.Info, w io.Writer) error {
// .PKGINFO must be the first entry in .MTREE
entries = append([]MtreeEntry{*pkginfoEntry}, entries...)
err = createMtree(tw, entries)
err = createMtree(tw, entries, info.MTime)
if err != nil {
return fmt.Errorf("create mtree: %w", err)
}
@ -181,25 +182,23 @@ func createFilesInTar(info *nfpm.Info, tw *tar.Writer) ([]MtreeEntry, int64, err
Type: files.TypeDir,
})
err := tw.WriteHeader(&tar.Header{
if err := tw.WriteHeader(&tar.Header{
Name: content.Destination,
Mode: int64(content.Mode()),
Typeflag: tar.TypeDir,
ModTime: content.ModTime(),
Uname: content.FileInfo.Owner,
Gname: content.FileInfo.Group,
})
if err != nil {
}); err != nil {
return nil, 0, err
}
case files.TypeSymlink:
err := tw.WriteHeader(&tar.Header{
if err := tw.WriteHeader(&tar.Header{
Name: content.Destination,
Linkname: content.Source,
ModTime: content.ModTime(),
Typeflag: tar.TypeSymlink,
})
if err != nil {
}); err != nil {
return nil, 0, err
}
@ -311,7 +310,7 @@ func createPkginfo(info *nfpm.Info, tw *tar.Writer, totalSize int64) (*MtreeEntr
return nil, err
}
builddate := strconv.FormatInt(time.Now().Unix(), 10)
builddate := strconv.FormatInt(info.MTime.Unix(), 10)
totalSizeStr := strconv.FormatInt(totalSize, 10)
err = writeKVPairs(buf, map[string]string{
@ -362,8 +361,7 @@ func createPkginfo(info *nfpm.Info, tw *tar.Writer, totalSize int64) (*MtreeEntr
if content.Type == files.TypeConfig || content.Type == files.TypeConfigNoReplace {
path := files.AsRelativePath(content.Destination)
err = writeKVPair(buf, "backup", path)
if err != nil {
if err := writeKVPair(buf, "backup", path); err != nil {
return nil, err
}
}
@ -376,7 +374,7 @@ func createPkginfo(info *nfpm.Info, tw *tar.Writer, totalSize int64) (*MtreeEntr
Mode: 0o644,
Name: ".PKGINFO",
Size: int64(size),
ModTime: time.Now(),
ModTime: info.MTime,
})
if err != nil {
return nil, err
@ -388,14 +386,13 @@ func createPkginfo(info *nfpm.Info, tw *tar.Writer, totalSize int64) (*MtreeEntr
r := io.TeeReader(buf, md5Hash)
r = io.TeeReader(r, sha256Hash)
_, err = io.Copy(tw, r)
if err != nil {
if _, err = io.Copy(tw, r); err != nil {
return nil, err
}
return &MtreeEntry{
Destination: ".PKGINFO",
Time: time.Now().Unix(),
Time: info.MTime.Unix(),
Mode: 0o644,
Size: int64(size),
Type: files.TypeFile,
@ -404,10 +401,9 @@ func createPkginfo(info *nfpm.Info, tw *tar.Writer, totalSize int64) (*MtreeEntr
}, nil
}
func writeKVPairs(w io.Writer, s map[string]string) error {
for key, val := range s {
err := writeKVPair(w, key, val)
if err != nil {
func writeKVPairs(w io.Writer, pairs map[string]string) error {
for _, key := range maps.Keys(pairs) {
if err := writeKVPair(w, key, pairs[key]); err != nil {
return err
}
}
@ -485,7 +481,7 @@ func (me *MtreeEntry) WriteTo(w io.Writer) (int64, error) {
}
}
func createMtree(tw *tar.Writer, entries []MtreeEntry) error {
func createMtree(tw *tar.Writer, entries []MtreeEntry, mtime time.Time) error {
buf := &bytes.Buffer{}
gw := pgzip.NewWriter(buf)
defer gw.Close()
@ -509,7 +505,7 @@ func createMtree(tw *tar.Writer, entries []MtreeEntry) error {
Mode: 0o644,
Name: ".MTREE",
Size: int64(buf.Len()),
ModTime: time.Now(),
ModTime: mtime,
})
if err != nil {
return err
@ -562,7 +558,7 @@ func createScripts(info *nfpm.Info, tw *tar.Writer) error {
Mode: 0o644,
Name: ".INSTALL",
Size: int64(buf.Len()),
ModTime: time.Now(),
ModTime: info.MTime,
})
if err != nil {
return err
@ -573,10 +569,10 @@ func createScripts(info *nfpm.Info, tw *tar.Writer) error {
}
func writeScripts(w io.Writer, scripts map[string]string) error {
for script, path := range scripts {
for _, script := range maps.Keys(scripts) {
fmt.Fprintf(w, "function %s() {\n", script)
fl, err := os.Open(path)
fl, err := os.Open(scripts[script])
if err != nil {
return err
}

View File

@ -3,11 +3,12 @@ package arch
import (
"archive/tar"
"bytes"
"fmt"
"io"
"os"
"regexp"
"strings"
"testing"
"time"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
@ -16,6 +17,8 @@ import (
"github.com/stretchr/testify/require"
)
var mtime = time.Date(2023, 11, 5, 23, 15, 17, 0, time.UTC)
func exampleInfo() *nfpm.Info {
return nfpm.WithDefaults(&nfpm.Info{
Name: "foo-test",
@ -297,7 +300,7 @@ func TestArchMtree(t *testing.T) {
Mode: 0o777,
Type: files.TypeSymlink,
},
})
}, mtime)
require.NoError(t, err)
tw.Close()
@ -322,6 +325,7 @@ func TestGlob(t *testing.T) {
Name: "nfpm-repro",
Version: "1.0.0",
Maintainer: "asdfasdf",
MTime: mtime,
Overridables: nfpm.Overridables{
Contents: files.Contents{
@ -329,7 +333,8 @@ func TestGlob(t *testing.T) {
Destination: "/usr/share/nfpm-repro",
Source: "../files/testdata/globtest/different-sizes/*/*.txt",
FileInfo: &files.ContentFileInfo{
Mode: 0o644,
Mode: 0o644,
MTime: mtime,
},
},
},
@ -361,15 +366,16 @@ func TestGlob(t *testing.T) {
mtreeContentBts, err := io.ReadAll(mtreeGzip)
require.NoError(t, err)
expectedTime := fmt.Sprintf("time=%d.0", mtime.Unix())
expected := map[string][]string{
"./.PKGINFO": {"mode=644", "size=185", "type=file"},
"./usr/": {"mode=755", "type=dir"},
"./usr/share/": {"mode=755", "type=dir"},
"./usr/share/nfpm-repro/": {"mode=755", "type=dir"},
"./usr/share/nfpm-repro/a/": {"mode=755", "type=dir"},
"./usr/share/nfpm-repro/a/a.txt": {"mode=644", "size=4", "type=file", "md5digest=d3b07384d113edec49eaa6238ad5ff00", "sha256digest=b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
"./usr/share/nfpm-repro/b/": {"mode=755", "type=dir"},
"./usr/share/nfpm-repro/b/b.txt": {"mode=644", "size=7", "type=file", "md5digest=551a67cc6e06de1910061fe318d28f72", "sha256digest=73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f"},
"./.PKGINFO": {expectedTime, "mode=644", "size=185", "type=file", "md5digest=408daafbd01f6622f0bfd6ccdf96735f", "sha256digest=98468a4b87a677958f872662f476b14ff28cc1f8c6bd0029869e21946b4cd8d2"},
"./usr/": {expectedTime, "mode=755", "type=dir"},
"./usr/share/": {expectedTime, "mode=755", "type=dir"},
"./usr/share/nfpm-repro/": {expectedTime, "mode=755", "type=dir"},
"./usr/share/nfpm-repro/a/": {expectedTime, "mode=755", "type=dir"},
"./usr/share/nfpm-repro/a/a.txt": {expectedTime, "mode=644", "size=4", "type=file", "md5digest=d3b07384d113edec49eaa6238ad5ff00", "sha256digest=b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
"./usr/share/nfpm-repro/b/": {expectedTime, "mode=755", "type=dir"},
"./usr/share/nfpm-repro/b/b.txt": {expectedTime, "mode=644", "size=7", "type=file", "md5digest=551a67cc6e06de1910061fe318d28f72", "sha256digest=73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f"},
}
for _, line := range strings.Split(string(mtreeContentBts), "\n") {
@ -379,8 +385,6 @@ func TestGlob(t *testing.T) {
parts := strings.Fields(line)
filename := parts[0]
expect := expected[filename]
modTime := parts[1]
require.Regexp(t, regexp.MustCompile(`time=\d+\.\d`), modTime)
require.Equal(t, expect, parts[2:len(expect)+2], filename)
require.Equal(t, expect, strings.Split(line, " ")[1:], filename)
}
}

View File

@ -22,6 +22,7 @@ import (
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/deprecation"
"github.com/goreleaser/nfpm/v2/files"
"github.com/goreleaser/nfpm/v2/internal/maps"
"github.com/goreleaser/nfpm/v2/internal/sign"
"github.com/klauspost/compress/zstd"
"github.com/ulikunitz/xz"
@ -125,15 +126,15 @@ func (d *Deb) Package(info *nfpm.Info, deb io.Writer) (err error) { // nolint: f
return fmt.Errorf("cannot write ar header to deb file: %w", err)
}
if err := addArFile(w, "debian-binary", debianBinary); err != nil {
if err := addArFile(w, "debian-binary", debianBinary, info.MTime); err != nil {
return fmt.Errorf("cannot pack debian-binary: %w", err)
}
if err := addArFile(w, "control.tar.gz", controlTarGz); err != nil {
if err := addArFile(w, "control.tar.gz", controlTarGz, info.MTime); err != nil {
return fmt.Errorf("cannot add control.tar.gz to deb: %w", err)
}
if err := addArFile(w, dataTarballName, dataTarball); err != nil {
if err := addArFile(w, dataTarballName, dataTarball, info.MTime); err != nil {
return fmt.Errorf("cannot add data.tar.gz to deb: %w", err)
}
@ -143,7 +144,7 @@ func (d *Deb) Package(info *nfpm.Info, deb io.Writer) (err error) { // nolint: f
return err
}
if err := addArFile(w, "_gpg"+sigType, sig); err != nil {
if err := addArFile(w, "_gpg"+sigType, sig, info.MTime); err != nil {
return &nfpm.ErrSigningFailure{
Err: fmt.Errorf("add signature to ar file: %w", err),
}
@ -255,7 +256,7 @@ func newDpkgSigFileLine(name string, fileContent []byte) dpkgSigFileLine {
func readDpkgSigData(info *nfpm.Info, debianBinary, controlTarGz, dataTarball []byte) (io.Reader, error) {
data := dpkgSigData{
Signer: info.Deb.Signature.Signer,
Date: time.Now(),
Date: info.MTime,
Role: info.Deb.Signature.Type,
Files: []dpkgSigFileLine{
newDpkgSigFileLine("debian-binary", debianBinary),
@ -290,12 +291,12 @@ func (*Deb) SetPackagerDefaults(info *nfpm.Info) {
}
}
func addArFile(w *ar.Writer, name string, body []byte) error {
func addArFile(w *ar.Writer, name string, body []byte, date time.Time) error {
header := ar.Header{
Name: files.ToNixPath(name),
Size: int64(len(body)),
Mode: 0o644,
ModTime: time.Now(),
ModTime: date,
}
if err := w.WriteHeader(&header); err != nil {
return fmt.Errorf("cannot write file header: %w", err)
@ -510,7 +511,7 @@ func createChangelogInsideDataTar(
return 0, err
}
if err = newFileInsideTar(tarw, fileName, changelogData); err != nil {
if err = newFileInsideTar(tarw, fileName, changelogData, info.MTime); err != nil {
return 0, err
}
@ -554,28 +555,18 @@ func createControl(instSize int64, md5sums []byte, info *nfpm.Info) (controlTarG
return nil, err
}
// ensure predefined sort order of these items
filesToCreateNames := []string{
"./control",
"./md5sums",
"./conffiles",
if err := newFileInsideTar(out, "./control", body.Bytes(), info.MTime); err != nil {
return nil, err
}
if err := newFileInsideTar(out, "./md5sums", md5sums, info.MTime); err != nil {
return nil, err
}
if err := newFileInsideTar(out, "./conffiles", conffiles(info), info.MTime); err != nil {
return nil, err
}
filesToCreateContent := [][]byte{
body.Bytes(),
md5sums,
conffiles(info),
}
triggers := createTriggers(info)
if len(triggers) > 0 {
filesToCreateNames = append(filesToCreateNames, "./triggers")
filesToCreateContent = append(filesToCreateContent, triggers)
}
for idx, name := range filesToCreateNames {
content := filesToCreateContent[idx]
if err := newFileInsideTar(out, name, content); err != nil {
if triggers := createTriggers(info); len(triggers) > 0 {
if err := newFileInsideTar(out, "./triggers", triggers, info.MTime); err != nil {
return nil, err
}
}
@ -585,41 +576,50 @@ func createControl(instSize int64, md5sums []byte, info *nfpm.Info) (controlTarG
mode int64
}
specialFiles := map[string]*fileAndMode{}
specialFiles[info.Scripts.PreInstall] = &fileAndMode{
fileName: "preinst",
mode: 0o755,
}
specialFiles[info.Scripts.PostInstall] = &fileAndMode{
fileName: "postinst",
mode: 0o755,
}
specialFiles[info.Scripts.PreRemove] = &fileAndMode{
fileName: "prerm",
mode: 0o755,
}
specialFiles[info.Scripts.PostRemove] = &fileAndMode{
fileName: "postrm",
mode: 0o755,
}
specialFiles[info.Overridables.Deb.Scripts.Rules] = &fileAndMode{
fileName: "rules",
mode: 0o755,
}
specialFiles[info.Overridables.Deb.Scripts.Templates] = &fileAndMode{
fileName: "templates",
mode: 0o644,
}
specialFiles[info.Overridables.Deb.Scripts.Config] = &fileAndMode{
fileName: "config",
mode: 0o755,
specialFiles := map[string]*fileAndMode{
"preinst": {
fileName: info.Scripts.PreInstall,
mode: 0o755,
},
"postinst": {
fileName: info.Scripts.PostInstall,
mode: 0o755,
},
"prerm": {
fileName: info.Scripts.PreRemove,
mode: 0o755,
},
"postrm": {
fileName: info.Scripts.PostRemove,
mode: 0o755,
},
"rules": {
fileName: info.Overridables.Deb.Scripts.Rules,
mode: 0o755,
},
"templates": {
fileName: info.Overridables.Deb.Scripts.Templates,
mode: 0o644,
},
"config": {
fileName: info.Overridables.Deb.Scripts.Config,
mode: 0o755,
},
}
for path, destMode := range specialFiles {
if path != "" {
if err := newFilePathInsideTar(out, path, destMode.fileName, destMode.mode); err != nil {
return nil, err
}
for _, filename := range maps.Keys(specialFiles) {
dets := specialFiles[filename]
if dets.fileName == "" {
continue
}
if err := newFilePathInsideTar(
out,
dets.fileName,
filename,
dets.mode,
info.MTime,
); err != nil {
return nil, err
}
}
@ -642,18 +642,18 @@ func newItemInsideTar(out *tar.Writer, content []byte, header *tar.Header) error
return nil
}
func newFileInsideTar(out *tar.Writer, name string, content []byte) error {
func newFileInsideTar(out *tar.Writer, name string, content []byte, modtime time.Time) error {
return newItemInsideTar(out, content, &tar.Header{
Name: files.AsExplicitRelativePath(name),
Size: int64(len(content)),
Mode: 0o644,
ModTime: time.Unix(0, 0),
ModTime: modtime,
Typeflag: tar.TypeReg,
Format: tar.FormatGNU,
})
}
func newFilePathInsideTar(out *tar.Writer, path, dest string, mode int64) error {
func newFilePathInsideTar(out *tar.Writer, path, dest string, mode int64, modtime time.Time) error {
file, err := os.Open(path) //nolint:gosec
if err != nil {
return err
@ -666,7 +666,7 @@ func newFilePathInsideTar(out *tar.Writer, path, dest string, mode int64) error
Name: files.AsExplicitRelativePath(dest),
Size: int64(len(content)),
Mode: mode,
ModTime: time.Unix(0, 0),
ModTime: modtime,
Typeflag: tar.TypeReg,
Format: tar.FormatGNU,
})

View File

@ -16,6 +16,7 @@ import (
"strconv"
"strings"
"testing"
"time"
"github.com/blakesmith/ar"
"github.com/goreleaser/chglog"
@ -30,6 +31,8 @@ import (
// nolint: gochecknoglobals
var update = flag.Bool("update", false, "update .golden files")
var mtime = time.Date(2023, 11, 5, 23, 15, 17, 0, time.UTC)
func exampleInfo() *nfpm.Info {
return nfpm.WithDefaults(&nfpm.Info{
Name: "foo",
@ -269,8 +272,8 @@ func TestSpecialFiles(t *testing.T) {
var w bytes.Buffer
out := tar.NewWriter(&w)
filePath := "testdata/templates.golden"
require.Error(t, newFilePathInsideTar(out, "doesnotexit", "templates", 0o644))
require.NoError(t, newFilePathInsideTar(out, filePath, "templates", 0o644))
require.Error(t, newFilePathInsideTar(out, "doesnotexit", "templates", 0o644, mtime))
require.NoError(t, newFilePathInsideTar(out, filePath, "templates", 0o644, mtime))
in := tar.NewReader(&w)
header, err := in.Next()
require.NoError(t, err)

View File

@ -105,7 +105,7 @@ func (c Contents) ContainsDestination(dst string) bool {
return false
}
func (c *Content) WithFileInfoDefaults(umask fs.FileMode) *Content {
func (c *Content) WithFileInfoDefaults(umask fs.FileMode, mtime time.Time) *Content {
cc := &Content{
Source: c.Source,
Destination: c.Destination,
@ -129,7 +129,7 @@ func (c *Content) WithFileInfoDefaults(umask fs.FileMode) *Content {
cc.FileInfo.Mode = 0o755
}
if (cc.Type == TypeDir || cc.Type == TypeImplicitDir) && cc.FileInfo.MTime.IsZero() {
cc.FileInfo.MTime = time.Now()
cc.FileInfo.MTime = mtime
}
// determine if we still need info
@ -152,7 +152,7 @@ func (c *Content) WithFileInfoDefaults(umask fs.FileMode) *Content {
}
if cc.FileInfo.MTime.IsZero() {
cc.FileInfo.MTime = time.Now().UTC()
cc.FileInfo.MTime = mtime
}
return cc
}
@ -234,7 +234,13 @@ func (c *Content) String() string {
//
// If no packager is specified, only the files that are relevant for any
// packager are considered.
func PrepareForPackager(rawContents Contents, umask fs.FileMode, packager string, disableGlobbing bool) (Contents, error) {
func PrepareForPackager(
rawContents Contents,
umask fs.FileMode,
packager string,
disableGlobbing bool,
mtime time.Time,
) (Contents, error) {
contentMap := make(map[string]*Content)
for _, content := range rawContents {
@ -250,12 +256,12 @@ func PrepareForPackager(rawContents Contents, umask fs.FileMode, packager string
return nil, contentCollisionError(content, presentContent)
}
err := addParents(contentMap, content.Destination)
err := addParents(contentMap, content.Destination, mtime)
if err != nil {
return nil, err
}
cc := content.WithFileInfoDefaults(umask)
cc := content.WithFileInfoDefaults(umask, mtime)
cc.Source = ToNixPath(cc.Source)
cc.Destination = NormalizeAbsoluteDirPath(cc.Destination)
contentMap[cc.Destination] = cc
@ -269,17 +275,17 @@ func PrepareForPackager(rawContents Contents, umask fs.FileMode, packager string
return nil, contentCollisionError(content, presentContent)
}
err := addParents(contentMap, content.Destination)
err := addParents(contentMap, content.Destination, mtime)
if err != nil {
return nil, err
}
cc := content.WithFileInfoDefaults(umask)
cc := content.WithFileInfoDefaults(umask, mtime)
cc.Source = ToNixPath(cc.Source)
cc.Destination = NormalizeAbsoluteFilePath(cc.Destination)
contentMap[cc.Destination] = cc
case TypeTree:
err := addTree(contentMap, content, umask)
err := addTree(contentMap, content, umask, mtime)
if err != nil {
return nil, fmt.Errorf("add tree: %w", err)
}
@ -293,8 +299,7 @@ func PrepareForPackager(rawContents Contents, umask fs.FileMode, packager string
return nil, err
}
err = addGlobbedFiles(contentMap, globbed, content, umask)
if err != nil {
if err := addGlobbedFiles(contentMap, globbed, content, umask, mtime); err != nil {
return nil, fmt.Errorf("add globbed files from %q: %w", content.Source, err)
}
default:
@ -336,7 +341,7 @@ func isRelevantForPackager(packager string, content *Content) bool {
return true
}
func addParents(contentMap map[string]*Content, path string) error {
func addParents(contentMap map[string]*Content, path string, mtime time.Time) error {
for _, parent := range sortedParents(path) {
parent = NormalizeAbsoluteDirPath(parent)
// check for content collision and just overwrite previously created
@ -362,7 +367,7 @@ func addParents(contentMap map[string]*Content, path string) error {
Owner: "root",
Group: "root",
Mode: 0o755,
MTime: time.Now(),
MTime: mtime,
},
}
}
@ -390,7 +395,13 @@ func sortedParents(dst string) []string {
return paths
}
func addGlobbedFiles(all map[string]*Content, globbed map[string]string, origFile *Content, umask fs.FileMode) error {
func addGlobbedFiles(
all map[string]*Content,
globbed map[string]string,
origFile *Content,
umask fs.FileMode,
mtime time.Time,
) error {
for src, dst := range globbed {
dst = NormalizeAbsoluteFilePath(dst)
presentContent, destinationOccupied := all[dst]
@ -400,8 +411,7 @@ func addGlobbedFiles(all map[string]*Content, globbed map[string]string, origFil
return contentCollisionError(&c, presentContent)
}
err := addParents(all, dst)
if err != nil {
if err := addParents(all, dst, mtime); err != nil {
return err
}
@ -419,7 +429,7 @@ func addGlobbedFiles(all map[string]*Content, globbed map[string]string, origFil
Type: origFile.Type,
FileInfo: newFileInfo,
Packager: origFile.Packager,
}).WithFileInfoDefaults(umask)
}).WithFileInfoDefaults(umask, mtime)
if dst, err := os.Readlink(src); err == nil {
newFile.Source = dst
newFile.Type = TypeSymlink
@ -431,7 +441,12 @@ func addGlobbedFiles(all map[string]*Content, globbed map[string]string, origFil
return nil
}
func addTree(all map[string]*Content, tree *Content, umask os.FileMode) error {
func addTree(
all map[string]*Content,
tree *Content,
umask os.FileMode,
mtime time.Time,
) error {
if tree.Destination != "/" && tree.Destination != "" {
presentContent, destinationOccupied := all[NormalizeAbsoluteDirPath(tree.Destination)]
if destinationOccupied && presentContent.Type != TypeImplicitDir {
@ -439,7 +454,7 @@ func addTree(all map[string]*Content, tree *Content, umask os.FileMode) error {
}
}
err := addParents(all, tree.Destination)
err := addParents(all, tree.Destination, mtime)
if err != nil {
return err
}
@ -491,7 +506,7 @@ func addTree(all map[string]*Content, tree *Content, umask os.FileMode) error {
}
}
all[c.Destination] = c.WithFileInfoDefaults(umask)
all[c.Destination] = c.WithFileInfoDefaults(umask, mtime)
return nil
})

View File

@ -17,6 +17,8 @@ import (
"gopkg.in/yaml.v3"
)
var mtime = time.Date(2023, 11, 5, 23, 15, 17, 0, time.UTC)
type testStruct struct {
Contents files.Contents `yaml:"contents"`
}
@ -68,7 +70,13 @@ contents:
err := dec.Decode(&config)
require.NoError(t, err)
require.Len(t, config.Contents, 3)
parsedContents, err := files.PrepareForPackager(config.Contents, 0o133, "", false)
parsedContents, err := files.PrepareForPackager(
config.Contents,
0o133,
"",
false,
mtime,
)
require.NoError(t, err)
for _, c := range parsedContents {
switch c.Source {
@ -99,7 +107,13 @@ contents:
err := dec.Decode(&config)
require.NoError(t, err)
require.Len(t, config.Contents, 1)
parsedContents, err := files.PrepareForPackager(config.Contents, 0, "", true)
parsedContents, err := files.PrepareForPackager(
config.Contents,
0,
"",
true,
mtime,
)
require.NoError(t, err)
present := false
@ -129,7 +143,13 @@ contents:
err := dec.Decode(&config)
require.NoError(t, err)
config.Contents, err = files.PrepareForPackager(config.Contents, 0, "", true)
config.Contents, err = files.PrepareForPackager(
config.Contents,
0,
"",
true,
mtime,
)
require.NoError(t, err)
require.Len(t, config.Contents, 1)
@ -159,7 +179,13 @@ contents:
err := dec.Decode(&config)
require.NoError(t, err)
config.Contents, err = files.PrepareForPackager(config.Contents, 0, "rpm", true)
config.Contents, err = files.PrepareForPackager(
config.Contents,
0,
"rpm",
true,
mtime,
)
require.NoError(t, err)
require.Len(t, config.Contents, 1)
@ -191,7 +217,13 @@ contents:
err := dec.Decode(&config)
require.NoError(t, err)
config.Contents, err = files.PrepareForPackager(config.Contents, 0, "", true)
config.Contents, err = files.PrepareForPackager(
config.Contents,
0,
"",
true,
mtime,
)
require.NoError(t, err)
config.Contents = withoutFileInfo(config.Contents)
@ -251,7 +283,13 @@ contents:
wg.Add(1)
go func() {
defer wg.Done()
_, err := files.PrepareForPackager(config.Contents, 0, "", false)
_, err := files.PrepareForPackager(
config.Contents,
0,
"",
false,
mtime,
)
require.NoError(t, err)
}()
}
@ -265,7 +303,13 @@ func TestCollision(t *testing.T) {
{Source: "../testdata/whatever2.conf", Destination: "/samedestination"},
}
_, err := files.PrepareForPackager(configuredFiles, 0, "", true)
_, err := files.PrepareForPackager(
configuredFiles,
0,
"",
true,
mtime,
)
require.ErrorIs(t, err, files.ErrContentCollision)
})
@ -275,7 +319,13 @@ func TestCollision(t *testing.T) {
{Source: "../testdata/whatever2.conf", Destination: "/samedestination", Packager: "bar"},
}
_, err := files.PrepareForPackager(configuredFiles, 0, "foo", true)
_, err := files.PrepareForPackager(
configuredFiles,
0,
"foo",
true,
mtime,
)
require.NoError(t, err)
})
@ -285,7 +335,13 @@ func TestCollision(t *testing.T) {
{Source: "../testdata/whatever2.conf", Destination: "/samedestination", Packager: ""},
}
_, err := files.PrepareForPackager(configuredFiles, 0, "foo", true)
_, err := files.PrepareForPackager(
configuredFiles,
0,
"foo",
true,
mtime,
)
require.ErrorIs(t, err, files.ErrContentCollision)
})
}
@ -316,7 +372,13 @@ func TestDisableGlobbing(t *testing.T) {
content := testCase
t.Run(strconv.Itoa(i), func(t *testing.T) {
result, err := files.PrepareForPackager(files.Contents{&content}, 0, "", disableGlobbing)
result, err := files.PrepareForPackager(
files.Contents{&content},
0,
"",
disableGlobbing,
mtime,
)
if err != nil {
t.Fatalf("expand content globs: %v", err)
}
@ -358,12 +420,18 @@ func TestGlobbingWhenFilesHaveBrackets(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("doesn't work on windows")
}
result, err := files.PrepareForPackager(files.Contents{
{
Source: "./testdata/\\{test\\}/",
Destination: ".",
result, err := files.PrepareForPackager(
files.Contents{
{
Source: "./testdata/\\{test\\}/",
Destination: ".",
},
},
}, 0, "", false)
0,
"",
false,
mtime,
)
if err != nil {
t.Fatalf("expand content globs: %v", err)
}
@ -396,15 +464,21 @@ func TestGlobbingWhenFilesHaveBrackets(t *testing.T) {
}
func TestGlobbingFilesWithDifferentSizesWithFileInfo(t *testing.T) {
result, err := files.PrepareForPackager(files.Contents{
{
Source: "./testdata/globtest/different-sizes/**/*",
Destination: ".",
FileInfo: &files.ContentFileInfo{
Mode: 0o777,
result, err := files.PrepareForPackager(
files.Contents{
{
Source: "./testdata/globtest/different-sizes/**/*",
Destination: ".",
FileInfo: &files.ContentFileInfo{
Mode: 0o777,
},
},
},
}, 0, "", false)
0,
"",
false,
mtime,
)
if err != nil {
t.Fatalf("expand content globs: %v", err)
}
@ -421,12 +495,18 @@ func TestGlobbingFilesWithDifferentSizesWithFileInfo(t *testing.T) {
}
func TestDestEndsWithSlash(t *testing.T) {
result, err := files.PrepareForPackager(files.Contents{
{
Source: "./testdata/globtest/a.txt",
Destination: "./foo/",
result, err := files.PrepareForPackager(
files.Contents{
{
Source: "./testdata/globtest/a.txt",
Destination: "./foo/",
},
},
}, 0, "", false)
0,
"",
false,
mtime,
)
result = withoutImplicitDirs(result)
require.NoError(t, err)
require.Len(t, result, 1)
@ -443,7 +523,13 @@ contents:
`))
dec.KnownFields(true)
require.NoError(t, dec.Decode(&config))
_, err := files.PrepareForPackager(config.Contents, 0, "", false)
_, err := files.PrepareForPackager(
config.Contents,
0,
"",
false,
mtime,
)
require.EqualError(t, err, "invalid content type: filr")
}
@ -474,17 +560,29 @@ contents:
`))
dec.KnownFields(true)
require.NoError(t, dec.Decode(&config))
_, err := files.PrepareForPackager(config.Contents, 0, "", false)
_, err := files.PrepareForPackager(
config.Contents,
0,
"",
false,
mtime,
)
require.NoError(t, err)
}
func TestImplicitDirectories(t *testing.T) {
results, err := files.PrepareForPackager(files.Contents{
{
Source: "./testdata/globtest/a.txt",
Destination: "./foo/bar/baz",
results, err := files.PrepareForPackager(
files.Contents{
{
Source: "./testdata/globtest/a.txt",
Destination: "./foo/bar/baz",
},
},
}, 0, "", false)
0,
"",
false,
mtime,
)
require.NoError(t, err)
expected := files.Contents{
@ -557,7 +655,13 @@ func TestRelevantFiles(t *testing.T) {
}
t.Run("deb", func(t *testing.T) {
results, err := files.PrepareForPackager(contents, 0, "deb", false)
results, err := files.PrepareForPackager(
contents,
0,
"deb",
false,
mtime,
)
require.NoError(t, err)
require.Equal(t, files.Contents{
{
@ -580,7 +684,13 @@ func TestRelevantFiles(t *testing.T) {
})
t.Run("rpm", func(t *testing.T) {
results, err := files.PrepareForPackager(contents, 0, "rpm", false)
results, err := files.PrepareForPackager(
contents,
0,
"rpm",
false,
mtime,
)
require.NoError(t, err)
require.Equal(t, files.Contents{
{
@ -623,7 +733,13 @@ func TestRelevantFiles(t *testing.T) {
})
t.Run("apk", func(t *testing.T) {
results, err := files.PrepareForPackager(contents, 0, "apk", false)
results, err := files.PrepareForPackager(
contents,
0,
"apk",
false,
mtime,
)
require.NoError(t, err)
require.Equal(t, files.Contents{
{
@ -636,13 +752,19 @@ func TestRelevantFiles(t *testing.T) {
}
func TestTree(t *testing.T) {
results, err := files.PrepareForPackager(files.Contents{
{
Source: filepath.Join("testdata", "tree"),
Destination: "/base",
Type: files.TypeTree,
results, err := files.PrepareForPackager(
files.Contents{
{
Source: filepath.Join("testdata", "tree"),
Destination: "/base",
Type: files.TypeTree,
},
},
}, 0, "", false)
0,
"",
false,
mtime,
)
require.NoError(t, err)
require.Equal(t, files.Contents{

1
go.mod
View File

@ -23,6 +23,7 @@ require (
github.com/stretchr/testify v1.8.4
github.com/ulikunitz/xz v0.5.11
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
gopkg.in/yaml.v3 v3.0.1
)

4
go.sum
View File

@ -180,6 +180,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@ -237,8 +239,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

13
internal/maps/maps.go Normal file
View File

@ -0,0 +1,13 @@
package maps
import (
"sort"
"golang.org/x/exp/maps"
)
func Keys[T any](m map[string]T) []string {
keys := maps.Keys(m)
sort.Strings(keys)
return keys
}

74
nfpm.go
View File

@ -8,8 +8,10 @@ import (
"io"
"io/fs"
"os"
"strconv"
"strings"
"sync"
"time"
"dario.cat/mergo"
"github.com/AlekSi/pointer"
@ -255,25 +257,26 @@ func (c *Config) expandEnvVars() {
// Info contains information about a single package.
type Info struct {
Overridables `yaml:",inline" json:",inline"`
Name string `yaml:"name" json:"name" jsonschema:"title=package name"`
Arch string `yaml:"arch" json:"arch" jsonschema:"title=target architecture,example=amd64"`
Platform string `yaml:"platform,omitempty" json:"platform,omitempty" jsonschema:"title=target platform,example=linux,default=linux"`
Epoch string `yaml:"epoch,omitempty" json:"epoch,omitempty" jsonschema:"title=version epoch,example=2,default=extracted from version"`
Version string `yaml:"version" json:"version" jsonschema:"title=version,example=v1.0.2,example=2.0.1"`
VersionSchema string `yaml:"version_schema,omitempty" json:"version_schema,omitempty" jsonschema:"title=version schema,enum=semver,enum=none,default=semver"`
Release string `yaml:"release,omitempty" json:"release,omitempty" jsonschema:"title=version release,example=1"`
Prerelease string `yaml:"prerelease,omitempty" json:"prerelease,omitempty" jsonschema:"title=version prerelease,default=extracted from version"`
VersionMetadata string `yaml:"version_metadata,omitempty" json:"version_metadata,omitempty" jsonschema:"title=version metadata,example=git"`
Section string `yaml:"section,omitempty" json:"section,omitempty" jsonschema:"title=package section,example=default"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty" jsonschema:"title=package priority,example=extra"`
Maintainer string `yaml:"maintainer,omitempty" json:"maintainer,omitempty" jsonschema:"title=package maintainer,example=me@example.com"`
Description string `yaml:"description,omitempty" json:"description,omitempty" jsonschema:"title=package description"`
Vendor string `yaml:"vendor,omitempty" json:"vendor,omitempty" jsonschema:"title=package vendor,example=MyCorp"`
Homepage string `yaml:"homepage,omitempty" json:"homepage,omitempty" jsonschema:"title=package homepage,example=https://example.com"`
License string `yaml:"license,omitempty" json:"license,omitempty" jsonschema:"title=package license,example=MIT"`
Changelog string `yaml:"changelog,omitempty" json:"changelog,omitempty" jsonschema:"title=package changelog,example=changelog.yaml,description=see https://github.com/goreleaser/chglog for more details"`
DisableGlobbing bool `yaml:"disable_globbing,omitempty" json:"disable_globbing,omitempty" jsonschema:"title=whether to disable file globbing,default=false"`
Target string `yaml:"-" json:"-"`
Name string `yaml:"name" json:"name" jsonschema:"title=package name"`
Arch string `yaml:"arch" json:"arch" jsonschema:"title=target architecture,example=amd64"`
Platform string `yaml:"platform,omitempty" json:"platform,omitempty" jsonschema:"title=target platform,example=linux,default=linux"`
Epoch string `yaml:"epoch,omitempty" json:"epoch,omitempty" jsonschema:"title=version epoch,example=2,default=extracted from version"`
Version string `yaml:"version" json:"version" jsonschema:"title=version,example=v1.0.2,example=2.0.1"`
VersionSchema string `yaml:"version_schema,omitempty" json:"version_schema,omitempty" jsonschema:"title=version schema,enum=semver,enum=none,default=semver"`
Release string `yaml:"release,omitempty" json:"release,omitempty" jsonschema:"title=version release,example=1"`
Prerelease string `yaml:"prerelease,omitempty" json:"prerelease,omitempty" jsonschema:"title=version prerelease,default=extracted from version"`
VersionMetadata string `yaml:"version_metadata,omitempty" json:"version_metadata,omitempty" jsonschema:"title=version metadata,example=git"`
Section string `yaml:"section,omitempty" json:"section,omitempty" jsonschema:"title=package section,example=default"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty" jsonschema:"title=package priority,example=extra"`
Maintainer string `yaml:"maintainer,omitempty" json:"maintainer,omitempty" jsonschema:"title=package maintainer,example=me@example.com"`
Description string `yaml:"description,omitempty" json:"description,omitempty" jsonschema:"title=package description"`
Vendor string `yaml:"vendor,omitempty" json:"vendor,omitempty" jsonschema:"title=package vendor,example=MyCorp"`
Homepage string `yaml:"homepage,omitempty" json:"homepage,omitempty" jsonschema:"title=package homepage,example=https://example.com"`
License string `yaml:"license,omitempty" json:"license,omitempty" jsonschema:"title=package license,example=MIT"`
Changelog string `yaml:"changelog,omitempty" json:"changelog,omitempty" jsonschema:"title=package changelog,example=changelog.yaml,description=see https://github.com/goreleaser/chglog for more details"`
DisableGlobbing bool `yaml:"disable_globbing,omitempty" json:"disable_globbing,omitempty" jsonschema:"title=whether to disable file globbing,default=false"`
MTime time.Time `yaml:"mtime,omitempty" json:"mtime,omitempty" jsonschema:"title=time to set into the files generated by nFPM"`
Target string `yaml:"-" json:"-"`
}
func (i *Info) Validate() error {
@ -470,7 +473,13 @@ func PrepareForPackager(info *Info, packager string) (err error) {
return ErrFieldEmpty{"version"}
}
info.Contents, err = files.PrepareForPackager(info.Contents, info.Umask, packager, info.DisableGlobbing)
info.Contents, err = files.PrepareForPackager(
info.Contents,
info.Umask,
packager,
info.DisableGlobbing,
info.MTime,
)
return err
}
@ -489,7 +498,13 @@ func Validate(info *Info) (err error) {
}
for packager := range packagers {
_, err := files.PrepareForPackager(info.Contents, info.Umask, packager, info.DisableGlobbing)
_, err := files.PrepareForPackager(
info.Contents,
info.Umask,
packager,
info.DisableGlobbing,
info.MTime,
)
if err != nil {
return err
}
@ -521,7 +536,9 @@ func WithDefaults(info *Info) *Info {
if info.Umask == 0 {
info.Umask = 0o02
}
if info.MTime.IsZero() {
info.MTime = getSourceDateEpoch()
}
switch info.VersionSchema {
case "none":
// No change to the version or prerelease info set in the YAML file
@ -535,6 +552,19 @@ func WithDefaults(info *Info) *Info {
return info
}
func getSourceDateEpoch() time.Time {
now := time.Now().UTC()
epoch := os.Getenv("SOURCE_DATE_EPOCH")
if epoch == "" {
return now
}
sde, err := strconv.ParseInt(epoch, 10, 64)
if err != nil {
return now
}
return time.Unix(sde, 0).UTC()
}
// ErrSigningFailure is returned whenever something went wrong during
// the package signing process. The underlying error can be unwrapped
// and could be crypto-related or something that occurred while adding

View File

@ -5,15 +5,18 @@ import (
"io"
"net/mail"
"os"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"github.com/stretchr/testify/require"
)
var mtime = time.Date(2023, 11, 5, 23, 15, 17, 0, time.UTC)
func TestRegister(t *testing.T) {
format := "TestRegister"
pkgr := &fakePackager{}
@ -97,6 +100,7 @@ func TestDefaults(t *testing.T) {
Version: "2.4.1",
Description: "no description given",
Arch: "arm64",
MTime: mtime,
Overridables: nfpm.Overridables{
Umask: 0o112,
},
@ -107,6 +111,7 @@ func TestDefaults(t *testing.T) {
require.Equal(t, makeinfo(), info)
})
t.Run("none given", func(t *testing.T) {
t.Setenv("SOURCE_DATE_EPOCH", strconv.FormatInt(mtime.Unix(), 10))
got := nfpm.WithDefaults(&nfpm.Info{})
require.Equal(t, nfpm.Info{
Platform: "linux",
@ -114,6 +119,7 @@ func TestDefaults(t *testing.T) {
Version: "0.0.0",
Prerelease: "rc0",
Description: "no description given",
MTime: mtime,
Overridables: nfpm.Overridables{
Umask: 0o002,
},
@ -573,9 +579,9 @@ func TestOverrides(t *testing.T) {
}
t.Run("no_overrides", func(t *testing.T) {
info, err := config.Get("doesnotexist")
pkg, err := config.Get("doesnotexist")
require.NoError(t, err)
require.True(t, reflect.DeepEqual(&config.Info, info))
require.Empty(t, pkg.Depends)
})
}

View File

@ -256,7 +256,7 @@ func buildRPMMeta(info *nfpm.Info) (*rpmpack.RPMMetaData, error) {
Suggests: suggests,
Conflicts: conflicts,
Compressor: info.RPM.Compression,
BuildTime: time.Now(),
BuildTime: info.MTime,
BuildHost: hostname,
}, nil
}
@ -372,7 +372,7 @@ func createFilesInsideRPM(info *nfpm.Info, rpm *rpmpack.RPM) (err error) {
case files.TypeSymlink:
file = asRPMSymlink(content)
case files.TypeDir:
file = asRPMDirectory(content)
file = asRPMDirectory(content, info.MTime)
case files.TypeImplicitDir:
// we don't need to add imlicit directories to RPMs
continue
@ -393,11 +393,11 @@ func createFilesInsideRPM(info *nfpm.Info, rpm *rpmpack.RPM) (err error) {
return nil
}
func asRPMDirectory(content *files.Content) *rpmpack.RPMFile {
func asRPMDirectory(content *files.Content, mtime time.Time) *rpmpack.RPMFile {
return &rpmpack.RPMFile{
Name: content.Destination,
Mode: uint(content.Mode()) | tagDirectory,
MTime: uint32(time.Now().Unix()),
MTime: uint32(mtime.Unix()),
Owner: content.FileInfo.Owner,
Group: content.FileInfo.Group,
}

View File

@ -100,6 +100,13 @@ homepage: https://nfpm.goreleaser.com
# License.
license: MIT
# Date to be used as mtime on internal files.
#
# Default is the value of $SOURCE_DATE_EPOCH (which should be an Unix time),
# or the current time.
# Read more about SOURCE_DATE_EPOCH at https://reproducible-builds.org/docs/source-date-epoch/
mtime: "2009-11-10T23:00:00Z"
# Changelog YAML file, see: https://github.com/goreleaser/chglog
changelog: "changelog.yaml"