github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/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 "bytes" 10 "encoding/json" 11 "fmt" 12 "go/build" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "strings" 17 "time" 18 ) 19 20 type Environ struct { 21 build.Context 22 23 GO111MODULE string 24 } 25 26 // Default is the default build environment comprised of the default GOPATH, 27 // GOROOT, GOOS, GOARCH, and CGO_ENABLED values. 28 func Default() Environ { 29 return Environ{ 30 Context: build.Default, 31 GO111MODULE: os.Getenv("GO111MODULE"), 32 } 33 } 34 35 // Package matches a subset of the JSON output of the `go list -json` 36 // command. 37 // 38 // See `go help list` for the full structure. 39 // 40 // This currently contains an incomplete list of dependencies. 41 type Package struct { 42 Name string 43 Dir string 44 Deps []string 45 GoFiles []string 46 SFiles []string 47 HFiles []string 48 Goroot bool 49 Root string 50 Module *Module 51 ImportPath string 52 } 53 54 type Module struct { 55 Path string // module path 56 Version string // module version 57 Versions []string // available module versions (with -versions) 58 Replace *Module // replaced by this module 59 Time *time.Time // time version was created 60 Update *Module // available update, if any (with -u) 61 Main bool // is this the main module? 62 Indirect bool // is this module only an indirect dependency of main module? 63 Dir string // directory holding files for this module, if any 64 GoMod string // path to go.mod file for this module, if any 65 GoVersion string // go version used in module 66 Error *ModuleError // error loading module 67 } 68 69 type ModuleError struct { 70 Err string 71 } 72 73 func (c Environ) goCmd(args ...string) *exec.Cmd { 74 cmd := exec.Command(filepath.Join(c.GOROOT, "bin", "go"), args...) 75 cmd.Env = append(os.Environ(), c.Env()...) 76 return cmd 77 } 78 79 // Version returns the Go version string that runtime.Version would return for 80 // the Go compiler in this environ. 81 func (c Environ) Version() (string, error) { 82 cmd := c.goCmd("version") 83 v, err := cmd.CombinedOutput() 84 if err != nil { 85 return "", err 86 } 87 s := strings.Fields(string(v)) 88 if len(s) < 3 { 89 return "", fmt.Errorf("unknown go version, tool returned weird output for 'go version': %v", string(v)) 90 } 91 return s[2], nil 92 } 93 94 // Find lists all dependencies of the package given by `importPath`. 95 func (c Environ) Find(pattern string) ([]*Package, error) { 96 // The output of this is almost the same as build.Import, except for 97 // the dependencies. 98 cmd := c.goCmd("list", "-json", pattern) 99 stdout := new(bytes.Buffer) 100 stderr := new(bytes.Buffer) 101 cmd.Stdout = stdout 102 cmd.Stderr = stderr 103 if err := cmd.Run(); err != nil { 104 return nil, fmt.Errorf("go list -json %q: %v", pattern, stderr.String()) 105 } 106 107 var ps []*Package 108 for dec := json.NewDecoder(stdout); dec.More(); { 109 var p Package 110 if err := dec.Decode(&p); err != nil { 111 return nil, fmt.Errorf("json unmarshal of go list -json %q: %v", pattern, err) 112 } 113 ps = append(ps, &p) 114 } 115 return ps, nil 116 } 117 118 func (c Environ) FindCmds(pattern string) ([]*Package, error) { 119 ps, err := c.Find(pattern) 120 if err != nil { 121 return nil, err 122 } 123 var cmds []*Package 124 for _, p := range ps { 125 if p.Name == "main" { 126 cmds = append(cmds, p) 127 } 128 } 129 if len(cmds) == 0 { 130 return nil, fmt.Errorf("pattern %q did not find commands, only packages", pattern) 131 } 132 return cmds, nil 133 } 134 135 func (c Environ) FindOne(pattern string) (*Package, error) { 136 ps, err := c.Find(pattern) 137 if err != nil { 138 return nil, err 139 } 140 if len(ps) != 1 { 141 return nil, fmt.Errorf("pattern %q returned %d packages, wanted one", pattern, len(ps)) 142 } 143 return ps[0], nil 144 } 145 146 func (c Environ) FindOneCmd(pattern string) (*Package, error) { 147 ps, err := c.FindCmds(pattern) 148 if err != nil { 149 return nil, err 150 } 151 if len(ps) != 1 { 152 return nil, fmt.Errorf("pattern %q returned %d packages, wanted one", pattern, len(ps)) 153 } 154 return ps[0], nil 155 } 156 157 // Env returns all environment variables for invoking a Go command. 158 func (c Environ) Env() []string { 159 var env []string 160 if c.GOARCH != "" { 161 env = append(env, fmt.Sprintf("GOARCH=%s", c.GOARCH)) 162 } 163 if c.GOOS != "" { 164 env = append(env, fmt.Sprintf("GOOS=%s", c.GOOS)) 165 } 166 if c.GOPATH != "" { 167 env = append(env, fmt.Sprintf("GOPATH=%s", c.GOPATH)) 168 } 169 var cgo int8 170 if c.CgoEnabled { 171 cgo = 1 172 } 173 env = append(env, fmt.Sprintf("CGO_ENABLED=%d", cgo)) 174 env = append(env, fmt.Sprintf("GO111MODULE=%s", c.GO111MODULE)) 175 176 if c.GOROOT != "" { 177 env = append(env, fmt.Sprintf("GOROOT=%s", c.GOROOT)) 178 179 // If GOROOT is set to a different version of Go, we must 180 // ensure that $GOROOT/bin is also in path to make the "go" 181 // binary available to golang.org/x/tools/packages. 182 env = append(env, fmt.Sprintf("PATH=%s:%s", filepath.Join(c.GOROOT, "bin"), os.Getenv("PATH"))) 183 } 184 return env 185 } 186 187 // String returns all environment variables for Go invocations. 188 func (c Environ) String() string { 189 return strings.Join(c.Env(), " ") 190 } 191 192 // Build compiles the package given by `importPath`, writing the build object 193 // to `binaryPath`. 194 func (c Environ) Build(importPath string, binaryPath string) error { 195 p, err := c.FindOneCmd(importPath) 196 if err != nil { 197 return err 198 } 199 return c.BuildDir(p.Dir, binaryPath) 200 } 201 202 // BuildDir compiles the package in the directory `dirPath`, writing the build 203 // object to `binaryPath`. 204 func (c Environ) BuildDir(dirPath string, binaryPath string) error { 205 args := []string{ 206 "build", 207 208 // Force rebuilding of packages. 209 "-a", 210 211 // Strip all symbols, and don't embed a Go build ID to be reproducible. 212 "-ldflags", "-s -w -buildid=", 213 214 "-o", binaryPath, 215 "-installsuffix", "uroot", 216 } 217 218 v, err := c.Version() 219 if err != nil { 220 return err 221 } 222 223 // Reproducible builds: Trim any GOPATHs out of the executable's 224 // debugging information. 225 // 226 // E.g. Trim /tmp/bb-*/ from /tmp/bb-12345567/src/github.com/... 227 if strings.Contains(v, "go1.13") || strings.Contains(v, "go1.14") || strings.Contains(v, "gotip") { 228 args = append(args, "-trimpath") 229 } else { 230 args = append(args, "-gcflags", fmt.Sprintf("-trimpath=%s", c.GOPATH)) 231 args = append(args, "-asmflags", fmt.Sprintf("-trimpath=%s", c.GOPATH)) 232 } 233 234 if len(c.BuildTags) > 0 { 235 args = append(args, []string{"-tags", strings.Join(c.BuildTags, " ")}...) 236 } 237 // We always set the working directory, so this is always '.'. 238 args = append(args, ".") 239 240 cmd := c.goCmd(args...) 241 cmd.Dir = dirPath 242 243 if o, err := cmd.CombinedOutput(); err != nil { 244 return fmt.Errorf("error building go package in %q: %v, %v", dirPath, string(o), err) 245 } 246 return nil 247 }