1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-16 03:52:12 +02:00

feat: support setting mod_timestamp in universalbinary (#4172)

refs #4167

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
Carlos Alexandro Becker 2023-07-13 23:23:41 -03:00 committed by GitHub
parent 52792f4a15
commit 3c1ebe82cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 116 additions and 43 deletions

View File

@ -7,6 +7,8 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"time"
"github.com/caarlos0/go-shellwords"
"github.com/caarlos0/log"
@ -134,10 +136,13 @@ const (
// heavily based on https://github.com/randall77/makefat
func makeUniversalBinary(ctx *context.Context, opts *build.Options, unibin config.UniversalBinary) error {
name, err := tmpl.New(ctx).Apply(unibin.NameTemplate)
if err != nil {
if err := tmpl.New(ctx).ApplyAll(
&unibin.NameTemplate,
&unibin.ModTimestamp,
); err != nil {
return err
}
name := unibin.NameTemplate
opts.Name = name
path := filepath.Join(ctx.Config.Dist, unibin.ID+"_darwin_all", name)
@ -221,6 +226,17 @@ func makeUniversalBinary(ctx *context.Context, opts *build.Options, unibin confi
return fmt.Errorf("failed to close file: %w", err)
}
if unibin.ModTimestamp != "" {
modUnix, err := strconv.ParseInt(unibin.ModTimestamp, 10, 64)
if err != nil {
return err
}
modTime := time.Unix(modUnix, 0)
if err := os.Chtimes(path, modTime, modTime); err != nil {
return fmt.Errorf("failed to change times for %s: %w", path, err)
}
}
extra := map[string]interface{}{}
for k, v := range binaries[0].Extra {
extra[k] = v

View File

@ -2,9 +2,11 @@ package universalbinary
import (
"debug/macho"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
@ -194,6 +196,28 @@ func TestRun(t *testing.T) {
},
})
modTime := time.Now().AddDate(-1, 0, 0).Round(1 * time.Second).UTC()
ctx7 := testctx.NewWithCfg(config.Project{
Dist: dist,
UniversalBinaries: []config.UniversalBinary{
{
ID: "foo",
IDs: []string{"foo"},
NameTemplate: "foo",
ModTimestamp: fmt.Sprintf("%d", modTime.Unix()),
Hooks: config.BuildHookConfig{
Pre: []config.Hook{
{Cmd: "touch " + pre},
},
Post: []config.Hook{
{Cmd: "touch " + post},
{Cmd: `sh -c 'echo "{{ .Name }} {{ .Os }} {{ .Arch }} {{ .Arm }} {{ .Target }} {{ .Ext }}" > {{ .Path }}.post'`, Output: true},
},
},
},
},
})
for arch, path := range paths {
cmd := exec.Command("go", "build", "-o", path, src)
cmd.Env = append(os.Environ(), "GOOS=darwin", "GOARCH="+arch)
@ -218,6 +242,7 @@ func TestRun(t *testing.T) {
ctx2.Artifacts.Add(&art)
ctx5.Artifacts.Add(&art)
ctx6.Artifacts.Add(&art)
ctx7.Artifacts.Add(&art)
ctx4.Artifacts.Add(&artifact.Artifact{
Name: "fake",
Path: path + "wrong",
@ -342,6 +367,24 @@ func TestRun(t *testing.T) {
ctx.Config.UniversalBinaries[0].Hooks.Post = []config.Hook{}
testlib.RequireTemplateError(t, Pipe{}.Run(ctx))
})
t.Run("mod timestamp", func(t *testing.T) {
ctx := ctx7
require.NoError(t, Pipe{}.Run(ctx))
unibins := ctx.Artifacts.Filter(artifact.ByType(artifact.UniversalBinary)).List()
require.Len(t, unibins, 1)
stat, err := os.Stat(unibins[0].Path)
require.NoError(t, err)
require.Equal(t, modTime.Unix(), stat.ModTime().Unix())
})
t.Run("bad mod timestamp", func(t *testing.T) {
ctx := ctx5
ctx.Config.UniversalBinaries[0].ModTimestamp = "not a number"
ctx.Config.UniversalBinaries[0].Hooks.Pre = []config.Hook{}
ctx.Config.UniversalBinaries[0].Hooks.Post = []config.Hook{}
require.ErrorIs(t, Pipe{}.Run(ctx), strconv.ErrSyntax)
})
}
func checkUniversalBinary(tb testing.TB, unibin *artifact.Artifact) {

View File

@ -609,6 +609,7 @@ type UniversalBinary struct {
NameTemplate string `yaml:"name_template,omitempty" json:"name_template,omitempty"`
Replace bool `yaml:"replace,omitempty" json:"replace,omitempty"`
Hooks BuildHookConfig `yaml:"hooks,omitempty" json:"hooks,omitempty"`
ModTimestamp string `yaml:"mod_timestamp,omitempty" json:"mod_timestamp,omitempty"`
}
// UPX allows to compress binaries with `upx`.

View File

@ -183,6 +183,8 @@ builds:
# Set the modified timestamp on the output binary, typically
# you would do this to ensure a build was reproducible. Pass
# empty string to skip modifying the output.
#
# Templates: allowed.
mod_timestamp: "{{ .CommitTimestamp }}"
# Hooks can be used to customize the final binary,

View File

@ -9,52 +9,61 @@ Here's how to use it:
```yaml
# .goreleaser.yaml
universal_binaries:
-
# ID of resulting universal binary.
#
# Default: the project name
id: foo
- # ID of resulting universal binary.
#
# Default: the project name
id: foo
# IDs to use to filter the built binaries.
#
# Default: the value of the id field
# Since: v1.3
ids:
- build1
- build2
# IDs to use to filter the built binaries.
#
# Default: the value of the id field
# Since: v1.3
ids:
- build1
- build2
# Universal binary name.
#
# You will want to change this if you have multiple builds!
#
# Default: '{{ .ProjectName }}'
# Templates: allowed
name_template: '{{.ProjectName}}_{{.Version}}'
# Universal binary name.
#
# You will want to change this if you have multiple builds!
#
# Default: '{{ .ProjectName }}'
# Templates: allowed
name_template: "{{.ProjectName}}_{{.Version}}"
# Whether to remove the previous single-arch binaries from the artifact list.
# If left as false, your end release might have both several macOS archives:
# amd64, arm64 and all.
replace: true
# Whether to remove the previous single-arch binaries from the artifact list.
# If left as false, your end release might have both several macOS archives:
# amd64, arm64 and all.
replace: true
# Hooks can be used to customize the final binary,
# for example, to run generators.
#
# Templates: allowed
hooks:
pre: rice embed-go
post: ./script.sh {{ .Path }}
# Set the modified timestamp on the output binary, typically
# you would do this to ensure a build was reproducible. Pass
# empty string to skip modifying the output.
#
# Templates: allowed.
# Since: v1.20.
mod_timestamp: "{{ .CommitTimestamp }}"
# Hooks can be used to customize the final binary,
# for example, to run generators.
#
# Templates: allowed
hooks:
pre: rice embed-go
post: ./script.sh {{ .Path }}
```
!!! tip
Learn more about the [name template engine](/customization/templates/).
For more info about hooks, see the [build section](/customization/build/#build-hooks).
The minimal configuration for most setups would look like this:
```yaml
# .goreleaser.yml
universal_binaries:
- replace: true
- replace: true
```
That config will join your default build macOS binaries into a Universal Binary,
@ -64,6 +73,7 @@ From there, the `Arch` template variable for this file will be `all`.
You can use the Go template engine to remove it if you'd like.
!!! warning
You'll want to change `name_template` for each `id` you add in universal
binaries, otherwise they'll have the same name.
@ -79,21 +89,22 @@ You can use the Go template engine to remove it if you'd like.
## Naming templates
Most fields that support [templating](/customization/templates/) will also
Most fields that support [templates](/customization/templates/) will also
support the following build details:
<!-- to format the tables, use: https://tabletomarkdown.com/format-markdown-table/ -->
Key |Description
-------|---------------------------------
.Os |`GOOS`, always `darwin`
.Arch |`GOARCH`, always `all`
.Arm |`GOARM`, always empty
.Ext |Extension, always empty
.Target|Build target, always `darwin_all`
.Path |The binary path
.Name |The binary name
| Key | Description |
| ------- | --------------------------------- |
| .Os | `GOOS`, always `darwin` |
| .Arch | `GOARCH`, always `all` |
| .Arm | `GOARM`, always empty |
| .Ext | Extension, always empty |
| .Target | Build target, always `darwin_all` |
| .Path | The binary path |
| .Name | The binary name |
!!! tip
Notice that `.Path` and `.Name` will only be available after they are
evaluated, so they are mostly only useful in the `post` hooks.