diff --git a/apk/apk.go b/apk/apk.go index 702d8f49..33304bc4 100644 --- a/apk/apk.go +++ b/apk/apk.go @@ -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 } } diff --git a/arch/arch.go b/arch/arch.go index 685219a8..d2485744 100644 --- a/arch/arch.go +++ b/arch/arch.go @@ -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 } diff --git a/arch/arch_test.go b/arch/arch_test.go index 2e019dea..3eaa08e3 100644 --- a/arch/arch_test.go +++ b/arch/arch_test.go @@ -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) } } diff --git a/deb/deb.go b/deb/deb.go index afad3d47..66f4a13e 100644 --- a/deb/deb.go +++ b/deb/deb.go @@ -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 } - - filesToCreateContent := [][]byte{ - body.Bytes(), - md5sums, - conffiles(info), + if err := newFileInsideTar(out, "./md5sums", md5sums, info.MTime); err != nil { + return nil, err } - - triggers := createTriggers(info) - if len(triggers) > 0 { - filesToCreateNames = append(filesToCreateNames, "./triggers") - filesToCreateContent = append(filesToCreateContent, triggers) + if err := newFileInsideTar(out, "./conffiles", conffiles(info), info.MTime); err != nil { + return nil, err } - 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, }) diff --git a/deb/deb_test.go b/deb/deb_test.go index 68fbd586..24c30858 100644 --- a/deb/deb_test.go +++ b/deb/deb_test.go @@ -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) diff --git a/files/files.go b/files/files.go index 281f8c51..6b3b898c 100644 --- a/files/files.go +++ b/files/files.go @@ -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 }) diff --git a/files/files_test.go b/files/files_test.go index a674d5dc..52f75884 100644 --- a/files/files_test.go +++ b/files/files_test.go @@ -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{ diff --git a/go.mod b/go.mod index 0e01ae40..07021fb6 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 36ec5241..75b617ca 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/maps/maps.go b/internal/maps/maps.go new file mode 100644 index 00000000..dbee51ab --- /dev/null +++ b/internal/maps/maps.go @@ -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 +} diff --git a/nfpm.go b/nfpm.go index ca374461..9181034d 100644 --- a/nfpm.go +++ b/nfpm.go @@ -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 diff --git a/nfpm_test.go b/nfpm_test.go index 5639fe47..002725d3 100644 --- a/nfpm_test.go +++ b/nfpm_test.go @@ -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) }) } diff --git a/rpm/rpm.go b/rpm/rpm.go index da476559..b07cf156 100644 --- a/rpm/rpm.go +++ b/rpm/rpm.go @@ -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, } diff --git a/www/docs/configuration.md b/www/docs/configuration.md index f99657bb..b7abfbf1 100644 --- a/www/docs/configuration.md +++ b/www/docs/configuration.md @@ -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" diff --git a/www/docs/tips.md b/www/docs/tips.md index d7786b02..aa390a63 100644 --- a/www/docs/tips.md +++ b/www/docs/tips.md @@ -1,29 +1,30 @@ # Tips, Hints and useful information ## General maintainability of your packages -* Try hard to make all files work on all platforms you support. - * Maintaining separate scripts, config, service files, etc for each platform - quickly becomes difficult -* Put as much conditional logic in the pre/post install scripts as possible + +- Try hard to make all files work on all platforms you support. + - Maintaining separate scripts, config, service files, etc for each platform + quickly becomes difficult +- Put as much conditional logic in the pre/post install scripts as possible instead of trying to build it into the nfpm.yaml -* *if* you need to know the packaging system I have found it useful to add a +- _if_ you need to know the packaging system I have found it useful to add a `/etc/path-to-cfg/package.env` that contains `_INSTALLED_FROM=apk|deb|rpm` which can be sourced into the pre/post install/remove scripts -* *if/when* you need to ask questions during the installation process, create an +- _if/when_ you need to ask questions during the installation process, create an `install.sh` || `setup.sh` script that asks those questions and stores the answers as env vars in `/etc/path-to-cfg/package.env` for use by the pre/post install/remove scripts - * If you only need to support deb packages you can use the debconf - template/config feature, but since rpm does not support this I would try to - unify the way you ask questions. + - If you only need to support deb packages you can use the debconf + template/config feature, but since rpm does not support this I would try to + unify the way you ask questions. ## Pre/post install scripts Here are some useful links for how these scripts work in each packager: -* [APK Docs](https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package#install) -* [RPM Docs](https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/) -* [DEB Docs](https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html) +- [APK Docs](https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package#install) +- [RPM Docs](https://docs.fedoraproject.org/en-US/packaging-guidelines/Scriptlets/) +- [DEB Docs](https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html) ### Examples @@ -118,7 +119,6 @@ cleanup -
Example Multi platform (RPM & Deb) post-remove script @@ -163,8 +163,8 @@ esac
- ### Execution order + On upgrade, the scripts are being executed in the following order: 1. `pretrans` of new package (only applies to RPM) @@ -177,25 +177,24 @@ On upgrade, the scripts are being executed in the following order: ## SystemD and upstart/init ### upstart / init -* try to just say no to supporting this, but if you must make sure you have a + +- try to just say no to supporting this, but if you must make sure you have a single script that works on all platforms you need to support. - * as the `post-install` script above does. + - as the `post-install` script above does. ### SystemD -* The docs you find for SystemD are generally for the latest and greatest +- The docs you find for SystemD are generally for the latest and greatest version, and it can be hard to find docs for older versions. - * In the above `post-install` script you see I am doing a SystemD version - check to correct the `ExecStartPre=+...` and `ExecStop=+...` lines -* You should always use + - In the above `post-install` script you see I am doing a SystemD version + check to correct the `ExecStartPre=+...` and `ExecStop=+...` lines +- You should always use [automatic directory creation and environment variables](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#id-1.14.4.3.6.2) - * With the note that only `RuntimeDirectory` is used in SystemD < 231 -* `/bin/bash -c "$(which ...) ...` is a great way to make your single service + - With the note that only `RuntimeDirectory` is used in SystemD < 211 +- `/bin/bash -c "$(which ...) ...` is a great way to make your single service file work on all platforms since RHEL and Debian-based systems have standard - executables in differing locations and complain about `executable path is not - absolute` - * e.g. `/bin/bash -c '$(which mkdir) -p /var/log/your-service'` - + executables in differing locations and complain about `executable path is not absolute` + - e.g. `/bin/bash -c '$(which mkdir) -p /var/log/your-service'` ## Debian packages @@ -222,7 +221,6 @@ You can read more in [lintian's documentation](https://lintian.debian.org/manual If you need a `copyright` file, you can add it using the `contents` directive: - ```yaml # nfpm.yaml contents: @@ -232,4 +230,3 @@ contents: file_info: mode: 0644 ``` -