dashboard, cmd/coordinator, maintner/maintnerd: add support for BuildConfig.MinimumGoVersion field

The new BuildConfig.MinimumGoVersion field specifies the minimum
Go version the builder is allowed to use. It's useful when some
of the builders are too new, and do not support all of the supported
Go releases (e.g., openbsd-amd64-64 and freebsd-amd64-12_0 currently
require Go 1.11 and don't work on Go 1.10).

It only needs to be set when a builder doesn't support all supported
Go releases, since we don't typically test unsupported Go releases.

To allow cmd/coordinator to use this field and filter out work it
receives from maintner/maintnerd's GoFindTryWork RPC call,
we add a GoVersion slice to apipb.GerritTryWorkItem,
and populate it in maintapi.apiService.GoFindTryWork method.

For trybots on the Go repo, the GoVersion field is determined from
the branch name. For "release-branch.goX.Y" branches, it parses out
the major-minor Go version from the branch name. For master and
other branches, it assumes the latest Go release.

For trybots on subrepos, we already have the Go version information
available, so use it directly.

Afterwards, all that's left is to modify newTrySet in cmd/coordinator
to make use of BuildConfig.MinimumGoVersion and work.GoVersion to skip
builders that are too new for the Go version that needs to be tested.

Fixes golang/go#29265

Change-Id: I50b01830647e33e37e9eb8b89e0f2518812fa44f
Reviewed-on: https://go-review.googlesource.com/c/155463
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/cmd/coordinator/coordinator.go b/cmd/coordinator/coordinator.go
index fa65636..9435c92 100644
--- a/cmd/coordinator/coordinator.go
+++ b/cmd/coordinator/coordinator.go
@@ -1103,6 +1103,22 @@
 		goRev = work.GoCommit[0]
 	}
 
