Skip to content

Commit 16962fa

Browse files
committed
cmd/go: add 'go version' statement in go.mod
We aren't planning to use this or advertise it much yet, but having support for it now will make it easier to start using in the future - older go commands will understand what 'go 1.20' means and that they don't have go 1.20. Fixes #23969. Change-Id: I729130b2690d3c0b794b49201526b53de5093c45 Reviewed-on: https://go-review.googlesource.com/125940 Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 740e589 commit 16962fa

File tree

10 files changed

+180
-36
lines changed

10 files changed

+180
-36
lines changed

src/cmd/go/alldocs.go

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/internal/imports/tags.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ func loadTags() map[string]bool {
2424
if cfg.BuildContext.CgoEnabled {
2525
tags["cgo"] = true
2626
}
27-
// TODO: Should read these out of GOROOT source code?
2827
for _, tag := range cfg.BuildContext.BuildTags {
2928
tags[tag] = true
3029
}

src/cmd/go/internal/modcmd/mod.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ To override this guess, use the -module flag.
5151
The -module flag changes (or, with -init, sets) the module's path
5252
(the go.mod file's module line).
5353
54+
The -go flag changes the minimum required version of Go listed in go.mod.
55+
5456
The -require=path@version and -droprequire=path flags
5557
add and drop a requirement on the given module path and version.
5658
Note that -require overrides any existing requirements on path.

src/cmd/go/internal/modfile/rule.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"errors"
1010
"fmt"
1111
"path/filepath"
12+
"regexp"
1213
"sort"
1314
"strconv"
1415
"strings"
@@ -21,6 +22,7 @@ import (
2122
// A File is the parsed, interpreted form of a go.mod file.
2223
type File struct {
2324
Module *Module
25+
Go *Go
2426
Require []*Require
2527
Exclude []*Exclude
2628
Replace []*Replace
@@ -34,6 +36,12 @@ type Module struct {
3436
Syntax *Line
3537
}
3638

39+
// A Go is the go statement.
40+
type Go struct {
41+
Version string // "1.23"
42+
Syntax *Line
43+
}
44+
3745
// A Require is a single require statement.
3846
type Require struct {
3947
Mod module.Version
@@ -146,20 +154,39 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File
146154
return f, nil
147155
}
148156

157+
var goVersionRE = regexp.MustCompile(`([1-9][0-9]*)\.(0|[1-9][0-9]*)`)
158+
149159
func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
150160
// If strict is false, this module is a dependency.
151-
// We ignore all unknown directives and do not attempt to parse
152-
// replace and exclude either. They don't matter, and it will work better for
161+
// We ignore all unknown directives as well as main-module-only
162+
// directives like replace and exclude. It will work better for
153163
// forward compatibility if we can depend on modules that have unknown
154-
// statements (presumed relevant only when acting as the main module).
155-
if !strict && verb != "module" && verb != "require" {
156-
return
164+
// statements (presumed relevant only when acting as the main module)
165+
// and simply ignore those statements.
166+
if !strict {
167+
switch verb {
168+
case "module", "require", "go":
169+
// want these even for dependency go.mods
170+
default:
171+
return
172+
}
157173
}
158174

159175
switch verb {
160176
default:
161177
fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb)
162178

179+
case "go":
180+
if f.Go != nil {
181+
fmt.Fprintf(errs, "%s:%d: repeated go statement\n", f.Syntax.Name, line.Start.Line)
182+
return
183+
}
184+
if len(args) != 1 || !goVersionRE.MatchString(args[0]) {
185+
fmt.Fprintf(errs, "%s:%d: usage: go 1.23\n", f.Syntax.Name, line.Start.Line)
186+
return
187+
}
188+
f.Go = &Go{Syntax: line}
189+
f.Go.Version = args[0]
163190
case "module":
164191
if f.Module != nil {
165192
fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line)

src/cmd/go/internal/modget/get.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ func runGet(cmd *base.Command, args []string) {
344344
base.ExitIfErrors()
345345

346346
// Now we've reduced the upgrade/downgrade work to a list of path@vers pairs (tasks).
347-
// Resolve each one in parallell.
347+
// Resolve each one in parallel.
348348
reqs := modload.Reqs()
349349
var lookup par.Work
350350
for _, t := range tasks {

src/cmd/go/internal/modinfo/info.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,18 @@ import "time"
1010
// and the fields are documented in the help text in ../list/list.go
1111

1212
type ModulePublic struct {
13-
Path string `json:",omitempty"` // module path
14-
Version string `json:",omitempty"` // module version
15-
Versions []string `json:",omitempty"` // available module versions
16-
Replace *ModulePublic `json:",omitempty"` // replaced by this module
17-
Time *time.Time `json:",omitempty"` // time version was created
18-
Update *ModulePublic `json:",omitempty"` // available update (with -u)
19-
Main bool `json:",omitempty"` // is this the main module?
20-
Indirect bool `json:",omitempty"` // module is only indirectly needed by main module
21-
Dir string `json:",omitempty"` // directory holding local copy of files, if any
22-
GoMod string `json:",omitempty"` // path to go.mod file describing module, if any
23-
Error *ModuleError `json:",omitempty"` // error loading module
13+
Path string `json:",omitempty"` // module path
14+
Version string `json:",omitempty"` // module version
15+
Versions []string `json:",omitempty"` // available module versions
16+
Replace *ModulePublic `json:",omitempty"` // replaced by this module
17+
Time *time.Time `json:",omitempty"` // time version was created
18+
Update *ModulePublic `json:",omitempty"` // available update (with -u)
19+
Main bool `json:",omitempty"` // is this the main module?
20+
Indirect bool `json:",omitempty"` // module is only indirectly needed by main module
21+
Dir string `json:",omitempty"` // directory holding local copy of files, if any
22+
GoMod string `json:",omitempty"` // path to go.mod file describing module, if any
23+
Error *ModuleError `json:",omitempty"` // error loading module
24+
GoVersion string `json:",omitempty"` // go version used in module
2425
}
2526

2627
type ModuleError struct {

src/cmd/go/internal/modload/build.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,27 @@ func addVersions(m *modinfo.ModulePublic) {
8686

8787
func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
8888
if m == Target {
89-
return &modinfo.ModulePublic{
89+
info := &modinfo.ModulePublic{
9090
Path: m.Path,
9191
Version: m.Version,
9292
Main: true,
9393
Dir: ModRoot,
9494
GoMod: filepath.Join(ModRoot, "go.mod"),
9595
}
96+
if modFile.Go != nil {
97+
info.GoVersion = modFile.Go.Version
98+
}
99+
return info
96100
}
97101

98102
info := &modinfo.ModulePublic{
99103
Path: m.Path,
100104
Version: m.Version,
101105
Indirect: fromBuildList && loaded != nil && !loaded.direct[m.Path],
102106
}
107+
if loaded != nil {
108+
info.GoVersion = loaded.goVersion[m.Path]
109+
}
103110

104111
if cfg.BuildGetmode == "vendor" {
105112
info.Dir = filepath.Join(ModRoot, "vendor", m.Path)
@@ -139,8 +146,9 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
139146

140147
if r := Replacement(m); r.Path != "" {
141148
info.Replace = &modinfo.ModulePublic{
142-
Path: r.Path,
143-
Version: r.Version,
149+
Path: r.Path,
150+
Version: r.Version,
151+
GoVersion: info.GoVersion,
144152
}
145153
if r.Version == "" {
146154
if filepath.IsAbs(r.Path) {

src/cmd/go/internal/modload/load.go

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,8 @@ type loader struct {
358358
pkgCache *par.Cache // map from string to *loadPkg
359359

360360
// computed at end of iterations
361-
direct map[string]bool // imported directly by main module
361+
direct map[string]bool // imported directly by main module
362+
goVersion map[string]string // go version recorded in each module
362363
}
363364

364365
func newLoader() *loader {
@@ -399,7 +400,8 @@ var errMissing = errors.New("cannot find package")
399400
// which must call add(path) with the import path of each root package.
400401
func (ld *loader) load(roots func() []string) {
401402
var err error
402-
buildList, err = mvs.BuildList(Target, Reqs())
403+
reqs := Reqs()
404+
buildList, err = mvs.BuildList(Target, reqs)
403405
if err != nil {
404406
base.Fatalf("go: %v", err)
405407
}
@@ -445,7 +447,8 @@ func (ld *loader) load(roots func() []string) {
445447
}
446448

447449
// Recompute buildList with all our additions.
448-
buildList, err = mvs.BuildList(Target, Reqs())
450+
reqs = Reqs()
451+
buildList, err = mvs.BuildList(Target, reqs)
449452
if err != nil {
450453
base.Fatalf("go: %v", err)
451454
}
@@ -464,6 +467,13 @@ func (ld *loader) load(roots func() []string) {
464467
}
465468
}
466469

470+
// Add Go versions, computed during walk.
471+
ld.goVersion = make(map[string]string)
472+
for _, m := range buildList {
473+
v, _ := reqs.(*mvsReqs).versions.Load(m)
474+
ld.goVersion[m.Path], _ = v.(string)
475+
}
476+
467477
// Mix in direct markings (really, lack of indirect markings)
468478
// from go.mod, unless we scanned the whole module
469479
// and can therefore be sure we know better than go.mod.
@@ -670,6 +680,7 @@ func Replacement(mod module.Version) module.Version {
670680
type mvsReqs struct {
671681
buildList []module.Version
672682
cache par.Cache
683+
versions sync.Map
673684
}
674685

675686
// Reqs returns the current module requirement graph.
@@ -745,11 +756,21 @@ func readVendorList() {
745756
})
746757
}
747758

759+
func (r *mvsReqs) modFileToList(f *modfile.File) []module.Version {
760+
var list []module.Version
761+
for _, r := range f.Require {
762+
list = append(list, r.Mod)
763+
}
764+
return list
765+
}
766+
748767
func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
749768
if mod == Target {
769+
if modFile.Go != nil {
770+
r.versions.LoadOrStore(mod, modFile.Go.Version)
771+
}
750772
var list []module.Version
751-
list = append(list, r.buildList[1:]...)
752-
return list, nil
773+
return append(list, r.buildList[1:]...), nil
753774
}
754775

755776
if cfg.BuildGetmode == "vendor" {
@@ -778,11 +799,10 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
778799
base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
779800
return nil, ErrRequire
780801
}
781-
var list []module.Version
782-
for _, r := range f.Require {
783-
list = append(list, r.Mod)
802+
if f.Go != nil {
803+
r.versions.LoadOrStore(mod, f.Go.Version)
784804
}
785-
return list, nil
805+
return r.modFileToList(f), nil
786806
}
787807
mod = repl
788808
}
@@ -815,12 +835,11 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
815835
base.Errorf("go: %s@%s: parsing go.mod: unexpected module path %q", mod.Path, mod.Version, mpath)
816836
return nil, ErrRequire
817837
}
818-
819-
var list []module.Version
820-
for _, req := range f.Require {
821-
list = append(list, req.Mod)
838+
if f.Go != nil {
839+
r.versions.LoadOrStore(mod, f.Go.Version)
822840
}
823-
return list, nil
841+
842+
return r.modFileToList(f), nil
824843
}
825844

826845
// ErrRequire is the sentinel error returned when Require encounters problems.

src/cmd/go/internal/work/exec.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,27 @@ func (b *Builder) needCgoHdr(a *Action) bool {
320320
return false
321321
}
322322

323+
// allowedVersion reports whether the version v is an allowed version of go
324+
// (one that we can compile).
325+
// v is known to be of the form "1.23".
326+
func allowedVersion(v string) bool {
327+
// Special case: no requirement.
328+
if v == "" {
329+
return true
330+
}
331+
// Special case "1.0" means "go1", which is OK.
332+
if v == "1.0" {
333+
return true
334+
}
335+
// Otherwise look through release tags of form "go1.23" for one that matches.
336+
for _, tag := range cfg.BuildContext.ReleaseTags {
337+
if strings.HasPrefix(tag, "go") && tag[2:] == v {
338+
return true
339+
}
340+
}
341+
return false
342+
}
343+
323344
const (
324345
needBuild uint32 = 1 << iota
325346
needCgoHdr
@@ -414,6 +435,10 @@ func (b *Builder) build(a *Action) (err error) {
414435
return fmt.Errorf("missing or invalid binary-only package; expected file %q", a.Package.Target)
415436
}
416437

438+
if p.Module != nil && !allowedVersion(p.Module.GoVersion) {
439+
return fmt.Errorf("module requires Go %s", p.Module.GoVersion)
440+
}
441+
417442
if err := b.Mkdir(a.Objdir); err != nil {
418443
return err
419444
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Test support for declaring needed Go version in module.
2+
3+
env GO111MODULE=on
4+
5+
go list
6+
! go build
7+
stderr 'module requires Go 1.999'
8+
go build sub.1
9+
! go build badsub.1
10+
stderr 'module requires Go 1.11111'
11+
12+
go build versioned.1
13+
go mod -require [email protected]
14+
! go build versioned.1
15+
stderr 'module requires Go 1.99999'
16+
17+
-- go.mod --
18+
module m
19+
go 1.999
20+
require (
21+
sub.1 v1.0.0
22+
badsub.1 v1.0.0
23+
versioned.1 v1.0.0
24+
)
25+
replace (
26+
sub.1 => ./sub
27+
badsub.1 => ./badsub
28+
versioned.1 v1.0.0 => ./versioned1
29+
versioned.1 v1.1.0 => ./versioned2
30+
)
31+
32+
-- x.go --
33+
package x
34+
35+
-- sub/go.mod --
36+
module m
37+
go 1.11
38+
39+
-- sub/x.go --
40+
package x
41+
42+
-- badsub/go.mod --
43+
module m
44+
go 1.11111
45+
46+
-- badsub/x.go --
47+
package x
48+
49+
-- versioned1/go.mod --
50+
module versioned
51+
go 1.0
52+
53+
-- versioned1/x.go --
54+
package x
55+
56+
-- versioned2/go.mod --
57+
module versioned
58+
go 1.99999
59+
60+
-- versioned2/x.go --
61+
package x

0 commit comments

Comments
 (0)