github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/misc/dist/bindist.go (about) 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. 4 5 // This is a tool for packaging binary releases. 6 // It supports FreeBSD, Linux, NetBSD, OS X, and Windows. 7 package main 8 9 import ( 10 "archive/tar" 11 "archive/zip" 12 "bufio" 13 "bytes" 14 "compress/gzip" 15 "encoding/base64" 16 "flag" 17 "fmt" 18 "io" 19 "io/ioutil" 20 "log" 21 "mime/multipart" 22 "net/http" 23 "os" 24 "os/exec" 25 "path/filepath" 26 "regexp" 27 "runtime" 28 "strings" 29 ) 30 31 var ( 32 tag = flag.String("tag", "release", "mercurial tag to check out") 33 repo = flag.String("repo", "https://code.google.com/p/go", "repo URL") 34 tourPath = flag.String("tour", "code.google.com/p/go-tour", "Go tour repo import path") 35 verbose = flag.Bool("v", false, "verbose output") 36 upload = flag.Bool("upload", true, "upload resulting files to Google Code") 37 wxsFile = flag.String("wxs", "", "path to custom installer.wxs") 38 addLabel = flag.String("label", "", "additional label to apply to file when uploading") 39 includeRace = flag.Bool("race", true, "build race detector packages") 40 versionOverride = flag.String("version", "", "override version name") 41 42 username, password string // for Google Code upload 43 ) 44 45 const ( 46 uploadURL = "https://go.googlecode.com/files" 47 ) 48 49 var preBuildCleanFiles = []string{ 50 "lib/codereview", 51 "misc/dashboard/godashboard", 52 "src/cmd/cov", 53 "src/cmd/prof", 54 "src/pkg/exp", 55 "src/pkg/old", 56 } 57 58 var cleanFiles = []string{ 59 ".hg", 60 ".hgtags", 61 ".hgignore", 62 "VERSION.cache", 63 } 64 65 var sourceCleanFiles = []string{ 66 "bin", 67 "pkg", 68 } 69 70 var tourPackages = []string{ 71 "pic", 72 "tree", 73 "wc", 74 } 75 76 var tourContent = []string{ 77 "js", 78 "prog", 79 "solutions", 80 "static", 81 "template", 82 "tour.article", 83 } 84 85 // The os-arches that support the race toolchain. 86 var raceAvailable = []string{ 87 "darwin-amd64", 88 "linux-amd64", 89 "windows-amd64", 90 } 91 92 var fileRe = regexp.MustCompile(`^(go[a-z0-9-.]+)\.(src|([a-z0-9]+)-([a-z0-9]+))\.`) 93 94 func main() { 95 flag.Usage = func() { 96 fmt.Fprintf(os.Stderr, "usage: %s [flags] targets...\n", os.Args[0]) 97 flag.PrintDefaults() 98 os.Exit(2) 99 } 100 flag.Parse() 101 if flag.NArg() == 0 { 102 flag.Usage() 103 } 104 if runtime.GOOS == "windows" { 105 checkWindowsDeps() 106 } 107 108 if *upload { 109 if err := readCredentials(); err != nil { 110 log.Println("readCredentials:", err) 111 } 112 } 113 for _, targ := range flag.Args() { 114 var b Build 115 if m := fileRe.FindStringSubmatch(targ); m != nil { 116 // targ is a file name; upload it to googlecode. 117 version := m[1] 118 if m[2] == "src" { 119 b.Source = true 120 } else { 121 b.OS = m[3] 122 b.Arch = m[4] 123 } 124 if !*upload { 125 log.Printf("%s: -upload=false, skipping", targ) 126 continue 127 } 128 if err := b.Upload(version, targ); err != nil { 129 log.Printf("%s: %v", targ, err) 130 } 131 continue 132 } 133 if targ == "source" { 134 b.Source = true 135 } else { 136 p := strings.SplitN(targ, "-", 2) 137 if len(p) != 2 { 138 log.Println("Ignoring unrecognized target:", targ) 139 continue 140 } 141 b.OS = p[0] 142 b.Arch = p[1] 143 if *includeRace { 144 for _, t := range raceAvailable { 145 if t == targ { 146 b.Race = true 147 } 148 } 149 } 150 } 151 if err := b.Do(); err != nil { 152 log.Printf("%s: %v", targ, err) 153 } 154 } 155 } 156 157 type Build struct { 158 Source bool // if true, OS and Arch must be empty 159 Race bool // build race toolchain 160 OS string 161 Arch string 162 root string 163 gopath string 164 } 165 166 func (b *Build) Do() error { 167 work, err := ioutil.TempDir("", "bindist") 168 if err != nil { 169 return err 170 } 171 defer os.RemoveAll(work) 172 b.root = filepath.Join(work, "go") 173 b.gopath = work 174 175 // Clone Go distribution and update to tag. 176 _, err = b.run(work, "hg", "clone", "-q", *repo, b.root) 177 if err != nil { 178 return err 179 } 180 _, err = b.run(b.root, "hg", "update", *tag) 181 if err != nil { 182 return err 183 } 184 185 // Remove exp and old packages. 186 if err := b.clean(preBuildCleanFiles); err != nil { 187 return err 188 } 189 190 src := filepath.Join(b.root, "src") 191 if b.Source { 192 if runtime.GOOS == "windows" { 193 log.Print("Warning: running make.bash on Windows; source builds are intended to be run on a Unix machine") 194 } 195 // Build dist tool only. 196 _, err = b.run(src, "bash", "make.bash", "--dist-tool") 197 } else { 198 // Build. 199 if b.OS == "windows" { 200 _, err = b.run(src, "cmd", "/C", "make.bat") 201 } else { 202 _, err = b.run(src, "bash", "make.bash") 203 } 204 if b.Race { 205 if err != nil { 206 return err 207 } 208 goCmd := filepath.Join(b.root, "bin", "go") 209 if b.OS == "windows" { 210 goCmd += ".exe" 211 } 212 _, err = b.run(src, goCmd, "install", "-race", "std") 213 if err != nil { 214 return err 215 } 216 // Re-install std without -race, so that we're not left 217 // with a slower, race-enabled cmd/go, cmd/godoc, etc. 218 _, err = b.run(src, goCmd, "install", "-a", "std") 219 } 220 if err != nil { 221 return err 222 } 223 err = b.tour() 224 } 225 if err != nil { 226 return err 227 } 228 229 // Get version strings. 230 var ( 231 version string // "weekly.2012-03-04" 232 fullVersion []byte // "weekly.2012-03-04 9353aa1efdf3" 233 ) 234 pat := filepath.Join(b.root, "pkg/tool/*/dist*") // trailing * for .exe 235 m, err := filepath.Glob(pat) 236 if err != nil { 237 return err 238 } 239 if len(m) == 0 { 240 return fmt.Errorf("couldn't find dist in %q", pat) 241 } 242 fullVersion, err = b.run("", m[0], "version") 243 if err != nil { 244 return err 245 } 246 fullVersion = bytes.TrimSpace(fullVersion) 247 v := bytes.SplitN(fullVersion, []byte(" "), 2) 248 version = string(v[0]) 249 if *versionOverride != "" { 250 version = *versionOverride 251 } 252 253 // Write VERSION file. 254 err = ioutil.WriteFile(filepath.Join(b.root, "VERSION"), fullVersion, 0644) 255 if err != nil { 256 return err 257 } 258 259 // Clean goroot. 260 if err := b.clean(cleanFiles); err != nil { 261 return err 262 } 263 if b.Source { 264 if err := b.clean(sourceCleanFiles); err != nil { 265 return err 266 } 267 } 268 269 // Create packages. 270 base := fmt.Sprintf("%s.%s-%s", version, b.OS, b.Arch) 271 if !strings.HasPrefix(base, "go") { 272 base = "go." + base 273 } 274 var targs []string 275 switch b.OS { 276 case "linux", "freebsd", "netbsd", "": 277 // build tarball 278 targ := base 279 if b.Source { 280 targ = fmt.Sprintf("%s.src", version) 281 if !strings.HasPrefix(targ, "go") { 282 targ = "go." + targ 283 } 284 } 285 targ += ".tar.gz" 286 err = makeTar(targ, work) 287 targs = append(targs, targ) 288 case "darwin": 289 // build tarball 290 targ := base + ".tar.gz" 291 err = makeTar(targ, work) 292 targs = append(targs, targ) 293 294 // build pkg 295 // arrange work so it's laid out as the dest filesystem 296 etc := filepath.Join(b.root, "misc/dist/darwin/etc") 297 _, err = b.run(work, "cp", "-r", etc, ".") 298 if err != nil { 299 return err 300 } 301 localDir := filepath.Join(work, "usr/local") 302 err = os.MkdirAll(localDir, 0755) 303 if err != nil { 304 return err 305 } 306 _, err = b.run(work, "mv", "go", localDir) 307 if err != nil { 308 return err 309 } 310 // build package 311 pkgdest, err := ioutil.TempDir("", "pkgdest") 312 if err != nil { 313 return err 314 } 315 defer os.RemoveAll(pkgdest) 316 dist := filepath.Join(runtime.GOROOT(), "misc/dist") 317 _, err = b.run("", "pkgbuild", 318 "--identifier", "com.googlecode.go", 319 "--version", "1.0", 320 "--scripts", filepath.Join(dist, "darwin/scripts"), 321 "--root", work, 322 filepath.Join(pkgdest, "com.googlecode.go.pkg")) 323 if err != nil { 324 return err 325 } 326 targ = base + ".pkg" 327 _, err = b.run("", "productbuild", 328 "--distribution", filepath.Join(dist, "darwin/Distribution"), 329 "--resources", filepath.Join(dist, "darwin/Resources"), 330 "--package-path", pkgdest, 331 targ) 332 if err != nil { 333 return err 334 } 335 targs = append(targs, targ) 336 case "windows": 337 // Create ZIP file. 338 zip := filepath.Join(work, base+".zip") 339 err = makeZip(zip, work) 340 // Copy zip to target file. 341 targ := base + ".zip" 342 err = cp(targ, zip) 343 if err != nil { 344 return err 345 } 346 targs = append(targs, targ) 347 348 // Create MSI installer. 349 win := filepath.Join(b.root, "misc/dist/windows") 350 installer := filepath.Join(win, "installer.wxs") 351 if *wxsFile != "" { 352 installer = *wxsFile 353 } 354 appfiles := filepath.Join(work, "AppFiles.wxs") 355 msi := filepath.Join(work, "installer.msi") 356 // Gather files. 357 _, err = b.run(work, "heat", "dir", "go", 358 "-nologo", 359 "-gg", "-g1", "-srd", "-sfrag", 360 "-cg", "AppFiles", 361 "-template", "fragment", 362 "-dr", "INSTALLDIR", 363 "-var", "var.SourceDir", 364 "-out", appfiles) 365 if err != nil { 366 return err 367 } 368 // Build package. 369 _, err = b.run(work, "candle", 370 "-nologo", 371 "-dVersion="+version, 372 "-dArch="+b.Arch, 373 "-dSourceDir=go", 374 installer, appfiles) 375 if err != nil { 376 return err 377 } 378 appfiles = filepath.Join(work, "AppFiles.wixobj") 379 installer = filepath.Join(work, "installer.wixobj") 380 _, err = b.run(win, "light", 381 "-nologo", 382 "-ext", "WixUIExtension", 383 "-ext", "WixUtilExtension", 384 installer, appfiles, 385 "-o", msi) 386 if err != nil { 387 return err 388 } 389 // Copy installer to target file. 390 targ = base + ".msi" 391 err = cp(targ, msi) 392 targs = append(targs, targ) 393 } 394 if err == nil && *upload { 395 for _, targ := range targs { 396 err = b.Upload(version, targ) 397 if err != nil { 398 return err 399 } 400 } 401 } 402 return err 403 } 404 405 func (b *Build) tour() error { 406 // go get the gotour package. 407 _, err := b.run(b.gopath, filepath.Join(b.root, "bin", "go"), "get", *tourPath+"/gotour") 408 if err != nil { 409 return err 410 } 411 412 // Copy all the tour content to $GOROOT/misc/tour. 413 importPath := filepath.FromSlash(*tourPath) 414 tourSrc := filepath.Join(b.gopath, "src", importPath) 415 contentDir := filepath.Join(b.root, "misc", "tour") 416 if err = cpAllDir(contentDir, tourSrc, tourContent...); err != nil { 417 return err 418 } 419 420 // Copy the tour source code so it's accessible with $GOPATH pointing to $GOROOT/misc/tour. 421 if err = cpAllDir(filepath.Join(contentDir, "src", importPath), tourSrc, tourPackages...); err != nil { 422 return err 423 } 424 425 // Copy gotour binary to tool directory as "tour"; invoked as "go tool tour". 426 ext := "" 427 if runtime.GOOS == "windows" { 428 ext = ".exe" 429 } 430 return cp( 431 filepath.Join(b.root, "pkg", "tool", b.OS+"_"+b.Arch, "tour"+ext), 432 filepath.Join(b.gopath, "bin", "gotour"+ext), 433 ) 434 } 435 436 func (b *Build) run(dir, name string, args ...string) ([]byte, error) { 437 buf := new(bytes.Buffer) 438 absName, err := lookPath(name) 439 if err != nil { 440 return nil, err 441 } 442 cmd := exec.Command(absName, args...) 443 var output io.Writer = buf 444 if *verbose { 445 log.Printf("Running %q %q", absName, args) 446 output = io.MultiWriter(buf, os.Stdout) 447 } 448 cmd.Stdout = output 449 cmd.Stderr = output 450 cmd.Dir = dir 451 cmd.Env = b.env() 452 if err := cmd.Run(); err != nil { 453 fmt.Fprintf(os.Stderr, "%s", buf.Bytes()) 454 return nil, fmt.Errorf("%s %s: %v", name, strings.Join(args, " "), err) 455 } 456 return buf.Bytes(), nil 457 } 458 459 var cleanEnv = []string{ 460 "GOARCH", 461 "GOBIN", 462 "GOHOSTARCH", 463 "GOHOSTOS", 464 "GOOS", 465 "GOROOT", 466 "GOROOT_FINAL", 467 "GOPATH", 468 } 469 470 func (b *Build) env() []string { 471 env := os.Environ() 472 for i := 0; i < len(env); i++ { 473 for _, c := range cleanEnv { 474 if strings.HasPrefix(env[i], c+"=") { 475 env = append(env[:i], env[i+1:]...) 476 } 477 } 478 } 479 final := "/usr/local/go" 480 if b.OS == "windows" { 481 final = `c:\go` 482 } 483 env = append(env, 484 "GOARCH="+b.Arch, 485 "GOHOSTARCH="+b.Arch, 486 "GOHOSTOS="+b.OS, 487 "GOOS="+b.OS, 488 "GOROOT="+b.root, 489 "GOROOT_FINAL="+final, 490 "GOPATH="+b.gopath, 491 ) 492 return env 493 } 494 495 func (b *Build) Upload(version string, filename string) error { 496 // Prepare upload metadata. 497 var labels []string 498 os_, arch := b.OS, b.Arch 499 switch b.Arch { 500 case "386": 501 arch = "x86 32-bit" 502 case "amd64": 503 arch = "x86 64-bit" 504 } 505 if arch != "" { 506 labels = append(labels, "Arch-"+b.Arch) 507 } 508 var opsys, ftype string // labels 509 switch b.OS { 510 case "linux": 511 os_ = "Linux" 512 opsys = "Linux" 513 case "freebsd": 514 os_ = "FreeBSD" 515 opsys = "FreeBSD" 516 case "darwin": 517 os_ = "Mac OS X" 518 opsys = "OSX" 519 case "netbsd": 520 os_ = "NetBSD" 521 opsys = "NetBSD" 522 case "windows": 523 os_ = "Windows" 524 opsys = "Windows" 525 } 526 summary := fmt.Sprintf("%s %s (%s)", version, os_, arch) 527 switch { 528 case strings.HasSuffix(filename, ".msi"): 529 ftype = "Installer" 530 summary += " MSI installer" 531 case strings.HasSuffix(filename, ".pkg"): 532 ftype = "Installer" 533 summary += " PKG installer" 534 case strings.HasSuffix(filename, ".zip"): 535 ftype = "Archive" 536 summary += " ZIP archive" 537 case strings.HasSuffix(filename, ".tar.gz"): 538 ftype = "Archive" 539 summary += " tarball" 540 } 541 if b.Source { 542 ftype = "Source" 543 summary = fmt.Sprintf("%s (source only)", version) 544 } 545 if opsys != "" { 546 labels = append(labels, "OpSys-"+opsys) 547 } 548 if ftype != "" { 549 labels = append(labels, "Type-"+ftype) 550 } 551 if *addLabel != "" { 552 labels = append(labels, *addLabel) 553 } 554 // Put "Go" prefix on summary when it doesn't already begin with "go". 555 if !strings.HasPrefix(strings.ToLower(summary), "go") { 556 summary = "Go " + summary 557 } 558 559 // Open file to upload. 560 f, err := os.Open(filename) 561 if err != nil { 562 return err 563 } 564 defer f.Close() 565 566 // Prepare multipart payload. 567 body := new(bytes.Buffer) 568 w := multipart.NewWriter(body) 569 if err := w.WriteField("summary", summary); err != nil { 570 return err 571 } 572 for _, l := range labels { 573 if err := w.WriteField("label", l); err != nil { 574 return err 575 } 576 } 577 fw, err := w.CreateFormFile("filename", filename) 578 if err != nil { 579 return err 580 } 581 if _, err = io.Copy(fw, f); err != nil { 582 return err 583 } 584 if err := w.Close(); err != nil { 585 return err 586 } 587 588 // Send the file to Google Code. 589 req, err := http.NewRequest("POST", uploadURL, body) 590 if err != nil { 591 return err 592 } 593 token := fmt.Sprintf("%s:%s", username, password) 594 token = base64.StdEncoding.EncodeToString([]byte(token)) 595 req.Header.Set("Authorization", "Basic "+token) 596 req.Header.Set("Content-type", w.FormDataContentType()) 597 598 resp, err := http.DefaultTransport.RoundTrip(req) 599 if err != nil { 600 return err 601 } 602 if resp.StatusCode/100 != 2 { 603 fmt.Fprintln(os.Stderr, "upload failed") 604 defer resp.Body.Close() 605 io.Copy(os.Stderr, resp.Body) 606 return fmt.Errorf("upload: %s", resp.Status) 607 } 608 return nil 609 } 610 611 func (b *Build) clean(files []string) error { 612 for _, name := range files { 613 err := os.RemoveAll(filepath.Join(b.root, name)) 614 if err != nil { 615 return err 616 } 617 } 618 return nil 619 } 620 621 func exists(path string) bool { 622 _, err := os.Stat(path) 623 return err == nil 624 } 625 626 func readCredentials() error { 627 name := os.Getenv("HOME") 628 if runtime.GOOS == "windows" { 629 name = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") 630 } 631 name = filepath.Join(name, ".gobuildkey") 632 f, err := os.Open(name) 633 if err != nil { 634 return err 635 } 636 defer f.Close() 637 r := bufio.NewReader(f) 638 for i := 0; i < 3; i++ { 639 b, _, err := r.ReadLine() 640 if err != nil { 641 return err 642 } 643 b = bytes.TrimSpace(b) 644 switch i { 645 case 1: 646 username = string(b) 647 case 2: 648 password = string(b) 649 } 650 } 651 return nil 652 } 653 654 func cp(dst, src string) error { 655 sf, err := os.Open(src) 656 if err != nil { 657 return err 658 } 659 defer sf.Close() 660 fi, err := sf.Stat() 661 if err != nil { 662 return err 663 } 664 df, err := os.Create(dst) 665 if err != nil { 666 return err 667 } 668 defer df.Close() 669 // Windows doesn't currently implement Fchmod 670 if runtime.GOOS != "windows" { 671 if err := df.Chmod(fi.Mode()); err != nil { 672 return err 673 } 674 } 675 _, err = io.Copy(df, sf) 676 return err 677 } 678 679 func cpDir(dst, src string) error { 680 walk := func(srcPath string, info os.FileInfo, err error) error { 681 if err != nil { 682 return err 683 } 684 dstPath := filepath.Join(dst, srcPath[len(src):]) 685 if info.IsDir() { 686 return os.MkdirAll(dstPath, 0755) 687 } 688 return cp(dstPath, srcPath) 689 } 690 return filepath.Walk(src, walk) 691 } 692 693 func cpAllDir(dst, basePath string, dirs ...string) error { 694 for _, dir := range dirs { 695 if err := cpDir(filepath.Join(dst, dir), filepath.Join(basePath, dir)); err != nil { 696 return err 697 } 698 } 699 return nil 700 } 701 702 func makeTar(targ, workdir string) error { 703 f, err := os.Create(targ) 704 if err != nil { 705 return err 706 } 707 zout := gzip.NewWriter(f) 708 tw := tar.NewWriter(zout) 709 710 err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error { 711 if !strings.HasPrefix(path, workdir) { 712 log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir) 713 } 714 name := path[len(workdir):] 715 716 // Chop of any leading / from filename, leftover from removing workdir. 717 if strings.HasPrefix(name, "/") { 718 name = name[1:] 719 } 720 // Don't include things outside of the go subdirectory (for instance, 721 // the zip file that we're currently writing here.) 722 if !strings.HasPrefix(name, "go/") { 723 return nil 724 } 725 if *verbose { 726 log.Printf("adding to tar: %s", name) 727 } 728 target, _ := os.Readlink(path) 729 hdr, err := tar.FileInfoHeader(fi, target) 730 if err != nil { 731 return err 732 } 733 hdr.Name = name 734 hdr.Uname = "root" 735 hdr.Gname = "root" 736 hdr.Uid = 0 737 hdr.Gid = 0 738 739 // Force permissions to 0755 for executables, 0644 for everything else. 740 if fi.Mode().Perm()&0111 != 0 { 741 hdr.Mode = hdr.Mode&^0777 | 0755 742 } else { 743 hdr.Mode = hdr.Mode&^0777 | 0644 744 } 745 746 err = tw.WriteHeader(hdr) 747 if err != nil { 748 return fmt.Errorf("Error writing file %q: %v", name, err) 749 } 750 if fi.IsDir() { 751 return nil 752 } 753 r, err := os.Open(path) 754 if err != nil { 755 return err 756 } 757 defer r.Close() 758 _, err = io.Copy(tw, r) 759 return err 760 }) 761 if err != nil { 762 return err 763 } 764 if err := tw.Close(); err != nil { 765 return err 766 } 767 if err := zout.Close(); err != nil { 768 return err 769 } 770 return f.Close() 771 } 772 773 func makeZip(targ, workdir string) error { 774 f, err := os.Create(targ) 775 if err != nil { 776 return err 777 } 778 zw := zip.NewWriter(f) 779 780 err = filepath.Walk(workdir, func(path string, fi os.FileInfo, err error) error { 781 if !strings.HasPrefix(path, workdir) { 782 log.Panicf("walked filename %q doesn't begin with workdir %q", path, workdir) 783 } 784 name := path[len(workdir):] 785 786 // Convert to Unix-style named paths, as that's the 787 // type of zip file that archive/zip creates. 788 name = strings.Replace(name, "\\", "/", -1) 789 // Chop of any leading / from filename, leftover from removing workdir. 790 if strings.HasPrefix(name, "/") { 791 name = name[1:] 792 } 793 // Don't include things outside of the go subdirectory (for instance, 794 // the zip file that we're currently writing here.) 795 if !strings.HasPrefix(name, "go/") { 796 return nil 797 } 798 if *verbose { 799 log.Printf("adding to zip: %s", name) 800 } 801 fh, err := zip.FileInfoHeader(fi) 802 if err != nil { 803 return err 804 } 805 fh.Name = name 806 fh.Method = zip.Deflate 807 if fi.IsDir() { 808 fh.Name += "/" // append trailing slash 809 fh.Method = zip.Store // no need to deflate 0 byte files 810 } 811 w, err := zw.CreateHeader(fh) 812 if err != nil { 813 return err 814 } 815 if fi.IsDir() { 816 return nil 817 } 818 r, err := os.Open(path) 819 if err != nil { 820 return err 821 } 822 defer r.Close() 823 _, err = io.Copy(w, r) 824 return err 825 }) 826 if err != nil { 827 return err 828 } 829 if err := zw.Close(); err != nil { 830 return err 831 } 832 return f.Close() 833 } 834 835 type tool struct { 836 name string 837 commonDirs []string 838 } 839 840 var wixTool = tool{ 841 "http://wix.sourceforge.net/, version 3.5", 842 []string{`C:\Program Files\Windows Installer XML v3.5\bin`, 843 `C:\Program Files (x86)\Windows Installer XML v3.5\bin`}, 844 } 845 846 var hgTool = tool{ 847 "http://mercurial.selenic.com/wiki/WindowsInstall", 848 []string{`C:\Program Files\Mercurial`, 849 `C:\Program Files (x86)\Mercurial`, 850 }, 851 } 852 853 var gccTool = tool{ 854 "Mingw gcc; http://sourceforge.net/projects/mingw/files/Installer/mingw-get-inst/", 855 []string{`C:\Mingw\bin`}, 856 } 857 858 var windowsDeps = map[string]tool{ 859 "gcc": gccTool, 860 "heat": wixTool, 861 "candle": wixTool, 862 "light": wixTool, 863 "cmd": {"Windows cmd.exe", nil}, 864 "hg": hgTool, 865 } 866 867 func checkWindowsDeps() { 868 for prog, help := range windowsDeps { 869 absPath, err := lookPath(prog) 870 if err != nil { 871 log.Fatalf("Failed to find necessary binary %q in path or common locations; %s", prog, help) 872 } 873 if *verbose { 874 log.Printf("found windows dep %s at %s", prog, absPath) 875 } 876 } 877 } 878 879 func lookPath(prog string) (absPath string, err error) { 880 absPath, err = exec.LookPath(prog) 881 if err == nil { 882 return 883 } 884 t, ok := windowsDeps[prog] 885 if !ok { 886 return 887 } 888 for _, dir := range t.commonDirs { 889 for _, ext := range []string{"exe", "bat"} { 890 absPath = filepath.Join(dir, prog+"."+ext) 891 if _, err1 := os.Stat(absPath); err1 == nil { 892 err = nil 893 os.Setenv("PATH", os.Getenv("PATH")+";"+dir) 894 return 895 } 896 } 897 } 898 return 899 }