+	// Defensive check that the input is well-formed.
+	// Each GoCommit should have a GoBranch and a GoVersion.
+	// There should always be at least one GoVersion.
+	if len(work.GoBranch) < len(work.GoCommit) {
+		log.Printf("WARNING: len(GoBranch) of %d != len(GoCommit) of %d", len(work.GoBranch), len(work.GoCommit))
+		work.GoCommit = work.GoCommit[:len(work.GoBranch)]
+	}
+	if len(work.GoVersion) < len(work.GoCommit) {
+		log.Printf("WARNING: len(GoVersion) of %d != len(GoCommit) of %d", len(work.GoVersion), len(work.GoCommit))
+		work.GoCommit = work.GoCommit[:len(work.GoVersion)]
+	}
+	if len(work.GoVersion) == 0 {
+		log.Print("WARNING: len(GoVersion) is zero, want at least one")
+		work.GoVersion = []*apipb.MajorMinor{{}}
+	}
+
 	addBuilderToSet := func(bs *buildStatus, brev buildgo.BuilderRev) {
 		bs.trySet = ts
 		status[brev] = bs
@@ -1121,6 +1137,10 @@
 		go ts.notifyStarting()
 	}
 	for _, bconf := range builders {
+		goVersion := types.MajorMinor{int(work.GoVersion[0].Major), int(work.GoVersion[0].Minor)}
+		if goVersion.Less(bconf.MinimumGoVersion) {
+			continue
+		}
 		brev := tryKeyToBuilderRev(bconf.Name, key, goRev)
 		bs, err := newBuild(brev)
 		if err != nil {
@@ -1130,13 +1150,6 @@
 		addBuilderToSet(bs, brev)
 	}
 
-	// Defensive check that the input is well-formed and each GoCommit
-	// has a GoBranch.
-	if len(work.GoBranch) < len(work.GoCommit) {
-		log.Printf("WARNING: len(GoBranch) of %d != len(GoCommit) of %d", len(work.GoBranch), len(work.GoCommit))
-		work.GoCommit = work.GoCommit[:len(work.GoBranch)]
-	}
-
 	// linuxBuilder is the standard builder we run for when testing x/* repos against
 	// the past two Go releases.
 	linuxBuilder := dashboard.Builders["linux-amd64"]
@@ -1152,6 +1165,10 @@
 		if !linuxBuilder.BuildBranch(key.Project, "master", branch) {
 			continue
 		}
+		goVersion := types.MajorMinor{int(work.GoVersion[i].Major), int(work.GoVersion[i].Minor)}
+		if goVersion.Less(linuxBuilder.MinimumGoVersion) {
+			continue
+		}
 		brev := tryKeyToBuilderRev(linuxBuilder.Name, key, goRev)
 		bs, err := newBuild(brev)
 		if err != nil {
diff --git a/cmd/coordinator/coordinator_test.go b/cmd/coordinator/coordinator_test.go
index 583407e..61ae7a0 100644
--- a/cmd/coordinator/coordinator_test.go
+++ b/cmd/coordinator/coordinator_test.go
@@ -118,12 +118,13 @@
 	testingKnobSkipBuilds = true
 
 	work := &apipb.GerritTryWorkItem{
-		Project:  "build",
-		Branch:   "master",
-		ChangeId: "I6f05da2186b38dc8056081252563a82c50f0ce05",
-		Commit:   "a62e6a3ab11cc9cc2d9e22a50025dd33fc35d22f",
-		GoCommit: []string{"a2e79571a9d3dbe3cf10dcaeb1f9c01732219869", "e39e43d7349555501080133bb426f1ead4b3ef97", "f5ff72d62301c4e9d0a78167fab5914ca12919bd"},
-		GoBranch: []string{"master", "release-branch.go1.11", "release-branch.go1.10"},
+		Project:   "build",
+		Branch:    "master",
+		ChangeId:  "I6f05da2186b38dc8056081252563a82c50f0ce05",
+		Commit:    "a62e6a3ab11cc9cc2d9e22a50025dd33fc35d22f",
+		GoCommit:  []string{"a2e79571a9d3dbe3cf10dcaeb1f9c01732219869", "e39e43d7349555501080133bb426f1ead4b3ef97", "f5ff72d62301c4e9d0a78167fab5914ca12919bd"},
+		GoBranch:  []string{"master", "release-branch.go1.11", "release-branch.go1.10"},
+		GoVersion: []*apipb.MajorMinor{{1, 12}, {1, 11}, {1, 10}},
 	}
 	ts := newTrySet(work)
 	for i, bs := range ts.builds {
diff --git a/dashboard/builders.go b/dashboard/builders.go
index b0e0985..7f60670 100644
--- a/dashboard/builders.go
+++ b/dashboard/builders.go
@@ -14,6 +14,7 @@
 	"strings"
 
 	"golang.org/x/build/buildenv"
+	"golang.org/x/build/types"
 )
 
 // Builders are the different build configurations.
@@ -616,6 +617,15 @@
 	CompileOnly bool                   // if true, compile tests, but don't run them
 	FlakyNet    bool                   // network tests are flaky (try anyway, but ignore some failures)
 
+	// MinimumGoVersion optionally specifies the minimum Go version
+	// this builder is allowed to use. It can be useful for skipping
+	// builders that are too new and no longer support some supported
+	// Go versions. It doesn't need to be set for builders that support
+	// all supported Go versions.
+	//
+	// Note: This field currently has effect on trybot runs only.
+	MinimumGoVersion types.MajorMinor
+
 	// MaxAtOnce optionally specifies a cap of how many builds of
 	// this type can run at once. Zero means unlimited. This is a
 	// temporary measure until the build scheduler
@@ -1092,6 +1102,7 @@
 	addBuilder(BuildConfig{
 		Name:              "freebsd-amd64-12_0",
 		HostType:          "host-freebsd-12_0",
+		MinimumGoVersion:  types.MajorMinor{1, 11},
 		tryBot:            defaultTrySet(),
 		ShouldRunDistTest: fasterTrybots,
 		numTryTestHelpers: 4,
@@ -1433,6 +1444,7 @@
 	addBuilder(BuildConfig{
 		Name:              "openbsd-amd64-64",
 		HostType:          "host-openbsd-amd64-64",
+		MinimumGoVersion:  types.MajorMinor{1, 11},
 		ShouldRunDistTest: noTestDir,
 		tryBot:            defaultTrySet(),
 		numTestHelpers:    0,
diff --git a/maintner/maintnerd/apipb/api.pb.go b/maintner/maintnerd/apipb/api.pb.go
index 7fa279d..7767d65 100644
--- a/maintner/maintnerd/apipb/api.pb.go
+++ b/maintner/maintnerd/apipb/api.pb.go
@@ -15,6 +15,7 @@
 	GoFindTryWorkRequest
 	GoFindTryWorkResponse
 	GerritTryWorkItem
+	MajorMinor
 	ListGoReleasesRequest
 	ListGoReleasesResponse
 	GoRelease
@@ -188,6 +189,10 @@
 	// a try set fails.
 	GoCommit []string `protobuf:"bytes,5,rep,name=go_commit,json=goCommit" json:"go_commit,omitempty"`
 	GoBranch []string `protobuf:"bytes,6,rep,name=go_branch,json=goBranch" json:"go_branch,omitempty"`
+	// go_version specifies the major and minor version of the targeted Go toolchain.
+	// For Go repo, it contains exactly one element.
+	// For subrepos, it contains elements that correspond to go_commit.
+	GoVersion []*MajorMinor `protobuf:"bytes,7,rep,name=go_version,json=goVersion" json:"go_version,omitempty"`
 }
 
 func (m *GerritTryWorkItem) Reset()                    { *m = GerritTryWorkItem{} }
@@ -237,6 +242,37 @@
 	return nil
 }
 
+func (m *GerritTryWorkItem) GetGoVersion() []*MajorMinor {
+	if m != nil {
+		return m.GoVersion
+	}
+	return nil
+}
+
+type MajorMinor struct {
+	Major int32 `protobuf:"varint,1,opt,name=major" json:"major,omitempty"`
+	Minor int32 `protobuf:"varint,2,opt,name=minor" json:"minor,omitempty"`
+}
+
+func (m *MajorMinor) Reset()                    { *m = MajorMinor{} }
+func (m *MajorMinor) String() string            { return proto.CompactTextString(m) }
+func (*MajorMinor) ProtoMessage()               {}
+func (*MajorMinor) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
+
+func (m *MajorMinor) GetMajor() int32 {
+	if m != nil {
+		return m.Major
+	}
+	return 0
+}
+
+func (m *MajorMinor) GetMinor() int32 {
+	if m != nil {
+		return m.Minor
+	}
+	return 0
+}
+
 // By default, ListGoReleases returns only the latest patches
 // of releases that are considered supported per policy.
 type ListGoReleasesRequest struct {
@@ -245,17 +281,17 @@
 func (m *ListGoReleasesRequest) Reset()                    { *m = ListGoReleasesRequest{} }
 func (m *ListGoReleasesRequest) String() string            { return proto.CompactTextString(m) }
 func (*ListGoReleasesRequest) ProtoMessage()               {}
-func (*ListGoReleasesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
+func (*ListGoReleasesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
 
 type ListGoReleasesResponse struct {
-	// Releases are Go releases, sorted with latest release first.
+	// Releases are Go releases, sorted by version with latest first.
 	Releases []*GoRelease `protobuf:"bytes,1,rep,name=releases" json:"releases,omitempty"`
 }
 
 func (m *ListGoReleasesResponse) Reset()                    { *m = ListGoReleasesResponse{} }
 func (m *ListGoReleasesResponse) String() string            { return proto.CompactTextString(m) }
 func (*ListGoReleasesResponse) ProtoMessage()               {}
-func (*ListGoReleasesResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
+func (*ListGoReleasesResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
 
 func (m *ListGoReleasesResponse) GetReleases() []*GoRelease {
 	if m != nil {
@@ -278,7 +314,7 @@
 func (m *GoRelease) Reset()                    { *m = GoRelease{} }
 func (m *GoRelease) String() string            { return proto.CompactTextString(m) }
 func (*GoRelease) ProtoMessage()               {}
-func (*GoRelease) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
+func (*GoRelease) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }
 
 func (m *GoRelease) GetMajor() int32 {
 	if m != nil {
@@ -337,6 +373,7 @@
 	proto.RegisterType((*GoFindTryWorkRequest)(nil), "apipb.GoFindTryWorkRequest")
 	proto.RegisterType((*GoFindTryWorkResponse)(nil), "apipb.GoFindTryWorkResponse")
 	proto.RegisterType((*GerritTryWorkItem)(nil), "apipb.GerritTryWorkItem")
+	proto.RegisterType((*MajorMinor)(nil), "apipb.MajorMinor")
 	proto.RegisterType((*ListGoReleasesRequest)(nil), "apipb.ListGoReleasesRequest")
 	proto.RegisterType((*ListGoReleasesResponse)(nil), "apipb.ListGoReleasesResponse")
 	proto.RegisterType((*GoRelease)(nil), "apipb.GoRelease")
@@ -530,42 +567,44 @@
 func init() { proto.RegisterFile("api.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 587 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x54, 0xcd, 0x6e, 0x13, 0x3d,
-	0x14, 0x55, 0x9a, 0x2f, 0x3f, 0x73, 0xd3, 0xf4, 0x2b, 0xa6, 0x2d, 0xe9, 0x94, 0xaa, 0x65, 0x2a,
-	0x50, 0x17, 0x28, 0x8b, 0x20, 0xc4, 0x1a, 0x8a, 0x9a, 0x16, 0x28, 0x42, 0x53, 0x24, 0x96, 0x23,
-	0x67, 0xe2, 0x4c, 0xdc, 0x76, 0xec, 0xc1, 0xe3, 0xb4, 0xe2, 0x91, 0xd8, 0xf3, 0x1a, 0xbc, 0x13,
-	0xb2, 0x7d, 0x3d, 0xe4, 0x8f, 0x5d, 0xee, 0x39, 0xe7, 0xfe, 0xcc, 0xb9, 0xd7, 0x81, 0x80, 0x16,
-	0xbc, 0x5f, 0x28, 0xa9, 0x25, 0x69, 0xd0, 0x82, 0x17, 0xa3, 0xe8, 0x02, 0xc8, 0x05, 0x2d, 0xdf,
-	0x8a, 0x94, 0x95, 0x5a, 0xaa, 0x98, 0x7d, 0x9f, 0xb1, 0x52, 0x93, 0x3d, 0x68, 0xa6, 0x32, 0xcf,
-	0xb9, 0xee, 0xd5, 0x8e, 0x6b, 0xa7, 0x41, 0x8c, 0x11, 0x09, 0xa1, 0x4d, 0x51, 0xda, 0xdb, 0xb0,
-	0x4c, 0x15, 0x47, 0x09, 0x3c, 0x5e, 0xa8, 0x54, 0x16, 0x52, 0x94, 0x8c, 0x3c, 0x83, 0xcd, 0x29,
-	0x2d, 0x93, 0x2a, 0xcd, 0x14, 0x6c, 0xc7, 0x9d, 0xe9, 0x5f, 0x29, 0x79, 0x0e, 0x5b, 0x33, 0x71,
-	0x2b, 0xe4, 0x83, 0x48, 0xb0, 0xeb, 0x86, 0x15, 0x75, 0x11, 0x3d, 0xb3, 0x60, 0x94, 0x43, 0x77,
-	0xc8, 0x74, 0xcc, 0x26, 0x7e, 0xca, 0x6d, 0xa8, 0x2b, 0x36, 0xc1, 0x11, 0xcd, 0x4f, 0x72, 0x02,
-	0xdd, 0x8c, 0x29, 0xc5, 0x75, 0x52, 0x32, 0x75, 0xcf, 0xfc, 0x90, 0x9b, 0x0e, 0xbc, 0xb6, 0x98,
-	0x69, 0x87, 0xa2, 0x42, 0xc9, 0x1b, 0x96, 0xea, 0x5e, 0xdd, 0xaa, 0x30, 0xf5, 0x8b, 0x03, 0xa3,
-	0x17, 0xb0, 0xe5, 0xdb, 0xe1, 0xa7, 0xec, 0x40, 0xe3, 0x9e, 0xde, 0xcd, 0x18, 0x76, 0x74, 0x41,
-	0xf4, 0x06, 0x76, 0x86, 0xf2, 0x9c, 0x8b, 0xf1, 0x57, 0xf5, 0xe3, 0x9b, 0x54, 0xb7, 0x7e, 0xba,
-	0x23, 0xe8, 0x4c, 0xa4, 0x4a, 0x4a, 0x4d, 0x33, 0x2e, 0x32, 0xfc, 0x6e, 0x98, 0x48, 0x75, 0xed,
-	0x90, 0xe8, 0x23, 0xec, 0x2e, 0x25, 0x62, 0x9f, 0x01, 0xb4, 0x1e, 0x28, 0xd7, 0x2e, 0xab, 0x7e,
-	0xda, 0x19, 0xf4, 0xfa, 0x76, 0x59, 0xfd, 0xa1, 0x1d, 0x10, 0xe5, 0x97, 0x9a, 0xe5, 0xb1, 0x17,
-	0x46, 0xbf, 0x6a, 0xf0, 0x68, 0x85, 0x26, 0x3d, 0x68, 0xf9, 0x6f, 0x74, 0x33, 0xfb, 0xd0, 0x6c,
-	0x78, 0xa4, 0xa8, 0x48, 0xa7, 0x68, 0x11, 0x46, 0xe4, 0x00, 0x82, 0x74, 0x4a, 0x45, 0xc6, 0x12,
-	0x3e, 0x46, 0x5f, 0xda, 0x0e, 0xb8, 0x1c, 0xcf, 0x9d, 0xc5, 0x7f, 0x0b, 0x67, 0x71, 0x00, 0x41,
-	0x26, 0xfd, 0xee, 0x1a, 0xc7, 0x75, 0x93, 0x94, 0xc9, 0xb3, 0x79, 0x12, 0x9b, 0x35, 0x3d, 0xf9,
-	0xce, 0xc6, 0xd1, 0x13, 0xd8, 0xfd, 0xc4, 0x4b, 0x3d, 0x94, 0x31, 0xbb, 0x63, 0xb4, 0x64, 0x25,
-	0xba, 0x17, 0x9d, 0xc3, 0xde, 0x32, 0x81, 0xee, 0xbc, 0x84, 0xb6, 0x42, 0x0c, 0xed, 0xd9, 0xf6,
-	0xf6, 0x78, 0x71, 0x5c, 0x29, 0xa2, 0xdf, 0x35, 0x08, 0x2a, 0xdc, 0x6c, 0x30, 0xa7, 0x37, 0x78,
-	0x85, 0x8d, 0xd8, 0x05, 0x16, 0xe5, 0x02, 0x4f, 0xda, 0xa0, 0x26, 0x30, 0x68, 0x41, 0x75, 0x3a,
-	0xb5, 0x2e, 0x34, 0x62, 0x17, 0x90, 0x7d, 0x68, 0x6b, 0x9a, 0x25, 0x82, 0xe6, 0x0c, 0x4d, 0x68,
-	0x69, 0x9a, 0x7d, 0xa6, 0x39, 0x23, 0x87, 0x00, 0x86, 0xaa, 0x6c, 0x30, 0x64, 0xa0, 0x69, 0x86,
-	0x3e, 0x1c, 0x41, 0xc7, 0x99, 0xe0, 0x92, 0x9b, 0x96, 0x07, 0x07, 0xd9, 0xfc, 0x13, 0xe8, 0xa2,
-	0x00, 0x4b, 0xb4, 0xdc, 0xf1, 0x3a, 0xd0, 0x55, 0x19, 0xfc, 0xdc, 0x80, 0xff, 0xaf, 0x28, 0x17,
-	0x5a, 0x30, 0x65, 0xee, 0x99, 0xa7, 0x8c, 0xbc, 0x87, 0xce, 0xdc, 0xcb, 0x23, 0xfb, 0x68, 0xc7,
-	0xea, 0xbb, 0x0e, 0xc3, 0x75, 0x14, 0xfa, 0xfa, 0x1a, 0x9a, 0xee, 0xde, 0xc9, 0x4e, 0x75, 0x6e,
-	0x73, 0xaf, 0x2d, 0xdc, 0x5d, 0x42, 0x31, 0xed, 0x03, 0x74, 0x17, 0xae, 0x98, 0x1c, 0x54, 0xdb,
-	0x58, 0x7d, 0x14, 0xe1, 0xd3, 0xf5, 0x24, 0xd6, 0xba, 0x82, 0xad, 0xc5, 0xa5, 0x13, 0xaf, 0x5f,
-	0x7b, 0x24, 0xe1, 0xe1, 0x3f, 0x58, 0x57, 0x6e, 0xd4, 0xb4, 0xff, 0x74, 0xaf, 0xfe, 0x04, 0x00,
-	0x00, 0xff, 0xff, 0x13, 0xc3, 0xc8, 0xa7, 0xf6, 0x04, 0x00, 0x00,
+	// 624 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xdf, 0x4e, 0x14, 0x3f,
+	0x14, 0xce, 0xb2, 0xbf, 0xfd, 0x33, 0x67, 0x59, 0x7e, 0x50, 0x01, 0x97, 0x41, 0x02, 0x0e, 0xd1,
+	0x70, 0x61, 0x88, 0xc1, 0x18, 0xbd, 0x55, 0x0c, 0x0b, 0xea, 0x1a, 0x33, 0x18, 0xbd, 0x9c, 0x94,
+	0xa1, 0x3b, 0x5b, 0x60, 0xda, 0xb1, 0x2d, 0x10, 0x1f, 0xc9, 0x87, 0xf1, 0x35, 0x7c, 0x0e, 0xd3,
+	0xf6, 0x74, 0xd8, 0x05, 0xbc, 0xf0, 0x6e, 0xce, 0xf7, 0x7d, 0xe7, 0x7f, 0xcf, 0x40, 0x44, 0x2b,
+	0xbe, 0x5b, 0x29, 0x69, 0x24, 0x69, 0xd1, 0x8a, 0x57, 0x27, 0xc9, 0x21, 0x90, 0x43, 0xaa, 0xdf,
+	0x88, 0x9c, 0x69, 0x23, 0x55, 0xca, 0xbe, 0x5f, 0x32, 0x6d, 0xc8, 0x2a, 0xb4, 0x73, 0x59, 0x96,
+	0xdc, 0x0c, 0x1a, 0x5b, 0x8d, 0x9d, 0x28, 0x45, 0x8b, 0xc4, 0xd0, 0xa5, 0x28, 0x1d, 0xcc, 0x39,
+	0xa6, 0xb6, 0x93, 0x0c, 0x1e, 0xcc, 0x44, 0xd2, 0x95, 0x14, 0x9a, 0x91, 0xc7, 0x30, 0x3f, 0xa1,
+	0x3a, 0xab, 0xdd, 0x6c, 0xc0, 0x6e, 0xda, 0x9b, 0xdc, 0x48, 0xc9, 0x13, 0x58, 0xb8, 0x14, 0xe7,
+	0x42, 0x5e, 0x8b, 0x0c, 0xb3, 0xce, 0x39, 0x51, 0x1f, 0xd1, 0x7d, 0x07, 0x26, 0x25, 0xf4, 0x87,
+	0xcc, 0xa4, 0x6c, 0x1c, 0xaa, 0x5c, 0x84, 0xa6, 0x62, 0x63, 0x2c, 0xd1, 0x7e, 0x92, 0x6d, 0xe8,
+	0x17, 0x4c, 0x29, 0x6e, 0x32, 0xcd, 0xd4, 0x15, 0x0b, 0x45, 0xce, 0x7b, 0xf0, 0xd8, 0x61, 0x36,
+	0x1d, 0x8a, 0x2a, 0x25, 0xcf, 0x58, 0x6e, 0x06, 0x4d, 0xa7, 0x42, 0xd7, 0xcf, 0x1e, 0x4c, 0x9e,
+	0xc2, 0x42, 0x48, 0x87, 0xad, 0x2c, 0x43, 0xeb, 0x8a, 0x5e, 0x5c, 0x32, 0xcc, 0xe8, 0x8d, 0xe4,
+	0x15, 0x2c, 0x0f, 0xe5, 0x01, 0x17, 0xa7, 0x5f, 0xd4, 0x8f, 0x6f, 0x52, 0x9d, 0x87, 0xea, 0x36,
+	0xa1, 0x37, 0x96, 0x2a, 0xd3, 0x86, 0x16, 0x5c, 0x14, 0xd8, 0x37, 0x8c, 0xa5, 0x3a, 0xf6, 0x48,
+	0xf2, 0x01, 0x56, 0x6e, 0x39, 0x62, 0x9e, 0x3d, 0xe8, 0x5c, 0x53, 0x6e, 0xbc, 0x57, 0x73, 0xa7,
+	0xb7, 0x37, 0xd8, 0x75, 0xcb, 0xda, 0x1d, 0xba, 0x02, 0x51, 0x7e, 0x64, 0x58, 0x99, 0x06, 0x61,
+	0xf2, 0xbb, 0x01, 0x4b, 0x77, 0x68, 0x32, 0x80, 0x4e, 0xe8, 0xd1, 0xd7, 0x1c, 0x4c, 0xbb, 0xe1,
+	0x13, 0x45, 0x45, 0x3e, 0xc1, 0x11, 0xa1, 0x45, 0xd6, 0x21, 0xca, 0x27, 0x54, 0x14, 0x2c, 0xe3,
+	0xa7, 0x38, 0x97, 0xae, 0x07, 0x8e, 0x4e, 0xa7, 0x9e, 0xc5, 0x7f, 0x33, 0xcf, 0x62, 0x1d, 0xa2,
+	0x42, 0x86, 0xdd, 0xb5, 0xb6, 0x9a, 0xd6, 0xa9, 0x90, 0xfb, 0xd3, 0x24, 0x26, 0x6b, 0x07, 0xf2,
+	0xad, 0x4f, 0xf7, 0x1c, 0xa0, 0x90, 0xd9, 0x15, 0x53, 0x9a, 0x4b, 0x31, 0xe8, 0xb8, 0x6e, 0x97,
+	0xb0, 0xdb, 0x11, 0x3d, 0x93, 0x6a, 0xc4, 0x85, 0x54, 0x69, 0x54, 0xc8, 0xaf, 0x5e, 0x93, 0xbc,
+	0x06, 0xb8, 0x21, 0xec, 0x4a, 0x4a, 0x6b, 0xb9, 0xf6, 0x5a, 0xa9, 0x37, 0x1c, 0x6a, 0x69, 0xd7,
+	0x9b, 0x45, 0xad, 0x91, 0x3c, 0x84, 0x95, 0x8f, 0x5c, 0x9b, 0xa1, 0x4c, 0xd9, 0x05, 0xa3, 0x9a,
+	0x69, 0xdc, 0x54, 0x72, 0x00, 0xab, 0xb7, 0x09, 0xdc, 0xc4, 0x33, 0xe8, 0x2a, 0xc4, 0x70, 0x15,
+	0x8b, 0x61, 0x15, 0x41, 0x9c, 0xd6, 0x8a, 0xe4, 0x57, 0x03, 0xa2, 0x1a, 0xff, 0x97, 0xd2, 0x2c,
+	0x5a, 0x51, 0x93, 0x4f, 0xdc, 0xc4, 0x5b, 0xa9, 0x37, 0xc8, 0x1a, 0x74, 0x0d, 0x2d, 0x32, 0x41,
+	0x4b, 0x86, 0x03, 0xef, 0x18, 0x5a, 0x7c, 0xa2, 0x25, 0x23, 0x1b, 0x00, 0x96, 0xaa, 0x47, 0x6e,
+	0xc9, 0xc8, 0xd0, 0x02, 0x67, 0xbe, 0x09, 0x3d, 0x3f, 0x70, 0xef, 0xdc, 0x76, 0x3c, 0x78, 0xc8,
+	0xf9, 0x6f, 0x43, 0x1f, 0x05, 0x18, 0xa2, 0xe3, 0x0f, 0xc5, 0x83, 0x3e, 0xca, 0xde, 0xcf, 0x39,
+	0xf8, 0x7f, 0x44, 0xb9, 0x30, 0x82, 0x29, 0x7b, 0x3b, 0x3c, 0x67, 0xe4, 0x1d, 0xf4, 0xa6, 0xae,
+	0x9c, 0xac, 0xe1, 0x38, 0xee, 0xfe, 0x43, 0xe2, 0xf8, 0x3e, 0x0a, 0xe7, 0xfa, 0x12, 0xda, 0xfe,
+	0xb6, 0xc8, 0x72, 0xfd, 0xb4, 0xa7, 0x2e, 0x3b, 0x5e, 0xb9, 0x85, 0xa2, 0xdb, 0x7b, 0xe8, 0xcf,
+	0x5c, 0x0c, 0x59, 0xaf, 0xb7, 0x71, 0xf7, 0x00, 0xe3, 0x47, 0xf7, 0x93, 0x18, 0x6b, 0x04, 0x0b,
+	0xb3, 0x4b, 0x27, 0x41, 0x7f, 0xef, 0x23, 0x89, 0x37, 0xfe, 0xc2, 0xfa, 0x70, 0x27, 0x6d, 0xf7,
+	0x57, 0x7d, 0xf1, 0x27, 0x00, 0x00, 0xff, 0xff, 0xad, 0x6d, 0xbd, 0x4e, 0x62, 0x05, 0x00, 0x00,
 }
diff --git a/maintner/maintnerd/apipb/api.proto b/maintner/maintnerd/apipb/api.proto
index d300b59..dded094 100644
--- a/maintner/maintnerd/apipb/api.proto
+++ b/maintner/maintnerd/apipb/api.proto
@@ -51,7 +51,7 @@
 
 message GerritTryWorkItem {
   string project = 1;    // "go", "net", etc. (Gerrit Project)
-  string branch = 2;     // "master"
+  string branch = 2;     // "master", "release-branch.go1.8", etc.
   string change_id = 3;  // "I1a27695838409259d1586a0adfa9f92bccf7ceba"
   string commit = 4;     // "ecf3dffc81dc21408fb02159af352651882a8383"
 
@@ -59,7 +59,17 @@
   // go_branch is a branch name of go_commit, for showing to users when
   // a try set fails.
   repeated string go_commit = 5;  // "4833e920c1d7f6b23458e6ff3c73951fcf754219"
-  repeated string go_branch = 6;  // "master", "release-branch.go1.8", etc
+  repeated string go_branch = 6;  // "master", "release-branch.go1.8", etc.
+
+  // go_version specifies the major and minor version of the targeted Go toolchain.
+  // For Go repo, it contains exactly one element.
+  // For subrepos, it contains elements that correspond to go_commit.
+  repeated MajorMinor go_version = 7;
+}
+
+message MajorMinor {
+  int32 major = 1;
+  int32 minor = 2;
 }
 
 // By default, ListGoReleases returns only the latest patches
@@ -67,7 +77,7 @@
 message ListGoReleasesRequest {}
 
 message ListGoReleasesResponse {
-  // Releases are Go releases, sorted with latest release first.
+  // Releases are Go releases, sorted by version with latest first.
   repeated GoRelease releases = 1;
 }
 
diff --git a/maintner/maintnerd/maintapi/api.go b/maintner/maintnerd/maintapi/api.go
index 62b52ee..39b9692 100644
--- a/maintner/maintnerd/maintapi/api.go
+++ b/maintner/maintnerd/maintapi/api.go
@@ -8,6 +8,7 @@
 import (
 	"context"
 	"errors"
+	"fmt"
 	"log"
 	"sort"
 	"strings"
@@ -168,14 +169,13 @@
 	}
 	tryCache.forNumChanges = sumChanges
 
-	res := new(apipb.GoFindTryWorkResponse)
 	goProj := s.c.Gerrit().Project("go.googlesource.com", "go")
-
 	supportedReleases, err := supportedGoReleases(goProj)
 	if err != nil {
 		return nil, err
 	}
 
+	res := new(apipb.GoFindTryWorkResponse)
 	for _, ci := range cis {
 		cl := s.c.Gerrit().Project("go.googlesource.com", ci.Project).CL(int32(ci.ChangeNumber))
 		if cl == nil {
@@ -187,13 +187,32 @@
 			// In case maintner is behind.
 			work.Commit = ci.CurrentRevision
 		}
-		if work.Project != "go" {
+		if work.Project == "go" {
+			// Trybot on Go repo. Set the GoVersion field based on branch name.
+			if work.Branch == "master" {
+				latest := supportedReleases[0]
+				work.GoVersion = []*apipb.MajorMinor{{latest.Major, latest.Minor}}
+			} else if major, minor, ok := parseReleaseBranchVersion(work.Branch); ok {
+				// A release branch like release-branch.goX.Y.
+				// Use the major-minor Go version determined from the branch name.
+				work.GoVersion = []*apipb.MajorMinor{{major, minor}}
+			} else {
+				// A branch that is neither master nor release-branch.goX.Y.
+				// I don't see a straightforward way to compute its version,
+				// so use the latest Go release until we need to do more.
+				latest := supportedReleases[0]
+				work.GoVersion = []*apipb.MajorMinor{{latest.Major, latest.Minor}}
+			}
+		} else {
 			// Trybot on a subrepo. Set the Go fields to master and the supported releases.
-			work.GoBranch = []string{"master"}
 			work.GoCommit = []string{goProj.Ref("refs/heads/master").String()}
+			work.GoBranch = []string{"master"}
+			latest := supportedReleases[0]
+			work.GoVersion = []*apipb.MajorMinor{{latest.Major, latest.Minor}}
 			for _, r := range supportedReleases {
-				work.GoBranch = append(work.GoBranch, r.BranchName)
 				work.GoCommit = append(work.GoCommit, r.BranchCommit)
+				work.GoBranch = append(work.GoBranch, r.BranchName)
+				work.GoVersion = append(work.GoVersion, &apipb.MajorMinor{r.Major, r.Minor})
 			}
 		}
 		res.Waiting = append(res.Waiting, work)
@@ -268,8 +287,9 @@
 	ForeachNonChangeRef(fn func(ref string, hash maintner.GitHash) error) error
 }
 
-// supportedGoReleases returns the latest patches of releases
-// that are considered supported per policy.
+// supportedGoReleases returns the latest patches of releases that are
+// considered supported per policy. Sorted by version with latest first.
+// The returned list will be empty if and only if the error is non-nil.
 func supportedGoReleases(goProj nonChangeRefLister) ([]*apipb.GoRelease, error) {
 	type majorMinor struct {
 		Major, Minor int32
@@ -360,9 +380,10 @@
 	})
 
 	// Per policy, only the latest two releases are considered supported.
-	if len(rs) > 2 {
-		rs = rs[:2]
+	// Return an error if there aren't at least two releases, so callers
+	// don't have to check for empty list.
+	if len(rs) < 2 {
+		return nil, fmt.Errorf("there was a problem finding supported Go releases")
 	}
-
-	return rs, nil
+	return rs[:2], nil
 }
diff --git a/types/types.go b/types/types.go
index ec35f19..bf7c4d5 100644
--- a/types/types.go
+++ b/types/types.go
@@ -153,3 +153,16 @@
 	s.HostTypes[hostType] = hs
 	return hs
 }
+
+// MajorMinor is a major-minor version pair.
+type MajorMinor struct {
+	Major, Minor int
+}
+
+// Less reports whether a is less than b.
+func (a MajorMinor) Less(b MajorMinor) bool {
+	if a.Major != b.Major {
+		return a.Major < b.Major
+	}
+	return a.Minor < b.Minor
+}