github.com/reiver/go@v0.0.0-20150109200633-1d0c7792f172/src/cmd/api/run.go (about)

     1  // Copyright 2013 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  // +build ignore
     6  
     7  // The run program is invoked via "go run" from src/run.bash or
     8  // src/run.bat conditionally builds and runs the cmd/api tool.
     9  //
    10  // TODO(bradfitz): the "conditional" condition is always true.
    11  // We should only do this if the user has the hg codereview extension
    12  // enabled and verifies that the go.tools subrepo is checked out with
    13  // a suitably recently version. In prep for the cmd/api rewrite.
    14  package main
    15  
    16  import (
    17  	"fmt"
    18  	"log"
    19  	"net/http"
    20  	"os"
    21  	"os/exec"
    22  	"os/user"
    23  	"path/filepath"
    24  	"runtime"
    25  	"strings"
    26  )
    27  
    28  // goToolsVersion is the git revision of the x/tools subrepo we need
    29  // to build cmd/api.  This only needs to be updated whenever a go/types
    30  // bug fix is needed by the cmd/api tool.
    31  const goToolsVersion = "875ff2496f865e" // aka hg 6698ca2900e2
    32  
    33  var goroot string
    34  
    35  func main() {
    36  	log.SetFlags(0)
    37  	goroot = os.Getenv("GOROOT") // should be set by run.{bash,bat}
    38  	if goroot == "" {
    39  		log.Fatal("No $GOROOT set.")
    40  	}
    41  	_, err := exec.LookPath("git")
    42  	if err != nil {
    43  		fmt.Println("Skipping cmd/api checks; git not available")
    44  		return
    45  	}
    46  
    47  	gopath := prepGoPath()
    48  
    49  	cmd := exec.Command("go", "install", "--tags=api_tool", "cmd/api")
    50  	cmd.Env = append(filterOut(os.Environ(), "GOARCH", "GOPATH"), "GOPATH="+gopath)
    51  	out, err := cmd.CombinedOutput()
    52  	if err != nil {
    53  		log.Fatalf("Error installing cmd/api: %v\n%s", err, out)
    54  	}
    55  
    56  	out, err = exec.Command("go", "tool", "api",
    57  		"-c", file("go1", "go1.1", "go1.2", "go1.3", "go1.4"),
    58  		"-next", file("next"),
    59  		"-except", file("except")).CombinedOutput()
    60  	if err != nil {
    61  		log.Fatalf("Error running API checker: %v\n%s", err, out)
    62  	}
    63  	fmt.Print(string(out))
    64  }
    65  
    66  // filterOut returns a copy of the src environment without environment
    67  // variables from remove.
    68  // TODO: delete when issue 6201 is fixed.
    69  func filterOut(src []string, remove ...string) (out []string) {
    70  S:
    71  	for _, s := range src {
    72  		for _, r := range remove {
    73  			if strings.HasPrefix(s, r) && strings.HasPrefix(s, r+"=") {
    74  				continue S
    75  			}
    76  		}
    77  		out = append(out, s)
    78  	}
    79  	return
    80  }
    81  
    82  // file expands s to $GOROOT/api/s.txt.
    83  // If there are more than 1, they're comma-separated.
    84  func file(s ...string) string {
    85  	if len(s) > 1 {
    86  		return file(s[0]) + "," + file(s[1:]...)
    87  	}
    88  	return filepath.Join(goroot, "api", s[0]+".txt")
    89  }
    90  
    91  // prepGoPath returns a GOPATH for the "go" tool to compile the API tool with.
    92  // It tries to re-use a go.tools checkout from a previous run if possible,
    93  // else it hg clones it.
    94  func prepGoPath() string {
    95  	// Use a builder-specific temp directory name, so builders running
    96  	// two copies don't trample on each other: https://golang.org/issue/9407
    97  	// We don't use io.TempDir or a PID or timestamp here because we do
    98  	// want this to be stable between runs, to minimize "git clone" calls
    99  	// in the common case.
   100  	var tempBase = fmt.Sprintf("go.tools.TMP.%s.%s", runtime.GOOS, runtime.GOARCH)
   101  
   102  	username := ""
   103  	u, err := user.Current()
   104  	if err == nil {
   105  		username = u.Username
   106  	} else {
   107  		username = os.Getenv("USER")
   108  		if username == "" {
   109  			username = "nobody"
   110  		}
   111  	}
   112  
   113  	// The GOPATH we'll return
   114  	gopath := filepath.Join(os.TempDir(), "gopath-api-"+cleanUsername(username)+"-"+cleanUsername(strings.Fields(runtime.Version())[0]), goToolsVersion)
   115  
   116  	// cloneDir is where we run "git clone".
   117  	cloneDir := filepath.Join(gopath, "src", "code.google.com", "p")
   118  
   119  	// The dir we clone into. We only atomically rename it to finalDir on
   120  	// clone success.
   121  	tmpDir := filepath.Join(cloneDir, tempBase)
   122  
   123  	// finalDir is where the checkout will live once it's complete.
   124  	finalDir := filepath.Join(cloneDir, "go.tools")
   125  
   126  	if goToolsCheckoutGood(finalDir) {
   127  		return gopath
   128  	}
   129  	os.RemoveAll(finalDir) // in case it's there but corrupt
   130  	os.RemoveAll(tmpDir)   // in case of aborted hg clone before
   131  
   132  	if err := os.MkdirAll(cloneDir, 0700); err != nil {
   133  		log.Fatal(err)
   134  	}
   135  	cmd := exec.Command("git", "clone", "https://go.googlesource.com/tools", tempBase)
   136  	cmd.Dir = cloneDir
   137  	out, err := cmd.CombinedOutput()
   138  	if err != nil {
   139  		if _, err := http.Head("http://ip.appspot.com/"); err != nil {
   140  			log.Printf("# Skipping API check; network appears to be unavailable")
   141  			os.Exit(0)
   142  		}
   143  		log.Fatalf("Error running git clone on x/tools: %v\n%s", err, out)
   144  	}
   145  	cmd = exec.Command("git", "reset", "--hard", goToolsVersion)
   146  	cmd.Dir = tmpDir
   147  	out, err = cmd.CombinedOutput()
   148  	if err != nil {
   149  		log.Fatalf("Error updating x/tools in %v to %v: %v, %s", tmpDir, goToolsVersion, err, out)
   150  	}
   151  
   152  	if err := os.Rename(tmpDir, finalDir); err != nil {
   153  		if os.IsExist(err) {
   154  			// A different builder beat us into putting this repo into
   155  			// its final place. But that's fine; if it's there, it's
   156  			// the right version and we can use it.
   157  			//
   158  			// This happens on the Go project's Windows builders
   159  			// especially, where we have two builders (386 and amd64)
   160  			// running at the same time, trying to compete for moving
   161  			// it into place.
   162  			os.RemoveAll(tmpDir)
   163  		} else {
   164  			log.Fatal(err)
   165  		}
   166  	}
   167  	return gopath
   168  }
   169  
   170  func cleanUsername(n string) string {
   171  	b := make([]rune, len(n))
   172  	for i, r := range n {
   173  		if r == '\\' || r == '/' || r == ':' {
   174  			b[i] = '_'
   175  		} else {
   176  			b[i] = r
   177  		}
   178  	}
   179  	return string(b)
   180  }
   181  
   182  func goToolsCheckoutGood(dir string) bool {
   183  	if _, err := os.Stat(dir); err != nil {
   184  		return false
   185  	}
   186  
   187  	cmd := exec.Command("git", "rev-parse", "HEAD")
   188  	cmd.Dir = dir
   189  	out, err := cmd.Output()
   190  	if err != nil {
   191  		return false
   192  	}
   193  	id := strings.TrimSpace(string(out))
   194  	if !strings.HasPrefix(id, goToolsVersion) {
   195  		return false
   196  	}
   197  
   198  	cmd = exec.Command("git", "status", "--porcelain")
   199  	cmd.Dir = dir
   200  	out, err = cmd.Output()
   201  	if err != nil || strings.TrimSpace(string(out)) != "" {
   202  		return false
   203  	}
   204  	return true
   205  }