Commit 16962faf authored by Russ Cox's avatar Russ Cox

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 <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarBryan C. Mills <bcmills@google.com>
parent 740e589b
...@@ -778,7 +778,7 @@ ...@@ -778,7 +778,7 @@
// Main bool // is this the main module? // Main bool // is this the main module?
// Indirect bool // is this module only an indirect dependency of main module? // Indirect bool // is this module only an indirect dependency of main module?
// Dir string // directory holding files for this module, if any // Dir string // directory holding files for this module, if any
// GoMod string // go.mod file for this module, if any // GoMod string // path to go.mod file for this module, if any
// Error *ModuleError // error loading module // Error *ModuleError // error loading module
// } // }
// //
...@@ -882,6 +882,8 @@ ...@@ -882,6 +882,8 @@
// The -module flag changes (or, with -init, sets) the module's path // The -module flag changes (or, with -init, sets) the module's path
// (the go.mod file's module line). // (the go.mod file's module line).
// //
// The -go flag changes the minimum required version of Go listed in go.mod.
//
// The -require=path@version and -droprequire=path flags // The -require=path@version and -droprequire=path flags
// add and drop a requirement on the given module path and version. // add and drop a requirement on the given module path and version.
// Note that -require overrides any existing requirements on path. // Note that -require overrides any existing requirements on path.
......
...@@ -24,7 +24,6 @@ func loadTags() map[string]bool { ...@@ -24,7 +24,6 @@ func loadTags() map[string]bool {
if cfg.BuildContext.CgoEnabled { if cfg.BuildContext.CgoEnabled {
tags["cgo"] = true tags["cgo"] = true
} }
// TODO: Should read these out of GOROOT source code?
for _, tag := range cfg.BuildContext.BuildTags { for _, tag := range cfg.BuildContext.BuildTags {
tags[tag] = true tags[tag] = true
} }
......
...@@ -51,6 +51,8 @@ To override this guess, use the -module flag. ...@@ -51,6 +51,8 @@ To override this guess, use the -module flag.
The -module flag changes (or, with -init, sets) the module's path The -module flag changes (or, with -init, sets) the module's path
(the go.mod file's module line). (the go.mod file's module line).
The -go flag changes the minimum required version of Go listed in go.mod.
The -require=path@version and -droprequire=path flags The -require=path@version and -droprequire=path flags
add and drop a requirement on the given module path and version. add and drop a requirement on the given module path and version.
Note that -require overrides any existing requirements on path. Note that -require overrides any existing requirements on path.
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
...@@ -21,6 +22,7 @@ import ( ...@@ -21,6 +22,7 @@ import (
// A File is the parsed, interpreted form of a go.mod file. // A File is the parsed, interpreted form of a go.mod file.
type File struct { type File struct {
Module *Module Module *Module
Go *Go
Require []*Require Require []*Require
Exclude []*Exclude Exclude []*Exclude
Replace []*Replace Replace []*Replace
...@@ -34,6 +36,12 @@ type Module struct { ...@@ -34,6 +36,12 @@ type Module struct {
Syntax *Line Syntax *Line
} }
// A Go is the go statement.
type Go struct {
Version string // "1.23"
Syntax *Line
}
// A Require is a single require statement. // A Require is a single require statement.
type Require struct { type Require struct {
Mod module.Version Mod module.Version
...@@ -146,20 +154,39 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File ...@@ -146,20 +154,39 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File
return f, nil return f, nil
} }
var goVersionRE = regexp.MustCompile(`([1-9][0-9]*)\.(0|[1-9][0-9]*)`)
func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer, strict bool) { func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
// If strict is false, this module is a dependency. // If strict is false, this module is a dependency.
// We ignore all unknown directives and do not attempt to parse // We ignore all unknown directives as well as main-module-only
// replace and exclude either. They don't matter, and it will work better for // directives like replace and exclude. It will work better for
// forward compatibility if we can depend on modules that have unknown // forward compatibility if we can depend on modules that have unknown
// statements (presumed relevant only when acting as the main module). // statements (presumed relevant only when acting as the main module)
if !strict && verb != "module" && verb != "require" { // and simply ignore those statements.
return if !strict {
switch verb {
case "module", "require", "go":
// want these even for dependency go.mods
default:
return
}
} }
switch verb { switch verb {
default: default:
fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb) fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb)
case "go":
if f.Go != nil {
fmt.Fprintf(errs, "%s:%d: repeated go statement\n", f.Syntax.Name, line.Start.Line)
return
}
if len(args) != 1 || !goVersionRE.MatchString(args[0]) {
fmt.Fprintf(errs, "%s:%d: usage: go 1.23\n", f.Syntax.Name, line.Start.Line)
return
}
f.Go = &Go{Syntax: line}
f.Go.Version = args[0]
case "module": case "module":
if f.Module != nil { if f.Module != nil {
fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line) fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line)
......
...@@ -344,7 +344,7 @@ func runGet(cmd *base.Command, args []string) { ...@@ -344,7 +344,7 @@ func runGet(cmd *base.Command, args []string) {
base.ExitIfErrors() base.ExitIfErrors()
// Now we've reduced the upgrade/downgrade work to a list of path@vers pairs (tasks). // Now we've reduced the upgrade/downgrade work to a list of path@vers pairs (tasks).
// Resolve each one in parallell. // Resolve each one in parallel.
reqs := modload.Reqs() reqs := modload.Reqs()
var lookup par.Work var lookup par.Work
for _, t := range tasks { for _, t := range tasks {
......
...@@ -10,17 +10,18 @@ import "time" ...@@ -10,17 +10,18 @@ import "time"
// and the fields are documented in the help text in ../list/list.go // and the fields are documented in the help text in ../list/list.go
type ModulePublic struct { type ModulePublic struct {
Path string `json:",omitempty"` // module path Path string `json:",omitempty"` // module path
Version string `json:",omitempty"` // module version Version string `json:",omitempty"` // module version
Versions []string `json:",omitempty"` // available module versions Versions []string `json:",omitempty"` // available module versions
Replace *ModulePublic `json:",omitempty"` // replaced by this module Replace *ModulePublic `json:",omitempty"` // replaced by this module
Time *time.Time `json:",omitempty"` // time version was created Time *time.Time `json:",omitempty"` // time version was created
Update *ModulePublic `json:",omitempty"` // available update (with -u) Update *ModulePublic `json:",omitempty"` // available update (with -u)
Main bool `json:",omitempty"` // is this the main module? Main bool `json:",omitempty"` // is this the main module?
Indirect bool `json:",omitempty"` // module is only indirectly needed by main module Indirect bool `json:",omitempty"` // module is only indirectly needed by main module
Dir string `json:",omitempty"` // directory holding local copy of files, if any Dir string `json:",omitempty"` // directory holding local copy of files, if any
GoMod string `json:",omitempty"` // path to go.mod file describing module, if any GoMod string `json:",omitempty"` // path to go.mod file describing module, if any
Error *ModuleError `json:",omitempty"` // error loading module Error *ModuleError `json:",omitempty"` // error loading module
GoVersion string `json:",omitempty"` // go version used in module
} }
type ModuleError struct { type ModuleError struct {
......
...@@ -86,13 +86,17 @@ func addVersions(m *modinfo.ModulePublic) { ...@@ -86,13 +86,17 @@ func addVersions(m *modinfo.ModulePublic) {
func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic { func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
if m == Target { if m == Target {
return &modinfo.ModulePublic{ info := &modinfo.ModulePublic{
Path: m.Path, Path: m.Path,
Version: m.Version, Version: m.Version,
Main: true, Main: true,
Dir: ModRoot, Dir: ModRoot,
GoMod: filepath.Join(ModRoot, "go.mod"), GoMod: filepath.Join(ModRoot, "go.mod"),
} }
if modFile.Go != nil {
info.GoVersion = modFile.Go.Version
}
return info
} }
info := &modinfo.ModulePublic{ info := &modinfo.ModulePublic{
...@@ -100,6 +104,9 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic { ...@@ -100,6 +104,9 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
Version: m.Version, Version: m.Version,
Indirect: fromBuildList && loaded != nil && !loaded.direct[m.Path], Indirect: fromBuildList && loaded != nil && !loaded.direct[m.Path],
} }
if loaded != nil {
info.GoVersion = loaded.goVersion[m.Path]
}
if cfg.BuildGetmode == "vendor" { if cfg.BuildGetmode == "vendor" {
info.Dir = filepath.Join(ModRoot, "vendor", m.Path) info.Dir = filepath.Join(ModRoot, "vendor", m.Path)
...@@ -139,8 +146,9 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic { ...@@ -139,8 +146,9 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
if r := Replacement(m); r.Path != "" { if r := Replacement(m); r.Path != "" {
info.Replace = &modinfo.ModulePublic{ info.Replace = &modinfo.ModulePublic{
Path: r.Path, Path: r.Path,
Version: r.Version, Version: r.Version,
GoVersion: info.GoVersion,
} }
if r.Version == "" { if r.Version == "" {
if filepath.IsAbs(r.Path) { if filepath.IsAbs(r.Path) {
......
...@@ -358,7 +358,8 @@ type loader struct { ...@@ -358,7 +358,8 @@ type loader struct {
pkgCache *par.Cache // map from string to *loadPkg pkgCache *par.Cache // map from string to *loadPkg
// computed at end of iterations // computed at end of iterations
direct map[string]bool // imported directly by main module direct map[string]bool // imported directly by main module
goVersion map[string]string // go version recorded in each module
} }
func newLoader() *loader { func newLoader() *loader {
...@@ -399,7 +400,8 @@ var errMissing = errors.New("cannot find package") ...@@ -399,7 +400,8 @@ var errMissing = errors.New("cannot find package")
// which must call add(path) with the import path of each root package. // which must call add(path) with the import path of each root package.
func (ld *loader) load(roots func() []string) { func (ld *loader) load(roots func() []string) {
var err error var err error
buildList, err = mvs.BuildList(Target, Reqs()) reqs := Reqs()
buildList, err = mvs.BuildList(Target, reqs)
if err != nil { if err != nil {
base.Fatalf("go: %v", err) base.Fatalf("go: %v", err)
} }
...@@ -445,7 +447,8 @@ func (ld *loader) load(roots func() []string) { ...@@ -445,7 +447,8 @@ func (ld *loader) load(roots func() []string) {
} }
// Recompute buildList with all our additions. // Recompute buildList with all our additions.
buildList, err = mvs.BuildList(Target, Reqs()) reqs = Reqs()
buildList, err = mvs.BuildList(Target, reqs)
if err != nil { if err != nil {
base.Fatalf("go: %v", err) base.Fatalf("go: %v", err)
} }
...@@ -464,6 +467,13 @@ func (ld *loader) load(roots func() []string) { ...@@ -464,6 +467,13 @@ func (ld *loader) load(roots func() []string) {
} }
} }
// Add Go versions, computed during walk.
ld.goVersion = make(map[string]string)
for _, m := range buildList {
v, _ := reqs.(*mvsReqs).versions.Load(m)
ld.goVersion[m.Path], _ = v.(string)
}
// Mix in direct markings (really, lack of indirect markings) // Mix in direct markings (really, lack of indirect markings)
// from go.mod, unless we scanned the whole module // from go.mod, unless we scanned the whole module
// and can therefore be sure we know better than go.mod. // and can therefore be sure we know better than go.mod.
...@@ -670,6 +680,7 @@ func Replacement(mod module.Version) module.Version { ...@@ -670,6 +680,7 @@ func Replacement(mod module.Version) module.Version {
type mvsReqs struct { type mvsReqs struct {
buildList []module.Version buildList []module.Version
cache par.Cache cache par.Cache
versions sync.Map
} }
// Reqs returns the current module requirement graph. // Reqs returns the current module requirement graph.
...@@ -745,11 +756,21 @@ func readVendorList() { ...@@ -745,11 +756,21 @@ func readVendorList() {
}) })
} }
func (r *mvsReqs) modFileToList(f *modfile.File) []module.Version {
var list []module.Version
for _, r := range f.Require {
list = append(list, r.Mod)
}
return list
}
func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) { func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
if mod == Target { if mod == Target {
if modFile.Go != nil {
r.versions.LoadOrStore(mod, modFile.Go.Version)
}
var list []module.Version var list []module.Version
list = append(list, r.buildList[1:]...) return append(list, r.buildList[1:]...), nil
return list, nil
} }
if cfg.BuildGetmode == "vendor" { if cfg.BuildGetmode == "vendor" {
...@@ -778,11 +799,10 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) { ...@@ -778,11 +799,10 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err) base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
return nil, ErrRequire return nil, ErrRequire
} }
var list []module.Version if f.Go != nil {
for _, r := range f.Require { r.versions.LoadOrStore(mod, f.Go.Version)
list = append(list, r.Mod)
} }
return list, nil return r.modFileToList(f), nil
} }
mod = repl mod = repl
} }
...@@ -815,12 +835,11 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) { ...@@ -815,12 +835,11 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
base.Errorf("go: %s@%s: parsing go.mod: unexpected module path %q", mod.Path, mod.Version, mpath) base.Errorf("go: %s@%s: parsing go.mod: unexpected module path %q", mod.Path, mod.Version, mpath)
return nil, ErrRequire return nil, ErrRequire
} }
if f.Go != nil {
var list []module.Version r.versions.LoadOrStore(mod, f.Go.Version)
for _, req := range f.Require {
list = append(list, req.Mod)
} }
return list, nil
return r.modFileToList(f), nil
} }
// ErrRequire is the sentinel error returned when Require encounters problems. // ErrRequire is the sentinel error returned when Require encounters problems.
......
...@@ -320,6 +320,27 @@ func (b *Builder) needCgoHdr(a *Action) bool { ...@@ -320,6 +320,27 @@ func (b *Builder) needCgoHdr(a *Action) bool {
return false return false
} }
// allowedVersion reports whether the version v is an allowed version of go
// (one that we can compile).
// v is known to be of the form "1.23".
func allowedVersion(v string) bool {
// Special case: no requirement.
if v == "" {
return true
}
// Special case "1.0" means "go1", which is OK.
if v == "1.0" {
return true
}
// Otherwise look through release tags of form "go1.23" for one that matches.
for _, tag := range cfg.BuildContext.ReleaseTags {
if strings.HasPrefix(tag, "go") && tag[2:] == v {
return true
}
}
return false
}
const ( const (
needBuild uint32 = 1 << iota needBuild uint32 = 1 << iota
needCgoHdr needCgoHdr
...@@ -414,6 +435,10 @@ func (b *Builder) build(a *Action) (err error) { ...@@ -414,6 +435,10 @@ func (b *Builder) build(a *Action) (err error) {
return fmt.Errorf("missing or invalid binary-only package; expected file %q", a.Package.Target) return fmt.Errorf("missing or invalid binary-only package; expected file %q", a.Package.Target)
} }
if p.Module != nil && !allowedVersion(p.Module.GoVersion) {
return fmt.Errorf("module requires Go %s", p.Module.GoVersion)
}
if err := b.Mkdir(a.Objdir); err != nil { if err := b.Mkdir(a.Objdir); err != nil {
return err return err
} }
......
# Test support for declaring needed Go version in module.
env GO111MODULE=on
go list
! go build
stderr 'module requires Go 1.999'
go build sub.1
! go build badsub.1
stderr 'module requires Go 1.11111'
go build versioned.1
go mod -require versioned.1@v1.1.0
! go build versioned.1
stderr 'module requires Go 1.99999'
-- go.mod --
module m
go 1.999
require (
sub.1 v1.0.0
badsub.1 v1.0.0
versioned.1 v1.0.0
)
replace (
sub.1 => ./sub
badsub.1 => ./badsub
versioned.1 v1.0.0 => ./versioned1
versioned.1 v1.1.0 => ./versioned2
)
-- x.go --
package x
-- sub/go.mod --
module m
go 1.11
-- sub/x.go --
package x
-- badsub/go.mod --
module m
go 1.11111
-- badsub/x.go --
package x
-- versioned1/go.mod --
module versioned
go 1.0
-- versioned1/x.go --
package x
-- versioned2/go.mod --
module versioned
go 1.99999
-- versioned2/x.go --
package x
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment