cmd/releasebot, cmd/release: add darwin-arm64 target for Go 1.16

For golang/go#42756.

Change-Id: Ia28fb6878617715fb674ccfad0c5801c8925270b
Reviewed-on: https://go-review.googlesource.com/c/build/+/278436
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Alexander Rakoczy <alex@golang.org>
Trust: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/release/release.go b/cmd/release/release.go
index 59bb402..7680406 100644
--- a/cmd/release/release.go
+++ b/cmd/release/release.go
@@ -207,6 +207,13 @@
 		Builder: "darwin-amd64-10_15",
 	},
 	{
+		GoQuery: ">= go1.16beta1", // Go 1.16 Beta 1 is the first Go (pre-)release with the darwin/arm64 port.
+		OS:      "darwin",
+		Arch:    "arm64",
+		Race:    true,
+		Builder: "darwin-arm64-11_0-toothrot",
+	},
+	{
 		OS:        "linux",
 		Arch:      "s390x",
 		SkipTests: true,
diff --git a/cmd/releasebot/main.go b/cmd/releasebot/main.go
index f94eae3..d76f12c 100644
--- a/cmd/releasebot/main.go
+++ b/cmd/releasebot/main.go
@@ -32,6 +32,10 @@
 
 // A Target is a release target.
 type Target struct {
+	// GoQuery is a Go version query specifying the Go versions the
+	// release target applies to. Empty string means all Go versions.
+	GoQuery string
+
 	Name     string // Target name as accepted by cmd/release. For example, "linux-amd64".
 	TestOnly bool   // Run tests only; don't produce a release artifact.
 }
@@ -50,6 +54,7 @@
 	{Name: "windows-386"},
 	{Name: "windows-amd64"},
 	{Name: "darwin-amd64"},
+	{Name: "darwin-arm64", GoQuery: ">= go1.16beta1"},
 	{Name: "linux-s390x"},
 	{Name: "linux-ppc64le"},
 
@@ -88,8 +93,9 @@
 		fmt.Fprintln(os.Stderr, "need to provide a valid mode and a release name")
 		usage()
 	}
+	releaseVersion := flag.Arg(0)
 	for _, target := range strings.Fields(*skipTestFlag) {
-		if t, ok := releaseTarget(target); !ok {
+		if t, ok := releaseTarget(target, releaseVersion); !ok {
 			fmt.Fprintf(os.Stderr, "target %q in -skip-test=%q is not a known target\n", target, *skipTestFlag)
 			usage()
 		} else if !t.TestOnly {
@@ -108,13 +114,11 @@
 	loadGithubAuth()
 	loadGCSAuth()
 
-	release := flag.Arg(0)
-
 	w := &Work{
 		Prepare:     *modeFlag == "prepare",
-		Version:     release,
-		BetaRelease: strings.Contains(release, "beta"),
-		RCRelease:   strings.Contains(release, "rc"),
+		Version:     releaseVersion,
+		BetaRelease: strings.Contains(releaseVersion, "beta"),
+		RCRelease:   strings.Contains(releaseVersion, "rc"),
 		Security:    *security,
 	}
 
@@ -147,6 +151,9 @@
 		log.Fatalf("cannot understand version %q", w.Version)
 	}
 
+	// Select release targets for this Go version.
+	w.ReleaseTargets = matchTargets(w.Version)
+
 	// Find milestones.
 	var err error
 	w.Milestone, err = getMilestone(w.Version)
@@ -244,14 +251,15 @@
 	RCRelease   bool
 	Security    bool // cut a security release from the internal Gerrit
 
-	ReleaseIssue  int    // Release status issue number
-	ReleaseBranch string // "master" for beta releases
-	Dir           string // work directory ($HOME/go-releasebot-work/<release>)
-	StagingDir    string // staging directory (a temporary directory inside <work>/release-staging)
-	Errors        []string
-	ReleaseBinary string
-	Version       string
-	VersionCommit string
+	ReleaseIssue   int    // Release status issue number
+	ReleaseBranch  string // "master" for beta releases
+	Dir            string // work directory ($HOME/go-releasebot-work/<release>)
+	StagingDir     string // staging directory (a temporary directory inside <work>/release-staging)
+	Errors         []string
+	ReleaseBinary  string
+	ReleaseTargets []Target // Selected release targets for this release.
+	Version        string
+	VersionCommit  string
 
 	releaseMu   sync.Mutex
 	ReleaseInfo map[string]*ReleaseInfo // map and info protected by releaseMu
@@ -560,7 +568,7 @@
 	// TODO: print sha256
 	w.releaseMu.Lock()
 	defer w.releaseMu.Unlock()
-	for _, target := range releaseTargets {
+	for _, target := range w.ReleaseTargets {
 		fmt.Fprintf(md, "- %s", mdEscape(target.Name))
 		if target.TestOnly {
 			fmt.Fprintf(md, " (test only)")
@@ -671,7 +679,7 @@
 	}
 
 	var wg sync.WaitGroup
-	for _, target := range releaseTargets {
+	for _, target := range w.ReleaseTargets {
 		w.releaseMu.Lock()
 		w.ReleaseInfo[target.Name] = new(ReleaseInfo)
 		w.releaseMu.Unlock()
@@ -706,7 +714,7 @@
 
 	// Check for release errors and stop if any.
 	w.releaseMu.Lock()
-	for _, target := range releaseTargets {
+	for _, target := range w.ReleaseTargets {
 		for _, out := range w.ReleaseInfo[target.Name].Outputs {
 			if out.Error != "" || len(w.Errors) > 0 {
 				w.logError("RELEASE BUILD FAILED\n")
@@ -889,12 +897,43 @@
 	}
 }
 
-// releaseTarget returns a release target with the specified name.
-func releaseTarget(name string) (_ Target, ok bool) {
+// releaseTarget returns a release target with the specified name
+// for the specified Go version.
+func releaseTarget(name, goVer string) (_ Target, ok bool) {
 	for _, t := range releaseTargets {
+		if !match(t.GoQuery, goVer) {
+			continue
+		}
 		if t.Name == name {
 			return t, true
 		}
 	}
 	return Target{}, false
 }
+
+// matchTargets selects release targets that have a matching
+// GoQuery value for the specified Go version.
+func matchTargets(goVer string) (matched []Target) {
+	for _, t := range releaseTargets {
+		if !match(t.GoQuery, goVer) {
+			continue
+		}
+		matched = append(matched, t)
+	}
+	return matched
+}
+
+// match reports whether the Go version goVer matches the provided version query.
+// The empty query matches all Go versions.
+// match panics if given a query that it doesn't support.
+func match(query, goVer string) bool {
+	// TODO(golang.org/issue/40558): This should help inform the API for a Go version parser.
+	switch query {
+	case "": // A special case to make the zero Target.GoQuery value useful.
+		return true
+	case ">= go1.16beta1":
+		return !strings.HasPrefix(goVer, "go1.15") && !strings.HasPrefix(goVer, "go1.14")
+	default:
+		panic(fmt.Errorf("match: query %q is not supported", query))
+	}
+}
diff --git a/cmd/releasebot/main_test.go b/cmd/releasebot/main_test.go
new file mode 100644
index 0000000..5c83bdd
--- /dev/null
+++ b/cmd/releasebot/main_test.go
@@ -0,0 +1,87 @@
+// Copyright 2020 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 main
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestTargetSelectionPerGoVersion(t *testing.T) {
+	targetNames := func(targets []Target) (names []string) {
+		for _, t := range targets {
+			names = append(names, t.Name)
+		}
+		return names
+	}
+
+	for _, tc := range []struct {
+		goVer []string // Go versions to test.
+		want  []string // Expected release targets.
+	}{
+		{
+			goVer: []string{"go1.16beta1", "go1.16rc1", "go1.16", "go1.16.1"},
+			want: []string{
+				"src",
+				"linux-386",
+				"linux-armv6l",
+				"linux-amd64",
+				"linux-arm64",
+				"freebsd-386",
+				"freebsd-amd64",
+				"windows-386",
+				"windows-amd64",
+				"darwin-amd64",
+				"darwin-arm64", // New to Go 1.16.
+				"linux-s390x",
+				"linux-ppc64le",
+				"linux-amd64-longtest",
+				"windows-amd64-longtest",
+			},
+		},
+		{
+			goVer: []string{"go1.15.7", "go1.14.14"},
+			want: []string{
+				"src",
+				"linux-386",
+				"linux-armv6l",
+				"linux-amd64",
+				"linux-arm64",
+				"freebsd-386",
+				"freebsd-amd64",
+				"windows-386",
+				"windows-amd64",
+				"darwin-amd64",
+				"linux-s390x",
+				"linux-ppc64le",
+				"linux-amd64-longtest",
+				"windows-amd64-longtest",
+			},
+		},
+	} {
+		for _, goVer := range tc.goVer {
+			t.Run(goVer, func(t *testing.T) {
+				got := matchTargets(goVer)
+				if diff := cmp.Diff(tc.want, targetNames(got)); diff != "" {
+					t.Errorf("release target mismatch (-want +got):\n%s", diff)
+				}
+			})
+		}
+	}
+}
+
+func TestAllQueriesSupported(t *testing.T) {
+	for _, r := range releaseTargets {
+		t.Run(r.Name, func(t *testing.T) {
+			defer func() {
+				if err := recover(); err != nil {
+					t.Errorf("target %s uses an unsupported version query:\n%v", r.Name, err)
+				}
+			}()
+			match(r.GoQuery, "go1.15.7") // Shouldn't panic for any r.GoQuery.
+		})
+	}
+}