Repository: google/ko
Pull request #505 (2021-11-17 04:30:46)

Add `Trimpath` build option
Enables programmatic control of adding the `-trimpath` flag to `go build`.

The `-trimpath` flag removes file system paths from the resulting binary. `ko` adds `-trimpath` by default as it aids in achieving reproducible builds.

However, removing file system paths makes interactive debugging more challenging, in particular in mapping source file locations in the IDE to debug information in the binary.

If you set `Trimpath` to `false` to enable interactive debugging, you likely also want to set `DisableOptimizations` to `true` to disable compiler optimizations and inlining.

Reference for `-trimpath`: https://pkg.go.dev/cmd/go#hdr-Compile_packages_and_dependencies

Resolves: #500
Related: #71, #78, https://github.com/GoogleContainerTools/skaffold/issues/6843

codecov-commenter: # [Codecov](https://codecov.io/gh/google/ko/pull/505?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#505](https://codecov.io/gh/google/ko/pull/505?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (60969de) into [main](https://codecov.io/gh/google/ko/commit/08fccaa7896f36554bd0942f39ac5865916f6ec8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (08fccaa) will **increase** coverage by `0.97%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/google/ko/pull/505/graphs/tree.svg?width=650&height=150&src=pr&token=UhqMbLrA3r&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)](https://codecov.io/gh/google/ko/pull/505?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)

```diff
@@            Coverage Diff             @@
##             main     #505      +/-   ##
==========================================
+ Coverage   50.15%   51.12%   +0.97%     
==========================================
  Files          41       41              
  Lines        1944     1950       +6     
==========================================
+ Hits          975      997      +22     
+ Misses        809      794      -15     
+ Partials      160      159       -1     
```


| [Impacted Files](https://codecov.io/gh/google/ko/pull/505?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Coverage Δ | |
|---|---|---|
| [pkg/build/gobuild.go](https://codecov.io/gh/google/ko/pull/505/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-cGtnL2J1aWxkL2dvYnVpbGQuZ28=) | `56.83% <100.00%> (+0.57%)` | :arrow_up: |
| [pkg/build/options.go](https://codecov.io/gh/google/ko/pull/505/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-cGtnL2J1aWxkL29wdGlvbnMuZ28=) | `89.65% <100.00%> (+24.27%)` | :arrow_up: |
| [pkg/commands/options/build.go](https://codecov.io/gh/google/ko/pull/505/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-cGtnL2NvbW1hbmRzL29wdGlvbnMvYnVpbGQuZ28=) | `63.07% <100.00%> (+13.07%)` | :arrow_up: |
| [pkg/commands/resolver.go](https://codecov.io/gh/google/ko/pull/505/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-cGtnL2NvbW1hbmRzL3Jlc29sdmVyLmdv) | `31.18% <100.00%> (+0.34%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/google/ko/pull/505?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/ko/pull/505?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Last update [08fccaa...60969de](https://codecov.io/gh/google/ko/pull/505?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).


Patch:
diff --git a/pkg/build/gobuild.go b/pkg/build/gobuild.go
index 3b340565..eae5a9f3 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/options.go b/pkg/build/options.go
index 443cdd3e..da6e804d 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 7b324a2a..31654d31 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 | <os>[/<arch>[/<variant>]][,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/resolver.go b/pkg/commands/resolver.go
index 22fd75d8..a2dbbf4f 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 {


Test patch:
diff --git a/pkg/build/gobuild_test.go b/pkg/build/gobuild_test.go
index ac6fea0e..4a401ad7 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/commands/options/build_test.go b/pkg/commands/options/build_test.go
index a8acfb96..145fd859 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")
+	}
+}