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