From b37fc01b36f1a2a590fa79ecb1923266c5d9f00a Mon Sep 17 00:00:00 2001 From: Roger Standridge <9526806+archie2x@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:03:47 -0700 Subject: [PATCH 1/4] -compiler command line argument (e.g. tinygo) Signed-off-by: Roger Standridge <9526806+archie2x@users.noreply.github.com> --- src/cmd/makebb/makebb.go | 7 ++ src/pkg/golang/build.go | 194 +++++++++++++++++++++++++++++++++------ 2 files changed, 173 insertions(+), 28 deletions(-) diff --git a/src/cmd/makebb/makebb.go b/src/cmd/makebb/makebb.go index 05411ce..36a444f 100644 --- a/src/cmd/makebb/makebb.go +++ b/src/cmd/makebb/makebb.go @@ -43,7 +43,14 @@ func main() { l.Printf("Disabling CGO for u-root...") env.CgoEnabled = false } + + err = env.InitCompiler() + if err != nil { + l.Fatal(err) + } + l.Printf("Build environment: %s", env) + l.Printf("Compiler: %s", env.Compiler.VersionOutput) tmpDir := *genDir remove := false diff --git a/src/pkg/golang/build.go b/src/pkg/golang/build.go index ae4ba47..498e705 100644 --- a/src/pkg/golang/build.go +++ b/src/pkg/golang/build.go @@ -6,6 +6,7 @@ package golang import ( + "encoding/json" "flag" "fmt" "go/build" @@ -29,6 +30,15 @@ const ( ModVendor ModBehavior = "vendor" ) +type Compiler struct { + Path string + Identifier string // e.g. 'tinygo' or 'go' + Version string // compiler-tool version + VersionGo string // version of go: same as 'Version' for standard go + VersionOutput string // output of calling 'tool version' + IsInit bool // InitCompiler() succeeded +} + // Environ are the environment variables for the Go compiler. type Environ struct { build.Context @@ -36,6 +46,8 @@ type Environ struct { GO111MODULE string Mod ModBehavior GBBDEBUG bool + + Compiler Compiler } // Copy makes a copy of Environ with the given changes. @@ -45,6 +57,7 @@ func (c *Environ) Copy(opts ...Opt) *Environ { GO111MODULE: c.GO111MODULE, Mod: c.Mod, GBBDEBUG: c.GBBDEBUG, + Compiler: c.Compiler, } e.Apply(opts...) return e @@ -54,6 +67,8 @@ func (c *Environ) Copy(opts ...Opt) *Environ { func (c *Environ) RegisterFlags(f *flag.FlagSet) { f.Var((*uflag.Strings)(&c.BuildTags), "go-build-tags", "Go build tags") f.StringVar((*string)(&c.Mod), "go-mod", string(c.Mod), "Value of -mod to go commands (allowed: (empty), vendor, mod, readonly)") + f.StringVar((*string)(&c.Compiler.Path), "compiler", "", + "override go compiler to use (e.g. \"/path/to/tinygo\")") } // Valid returns an error if GOARCH, GOROOT, or GOOS are unset. @@ -176,13 +191,16 @@ func (c *Environ) Apply(opts ...Opt) { // Lookup looks up packages by patterns relative to dir, using the Go environment from c. func (c *Environ) Lookup(mode packages.LoadMode, patterns ...string) ([]*packages.Package, error) { + if err := c.InitCompiler(); err != nil { + return nil, err + } cfg := &packages.Config{ Mode: mode, Env: append(os.Environ(), c.Env()...), Dir: c.Dir, } if len(c.Context.BuildTags) > 0 { - tags := fmt.Sprintf("-tags=%s", strings.Join(c.Context.BuildTags, ",")) + tags := fmt.Sprintf("-tags=%s", strings.Join(c.BuildTags, ",")) cfg.BuildFlags = []string{tags} } if c.GO111MODULE != "off" && len(c.Mod) > 0 { @@ -193,7 +211,10 @@ func (c *Environ) Lookup(mode packages.LoadMode, patterns ...string) ([]*package // GoCmd runs a go command in the environment. func (c Environ) GoCmd(gocmd string, args ...string) *exec.Cmd { - goBin := filepath.Join(c.GOROOT, "bin", "go") + goBin := c.Compiler.Path + if "" == goBin { + goBin = filepath.Join(c.GOROOT, "bin", "go") + } args = append([]string{gocmd}, args...) cmd := exec.Command(goBin, args...) if c.GBBDEBUG { @@ -204,19 +225,107 @@ func (c Environ) GoCmd(gocmd string, args ...string) *exec.Cmd { return cmd } -// Version returns the Go version string that runtime.Version would return for -// the Go compiler in this environ. -func (c Environ) Version() (string, error) { +// resolve absolute path of go-compiler option from PATH +func (c *Environ) resolveCompiler() error { + if c.Compiler.Path != "" { + fname, err := exec.LookPath(string(c.Compiler.Path)) + if err == nil { + fname, err = filepath.Abs(fname) + } + if err != nil { + return fmt.Errorf("build: %v", err) + } + c.Compiler.Path = fname + } + return nil +} + +// runs GoCmd("version") and parse/caches output, to environ.Compiler +func (c *Environ) InitCompiler() error { + + if c.Compiler.IsInit { + return nil + } + + c.resolveCompiler() + cmd := c.GoCmd("version") v, err := cmd.CombinedOutput() if err != nil { - return "", err + return err } + + efmt := "go-compiler 'version' output unrecognized: %v" s := strings.Fields(string(v)) - if len(s) < 3 { - return "", fmt.Errorf("unknown go version, tool returned weird output for 'go version': %v", string(v)) + if len(s) < 1 { + return fmt.Errorf(efmt, string(v)) } - return s[2], nil + + compiler := c.Compiler + compiler.Identifier = s[0] + compiler.VersionOutput = string(v) + compiler.IsInit = true + + switch compiler.Identifier { + + case "go": + if len(s) < 3 { + return fmt.Errorf(efmt, string(v)) + } + compiler.Version = s[2] + compiler.VersionGo = s[2] + + case "tinygo": + // e.g. "tinygo version 0.33.0 darwin/arm64 (using go version go1.22.2 and LLVM version 18.1.2)" + if len(s) < 8 { + return fmt.Errorf(efmt, string(v)) + } + compiler.Version = s[2] + compiler.VersionGo = s[7] + + // Fetch additional go-build-tags from tinygo + // package fetch needs correct tags to prune + cmd := c.GoCmd("info", "-json") + infov, err := cmd.CombinedOutput() + if err != nil { + return err + } + var info map[string]interface{} + err = json.Unmarshal(infov, &info) + if err != nil { + return err + } + tags := []string{} + unique := make(map[string]bool) + addUnique := func(tag string) { + if unique[tag] { + return + } + unique[tag] = true + tags = append(tags, tag) + } + for _, tag := range c.BuildTags { + addUnique(tag) + } + for _, tag := range info["build_tags"].([]interface{}) { + addUnique(tag.(string)) + } + c.BuildTags = tags + + default: + return fmt.Errorf(efmt, string(v)) + } + c.Compiler = compiler + return nil +} + +// Version returns the Go version string that runtime.Version would return for +// the Go compiler in this environ. +func (c *Environ) Version() (string, error) { + if err := c.InitCompiler(); err != nil { + return "", err + } + return c.Compiler.VersionGo, nil } func (c Environ) envCommon() []string { @@ -301,40 +410,69 @@ func (b *BuildOpts) RegisterFlags(f *flag.FlagSet) { } func (c Environ) build(dirPath string, binaryPath string, pattern []string, opts *BuildOpts) error { - args := []string{ - // Force rebuilding of packages. - "-a", + if err := c.InitCompiler(); err != nil { + return err + } + + args := []string{ "-o", binaryPath, } + if c.GO111MODULE != "off" && len(c.Mod) > 0 { args = append(args, "-mod", string(c.Mod)) } if c.InstallSuffix != "" { args = append(args, "-installsuffix", c.Context.InstallSuffix) } - if opts == nil || !opts.EnableInlining { - // Disable "function inlining" to get a (likely) smaller binary. - args = append(args, "-gcflags=all=-l") - } - if opts == nil || !opts.NoStrip { - // Strip all symbols, and don't embed a Go build ID to be reproducible. - args = append(args, "-ldflags", "-s -w -buildid=") + + switch c.Compiler.Identifier { + case "go": + + // Force rebuilding of packages. + args = append(args, "-a") + + if opts == nil || !opts.EnableInlining { + // Disable "function inlining" to get a (likely) smaller binary. + args = append(args, "-gcflags=all=-l") + } + + if opts == nil || !opts.NoStrip { + // Strip all symbols, and don't embed a Go build ID to be reproducible. + args = append(args, "-ldflags", "-s -w -buildid=") + } + + if opts == nil || !opts.NoTrimPath { + // Reproducible builds: Trim any GOPATHs out of the executable's + // debugging information. + // + // E.g. Trim /tmp/bb-*/ from /tmp/bb-12345567/src/github.com/... + args = append(args, "-trimpath") + } + + case "tinygo": + + // TODO: handle force-rebuild of packages (-a to standard go) + // TODO: handle EnableInlining + + // Strip all symbols. TODO: not sure about buildid + if opts == nil || !opts.NoStrip { + // Strip all symbols + args = append(args, "-no-debug") + } + + // TODO: handle NoTrimpPath + } - if opts == nil || !opts.NoTrimPath { - // Reproducible builds: Trim any GOPATHs out of the executable's - // debugging information. - // - // E.g. Trim /tmp/bb-*/ from /tmp/bb-12345567/src/github.com/... - args = append(args, "-trimpath") + + if len(c.BuildTags) > 0 { + args = append(args, fmt.Sprintf("-tags=%s", strings.Join(c.BuildTags, ","))) } + if opts != nil { args = append(args, opts.ExtraArgs...) } - if len(c.BuildTags) > 0 { - args = append(args, []string{"-tags", strings.Join(c.BuildTags, " ")}...) - } args = append(args, pattern...) cmd := c.GoCmd("build", args...) From ee82c9a83f1e14ff121f658c0daafdca2918481c Mon Sep 17 00:00:00 2001 From: Roger Standridge <9526806+archie2x@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:21:42 -0700 Subject: [PATCH 2/4] add golang.WithCompiler() to set compiler on existing env Signed-off-by: Roger Standridge <9526806+archie2x@users.noreply.github.com> --- src/pkg/golang/build.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pkg/golang/build.go b/src/pkg/golang/build.go index 498e705..9c01c57 100644 --- a/src/pkg/golang/build.go +++ b/src/pkg/golang/build.go @@ -168,6 +168,13 @@ func WithWorkingDir(wd string) Opt { } } +// WithCompiler sets the compiler for Build() / BuildDir() +func WithCompiler(p string) Opt { + return func(c *Environ) { + c.Compiler.Path = p + } +} + // Default is the default build environment comprised of the default GOPATH, // GOROOT, GOOS, GOARCH, and CGO_ENABLED values. func Default(opts ...Opt) *Environ { From 81f0c369e30627d21008dfa872418aa96de6d55c Mon Sep 17 00:00:00 2001 From: Roger Standridge <9526806+archie2x@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:15:01 -0700 Subject: [PATCH 3/4] Address CRs Signed-off-by: Roger Standridge <9526806+archie2x@users.noreply.github.com> --- src/cmd/makebb/makebb.go | 2 +- src/pkg/golang/build.go | 224 +-------------------------------- src/pkg/golang/compiler.go | 250 +++++++++++++++++++++++++++++++++++++ 3 files changed, 257 insertions(+), 219 deletions(-) create mode 100644 src/pkg/golang/compiler.go diff --git a/src/cmd/makebb/makebb.go b/src/cmd/makebb/makebb.go index 36a444f..41fa847 100644 --- a/src/cmd/makebb/makebb.go +++ b/src/cmd/makebb/makebb.go @@ -44,7 +44,7 @@ func main() { env.CgoEnabled = false } - err = env.InitCompiler() + err = env.CompilerInit() if err != nil { l.Fatal(err) } diff --git a/src/pkg/golang/build.go b/src/pkg/golang/build.go index 9c01c57..7e3c4e8 100644 --- a/src/pkg/golang/build.go +++ b/src/pkg/golang/build.go @@ -6,13 +6,10 @@ package golang import ( - "encoding/json" "flag" "fmt" "go/build" - "log" "os" - "os/exec" "path/filepath" "strconv" "strings" @@ -30,15 +27,6 @@ const ( ModVendor ModBehavior = "vendor" ) -type Compiler struct { - Path string - Identifier string // e.g. 'tinygo' or 'go' - Version string // compiler-tool version - VersionGo string // version of go: same as 'Version' for standard go - VersionOutput string // output of calling 'tool version' - IsInit bool // InitCompiler() succeeded -} - // Environ are the environment variables for the Go compiler. type Environ struct { build.Context @@ -68,7 +56,7 @@ func (c *Environ) RegisterFlags(f *flag.FlagSet) { f.Var((*uflag.Strings)(&c.BuildTags), "go-build-tags", "Go build tags") f.StringVar((*string)(&c.Mod), "go-mod", string(c.Mod), "Value of -mod to go commands (allowed: (empty), vendor, mod, readonly)") f.StringVar((*string)(&c.Compiler.Path), "compiler", "", - "override go compiler to use (e.g. \"/path/to/tinygo\")") + "override go compiler used (e.g. \"/path/to/tinygo\")") } // Valid returns an error if GOARCH, GOROOT, or GOOS are unset. @@ -168,13 +156,6 @@ func WithWorkingDir(wd string) Opt { } } -// WithCompiler sets the compiler for Build() / BuildDir() -func WithCompiler(p string) Opt { - return func(c *Environ) { - c.Compiler.Path = p - } -} - // Default is the default build environment comprised of the default GOPATH, // GOROOT, GOOS, GOARCH, and CGO_ENABLED values. func Default(opts ...Opt) *Environ { @@ -198,16 +179,19 @@ func (c *Environ) Apply(opts ...Opt) { // Lookup looks up packages by patterns relative to dir, using the Go environment from c. func (c *Environ) Lookup(mode packages.LoadMode, patterns ...string) ([]*packages.Package, error) { - if err := c.InitCompiler(); err != nil { + + // required to compute compiler build tags + if err := c.CompilerInit(); err != nil { return nil, err } + cfg := &packages.Config{ Mode: mode, Env: append(os.Environ(), c.Env()...), Dir: c.Dir, } if len(c.Context.BuildTags) > 0 { - tags := fmt.Sprintf("-tags=%s", strings.Join(c.BuildTags, ",")) + tags := fmt.Sprintf("-tags=%s", strings.Join(c.Context.BuildTags, ",")) cfg.BuildFlags = []string{tags} } if c.GO111MODULE != "off" && len(c.Mod) > 0 { @@ -216,125 +200,6 @@ func (c *Environ) Lookup(mode packages.LoadMode, patterns ...string) ([]*package return packages.Load(cfg, patterns...) } -// GoCmd runs a go command in the environment. -func (c Environ) GoCmd(gocmd string, args ...string) *exec.Cmd { - goBin := c.Compiler.Path - if "" == goBin { - goBin = filepath.Join(c.GOROOT, "bin", "go") - } - args = append([]string{gocmd}, args...) - cmd := exec.Command(goBin, args...) - if c.GBBDEBUG { - log.Printf("GBB Go invocation: %s %s %#v", c, goBin, args) - } - cmd.Dir = c.Dir - cmd.Env = append(os.Environ(), c.Env()...) - return cmd -} - -// resolve absolute path of go-compiler option from PATH -func (c *Environ) resolveCompiler() error { - if c.Compiler.Path != "" { - fname, err := exec.LookPath(string(c.Compiler.Path)) - if err == nil { - fname, err = filepath.Abs(fname) - } - if err != nil { - return fmt.Errorf("build: %v", err) - } - c.Compiler.Path = fname - } - return nil -} - -// runs GoCmd("version") and parse/caches output, to environ.Compiler -func (c *Environ) InitCompiler() error { - - if c.Compiler.IsInit { - return nil - } - - c.resolveCompiler() - - cmd := c.GoCmd("version") - v, err := cmd.CombinedOutput() - if err != nil { - return err - } - - efmt := "go-compiler 'version' output unrecognized: %v" - s := strings.Fields(string(v)) - if len(s) < 1 { - return fmt.Errorf(efmt, string(v)) - } - - compiler := c.Compiler - compiler.Identifier = s[0] - compiler.VersionOutput = string(v) - compiler.IsInit = true - - switch compiler.Identifier { - - case "go": - if len(s) < 3 { - return fmt.Errorf(efmt, string(v)) - } - compiler.Version = s[2] - compiler.VersionGo = s[2] - - case "tinygo": - // e.g. "tinygo version 0.33.0 darwin/arm64 (using go version go1.22.2 and LLVM version 18.1.2)" - if len(s) < 8 { - return fmt.Errorf(efmt, string(v)) - } - compiler.Version = s[2] - compiler.VersionGo = s[7] - - // Fetch additional go-build-tags from tinygo - // package fetch needs correct tags to prune - cmd := c.GoCmd("info", "-json") - infov, err := cmd.CombinedOutput() - if err != nil { - return err - } - var info map[string]interface{} - err = json.Unmarshal(infov, &info) - if err != nil { - return err - } - tags := []string{} - unique := make(map[string]bool) - addUnique := func(tag string) { - if unique[tag] { - return - } - unique[tag] = true - tags = append(tags, tag) - } - for _, tag := range c.BuildTags { - addUnique(tag) - } - for _, tag := range info["build_tags"].([]interface{}) { - addUnique(tag.(string)) - } - c.BuildTags = tags - - default: - return fmt.Errorf(efmt, string(v)) - } - c.Compiler = compiler - return nil -} - -// Version returns the Go version string that runtime.Version would return for -// the Go compiler in this environ. -func (c *Environ) Version() (string, error) { - if err := c.InitCompiler(); err != nil { - return "", err - } - return c.Compiler.VersionGo, nil -} - func (c Environ) envCommon() []string { var env []string if c.GOARCH != "" { @@ -416,83 +281,6 @@ func (b *BuildOpts) RegisterFlags(f *flag.FlagSet) { f.Var((*uflag.Strings)(&b.ExtraArgs), "go-extra-args", "Extra args to 'go build'") } -func (c Environ) build(dirPath string, binaryPath string, pattern []string, opts *BuildOpts) error { - - if err := c.InitCompiler(); err != nil { - return err - } - - args := []string{ - "-o", binaryPath, - } - - if c.GO111MODULE != "off" && len(c.Mod) > 0 { - args = append(args, "-mod", string(c.Mod)) - } - if c.InstallSuffix != "" { - args = append(args, "-installsuffix", c.Context.InstallSuffix) - } - - switch c.Compiler.Identifier { - case "go": - - // Force rebuilding of packages. - args = append(args, "-a") - - if opts == nil || !opts.EnableInlining { - // Disable "function inlining" to get a (likely) smaller binary. - args = append(args, "-gcflags=all=-l") - } - - if opts == nil || !opts.NoStrip { - // Strip all symbols, and don't embed a Go build ID to be reproducible. - args = append(args, "-ldflags", "-s -w -buildid=") - } - - if opts == nil || !opts.NoTrimPath { - // Reproducible builds: Trim any GOPATHs out of the executable's - // debugging information. - // - // E.g. Trim /tmp/bb-*/ from /tmp/bb-12345567/src/github.com/... - args = append(args, "-trimpath") - } - - case "tinygo": - - // TODO: handle force-rebuild of packages (-a to standard go) - // TODO: handle EnableInlining - - // Strip all symbols. TODO: not sure about buildid - if opts == nil || !opts.NoStrip { - // Strip all symbols - args = append(args, "-no-debug") - } - - // TODO: handle NoTrimpPath - - } - - if len(c.BuildTags) > 0 { - args = append(args, fmt.Sprintf("-tags=%s", strings.Join(c.BuildTags, ","))) - } - - if opts != nil { - args = append(args, opts.ExtraArgs...) - } - - args = append(args, pattern...) - - cmd := c.GoCmd("build", args...) - if dirPath != "" { - cmd.Dir = dirPath - } - - if o, err := cmd.CombinedOutput(); err != nil { - return fmt.Errorf("error building go package in %q: %v, %v", dirPath, string(o), err) - } - return nil -} - // BuildDir compiles the package in the directory `dirPath`, writing the build // object to `binaryPath`. func (c Environ) BuildDir(dirPath string, binaryPath string, opts *BuildOpts) error { diff --git a/src/pkg/golang/compiler.go b/src/pkg/golang/compiler.go new file mode 100644 index 0000000..53066fe --- /dev/null +++ b/src/pkg/golang/compiler.go @@ -0,0 +1,250 @@ +// Copyright 2015-2024 the u-root Authors. All rights reserved +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package golang is an API to the Go compiler. +package golang + +import ( + "encoding/json" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strings" +) + +type CompilerType int + +const ( + CompilerGo CompilerType = iota + CompilerTinygo + CompilerUnkown +) + +// cached information re: the compiler +type Compiler struct { + Path string + Identifier string // e.g. 'tinygo' or 'go' + Type CompilerType + Version string // compiler-tool version, e.g. '0.32.0' for tinygo, go1.22.2 for + VersionGo string // version of go: same as 'Version' for standard go + VersionOutput string // output of calling 'tool version' + IsInit bool // CompilerInit() succeeded +} + +// map the compiler's identifier ("tinygo" or "go") to enum +func CompilerTypeFromString(name string) CompilerType { + val, ok := map[string]CompilerType{ + "go": CompilerGo, + "tinygo": CompilerTinygo, + }[name] + if ok { + return val + } + return CompilerUnkown +} + +// WithCompiler sets the compiler for Build() / BuildDir() +func WithCompiler(p string) Opt { + return func(c *Environ) { + c.Compiler.Path = p + c.Compiler.IsInit = false + } +} + +// compilerCmd returns a compiler command to be run in the environment. +func (c Environ) compilerCmd(gocmd string, args ...string) *exec.Cmd { + goBin := c.Compiler.Path + if "" == goBin { + goBin = filepath.Join(c.GOROOT, "bin", "go") + } + args = append([]string{gocmd}, args...) + cmd := exec.Command(goBin, args...) + if c.GBBDEBUG { + log.Printf("GBB Go invocation: %s %s %#v", c, goBin, args) + } + cmd.Dir = c.Dir + cmd.Env = append(os.Environ(), c.Env()...) + return cmd +} + +// if go-compiler specified, resolve absolute-path from PATH, +// otherwise return 'nil'. +func (c *Environ) compilerAbs() error { + if c.Compiler.Path != "" { + fname, err := exec.LookPath(string(c.Compiler.Path)) + if err == nil { + fname, err = filepath.Abs(fname) + } + if err != nil { + return fmt.Errorf("build: %v", err) + } + c.Compiler.Path = fname + } + return nil +} + +// runs compilerCmd("version") and parse/caches output, to environ.Compiler +func (c *Environ) CompilerInit() error { + + if c.Compiler.IsInit { + return nil + } + + c.compilerAbs() + + cmd := c.compilerCmd("version") + v, err := cmd.CombinedOutput() + if err != nil { + return err + } + + efmt := "go-compiler 'version' output unrecognized: %v" + s := strings.Fields(string(v)) + if len(s) < 1 { + return fmt.Errorf(efmt, string(v)) + } + + compiler := c.Compiler + compiler.VersionOutput = string(v) + compiler.Identifier = s[0] + compiler.Type = CompilerTypeFromString(compiler.Identifier) + compiler.IsInit = true + + switch compiler.Type { + + case CompilerGo: + if len(s) < 3 { + return fmt.Errorf(efmt, string(v)) + } + compiler.Version = s[2] + compiler.VersionGo = s[2] + + case CompilerTinygo: + // e.g. "tinygo version 0.33.0 darwin/arm64 (using go version go1.22.2 and LLVM version 18.1.2)" + if len(s) < 8 { + return fmt.Errorf(efmt, string(v)) + } + compiler.Version = s[2] + compiler.VersionGo = s[7] + + // Fetch additional go-build-tags from tinygo + // package fetch needs correct tags to prune + cmd := c.compilerCmd("info", "-json") + infov, err := cmd.CombinedOutput() + if err != nil { + return err + } + var info map[string]interface{} + err = json.Unmarshal(infov, &info) + if err != nil { + return err + } + + // extract unique build tags + tags := make(map[string]struct{}) + for _, tag := range c.BuildTags { + tags[tag] = struct{}{} + } + for _, tag := range info["build_tags"].([]interface{}) { + tags[tag.(string)] = struct{}{} + } + for tag := range tags { + c.BuildTags = append(c.BuildTags, tag) + } + + case CompilerUnkown: + return fmt.Errorf(efmt, string(v)) + } + c.Compiler = compiler + return nil +} + +// Version returns the Go version string that runtime.Version would return for +// the Go compiler in this environ. +func (c *Environ) Version() (string, error) { + if err := c.CompilerInit(); err != nil { + return "", err + } + return c.Compiler.VersionGo, nil +} + +func (c Environ) build(dirPath string, binaryPath string, pattern []string, opts *BuildOpts) error { + + if err := c.CompilerInit(); err != nil { + return err + } + + args := []string{ + "-o", binaryPath, + } + + if c.GO111MODULE != "off" && len(c.Mod) > 0 { + args = append(args, "-mod", string(c.Mod)) + } + if c.InstallSuffix != "" { + args = append(args, "-installsuffix", c.Context.InstallSuffix) + } + + switch c.Compiler.Type { + case CompilerGo: + + // Force rebuilding of packages. + args = append(args, "-a") + + if opts == nil || !opts.EnableInlining { + // Disable "function inlining" to get a (likely) smaller binary. + args = append(args, "-gcflags=all=-l") + } + + if opts == nil || !opts.NoStrip { + // Strip all symbols, and don't embed a Go build ID to be reproducible. + args = append(args, "-ldflags", "-s -w -buildid=") + } + + if opts == nil || !opts.NoTrimPath { + // Reproducible builds: Trim any GOPATHs out of the executable's + // debugging information. + // + // E.g. Trim /tmp/bb-*/ from /tmp/bb-12345567/src/github.com/... + args = append(args, "-trimpath") + } + + case CompilerTinygo: + + // TODO: handle force-rebuild of packages (-a to standard go) + // TODO: handle EnableInlining + + // Strip all symbols. TODO: not sure about buildid + if opts == nil || !opts.NoStrip { + // Strip all symbols + args = append(args, "-no-debug") + } + + // TODO: handle NoTrimpPath + + } + + if len(c.BuildTags) > 0 { + args = append(args, fmt.Sprintf("-tags=%s", strings.Join(c.BuildTags, ","))) + } + + if opts != nil { + args = append(args, opts.ExtraArgs...) + } + + args = append(args, pattern...) + + cmd := c.compilerCmd("build", args...) + if dirPath != "" { + cmd.Dir = dirPath + } + + if o, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("error building go package in %q: %v, %v", dirPath, string(o), err) + } + + return nil +} From be6e46842d8f57a5a0b25cbe6ae6f9cb8812135a Mon Sep 17 00:00:00 2001 From: Roger Standridge <9526806+archie2x@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:38:49 -0700 Subject: [PATCH 4/4] More CR. Signed-off-by: Roger Standridge <9526806+archie2x@users.noreply.github.com> --- src/cmd/makebb/makebb.go | 4 ++-- src/pkg/golang/compiler.go | 34 ++++++++++++++++------------------ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/cmd/makebb/makebb.go b/src/cmd/makebb/makebb.go index 41fa847..9738ad7 100644 --- a/src/cmd/makebb/makebb.go +++ b/src/cmd/makebb/makebb.go @@ -49,8 +49,8 @@ func main() { l.Fatal(err) } - l.Printf("Build environment: %s", env) - l.Printf("Compiler: %s", env.Compiler.VersionOutput) + l.Printf("Build environment: %s\n", env) + l.Printf("Compiler: %s\n", env.Compiler.VersionOutput) tmpDir := *genDir remove := false diff --git a/src/pkg/golang/compiler.go b/src/pkg/golang/compiler.go index 53066fe..bfa87e2 100644 --- a/src/pkg/golang/compiler.go +++ b/src/pkg/golang/compiler.go @@ -23,7 +23,7 @@ const ( CompilerUnkown ) -// cached information re: the compiler +// Cached information about the compiler used. type Compiler struct { Path string Identifier string // e.g. 'tinygo' or 'go' @@ -34,7 +34,7 @@ type Compiler struct { IsInit bool // CompilerInit() succeeded } -// map the compiler's identifier ("tinygo" or "go") to enum +// Map the compiler's identifier ("tinygo" or "go") to enum. func CompilerTypeFromString(name string) CompilerType { val, ok := map[string]CompilerType{ "go": CompilerGo, @@ -46,7 +46,7 @@ func CompilerTypeFromString(name string) CompilerType { return CompilerUnkown } -// WithCompiler sets the compiler for Build() / BuildDir() +// Sets the compiler for Build() / BuildDir() functions. func WithCompiler(p string) Opt { return func(c *Environ) { c.Compiler.Path = p @@ -54,7 +54,7 @@ func WithCompiler(p string) Opt { } } -// compilerCmd returns a compiler command to be run in the environment. +// Returns a compiler command to be run in the environment. func (c Environ) compilerCmd(gocmd string, args ...string) *exec.Cmd { goBin := c.Compiler.Path if "" == goBin { @@ -70,8 +70,7 @@ func (c Environ) compilerCmd(gocmd string, args ...string) *exec.Cmd { return cmd } -// if go-compiler specified, resolve absolute-path from PATH, -// otherwise return 'nil'. +// If go-compiler specified, return its absolute-path, otherwise return 'nil'. func (c *Environ) compilerAbs() error { if c.Compiler.Path != "" { fname, err := exec.LookPath(string(c.Compiler.Path)) @@ -86,9 +85,8 @@ func (c *Environ) compilerAbs() error { return nil } -// runs compilerCmd("version") and parse/caches output, to environ.Compiler +// Runs compilerCmd("version") and parse/caches output to c.Compiler. func (c *Environ) CompilerInit() error { - if c.Compiler.IsInit { return nil } @@ -96,19 +94,20 @@ func (c *Environ) CompilerInit() error { c.compilerAbs() cmd := c.compilerCmd("version") - v, err := cmd.CombinedOutput() + vb, err := cmd.CombinedOutput() if err != nil { return err } + v := string(vb) efmt := "go-compiler 'version' output unrecognized: %v" - s := strings.Fields(string(v)) + s := strings.Fields(v) if len(s) < 1 { - return fmt.Errorf(efmt, string(v)) + return fmt.Errorf(efmt, v) } compiler := c.Compiler - compiler.VersionOutput = string(v) + compiler.VersionOutput = strings.TrimSpace(v) compiler.Identifier = s[0] compiler.Type = CompilerTypeFromString(compiler.Identifier) compiler.IsInit = true @@ -117,7 +116,7 @@ func (c *Environ) CompilerInit() error { case CompilerGo: if len(s) < 3 { - return fmt.Errorf(efmt, string(v)) + return fmt.Errorf(efmt, v) } compiler.Version = s[2] compiler.VersionGo = s[2] @@ -125,7 +124,7 @@ func (c *Environ) CompilerInit() error { case CompilerTinygo: // e.g. "tinygo version 0.33.0 darwin/arm64 (using go version go1.22.2 and LLVM version 18.1.2)" if len(s) < 8 { - return fmt.Errorf(efmt, string(v)) + return fmt.Errorf(efmt, v) } compiler.Version = s[2] compiler.VersionGo = s[7] @@ -156,14 +155,14 @@ func (c *Environ) CompilerInit() error { } case CompilerUnkown: - return fmt.Errorf(efmt, string(v)) + return fmt.Errorf(efmt, v) } c.Compiler = compiler return nil } -// Version returns the Go version string that runtime.Version would return for -// the Go compiler in this environ. +// Returns the Go version string that runtime.Version would return for the Go +// compiler in this environ. func (c *Environ) Version() (string, error) { if err := c.CompilerInit(); err != nil { return "", err @@ -172,7 +171,6 @@ func (c *Environ) Version() (string, error) { } func (c Environ) build(dirPath string, binaryPath string, pattern []string, opts *BuildOpts) error { - if err := c.CompilerInit(); err != nil { return err }