1616package main
1717
1818import (
19+ "bytes"
1920 "errors"
2021 "fmt"
22+ "io/ioutil"
2123 "log"
2224 "os"
2325 "os/exec"
@@ -95,6 +97,9 @@ func installTip(root, clNumber string) error {
9597 cmd .Dir = root
9698 return cmd .Output ()
9799 }
100+ chomp := func (s []byte ) []byte {
101+ return bytes .TrimRight (s , " \t \r \n " )
102+ }
98103
99104 if _ , err := os .Stat (filepath .Join (root , ".git" )); err != nil {
100105 if err := os .MkdirAll (root , 0755 ); err != nil {
@@ -105,6 +110,16 @@ func installTip(root, clNumber string) error {
105110 }
106111 }
107112
113+ // Get current HEAD if there is an existing build.
114+ var oldHead []byte
115+ if _ , err := os .Stat (filepath .Join (root , builtOkay )); err == nil {
116+ head , err := gitOutput ("rev-parse" , "--short" , "HEAD" )
117+ if err != nil {
118+ return fmt .Errorf ("failed to parse old HEAD revision: %v" , err )
119+ }
120+ oldHead = head
121+ }
122+
108123 if clNumber != "" {
109124 fmt .Fprintf (os .Stderr , "This will download and execute code from golang.org/cl/%s, continue? [y/n] " , clNumber )
110125 var answer string
@@ -152,6 +167,23 @@ func installTip(root, clNumber string) error {
152167 if err := git ("-c" , "advice.detachedHead=false" , "checkout" , "FETCH_HEAD" ); err != nil {
153168 return fmt .Errorf ("failed to checkout git repository: %v" , err )
154169 }
170+
171+ // Compare old and new HEADs to avoid unnecessary rebuilds if there are no changes.
172+ // Notice that oldHead is not nil iff the last build was successful.
173+ if oldHead != nil {
174+ newHead , err := gitOutput ("rev-parse" , "--short" , "HEAD" )
175+ if err != nil {
176+ return fmt .Errorf ("failed to parse new HEAD revision: %v" , err )
177+ }
178+ if bytes .Equal (oldHead , newHead ) {
179+ log .Printf ("Already built %s in %v" , chomp (newHead ), root )
180+ return nil
181+ }
182+ if err := os .Remove (filepath .Join (root , builtOkay )); err != nil {
183+ return err
184+ }
185+ }
186+
155187 // It shouldn't be the case, but in practice sometimes binary artifacts
156188 // generated by earlier Go versions interfere with the build.
157189 //
@@ -181,7 +213,9 @@ func installTip(root, clNumber string) error {
181213 if err := cmd .Run (); err != nil {
182214 return fmt .Errorf ("failed to build go: %v" , err )
183215 }
184-
216+ if err := ioutil .WriteFile (filepath .Join (root , builtOkay ), nil , 0644 ); err != nil {
217+ return err
218+ }
185219 return nil
186220}
187221
@@ -198,6 +232,10 @@ func makeScript() string {
198232
199233const caseInsensitiveEnv = runtime .GOOS == "windows"
200234
235+ // builtOkay is a sentinel zero-byte file to indicate that Go
236+ // repository was cloned and built successfully.
237+ const builtOkay = ".built-success"
238+
201239func exe () string {
202240 if runtime .GOOS == "windows" {
203241 return ".exe"
0 commit comments