-
Notifications
You must be signed in to change notification settings - Fork 36
/
gonative.go
295 lines (256 loc) · 7.96 KB
/
gonative.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/codegangsta/cli"
"github.com/inconshreveable/axiom"
log "github.com/inconshreveable/log15"
)
var Log = log.Root()
const usage = `build Go installations with native stdlib packages
DESCRIPTION:
Cross compiled Go binaries are not suitable for production applications
because code in the standard library relies on Cgo for DNS resolution
with the native resolver, access to system certificate roots, and parts of os/user.
gonative is a simple tool which creates a build of Go that can cross compile
to all platforms while still using the Cgo-enabled versions of the stdlib
packages. It does this by downloading the binary distributions for each
platform and copying their libraries into the proper places. It sets
the correct access time so they don't get rebuilt. It also copies
some auto-generated runtime files into the build as well. gonative does
not modify any Go that you have installed and builds Go again in a separate
directory (the current directory by default).`
const equinoxAppId = "ap_VQ_K1O_27-tPsncKE3E2GszIPm"
const updatePublicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvMwGMSLLi3bfq6UZesVR
H+/EnPyVqbVTJs3zCiFSnLrXMkOMuXfmf7mC23q1cPaGOIFTfmhcx5/vkda10NJ1
owTAJKXVctC6TUei42vIiBSPsdhzyinNtCdkEkBT2f6Ac58OQV1dUBW/b0fQRQZN
9tEwW7PK1QnR++bmVu2XzoGEw17XZdeDoXftDBgYAzOWDqapZpHETPobL5oQHeQN
CVdCaNbNo52/HL6XKyDGCNudVqiKgIoExPzcOL6KKfvMla1Y4mrrArbuNBlE3qxW
CwmnjtWg+J7vb9rKfZvuVPXPD/RoruZUmHBc1f31KB/QFvn/zXSqeyBcsd6ywCfo
KwIDAQAB
-----END PUBLIC KEY-----`
type Options struct {
Version string
SrcPath string
TargetPath string
Platforms []Platform
}
func main() {
app := cli.NewApp()
app.Name = "gonative"
app.Author = "inconshreveable"
app.Email = "[email protected]"
app.Usage = usage
app.HideHelp = true
app.HideVersion = true
app.Version = "0.2.0"
app.Commands = []cli.Command{
cli.Command{
Name: "build",
Usage: "build a go installation with native stdlib packages",
Flags: []cli.Flag{
cli.StringFlag{"version", "1.5.2", "version of Go to build", "", nil},
cli.StringFlag{"src", "", "path to go source, empty string means to fetch from internet", "", nil},
cli.StringFlag{"target", "go", "target directory in which to build Go", "", nil},
cli.StringFlag{"platforms", "", "space separated list of platforms to build, default is 'darwin_amd64 freebsd_amd64 linux_386 linux_amd64 windows_386 windows_amd64'", "", nil},
},
Action: buildCmd,
},
}
axiom.WrapApp(app, axiom.NewLogged())
app.Commands = append(app.Commands, []cli.Command{
axiom.VersionCommand(),
axiom.NewUpdater(equinoxAppId, updatePublicKey).Command(),
}...)
app.Run(os.Args)
}
func buildCmd(c *cli.Context) {
exit := func(err error) {
if err == nil {
os.Exit(0)
} else {
log.Crit("command failed", "err", err)
os.Exit(1)
}
}
opts := &Options{
Version: c.String("version"),
SrcPath: c.String("src"),
TargetPath: c.String("target"),
}
platforms := c.String("platforms")
if platforms == "" {
opts.Platforms = defaultPlatforms
} else {
opts.Platforms = make([]Platform, 0)
for _, pString := range strings.Split(platforms, " ") {
parts := strings.Split(pString, "_")
if len(parts) != 2 {
exit(fmt.Errorf("Invalid platform string: %v", pString))
}
opts.Platforms = append(opts.Platforms, Platform{parts[0], parts[1]})
}
}
exit(Build(opts))
}
func Build(opts *Options) error {
// normalize paths
targetPath, err := filepath.Abs(opts.TargetPath)
if err != nil {
return err
}
src := opts.SrcPath
if src == "" {
src = "(from internet)"
}
Log.Info("building go", "version", opts.Version, "src", src, "target", targetPath, "platforms", opts.Platforms)
// tells the platform goroutines that the target path is ready
targetReady := make(chan struct{})
// platform gorouintes can report an error here
errors := make(chan error, len(opts.Platforms))
// need to wait for each platform to finish
var wg sync.WaitGroup
wg.Add(len(opts.Platforms))
// run all platform fetch/copies in parallel
for _, p := range opts.Platforms {
go getPlatform(p, targetPath, opts.Version, targetReady, errors, &wg)
}
// if no source path specified, fetch source from the internet
if opts.SrcPath == "" {
srcPath, err := srcPlatform.Download(opts.Version)
if err != nil {
return err
}
defer os.RemoveAll(srcPath)
opts.SrcPath = filepath.Join(srcPath, "go")
}
// copy the source to the target directory
err = CopyAll(targetPath, opts.SrcPath)
if err != nil {
return err
}
// build Go for the host platform
err = makeDotBash(targetPath)
Log.Debug("make.bash", "err", err)
if err != nil {
return err
}
// bootstrap compilers for all target platforms
Log.Info("boostraping go compilers")
for _, p := range opts.Platforms {
err = distBootstrap(targetPath, p)
Log.Debug("bootstrap compiler", "plat", p, "err", err)
if err != nil {
return err
}
}
// tell the platform goroutines that the target dir is ready
close(targetReady)
// wait for all platforms to finish
wg.Wait()
// return error if a platform failed
select {
case err := <-errors:
return err
default:
Log.Info("successfuly built Go", "path", targetPath)
return nil
}
}
func getPlatform(p Platform, targetPath, version string, targetReady chan struct{}, errors chan error, wg *sync.WaitGroup) {
lg := Log.New("plat", p)
defer wg.Done()
// download the binary distribution
path, err := p.Download(version)
if err != nil {
errors <- err
return
}
defer os.RemoveAll(path)
// wait for target directory to be ready
<-targetReady
// copy over the packages
targetPkgPath := filepath.Join(targetPath, "pkg", p.String())
srcPkgPath := filepath.Join(path, "go", "pkg", p.String())
err = CopyAll(targetPkgPath, srcPkgPath)
if err != nil {
errors <- err
return
}
// copy over the auto-generated z_ files
srcZPath := filepath.Join(path, "go", "src", "runtime", "z*_"+p.String())
targetZPath := filepath.Join(targetPath, "src", "runtime")
if version < "1.4" {
srcZPath = filepath.Join(path, "go", "src", "pkg", "runtime", "z*_"+p.String())
targetZPath = filepath.Join(targetPath, "src", "pkg", "runtime")
}
lg.Debug("copy zfile", "dst", targetZPath, "src", srcZPath, "err", err)
CopyFile(targetZPath, srcZPath)
// change the mod times
now := time.Now()
err = filepath.Walk(targetPkgPath, func(path string, info os.FileInfo, err error) error {
os.Chtimes(path, now, now)
return nil
})
lg.Debug("set modtimes", "err", err)
if err != nil {
errors <- err
return
}
}
// runs make.[bash|bat] in the source directory to build all of the compilers
// and standard library
func makeDotBash(goRoot string) (err error) {
scriptName := "make.bash"
if runtime.GOOS == "windows" {
scriptName = "make.bat"
}
scriptPath, err := filepath.Abs(filepath.Join(goRoot, "src", scriptName))
if err != nil {
return
}
scriptDir := filepath.Dir(scriptPath)
cmd := exec.Cmd{
Path: scriptPath,
Args: []string{scriptPath},
Env: os.Environ(),
Dir: scriptDir,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
return cmd.Run()
}
// runs dist bootrap to build the compilers for a target platform
func distBootstrap(goRoot string, p Platform) (err error) {
// the dist tool gets put in the pkg/tool/{host_platform} directory after we've built
// the compilers/stdlib for the host platform
hostPlatform := Platform{runtime.GOOS, runtime.GOARCH}
scriptPath, err := filepath.Abs(filepath.Join(goRoot, "pkg", "tool", hostPlatform.String(), "dist"))
if err != nil {
return
}
// but we want to run it from the src directory
scriptDir, err := filepath.Abs(filepath.Join(goRoot, "src"))
if err != nil {
return
}
bootstrapCmd := exec.Cmd{
Path: scriptPath,
Args: []string{scriptPath, "bootstrap", "-v"},
Env: append(os.Environ(),
"GOOS="+p.OS,
"GOARCH="+p.Arch,
"GOROOT="+goRoot),
Dir: scriptDir,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
return bootstrapCmd.Run()
}