diff --git a/pkg/build/gobuild.go b/pkg/build/gobuild.go index 3b340565f6..eae5a9f3c8 100644 --- a/pkg/build/gobuild.go +++ b/pkg/build/gobuild.go @@ -65,6 +65,7 @@ type gobuild struct { kodataCreationTime v1.Time build builder disableOptimizations bool + trimpath bool buildConfigs map[string]Config platformMatcher *platformMatcher dir string @@ -80,6 +81,7 @@ type gobuildOpener struct { kodataCreationTime v1.Time build builder disableOptimizations bool + trimpath bool buildConfigs map[string]Config platform string labels map[string]string @@ -100,6 +102,7 @@ func (gbo *gobuildOpener) Open() (Interface, error) { kodataCreationTime: gbo.kodataCreationTime, build: gbo.build, disableOptimizations: gbo.disableOptimizations, + trimpath: gbo.trimpath, buildConfigs: gbo.buildConfigs, labels: gbo.labels, dir: gbo.dir, @@ -556,9 +559,10 @@ func createBuildArgs(buildCfg Config) ([]string, error) { } func (g *gobuild) configForImportPath(ip string) Config { - config, ok := g.buildConfigs[ip] - if !ok { - // Apply default build flags in case none were supplied + config := g.buildConfigs[ip] + if g.trimpath { + // The `-trimpath` flag removes file system paths from the resulting binary, to aid reproducibility. + // Ref: https://pkg.go.dev/cmd/go#hdr-Compile_packages_and_dependencies config.Flags = append(config.Flags, "-trimpath") } diff --git a/pkg/build/gobuild_test.go b/pkg/build/gobuild_test.go index ac6fea0e24..4a401ad72a 100644 --- a/pkg/build/gobuild_test.go +++ b/pkg/build/gobuild_test.go @@ -31,6 +31,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" @@ -290,6 +291,102 @@ func TestBuildEnv(t *testing.T) { } } +func TestBuildConfig(t *testing.T) { + tests := []struct { + description string + options []Option + importpath string + expectConfig Config + }{ + { + description: "minimal options", + options: []Option{ + WithBaseImages(nilGetBase), + }, + }, + { + description: "trimpath flag", + options: []Option{ + WithBaseImages(nilGetBase), + WithTrimpath(true), + }, + expectConfig: Config{ + Flags: FlagArray{"-trimpath"}, + }, + }, + { + description: "no trimpath flag", + options: []Option{ + WithBaseImages(nilGetBase), + WithTrimpath(false), + }, + }, + { + description: "build config and trimpath", + options: []Option{ + WithBaseImages(nilGetBase), + WithConfig(map[string]Config{ + "example.com/foo": { + Flags: FlagArray{"-v"}, + }, + }), + WithTrimpath(true), + }, + importpath: "example.com/foo", + expectConfig: Config{ + Flags: FlagArray{"-v", "-trimpath"}, + }, + }, + { + description: "no trimpath overridden by build config flag", + options: []Option{ + WithBaseImages(nilGetBase), + WithConfig(map[string]Config{ + "example.com/bar": { + Flags: FlagArray{"-trimpath"}, + }, + }), + WithTrimpath(false), + }, + importpath: "example.com/bar", + expectConfig: Config{ + Flags: FlagArray{"-trimpath"}, + }, + }, + { + description: "disable optimizations", + options: []Option{ + WithBaseImages(nilGetBase), + WithDisabledOptimizations(), + }, + expectConfig: Config{ + Flags: FlagArray{"-gcflags", "all=-N -l"}, + }, + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + i, err := NewGo(context.Background(), "", test.options...) + if err != nil { + t.Fatalf("NewGo(): unexpected error: %+v", err) + } + gb, ok := i.(*gobuild) + if !ok { + t.Fatal("NewGo() did not return *gobuild{} as expected") + } + config := gb.configForImportPath(test.importpath) + if diff := cmp.Diff(test.expectConfig, config, cmpopts.EquateEmpty(), + cmpopts.SortSlices(func(x, y string) bool { return x < y })); diff != "" { + t.Errorf("%T differ (-got, +want): %s", test.expectConfig, diff) + } + }) + } +} + +func nilGetBase(_ context.Context, _ string) (name.Reference, Result, error) { + return nil, nil, nil +} + // A helper method we use to substitute for the default "build" method. func writeTempFile(_ context.Context, s string, _ string, _ v1.Platform, _ Config) (string, error) { tmpDir, err := ioutil.TempDir("", "ko") diff --git a/pkg/build/options.go b/pkg/build/options.go index 443cdd3e54..da6e804ddf 100644 --- a/pkg/build/options.go +++ b/pkg/build/options.go @@ -54,6 +54,15 @@ func WithDisabledOptimizations() Option { } } +// WithTrimpath is a functional option that controls whether the `-trimpath` +// flag is added to `go build`. +func WithTrimpath(v bool) Option { + return func(gbo *gobuildOpener) error { + gbo.trimpath = v + return nil + } +} + // WithConfig is a functional option for providing GoReleaser Build influenced // build settings for importpaths. // diff --git a/pkg/commands/options/build.go b/pkg/commands/options/build.go index 7b324a2a31..31654d3136 100644 --- a/pkg/commands/options/build.go +++ b/pkg/commands/options/build.go @@ -58,6 +58,12 @@ type BuildOptions struct { InsecureRegistry bool + // Trimpath controls whether ko adds the `-trimpath` flag to `go build` by default. + // The `-trimpath` flags aids in achieving reproducible builds, but it removes path information that is useful for interactive debugging. + // Set this field to `false` and `DisableOptimizations` to `true` if you want to interactively debug the binary in the resulting image. + // `AddBuildOptions()` defaults this field to `true`. + Trimpath bool + // BuildConfigs stores the per-image build config from `.ko.yaml`. BuildConfigs map[string]build.Config } @@ -71,6 +77,7 @@ func AddBuildOptions(cmd *cobra.Command, bo *BuildOptions) { "Which platform to use when pulling a multi-platform base. Format: all | [/[/]][,platform]*") cmd.Flags().StringSliceVar(&bo.Labels, "image-label", []string{}, "Which labels (key=value) to add to the image.") + bo.Trimpath = true } // LoadConfig reads build configuration from defaults, environment variables, and the `.ko.yaml` config file. diff --git a/pkg/commands/options/build_test.go b/pkg/commands/options/build_test.go index a8acfb9674..145fd8599f 100644 --- a/pkg/commands/options/build_test.go +++ b/pkg/commands/options/build_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/google/ko/pkg/build" + "github.com/spf13/cobra" ) func TestDefaultBaseImage(t *testing.T) { @@ -87,3 +88,12 @@ func TestCreateBuildConfigs(t *testing.T) { } } } + +func TestAddBuildOptionsSetsDefaultsForNonFlagOptions(t *testing.T) { + cmd := &cobra.Command{} + bo := &BuildOptions{} + AddBuildOptions(cmd, bo) + if !bo.Trimpath { + t.Error("expected Trimpath=true") + } +} diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go index 22fd75d81f..a2dbbf4fbd 100644 --- a/pkg/commands/resolver.go +++ b/pkg/commands/resolver.go @@ -99,6 +99,7 @@ func gobuildOptions(bo *options.BuildOptions) ([]build.Option, error) { if bo.DisableOptimizations { opts = append(opts, build.WithDisabledOptimizations()) } + opts = append(opts, build.WithTrimpath(bo.Trimpath)) for _, lf := range bo.Labels { parts := strings.SplitN(lf, "=", 2) if len(parts) != 2 {