github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/golang/build.go (about) 1 // Copyright 2015-2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package golang is an API to the Go compiler. 6 package golang 7 8 import ( 9 "encoding/json" 10 "fmt" 11 "go/build" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "strings" 16 ) 17 18 type Environ struct { 19 build.Context 20 } 21 22 // Default is the default build environment comprised of the default GOPATH, 23 // GOROOT, GOOS, GOARCH, and CGO_ENABLED values. 24 func Default() Environ { 25 return Environ{Context: build.Default} 26 } 27 28 // PackageByPath retrieves information about a package by its file system path. 29 // 30 // `path` is assumed to be the directory containing the package. 31 func (c Environ) PackageByPath(path string) (*build.Package, error) { 32 abs, err := filepath.Abs(path) 33 if err != nil { 34 return nil, err 35 } 36 return c.Context.ImportDir(abs, 0) 37 } 38 39 // Package retrieves information about a package by its Go import path. 40 func (c Environ) Package(importPath string) (*build.Package, error) { 41 return c.Context.Import(importPath, "", 0) 42 } 43 44 // ListPackage matches a subset of the JSON output of the `go list -json` 45 // command. 46 // 47 // See `go help list` for the full structure. 48 // 49 // This currently contains an incomplete list of dependencies. 50 type ListPackage struct { 51 Dir string 52 Deps []string 53 GoFiles []string 54 SFiles []string 55 HFiles []string 56 Goroot bool 57 Root string 58 ImportPath string 59 } 60 61 // GoCmd runs a go command in the environment. 62 func (c Environ) GoCmd(args ...string) *exec.Cmd { 63 cmd := exec.Command(filepath.Join(c.GOROOT, "bin", "go"), args...) 64 cmd.Env = append(os.Environ(), c.Env()...) 65 return cmd 66 } 67 68 // Version returns the Go version string that runtime.Version would return for 69 // the Go compiler in this environ. 70 func (c Environ) Version() (string, error) { 71 cmd := c.GoCmd("version") 72 v, err := cmd.CombinedOutput() 73 if err != nil { 74 return "", err 75 } 76 s := strings.Fields(string(v)) 77 if len(s) < 3 { 78 return "", fmt.Errorf("unknown go version, tool returned weird output for 'go version': %v", string(v)) 79 } 80 return s[2], nil 81 } 82 83 // Deps lists all dependencies of the package given by `importPath`. 84 func (c Environ) Deps(importPath string) (*ListPackage, error) { 85 // The output of this is almost the same as build.Import, except for 86 // the dependencies. 87 cmd := c.GoCmd("list", "-json", importPath) 88 out, err := cmd.CombinedOutput() 89 if err != nil { 90 return nil, err 91 } 92 93 var p ListPackage 94 if err := json.Unmarshal(out, &p); err != nil { 95 return nil, err 96 } 97 return &p, nil 98 } 99 100 func (c Environ) Env() []string { 101 var env []string 102 if c.GOARCH != "" { 103 env = append(env, fmt.Sprintf("GOARCH=%s", c.GOARCH)) 104 } 105 if c.GOOS != "" { 106 env = append(env, fmt.Sprintf("GOOS=%s", c.GOOS)) 107 } 108 if c.GOROOT != "" { 109 env = append(env, fmt.Sprintf("GOROOT=%s", c.GOROOT)) 110 } 111 if c.GOPATH != "" { 112 env = append(env, fmt.Sprintf("GOPATH=%s", c.GOPATH)) 113 } 114 var cgo int8 115 if c.CgoEnabled { 116 cgo = 1 117 } 118 env = append(env, fmt.Sprintf("CGO_ENABLED=%d", cgo)) 119 return env 120 } 121 122 func (c Environ) String() string { 123 return strings.Join(c.Env(), " ") 124 } 125 126 // Optional arguments to Environ.Build. 127 type BuildOpts struct { 128 // NoStrip builds an unstripped binary. 129 NoStrip bool 130 // ExtraArgs to `go build`. 131 ExtraArgs []string 132 } 133 134 // Build compiles the package given by `importPath`, writing the build object 135 // to `binaryPath`. 136 func (c Environ) Build(importPath string, binaryPath string, opts BuildOpts) error { 137 p, err := c.Package(importPath) 138 if err != nil { 139 return err 140 } 141 142 return c.BuildDir(p.Dir, binaryPath, opts) 143 } 144 145 // BuildDir compiles the package in the directory `dirPath`, writing the build 146 // object to `binaryPath`. 147 func (c Environ) BuildDir(dirPath string, binaryPath string, opts BuildOpts) error { 148 args := []string{ 149 "build", 150 "-a", // Force rebuilding of packages. 151 "-o", binaryPath, 152 "-installsuffix", "uroot", 153 "-gcflags=all=-l", // Disable "function inlining" to get a smaller binary 154 } 155 if !opts.NoStrip { 156 args = append(args, `-ldflags=-s -w`) // Strip all symbols. 157 } 158 if len(c.BuildTags) > 0 { 159 args = append(args, []string{"-tags", strings.Join(c.BuildTags, " ")}...) 160 } 161 if opts.ExtraArgs != nil { 162 args = append(args, opts.ExtraArgs...) 163 } 164 // We always set the working directory, so this is always '.'. 165 args = append(args, ".") 166 167 cmd := c.GoCmd(args...) 168 cmd.Dir = dirPath 169 170 if o, err := cmd.CombinedOutput(); err != nil { 171 return fmt.Errorf("error building go package in %q: %v, %v", dirPath, string(o), err) 172 } 173 return nil 174 }