Skip to content

Commit cb27fed

Browse files
committed
Do not rebuild gotip if the HEAD does not change
On successful build a sentinel zero-byte file is written, similar to .unpacked-success in internal/version. If such file exists on next gotip download, compare HEADs before and after FETCH_HEAD checkout, and trigger the build iff they differ. In that case, sentinel file is removed to make git clean happy and indicate that rebuild is needed.
1 parent 2d68e82 commit cb27fed

File tree

1 file changed

+39
-1
lines changed

1 file changed

+39
-1
lines changed

gotip/main.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
package main
1717

1818
import (
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

199233
const 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+
201239
func exe() string {
202240
if runtime.GOOS == "windows" {
203241
return ".exe"

0 commit comments

Comments
 (0)