Skip to content

Commit 1e75b26

Browse files
committed
_content: add rebuild page with reproducible build information
We now have a command to reproduce Go builds posted on go.dev/dl. Add a dashboard that people can check to see its results. We should be able to link to this page from https://reproducible-builds.org/citests/. For golang/go#57120. For golang/go#58884. Change-Id: I0bd1f9c26a9a003aa1f301125083195fdeb048b4 Reviewed-on: https://go-review.googlesource.com/c/website/+/513700 Reviewed-by: Heschi Kreinick <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent c999d51 commit 1e75b26

File tree

2 files changed

+169
-1
lines changed

2 files changed

+169
-1
lines changed

_content/rebuild.html

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<!--{
2+
"Title": "Go Reproducible Build Report",
3+
"layout": "article",
4+
"template": true
5+
}-->
6+
7+
<style>
8+
details { margin-left: 2em; }
9+
pre { margin-left: 2em; }
10+
.time { color: #777; }
11+
</style>
12+
13+
<p>
14+
As of Go 1.21, Go's binary toolchain downloads served by <a href="/dl/">go.dev/dl/</a> can be
15+
reproduced and verified by anyone, on any platform,
16+
using <a href="https://pkg.go.dev/golang.org/x/tools/cmd/gorebuild">golang.org/x/tools/cmd/gorebuild</a>.
17+
</p>
18+
19+
<p>
20+
This page is updated daily with the results of running <code>gorebuild</code> in an Ubuntu VM,
21+
using this script:
22+
</p>
23+
24+
<pre>
25+
apt-get update &&
26+
apt-get -y install software-properties-common &&
27+
add-apt-repository universe &&
28+
apt-get -y install golang-go msitools &&
29+
go run golang.org/x/build/cmd/gorebuild@latest -p=4
30+
</pre>
31+
32+
<p>
33+
The installation of <code>msitools</code> lets <code>gorebuild</code>
34+
check the contents of the Windows MSI installation file.
35+
The <code>-p=4</code> means to run up to four builds in parallel.
36+
</p>
37+
38+
{{define "marker"}}<span style="marker">{{template "markersymbol" .}}</span>{{end}}
39+
{{define "markersymbol"}}
40+
{{- if eq . "PASS" -}} ✅
41+
{{- else if eq . "SKIP" -}} —
42+
{{- else -}} ❌
43+
{{- end -}}
44+
{{end}}
45+
46+
{{define "log"}}
47+
<pre>
48+
{{range .Messages}}<span class="time">{{(rfc3339 .Time).UTC.Format "15:04:05"}}</span> {{.Text}}
49+
{{end}}
50+
</pre>
51+
{{end}}
52+
53+
{{define "autoopen"}} {{if not (eq . "PASS")}} open {{end}} {{end}}
54+
55+
{{$Report := json gorebuild}}
56+
{{with $Report}}
57+
58+
Using gorebuild from {{.Version}}<br><br>
59+
60+
Rebuild started at {{(rfc3339 .Start).UTC.Format "2006-01-02 15:04:05"}} UTC.<br>
61+
Rebuild finished at {{(rfc3339 .End).UTC.Format "2006-01-02 15:04:05"}} UTC.<br>
62+
Elapsed time: {{((rfc3339 .End).Sub (rfc3339 .Start)).Round 1e9}}.
63+
64+
<h2 id="releases">Releases</h2>
65+
66+
{{range .Releases}}
67+
<details {{template "autoopen" .Log.Status}} >
68+
<summary><b id="{{.Version}}">{{template "marker" .Log.Status}} {{.Version}}</b></summary>
69+
70+
<details>
71+
<summary>Log</summary>
72+
{{template "log" .Log}}
73+
</details>
74+
75+
{{range .Files}}
76+
<details {{template "autoopen" .Log.Status}}>
77+
<summary><b id="{{.Name}}">{{template "marker" .Log.Status}} {{.Name}}</b></summary>
78+
{{template "log" .Log}}
79+
</details>
80+
{{end}}
81+
82+
</details>
83+
{{end}}
84+
85+
<h2 id="bootstraps">Bootstraps</h2>
86+
87+
{{range .Bootstraps}}
88+
<details>
89+
<summary><b>Bootstrap {{.Version}}</b></summary>
90+
{{template "log" .Log}}
91+
</details>
92+
{{end}}
93+
94+
{{end}}

cmd/golangorg/server.go

+75-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"fmt"
1717
"go/format"
1818
"html/template"
19+
"io"
1920
"io/fs"
2021
"io/ioutil"
2122
"log"
@@ -27,6 +28,7 @@ import (
2728
"runtime"
2829
"runtime/debug"
2930
"strings"
31+
"sync"
3032
"sync/atomic"
3133
"time"
3234

@@ -242,6 +244,8 @@ func NewHandler(contentDir, goroot string) http.Handler {
242244
return h
243245
}
244246

247+
var gorebuild = NewCachedURL("https://gorebuild.storage.googleapis.com/gorebuild.json", 5*time.Minute)
248+
245249
// newSite creates a new site for a given content and goroot file system pair
246250
// and registers it in mux to handle requests for host.
247251
// If host is the empty string, the registrations are for the wildcard host.
@@ -251,11 +255,14 @@ func newSite(mux *http.ServeMux, host string, content, goroot fs.FS) (*web.Site,
251255
site.Funcs(template.FuncMap{
252256
"googleAnalytics": func() string { return googleAnalytics },
253257
"googleCN": func() bool { return host == "golang.google.cn" },
258+
"gorebuild": gorebuild.Get,
259+
"json": jsonUnmarshal,
254260
"newest": newest,
261+
"now": func() time.Time { return time.Now() },
255262
"releases": func() []*history.Major { return history.Majors },
263+
"rfc3339": parseRFC3339,
256264
"section": section,
257265
"version": func() string { return runtime.Version() },
258-
"now": func() time.Time { return time.Now() },
259266
})
260267
docs, err := pkgdoc.NewServer(fsys, site, googleCN)
261268
if err != nil {
@@ -269,6 +276,10 @@ func newSite(mux *http.ServeMux, host string, content, goroot fs.FS) (*web.Site,
269276
return site, nil
270277
}
271278

279+
func parseRFC3339(s string) (time.Time, error) {
280+
return time.Parse(time.RFC3339, s)
281+
}
282+
272283
// watchTip is a background goroutine that watches the main Go repo for updates.
273284
// When a new commit is available, watchTip downloads the new tree and calls
274285
// tipGoroot.Set to install the new file system.
@@ -803,3 +814,66 @@ func redirectPrefix(prefix string) http.Handler {
803814
http.Redirect(w, r, url, http.StatusMovedPermanently)
804815
})
805816
}
817+
818+
type CachedURL struct {
819+
url string
820+
timeout time.Duration
821+
822+
mu sync.Mutex
823+
data []byte
824+
err error
825+
etag string
826+
updated time.Time
827+
}
828+
829+
func NewCachedURL(url string, timeout time.Duration) *CachedURL {
830+
return &CachedURL{url: url, timeout: timeout}
831+
}
832+
833+
func (c *CachedURL) Get() (data []byte, err error) {
834+
c.mu.Lock()
835+
defer c.mu.Unlock()
836+
837+
if time.Since(c.updated) < c.timeout {
838+
return c.data, c.err
839+
}
840+
defer func() {
841+
c.updated = time.Now()
842+
c.data, c.err = data, err
843+
}()
844+
845+
cli := &http.Client{Timeout: 60 * time.Second}
846+
req, err := http.NewRequest("GET", c.url, nil)
847+
if err != nil {
848+
return nil, err
849+
}
850+
if c.etag != "" {
851+
req.Header.Set("If-None-Match", c.etag)
852+
}
853+
resp, err := cli.Do(req)
854+
if err != nil {
855+
return nil, fmt.Errorf("loading rebuild report JSON: %v", err)
856+
}
857+
defer resp.Body.Close()
858+
if resp.StatusCode == 206 {
859+
// Unmodified.
860+
log.Printf("checked %s - unmodified", c.url)
861+
return c.data, c.err
862+
}
863+
log.Printf("reloading %s", c.url)
864+
if resp.StatusCode != 200 {
865+
return nil, fmt.Errorf("loading rebuild report JSON: %v", resp.Status)
866+
}
867+
c.etag = resp.Header.Get("Etag")
868+
data, err = io.ReadAll(resp.Body)
869+
if err != nil {
870+
return nil, fmt.Errorf("loading rebuild report JSON: %v", err)
871+
}
872+
return data, nil
873+
}
874+
875+
func jsonUnmarshal(data []byte) (any, error) {
876+
var x any
877+
err := json.Unmarshal(data, &x)
878+
return x, err
879+
}

0 commit comments

Comments
 (0)