_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 <heschi@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/_content/rebuild.html b/_content/rebuild.html
new file mode 100644
index 0000000..8931be0
--- /dev/null
+++ b/_content/rebuild.html
@@ -0,0 +1,94 @@
+<!--{
+ "Title": "Go Reproducible Build Report",
+ "layout": "article",
+ "template": true
+}-->
+
+<style>
+details { margin-left: 2em; }
+pre { margin-left: 2em; }
+.time { color: #777; }
+</style>
+
+<p>
+As of Go 1.21, Go's binary toolchain downloads served by <a href="/dl/">go.dev/dl/</a> can be
+reproduced and verified by anyone, on any platform,
+using <a href="https://pkg.go.dev/golang.org/x/tools/cmd/gorebuild">golang.org/x/tools/cmd/gorebuild</a>.
+</p>
+
+<p>
+This page is updated daily with the results of running <code>gorebuild</code> in an Ubuntu VM,
+using this script:
+</p>
+
+<pre>
+apt-get update &&
+apt-get -y install software-properties-common &&
+add-apt-repository universe &&
+apt-get -y install golang-go msitools &&
+go run golang.org/x/build/cmd/gorebuild@latest -p=4
+</pre>
+
+<p>
+The installation of <code>msitools</code> lets <code>gorebuild</code>
+check the contents of the Windows MSI installation file.
+The <code>-p=4</code> means to run up to four builds in parallel.
+</p>
+
+{{define "marker"}}<span style="marker">{{template "markersymbol" .}}</span>{{end}}
+{{define "markersymbol"}}
+{{- if eq . "PASS" -}} ✅
+{{- else if eq . "SKIP" -}} —
+{{- else -}} ❌
+{{- end -}}
+{{end}}
+
+{{define "log"}}
+<pre>
+{{range .Messages}}<span class="time">{{(rfc3339 .Time).UTC.Format "15:04:05"}}</span> {{.Text}}
+{{end}}
+</pre>
+{{end}}
+
+{{define "autoopen"}} {{if not (eq . "PASS")}} open {{end}} {{end}}
+
+{{$Report := json gorebuild}}
+{{with $Report}}
+
+Using gorebuild from {{.Version}}<br><br>
+
+Rebuild started at {{(rfc3339 .Start).UTC.Format "2006-01-02 15:04:05"}} UTC.<br>
+Rebuild finished at {{(rfc3339 .End).UTC.Format "2006-01-02 15:04:05"}} UTC.<br>
+Elapsed time: {{((rfc3339 .End).Sub (rfc3339 .Start)).Round 1e9}}.
+
+<h2 id="releases">Releases</h2>
+
+{{range .Releases}}
+<details {{template "autoopen" .Log.Status}} >
+<summary><b id="{{.Version}}">{{template "marker" .Log.Status}} {{.Version}}</b></summary>
+
+<details>
+<summary>Log</summary>
+{{template "log" .Log}}
+</details>
+
+{{range .Files}}
+<details {{template "autoopen" .Log.Status}}>
+<summary><b id="{{.Name}}">{{template "marker" .Log.Status}} {{.Name}}</b></summary>
+{{template "log" .Log}}
+</details>
+{{end}}
+
+</details>
+{{end}}
+
+<h2 id="bootstraps">Bootstraps</h2>
+
+{{range .Bootstraps}}
+<details>
+<summary><b>Bootstrap {{.Version}}</b></summary>
+{{template "log" .Log}}
+</details>
+{{end}}
+
+{{end}}
diff --git a/cmd/golangorg/server.go b/cmd/golangorg/server.go
index 1fb1a7c..2a7f66b 100644
--- a/cmd/golangorg/server.go
+++ b/cmd/golangorg/server.go
@@ -16,6 +16,7 @@
"fmt"
"go/format"
"html/template"
+ "io"
"io/fs"
"io/ioutil"
"log"
@@ -27,6 +28,7 @@
"runtime"
"runtime/debug"
"strings"
+ "sync"
"sync/atomic"
"time"
@@ -242,6 +244,8 @@
return h
}
+var gorebuild = NewCachedURL("https://gorebuild.storage.googleapis.com/gorebuild.json", 5*time.Minute)
+
// newSite creates a new site for a given content and goroot file system pair
// and registers it in mux to handle requests for host.
// If host is the empty string, the registrations are for the wildcard host.
@@ -251,11 +255,14 @@
site.Funcs(template.FuncMap{
"googleAnalytics": func() string { return googleAnalytics },
"googleCN": func() bool { return host == "golang.google.cn" },
+ "gorebuild": gorebuild.Get,
+ "json": jsonUnmarshal,
"newest": newest,
+ "now": func() time.Time { return time.Now() },
"releases": func() []*history.Major { return history.Majors },
+ "rfc3339": parseRFC3339,
"section": section,
"version": func() string { return runtime.Version() },
- "now": func() time.Time { return time.Now() },
})
docs, err := pkgdoc.NewServer(fsys, site, googleCN)
if err != nil {
@@ -269,6 +276,10 @@
return site, nil
}
+func parseRFC3339(s string) (time.Time, error) {
+ return time.Parse(time.RFC3339, s)
+}
+
// watchTip is a background goroutine that watches the main Go repo for updates.
// When a new commit is available, watchTip downloads the new tree and calls
// tipGoroot.Set to install the new file system.
@@ -803,3 +814,66 @@
http.Redirect(w, r, url, http.StatusMovedPermanently)
})
}
+
+type CachedURL struct {
+ url string
+ timeout time.Duration
+
+ mu sync.Mutex
+ data []byte
+ err error
+ etag string
+ updated time.Time
+}
+
+func NewCachedURL(url string, timeout time.Duration) *CachedURL {
+ return &CachedURL{url: url, timeout: timeout}
+}
+
+func (c *CachedURL) Get() (data []byte, err error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ if time.Since(c.updated) < c.timeout {
+ return c.data, c.err
+ }
+ defer func() {
+ c.updated = time.Now()
+ c.data, c.err = data, err
+ }()
+
+ cli := &http.Client{Timeout: 60 * time.Second}
+ req, err := http.NewRequest("GET", c.url, nil)
+ if err != nil {
+ return nil, err
+ }
+ if c.etag != "" {
+ req.Header.Set("If-None-Match", c.etag)
+ }
+ resp, err := cli.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("loading rebuild report JSON: %v", err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == 206 {
+ // Unmodified.
+ log.Printf("checked %s - unmodified", c.url)
+ return c.data, c.err
+ }
+ log.Printf("reloading %s", c.url)
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("loading rebuild report JSON: %v", resp.Status)
+ }
+ c.etag = resp.Header.Get("Etag")
+ data, err = io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("loading rebuild report JSON: %v", err)
+ }
+ return data, nil
+}
+
+func jsonUnmarshal(data []byte) (any, error) {
+ var x any
+ err := json.Unmarshal(data, &x)
+ return x, err
+}