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 }