1
// Copyright 2012 The Go 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.
5
// This is a tool for packaging binary releases.
6
// It supports FreeBSD, Linux, NetBSD, OpenBSD, OS X, and Windows.
33
tag = flag.String("tag", "release", "mercurial tag to check out")
34
toolTag = flag.String("tool", defaultToolTag, "go.tools tag to check out")
35
repo = flag.String("repo", "https://code.google.com/p/go", "repo URL")
36
verbose = flag.Bool("v", false, "verbose output")
37
upload = flag.Bool("upload", true, "upload resulting files to Google Code")
38
wxsFile = flag.String("wxs", "", "path to custom installer.wxs")
39
addLabel = flag.String("label", "", "additional label to apply to file when uploading")
40
includeRace = flag.Bool("race", true, "build race detector packages")
41
versionOverride = flag.String("version", "", "override version name")
42
staticToolchain = flag.Bool("static", true, "try to build statically linked toolchain (only supported on ELF targets)")
44
username, password string // for Google Code upload
48
uploadURL = "https://go.googlecode.com/files"
49
blogPath = "code.google.com/p/go.blog"
50
toolPath = "code.google.com/p/go.tools"
51
tourPath = "code.google.com/p/go-tour"
52
defaultToolTag = "release-branch.go1.2"
55
// Import paths for tool commands.
56
// These must be the command that cmd/go knows to install to $GOROOT/bin
57
// or $GOROOT/pkg/tool.
58
var toolPaths = []string{
59
"code.google.com/p/go.tools/cmd/cover",
60
"code.google.com/p/go.tools/cmd/godoc",
61
"code.google.com/p/go.tools/cmd/vet",
64
var preBuildCleanFiles = []string{
66
"misc/dashboard/godashboard",
73
var cleanFiles = []string{
80
var sourceCleanFiles = []string{
85
var tourPackages = []string{
91
var tourContent = []string{
99
var blogContent = []string{
104
// The os-arches that support the race toolchain.
105
var raceAvailable = []string{
111
// The OSes that support building statically linked toolchain
112
// Only ELF platforms are supported.
113
var staticLinkAvailable = []string{
120
var fileRe = regexp.MustCompile(
121
`^(go[a-z0-9-.]+)\.(src|([a-z0-9]+)-([a-z0-9]+)(?:-([a-z0-9.]))?)\.`)
124
flag.Usage = func() {
125
fmt.Fprintf(os.Stderr, "usage: %s [flags] targets...\n", os.Args[0])
130
if flag.NArg() == 0 {
133
if runtime.GOOS == "windows" {
138
if err := readCredentials(); err != nil {
139
log.Println("readCredentials:", err)
142
for _, targ := range flag.Args() {
144
if m := fileRe.FindStringSubmatch(targ); m != nil {
145
// targ is a file name; upload it to googlecode.
155
log.Printf("%s: -upload=false, skipping", targ)
158
if err := b.Upload(version, targ); err != nil {
159
log.Printf("%s: %v", targ, err)
163
if targ == "source" {
166
p := strings.SplitN(targ, "-", 3)
168
log.Println("Ignoring unrecognized target:", targ)
177
for _, t := range raceAvailable {
178
if t == targ || strings.HasPrefix(targ, t+"-") {
183
if *staticToolchain {
184
for _, os := range staticLinkAvailable {
191
if err := b.Do(); err != nil {
192
log.Printf("%s: %v", targ, err)
198
Source bool // if true, OS and Arch must be empty
199
Race bool // build race toolchain
205
static bool // if true, build statically linked toolchain
208
func (b *Build) Do() error {
209
work, err := ioutil.TempDir("", "bindist")
213
defer os.RemoveAll(work)
214
b.root = filepath.Join(work, "go")
217
// Clone Go distribution and update to tag.
218
_, err = b.hgCmd(work, "clone", *repo, b.root)
222
_, err = b.hgCmd(b.root, "update", *tag)
227
// Remove exp and old packages.
228
if err := b.clean(preBuildCleanFiles); err != nil {
232
src := filepath.Join(b.root, "src")
234
if runtime.GOOS == "windows" {
235
log.Print("Warning: running make.bash on Windows; source builds are intended to be run on a Unix machine")
237
// Build dist tool only.
238
_, err = b.run(src, "bash", "make.bash", "--dist-tool")
241
if b.OS == "windows" {
242
_, err = b.run(src, "cmd", "/C", "make.bat")
244
_, err = b.run(src, "bash", "make.bash")
250
goCmd := filepath.Join(b.root, "bin", "go")
251
if b.OS == "windows" {
254
_, err = b.run(src, goCmd, "install", "-race", "std")
258
// Re-install std without -race, so that we're not left
259
// with a slower, race-enabled cmd/go, etc.
260
_, err = b.run(src, goCmd, "install", "-a", "std")
261
// Re-building go command leaves old versions of go.exe as go.exe~ on windows.
262
// See (*builder).copyFile in $GOROOT/src/cmd/go/build.go for details.
263
// Remove it manually.
264
if b.OS == "windows" {
265
os.Remove(goCmd + "~")
285
// Get version strings.
287
version string // "weekly.2012-03-04"
288
fullVersion []byte // "weekly.2012-03-04 9353aa1efdf3"
290
pat := filepath.Join(b.root, "pkg/tool/*/dist*") // trailing * for .exe
291
m, err := filepath.Glob(pat)
296
return fmt.Errorf("couldn't find dist in %q", pat)
298
fullVersion, err = b.run("", m[0], "version")
302
fullVersion = bytes.TrimSpace(fullVersion)
303
v := bytes.SplitN(fullVersion, []byte(" "), 2)
304
version = string(v[0])
305
if *versionOverride != "" {
306
version = *versionOverride
309
// Write VERSION file.
310
err = ioutil.WriteFile(filepath.Join(b.root, "VERSION"), fullVersion, 0644)
316
if err := b.clean(cleanFiles); err != nil {
320
if err := b.clean(sourceCleanFiles); err != nil {
326
base := fmt.Sprintf("%s.%s-%s", version, b.OS, b.Arch)
328
base += "-" + b.Label
330
if !strings.HasPrefix(base, "go") {
335
case "linux", "freebsd", "netbsd", "":
339
targ = fmt.Sprintf("%s.src", version)
340
if !strings.HasPrefix(targ, "go") {
345
err = makeTar(targ, work)
346
targs = append(targs, targ)
349
targ := base + ".tar.gz"
350
err = makeTar(targ, work)
351
targs = append(targs, targ)
354
// arrange work so it's laid out as the dest filesystem
355
etc := filepath.Join(b.root, "misc/dist/darwin/etc")
356
_, err = b.run(work, "cp", "-r", etc, ".")
360
localDir := filepath.Join(work, "usr/local")
361
err = os.MkdirAll(localDir, 0755)
365
_, err = b.run(work, "mv", "go", localDir)
370
pkgdest, err := ioutil.TempDir("", "pkgdest")
374
defer os.RemoveAll(pkgdest)
375
dist := filepath.Join(runtime.GOROOT(), "misc/dist")
376
_, err = b.run("", "pkgbuild",
377
"--identifier", "com.googlecode.go",
378
"--version", version,
379
"--scripts", filepath.Join(dist, "darwin/scripts"),
381
filepath.Join(pkgdest, "com.googlecode.go.pkg"))
386
_, err = b.run("", "productbuild",
387
"--distribution", filepath.Join(dist, "darwin/Distribution"),
388
"--resources", filepath.Join(dist, "darwin/Resources"),
389
"--package-path", pkgdest,
394
targs = append(targs, targ)
397
zip := filepath.Join(work, base+".zip")
398
err = makeZip(zip, work)
399
// Copy zip to target file.
400
targ := base + ".zip"
405
targs = append(targs, targ)
407
// Create MSI installer.
408
win := filepath.Join(b.root, "misc/dist/windows")
409
installer := filepath.Join(win, "installer.wxs")
413
appfiles := filepath.Join(work, "AppFiles.wxs")
414
msi := filepath.Join(work, "installer.msi")
416
_, err = b.run(work, "heat", "dir", "go",
418
"-gg", "-g1", "-srd", "-sfrag",
420
"-template", "fragment",
422
"-var", "var.SourceDir",
428
_, err = b.run(work, "candle",
430
"-dVersion="+version,
437
appfiles = filepath.Join(work, "AppFiles.wixobj")
438
installer = filepath.Join(work, "installer.wixobj")
439
_, err = b.run(win, "light",
441
"-ext", "WixUIExtension",
442
"-ext", "WixUtilExtension",
448
// Copy installer to target file.
451
targs = append(targs, targ)
453
if err == nil && *upload {
454
for _, targ := range targs {
455
err = b.Upload(version, targ)
464
func (b *Build) tools() error {
465
defer b.cleanGopath()
467
// Fetch the tool packages (without building/installing).
468
args := append([]string{"get", "-d"}, toolPaths...)
469
_, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), args...)
474
// Update the repo to the revision specified by -tool.
475
repoPath := filepath.Join(b.gopath, "src", filepath.FromSlash(toolPath))
476
_, err = b.run(repoPath, "hg", "update", *toolTag)
482
args = append([]string{"install"}, toolPaths...)
483
_, err = b.run(b.gopath, filepath.Join(b.root, "bin", "go"), args...)
488
// Copy doc.go from go.tools/cmd/$CMD to $GOROOT/src/cmd/$CMD
489
// while rewriting "package main" to "package documentation".
490
for _, p := range toolPaths {
491
d, err := ioutil.ReadFile(filepath.Join(b.gopath, "src",
492
filepath.FromSlash(p), "doc.go"))
496
d = bytes.Replace(d, []byte("\npackage main\n"),
497
[]byte("\npackage documentation\n"), 1)
498
cmdDir := filepath.Join(b.root, "src", "cmd", path.Base(p))
499
if err := os.MkdirAll(cmdDir, 0755); err != nil {
502
docGo := filepath.Join(cmdDir, "doc.go")
503
if err := ioutil.WriteFile(docGo, d, 0644); err != nil {
511
func (b *Build) blog() error {
512
defer b.cleanGopath()
514
// Fetch the blog repository.
515
_, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), "get", "-d", blogPath+"/blog")
520
// Copy blog content to $GOROOT/blog.
521
blogSrc := filepath.Join(b.gopath, "src", filepath.FromSlash(blogPath))
522
contentDir := filepath.Join(b.root, "blog")
523
return cpAllDir(contentDir, blogSrc, blogContent...)
526
func (b *Build) tour() error {
527
defer b.cleanGopath()
529
// go get the gotour package.
530
_, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), "get", tourPath+"/gotour")
535
// Copy all the tour content to $GOROOT/misc/tour.
536
importPath := filepath.FromSlash(tourPath)
537
tourSrc := filepath.Join(b.gopath, "src", importPath)
538
contentDir := filepath.Join(b.root, "misc", "tour")
539
if err = cpAllDir(contentDir, tourSrc, tourContent...); err != nil {
543
// Copy the tour source code so it's accessible with $GOPATH pointing to $GOROOT/misc/tour.
544
if err = cpAllDir(filepath.Join(contentDir, "src", importPath), tourSrc, tourPackages...); err != nil {
548
// Copy gotour binary to tool directory as "tour"; invoked as "go tool tour".
550
filepath.Join(b.root, "pkg", "tool", b.OS+"_"+b.Arch, "tour"+ext()),
551
filepath.Join(b.gopath, "bin", "gotour"+ext()),
555
func (b *Build) cleanGopath() {
556
for _, d := range []string{"bin", "pkg", "src"} {
557
os.RemoveAll(filepath.Join(b.gopath, d))
562
if runtime.GOOS == "windows" {
568
func (b *Build) hgCmd(dir string, args ...string) ([]byte, error) {
569
return b.run(dir, "hg", append([]string{"--config", "extensions.codereview=!"}, args...)...)
572
func (b *Build) run(dir, name string, args ...string) ([]byte, error) {
573
buf := new(bytes.Buffer)
574
absName, err := lookPath(name)
578
cmd := exec.Command(absName, args...)
579
var output io.Writer = buf
581
log.Printf("Running %q %q", absName, args)
582
output = io.MultiWriter(buf, os.Stdout)
588
if err := cmd.Run(); err != nil {
589
fmt.Fprintf(os.Stderr, "%s", buf.Bytes())
590
return nil, fmt.Errorf("%s %s: %v", name, strings.Join(args, " "), err)
592
return buf.Bytes(), nil
595
var cleanEnv = []string{
606
func (b *Build) env() []string {
608
for i := 0; i < len(env); i++ {
609
for _, c := range cleanEnv {
610
if strings.HasPrefix(env[i], c+"=") {
611
env = append(env[:i], env[i+1:]...)
615
final := "/usr/local/go"
616
if b.OS == "windows" {
621
"GOHOSTARCH="+b.Arch,
625
"GOROOT_FINAL="+final,
629
env = append(env, "GO_DISTFLAGS=-s")
634
func (b *Build) Upload(version string, filename string) error {
635
// Prepare upload metadata.
637
os_, arch := b.OS, b.Arch
645
labels = append(labels, "Arch-"+b.Arch)
647
var opsys, ftype string // labels
665
summary := fmt.Sprintf("%s %s (%s)", version, os_, arch)
667
case strings.HasSuffix(filename, ".msi"):
669
summary += " MSI installer"
670
case strings.HasSuffix(filename, ".pkg"):
672
summary += " PKG installer"
673
case strings.HasSuffix(filename, ".zip"):
675
summary += " ZIP archive"
676
case strings.HasSuffix(filename, ".tar.gz"):
678
summary += " tarball"
682
summary = fmt.Sprintf("%s (source only)", version)
685
labels = append(labels, "OpSys-"+opsys)
688
labels = append(labels, "Type-"+ftype)
691
labels = append(labels, b.Label)
694
labels = append(labels, *addLabel)
696
// Put "Go" prefix on summary when it doesn't already begin with "go".
697
if !strings.HasPrefix(strings.ToLower(summary), "go") {
698
summary = "Go " + summary
701
// Open file to upload.
702
f, err := os.Open(filename)
708
// Prepare multipart payload.
709
body := new(bytes.Buffer)
710
w := multipart.NewWriter(body)
711
if err := w.WriteField("summary", summary); err != nil {
714
for _, l := range labels {
715
if err := w.WriteField("label", l); err != nil {
719
fw, err := w.CreateFormFile("filename", filename)
723
if _, err = io.Copy(fw, f); err != nil {
726
if err := w.Close(); err != nil {
730
// Send the file to Google Code.
731
req, err := http.NewRequest("POST", uploadURL, body)
735
token := fmt.Sprintf("%s:%s", username, password)
736
token = base64.StdEncoding.EncodeToString([]byte(token))
737
req.Header.Set("Authorization", "Basic "+token)
738
req.Header.Set("Content-type", w.FormDataContentType())
740
resp, err := http.DefaultTransport.RoundTrip(req)
744
if resp.StatusCode/100 != 2 {
745
fmt.Fprintln(os.Stderr, "upload failed")
746
defer resp.Body.Close()
747
io.Copy(os.Stderr, resp.Body)
748
return fmt.Errorf("upload: %s", resp.Status)
753
func (b *Build) clean(files []string) error {
754
for _, name := range files {
755
err := os.RemoveAll(filepath.Join(b.root, name))
763
func exists(path string) bool {
764
_, err := os.Stat(path)
768
func readCredentials() error {
769
name := os.Getenv("HOME")
770
if runtime.GOOS == "windows" {
771
name = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
773
name = filepath.Join(name, ".gobuildkey")
774
f, err := os.Open(name)
779
r := bufio.NewReader(f)
780
for i := 0; i < 3; i++ {
781
b, _, err := r.ReadLine()
785
b = bytes.TrimSpace(b)
796
func cp(dst, src string) error {
797
sf, err := os.Open(src)
806
df, err := os.Create(dst)
811
// Windows doesn't currently implement Fchmod
812
if runtime.GOOS != "windows" {
813
if err := df.Chmod(fi.Mode()); err != nil {
817
_, err = io.Copy(df, sf)
821
func cpDir(dst, src string) error {
822
walk := func(srcPath string, info os.FileInfo, err error) error {
826
dstPath := filepath.Join(dst, srcPath[len(src):])
828
return os.MkdirAll(dstPath, 0755)
830
return cp(dstPath, srcPath)
832
return filepath.Walk(src, walk)
835
func cpAllDir(dst, basePath string, dirs ...string) error {
836
for _, dir := range dirs {
837
if err := cpDir(filepath.Join(dst, dir), filepath.Join(basePath, dir)); err != nil {
844
func makeTar(targ, workdir string) error {
845
f, err := os.Create(targ)
849
zout := gzip.NewWriter(f)
850
tw := tar.NewWriter(zout)
852
err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
853
if !strings.HasPrefix(path, workdir) {
854
log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir)
856
name := path[len(workdir):]
858
// Chop of any leading / from filename, leftover from removing workdir.
859
if strings.HasPrefix(name, "/") {
862
// Don't include things outside of the go subdirectory (for instance,
863
// the zip file that we're currently writing here.)
864
if !strings.HasPrefix(name, "go/") {
868
log.Printf("adding to tar: %s", name)
870
target, _ := os.Readlink(path)
871
hdr, err := tar.FileInfoHeader(fi, target)
881
// Force permissions to 0755 for executables, 0644 for everything else.
882
if fi.Mode().Perm()&0111 != 0 {
883
hdr.Mode = hdr.Mode&^0777 | 0755
885
hdr.Mode = hdr.Mode&^0777 | 0644
888
err = tw.WriteHeader(hdr)
890
return fmt.Errorf("Error writing file %q: %v", name, err)
895
r, err := os.Open(path)
900
_, err = io.Copy(tw, r)
906
if err := tw.Close(); err != nil {
909
if err := zout.Close(); err != nil {
915
func makeZip(targ, workdir string) error {
916
f, err := os.Create(targ)
920
zw := zip.NewWriter(f)
922
err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error {
923
if !strings.HasPrefix(path, workdir) {
924
log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir)
926
name := path[len(workdir):]
928
// Convert to Unix-style named paths, as that's the
929
// type of zip file that archive/zip creates.
930
name = strings.Replace(name, "\\", "/", -1)
931
// Chop of any leading / from filename, leftover from removing workdir.
932
if strings.HasPrefix(name, "/") {
935
// Don't include things outside of the go subdirectory (for instance,
936
// the zip file that we're currently writing here.)
937
if !strings.HasPrefix(name, "go/") {
941
log.Printf("adding to zip: %s", name)
943
fh, err := zip.FileInfoHeader(fi)
948
fh.Method = zip.Deflate
950
fh.Name += "/" // append trailing slash
951
fh.Method = zip.Store // no need to deflate 0 byte files
953
w, err := zw.CreateHeader(fh)
960
r, err := os.Open(path)
965
_, err = io.Copy(w, r)
971
if err := zw.Close(); err != nil {
983
"http://wix.sourceforge.net/, version 3.5",
984
[]string{`C:\Program Files\Windows Installer XML v3.5\bin`,
985
`C:\Program Files (x86)\Windows Installer XML v3.5\bin`},
989
"http://mercurial.selenic.com/wiki/WindowsInstall",
990
[]string{`C:\Program Files\Mercurial`,
991
`C:\Program Files (x86)\Mercurial`,
996
"Mingw gcc; http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/",
997
[]string{`C:\Mingw\bin`},
1000
var windowsDeps = map[string]tool{
1005
"cmd": {"Windows cmd.exe", nil},
1009
func checkWindowsDeps() {
1010
for prog, help := range windowsDeps {
1011
absPath, err := lookPath(prog)
1013
log.Fatalf("Failed to find necessary binary %q in path or common locations; %s", prog, help)
1016
log.Printf("found windows dep %s at %s", prog, absPath)
1021
func lookPath(prog string) (absPath string, err error) {
1022
absPath, err = exec.LookPath(prog)
1026
t, ok := windowsDeps[prog]
1030
for _, dir := range t.commonDirs {
1031
for _, ext := range []string{"exe", "bat"} {
1032
absPath = filepath.Join(dir, prog+"."+ext)
1033
if _, err1 := os.Stat(absPath); err1 == nil {
1035
os.Setenv("PATH", os.Getenv("PATH")+";"+dir)