| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package task implements tasks involved in making a Go release. |
| package task |
| |
| import ( |
| "bytes" |
| "context" |
| "sync" |
| "time" |
| |
| wf "golang.org/x/build/internal/workflow" |
| ) |
| |
| // Published holds information for a Go release |
| // that by this time has already been published. |
| // |
| // Published in this context refers to it being |
| // available for download at https://go.dev/dl/. |
| // It doesn't mean it has been announced by now; |
| // that step happens sometime after publication. |
| type Published struct { |
| Version string // Version that's published, in the same format as Go tags. For example, "go1.21rc1". |
| Files []WebsiteFile // Files that are published. |
| } |
| |
| // CommunicationTasks combines communication tasks together. |
| type CommunicationTasks struct { |
| AnnounceMailTasks |
| TweetTasks |
| } |
| |
| var AwaitDivisor int = 1 |
| |
| // AwaitCondition calls the condition function every period until it returns |
| // true to indicate success, or an error. If the condition succeeds, |
| // AwaitCondition returns its result. |
| func AwaitCondition[T any](ctx *wf.TaskContext, period time.Duration, condition func() (T, bool, error)) (T, error) { |
| pollTimer := time.NewTicker(period / time.Duration(AwaitDivisor)) |
| defer pollTimer.Stop() |
| for { |
| res, done, err := condition() |
| if done || err != nil { |
| return res, err |
| } |
| select { |
| case <-ctx.Done(): |
| var zero T |
| return zero, ctx.Err() |
| case <-pollTimer.C: |
| ctx.ResetWatchdog() |
| } |
| } |
| } |
| |
| // LogWriter is an io.Writer that writes to a workflow task's log, flushing |
| // its buffer periodically to avoid too many writes. |
| type LogWriter struct { |
| Logger wf.Logger |
| |
| flushTicker *time.Ticker |
| |
| mu sync.Mutex |
| buf []byte |
| } |
| |
| func (w *LogWriter) Write(b []byte) (int, error) { |
| w.mu.Lock() |
| defer w.mu.Unlock() |
| |
| w.buf = append(w.buf, b...) |
| if len(w.buf) > 1<<20 { |
| w.flushLocked(false) |
| w.flushTicker.Reset(10 * time.Second) |
| } |
| return len(b), nil |
| } |
| |
| func (w *LogWriter) flush(force bool) { |
| w.mu.Lock() |
| defer w.mu.Unlock() |
| w.flushLocked(force) |
| } |
| |
| func (w *LogWriter) flushLocked(force bool) { |
| if len(w.buf) == 0 { |
| return |
| } |
| log, rest := w.buf, []byte(nil) |
| if !force { |
| nl := bytes.LastIndexByte(w.buf, '\n') |
| if nl == -1 { |
| return |
| } |
| log, rest = w.buf[:nl], w.buf[nl+1:] |
| } |
| w.Logger.Printf("\n%s", string(log)) |
| w.buf = append([]byte(nil), rest...) // don't leak |
| } |
| |
| func (w *LogWriter) Run(ctx context.Context) { |
| w.flushTicker = time.NewTicker(10 * time.Second) |
| defer w.flushTicker.Stop() |
| for { |
| select { |
| case <-w.flushTicker.C: |
| w.flush(false) |
| case <-ctx.Done(): |
| w.flush(true) |
| return |
| } |
| } |
| } |
| |
| // WebsiteFile represents a file on the go.dev downloads page. |
| // It should be kept in sync with the download code in x/website/internal/dl. |
| type WebsiteFile struct { |
| Filename string `json:"filename"` |
| OS string `json:"os"` |
| Arch string `json:"arch"` |
| Version string `json:"version"` |
| ChecksumSHA256 string `json:"sha256"` |
| Size int64 `json:"size"` |
| Kind string `json:"kind"` // "archive", "installer", "source" |
| } |
| |
| func (f WebsiteFile) GOARCH() string { |
| if f.OS == "linux" && f.Arch == "armv6l" { |
| return "arm" |
| } |
| return f.Arch |
| } |
| |
| type WebsiteRelease struct { |
| Version string `json:"version"` |
| Stable bool `json:"stable"` |
| Files []WebsiteFile `json:"files"` |
| Visible bool `json:"-"` // show files on page load |
| SplitPortTable bool `json:"-"` // whether files should be split by primary/other ports. |
| } |