diff --git a/docs/configuration.md b/docs/configuration.md index 1ea0a29ae3..91f0d15838 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -147,6 +147,31 @@ For a given build, the environment variables are merged in the following order: - Global `env` - Build `env` (highest precedence) +### Setting build flags and ldflags + +You can specify both `flags` and `ldflags` globally as well as per-build. + +```yaml +flags: +- -v +ldflags: +- -s +builds: +- id: foo + dir: . + main: ./foobar/foo + flags: + - -trimpath # Build will use: -v -trimpath + ldflags: + - -w # Build will use: -s -w +- id: bar + dir: ./bar + main: . +``` + +The values for each `build` will be appended to the global values when creating each build. +Both global and per-build values may use [template parameters](#templating-support). + ### Environment Variables (advanced) For ease of use, backward compatibility and advanced use cases, `ko` supports the following environment variables to diff --git a/pkg/build/gobuild.go b/pkg/build/gobuild.go index 8e51a782e8..c3dc18ee5d 100644 --- a/pkg/build/gobuild.go +++ b/pkg/build/gobuild.go @@ -66,12 +66,13 @@ type GetBase func(context.Context, string) (name.Reference, Result, error) // buildContext provides parameters for a builder function. type buildContext struct { - creationTime v1.Time - ip string - dir string - mergedEnv []string - platform v1.Platform - config Config + creationTime v1.Time + ip string + dir string + mergedEnv []string + mergedFlags []string + mergedLdflags []string + platform v1.Platform } type builder func(context.Context, buildContext) (string, error) @@ -95,6 +96,8 @@ type gobuild struct { trimpath bool buildConfigs map[string]Config env []string + flags []string + ldflags []string platformMatcher *platformMatcher dir string labels map[string]string @@ -118,6 +121,8 @@ type gobuildOpener struct { trimpath bool buildConfigs map[string]Config env []string + flags []string + ldflags []string platforms []string labels map[string]string dir string @@ -147,6 +152,8 @@ func (gbo *gobuildOpener) Open() (Interface, error) { trimpath: gbo.trimpath, buildConfigs: gbo.buildConfigs, env: gbo.env, + flags: gbo.flags, + ldflags: gbo.ldflags, labels: gbo.labels, dir: gbo.dir, platformMatcher: matcher, @@ -830,8 +837,8 @@ func createBuildArgs(ctx context.Context, buildCtx buildContext) ([]string, erro return nil, err } - if len(buildCtx.config.Flags) > 0 { - flags, err := applyTemplating(buildCtx.config.Flags, data) + if len(buildCtx.mergedFlags) > 0 { + flags, err := applyTemplating(buildCtx.mergedFlags, data) if err != nil { return nil, err } @@ -839,8 +846,8 @@ func createBuildArgs(ctx context.Context, buildCtx buildContext) ([]string, erro args = append(args, flags...) } - if len(buildCtx.config.Ldflags) > 0 { - ldflags, err := applyTemplating(buildCtx.config.Ldflags, data) + if len(buildCtx.mergedLdflags) > 0 { + ldflags, err := applyTemplating(buildCtx.mergedLdflags, data) if err != nil { return nil, err } @@ -927,14 +934,25 @@ func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, pl return nil, fmt.Errorf("could not create env for %s: %w", ref.Path(), err) } + // Merge global and build config flags. + var mergedFlags []string + mergedFlags = append(mergedFlags, g.flags...) + mergedFlags = append(mergedFlags, config.Flags...) + + // Merge global and build config ldflags. + var mergedLdflags []string + mergedLdflags = append(mergedLdflags, g.ldflags...) + mergedLdflags = append(mergedLdflags, config.Ldflags...) + // Do the build into a temporary file. file, err := g.build(ctx, buildContext{ - creationTime: g.creationTime, - ip: ref.Path(), - dir: g.dir, - mergedEnv: mergedEnv, - platform: *platform, - config: config, + creationTime: g.creationTime, + ip: ref.Path(), + dir: g.dir, + mergedEnv: mergedEnv, + mergedFlags: mergedFlags, + mergedLdflags: mergedLdflags, + platform: *platform, }) if err != nil { return nil, fmt.Errorf("build: %w", err) diff --git a/pkg/build/gobuild_test.go b/pkg/build/gobuild_test.go index 57b6487209..621a2b8f75 100644 --- a/pkg/build/gobuild_test.go +++ b/pkg/build/gobuild_test.go @@ -915,6 +915,55 @@ func TestGoBuild(t *testing.T) { }) } +func TestGoBuildMergedValues(t *testing.T) { + baseLayers := int64(3) + base, err := random.Image(1024, baseLayers) + if err != nil { + t.Fatalf("random.Image() = %v", err) + } + importpath := "github.com/google/ko" + + creationTime := v1.Time{Time: time.Unix(5000, 0)} + var buildCtx buildContext + ng, err := NewGo( + context.Background(), + "", + WithCreationTime(creationTime), + WithBaseImages(func(context.Context, string) (name.Reference, Result, error) { return baseRef, base, nil }), + withBuilder(func(_ context.Context, b buildContext) (string, error) { + buildCtx = b + return "", errors.New("fake build error") + }), + withSBOMber(fauxSBOM), + WithPlatforms("all"), + WithEnv([]string{"FOO=foo", "BAR=bar"}), + WithFlags([]string{"-v"}), + WithLdflags([]string{"-s"}), + WithConfig(map[string]Config{ + "github.com/google/ko/test": { + Env: StringArray{"FOO=baz"}, + Flags: FlagArray{"-trimpath"}, + Ldflags: StringArray{"-w"}, + }, + }), + ) + require.NoError(t, err) + + // Build and capture the buildContext. + _, err = ng.Build(context.Background(), StrictScheme+filepath.Join(importpath, "test")) + require.ErrorContains(t, err, "fake build error") + require.Equal(t, []string{"-v", "-trimpath"}, buildCtx.mergedFlags) + require.Equal(t, []string{"-s", "-w"}, buildCtx.mergedLdflags) + + envVars := make(map[string]string) + for _, val := range buildCtx.mergedEnv { + kv := strings.SplitN(val, "=", 2) + envVars[kv[0]] = kv[1] + } + require.Equal(t, "baz", envVars["FOO"]) + require.Equal(t, "bar", envVars["BAR"]) +} + func TestGoBuildWithKOCACHE(t *testing.T) { now := time.Now() // current local time sec := now.Unix() diff --git a/pkg/build/options.go b/pkg/build/options.go index c4b4425aec..25c24d4f34 100644 --- a/pkg/build/options.go +++ b/pkg/build/options.go @@ -94,6 +94,22 @@ func WithEnv(env []string) Option { } } +// WithFlags is a functional option for providing a global set of flags across all builds. +func WithFlags(flags []string) Option { + return func(gbo *gobuildOpener) error { + gbo.flags = flags + return nil + } +} + +// WithLdflags is a functional option for providing a global set of ldflags across all builds. +func WithLdflags(ldflags []string) Option { + return func(gbo *gobuildOpener) error { + gbo.ldflags = ldflags + return nil + } +} + // WithPlatforms is a functional option for building certain platforms for // multi-platform base images. To build everything from the base, use "all", // otherwise use a list of platform specs, i.e.: diff --git a/pkg/commands/options/build.go b/pkg/commands/options/build.go index 0fe4c789a7..e40e444323 100644 --- a/pkg/commands/options/build.go +++ b/pkg/commands/options/build.go @@ -50,6 +50,12 @@ type BuildOptions struct { // Env allows setting environment variables globally and applying them to each build. Env []string + // Flags allows setting flags globally and applying them to each build. + Flags []string + + // Ldflags allows setting ldflags globally and applying them to each build. + Ldflags []string + // WorkingDirectory allows for setting the working directory for invocations of the `go` tool. // Empty string means the current working directory. WorkingDirectory string @@ -136,16 +142,22 @@ func (bo *BuildOptions) LoadConfig() error { } } - dp := v.GetStringSlice("defaultPlatforms") - if len(dp) > 0 { + if dp := v.GetStringSlice("defaultPlatforms"); len(dp) > 0 { bo.DefaultPlatforms = dp } - env := v.GetStringSlice("env") - if len(env) > 0 { + if env := v.GetStringSlice("env"); len(env) > 0 { bo.Env = env } + if flags := v.GetStringSlice("flags"); len(flags) > 0 { + bo.Flags = flags + } + + if ldflags := v.GetStringSlice("ldflags"); len(ldflags) > 0 { + bo.Ldflags = ldflags + } + if bo.BaseImage == "" { ref := v.GetString("defaultBaseImage") if _, err := name.ParseReference(ref); err != nil { diff --git a/pkg/commands/options/build_test.go b/pkg/commands/options/build_test.go index 8a6a5fa15a..eec17ae6e8 100644 --- a/pkg/commands/options/build_test.go +++ b/pkg/commands/options/build_test.go @@ -22,6 +22,7 @@ import ( "github.com/google/ko/pkg/build" "github.com/spf13/cobra" + "github.com/stretchr/testify/require" ) func TestDefaultBaseImage(t *testing.T) { @@ -72,14 +73,26 @@ func TestEnv(t *testing.T) { WorkingDirectory: "testdata/config", } err := bo.LoadConfig() - if err != nil { - t.Fatal(err) + require.NoError(t, err) + require.Equal(t, []string{"FOO=bar"}, bo.Env) +} + +func TestFlags(t *testing.T) { + bo := &BuildOptions{ + WorkingDirectory: "testdata/config", } + err := bo.LoadConfig() + require.NoError(t, err) + require.Equal(t, []string{"-tags", "netgo"}, bo.Flags) +} - wantEnv := []string{"FOO=bar"} // matches value in ./testdata/config/.ko.yaml - if !reflect.DeepEqual(bo.Env, wantEnv) { - t.Fatalf("wanted Env %s, got %s", wantEnv, bo.Env) +func TestLDFlags(t *testing.T) { + bo := &BuildOptions{ + WorkingDirectory: "testdata/config", } + err := bo.LoadConfig() + require.NoError(t, err) + require.Equal(t, []string{"-s -w"}, bo.Ldflags) } func TestBuildConfigWithWorkingDirectoryAndDirAndMain(t *testing.T) { diff --git a/pkg/commands/options/testdata/config/.ko.yaml b/pkg/commands/options/testdata/config/.ko.yaml index d1062112d2..44aef29e21 100644 --- a/pkg/commands/options/testdata/config/.ko.yaml +++ b/pkg/commands/options/testdata/config/.ko.yaml @@ -1,3 +1,8 @@ defaultBaseImage: alpine defaultPlatforms: all env: FOO=bar +flags: + - -tags + - netgo +ldflags: + - -s -w diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go index 185577efee..32f33d0f5f 100644 --- a/pkg/commands/resolver.go +++ b/pkg/commands/resolver.go @@ -87,6 +87,8 @@ func gobuildOptions(bo *options.BuildOptions) ([]build.Option, error) { opts := []build.Option{ build.WithBaseImages(getBaseImage(bo)), build.WithEnv(bo.Env), + build.WithFlags(bo.Flags), + build.WithLdflags(bo.Ldflags), build.WithPlatforms(bo.Platforms...), build.WithJobs(bo.ConcurrentBuilds), }