]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: add 'go version' statement in go.mod
authorRuss Cox <rsc@golang.org>
Wed, 25 Jul 2018 04:24:13 +0000 (00:24 -0400)
committerRuss Cox <rsc@golang.org>
Wed, 1 Aug 2018 00:35:16 +0000 (00:35 +0000)
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: Bryan C. Mills <bcmills@google.com>
src/cmd/go/alldocs.go
src/cmd/go/internal/imports/tags.go
src/cmd/go/internal/modcmd/mod.go
src/cmd/go/internal/modfile/rule.go
src/cmd/go/internal/modget/get.go
src/cmd/go/internal/modinfo/info.go
src/cmd/go/internal/modload/build.go
src/cmd/go/internal/modload/load.go
src/cmd/go/internal/work/exec.go
src/cmd/go/testdata/script/mod_go_version.txt [new file with mode: 0644]

index f7dcb1099282f197d5bcc8ad0ecc1d019a4c8403..1178629afb749a5e2bf08c5ebd4feb11dc7d625e 100644 (file)
 //         Main     bool         // is this the main module?
 //         Indirect bool         // is this module only an indirect dependency of main module?
 //         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
 //     }
 //
 // The -module flag changes (or, with -init, sets) the module's path
 // (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
 // add and drop a requirement on the given module path and version.
 // Note that -require overrides any existing requirements on path.
index ba0ca945353c31362c8e4a9a120e7bf47dd51a11..1c22a472b80d3ff5f5a855fb64b19947d7750be0 100644 (file)
@@ -24,7 +24,6 @@ func loadTags() map[string]bool {
        if cfg.BuildContext.CgoEnabled {
                tags["cgo"] = true
        }
-       // TODO: Should read these out of GOROOT source code?
        for _, tag := range cfg.BuildContext.BuildTags {
                tags[tag] = true
        }
index 2c0dfb14587a4ff8a1ef9d40ed616a69000baad6..fa6e17cd680a5fa6a74cb9f1e112e5ed431f1bac 100644 (file)
@@ -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 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
 add and drop a requirement on the given module path and version.
 Note that -require overrides any existing requirements on path.
index 21fce58331edfa98e796966a83baa334294477a6..f669575c860e86501f0bf16b0c5ea1b86dc5423b 100644 (file)
@@ -9,6 +9,7 @@ import (
        "errors"
        "fmt"
        "path/filepath"
+       "regexp"
        "sort"
        "strconv"
        "strings"
@@ -21,6 +22,7 @@ import (
 // A File is the parsed, interpreted form of a go.mod file.
 type File struct {
        Module  *Module
+       Go      *Go
        Require []*Require
        Exclude []*Exclude
        Replace []*Replace
@@ -34,6 +36,12 @@ type Module struct {
        Syntax *Line
 }
 
+// A Go is the go statement.
+type Go struct {
+       Version string // "1.23"
+       Syntax  *Line
+}
+
 // A Require is a single require statement.
 type Require struct {
        Mod      module.Version
@@ -146,20 +154,39 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File
        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) {
        // If strict is false, this module is a dependency.
-       // We ignore all unknown directives and do not attempt to parse
-       // replace and exclude either. They don't matter, and it will work better for
+       // We ignore all unknown directives as well as main-module-only
+       // directives like replace and exclude. It will work better for
        // forward compatibility if we can depend on modules that have unknown
-       // statements (presumed relevant only when acting as the main module).
-       if !strict && verb != "module" && verb != "require" {
-               return
+       // statements (presumed relevant only when acting as the main module)
+       // and simply ignore those statements.
+       if !strict {
+               switch verb {
+               case "module", "require", "go":
+                       // want these even for dependency go.mods
+               default:
+                       return
+               }
        }
 
        switch verb {
        default:
                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":
                if f.Module != nil {
                        fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line)
index 7cbd1f9406b0d1e7fcb3a0b1033f9ade2cf48037..610c9b2516cc5b95913e9175fd6b6cd824bea706 100644 (file)
@@ -344,7 +344,7 @@ func runGet(cmd *base.Command, args []string) {
        base.ExitIfErrors()
 
        // 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()
        var lookup par.Work
        for _, t := range tasks {
index 761b526b936926a4fdc7f7fbd2f64683767852ef..7341ce44d20ae01bd1876a27f1d2eafd5ec616da 100644 (file)
@@ -10,17 +10,18 @@ import "time"
 // and the fields are documented in the help text in ../list/list.go
 
 type ModulePublic struct {
-       Path     string        `json:",omitempty"` // module path
-       Version  string        `json:",omitempty"` // module version
-       Versions []string      `json:",omitempty"` // available module versions
-       Replace  *ModulePublic `json:",omitempty"` // replaced by this module
-       Time     *time.Time    `json:",omitempty"` // time version was created
-       Update   *ModulePublic `json:",omitempty"` // available update (with -u)
-       Main     bool          `json:",omitempty"` // is this the 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
-       GoMod    string        `json:",omitempty"` // path to go.mod file describing module, if any
-       Error    *ModuleError  `json:",omitempty"` // error loading module
+       Path      string        `json:",omitempty"` // module path
+       Version   string        `json:",omitempty"` // module version
+       Versions  []string      `json:",omitempty"` // available module versions
+       Replace   *ModulePublic `json:",omitempty"` // replaced by this module
+       Time      *time.Time    `json:",omitempty"` // time version was created
+       Update    *ModulePublic `json:",omitempty"` // available update (with -u)
+       Main      bool          `json:",omitempty"` // is this the 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
+       GoMod     string        `json:",omitempty"` // path to go.mod file describing module, if any
+       Error     *ModuleError  `json:",omitempty"` // error loading module
+       GoVersion string        `json:",omitempty"` // go version used in module
 }
 
 type ModuleError struct {
index a5ff4bcc99235ee7765479641c925ccbadd3b495..f63555101a56553e09aeb4027e25d29ba1c1af18 100644 (file)
@@ -86,13 +86,17 @@ func addVersions(m *modinfo.ModulePublic) {
 
 func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
        if m == Target {
-               return &modinfo.ModulePublic{
+               info := &modinfo.ModulePublic{
                        Path:    m.Path,
                        Version: m.Version,
                        Main:    true,
                        Dir:     ModRoot,
                        GoMod:   filepath.Join(ModRoot, "go.mod"),
                }
+               if modFile.Go != nil {
+                       info.GoVersion = modFile.Go.Version
+               }
+               return info
        }
 
        info := &modinfo.ModulePublic{
@@ -100,6 +104,9 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
                Version:  m.Version,
                Indirect: fromBuildList && loaded != nil && !loaded.direct[m.Path],
        }
+       if loaded != nil {
+               info.GoVersion = loaded.goVersion[m.Path]
+       }
 
        if cfg.BuildGetmode == "vendor" {
                info.Dir = filepath.Join(ModRoot, "vendor", m.Path)
@@ -139,8 +146,9 @@ func moduleInfo(m module.Version, fromBuildList bool) *modinfo.ModulePublic {
 
        if r := Replacement(m); r.Path != "" {
                info.Replace = &modinfo.ModulePublic{
-                       Path:    r.Path,
-                       Version: r.Version,
+                       Path:      r.Path,
+                       Version:   r.Version,
+                       GoVersion: info.GoVersion,
                }
                if r.Version == "" {
                        if filepath.IsAbs(r.Path) {
index e8c984baa79e3a5ca66693519c006b65195f0aa1..a668795a7730128af821b11131cfbba3e07426f7 100644 (file)
@@ -358,7 +358,8 @@ type loader struct {
        pkgCache *par.Cache // map from string to *loadPkg
 
        // 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 {
@@ -399,7 +400,8 @@ var errMissing = errors.New("cannot find package")
 // which must call add(path) with the import path of each root package.
 func (ld *loader) load(roots func() []string) {
        var err error
-       buildList, err = mvs.BuildList(Target, Reqs())
+       reqs := Reqs()
+       buildList, err = mvs.BuildList(Target, reqs)
        if err != nil {
                base.Fatalf("go: %v", err)
        }
@@ -445,7 +447,8 @@ func (ld *loader) load(roots func() []string) {
                }
 
                // Recompute buildList with all our additions.
-               buildList, err = mvs.BuildList(Target, Reqs())
+               reqs = Reqs()
+               buildList, err = mvs.BuildList(Target, reqs)
                if err != nil {
                        base.Fatalf("go: %v", err)
                }
@@ -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)
        // from go.mod, unless we scanned the whole module
        // and can therefore be sure we know better than go.mod.
@@ -670,6 +680,7 @@ func Replacement(mod module.Version) module.Version {
 type mvsReqs struct {
        buildList []module.Version
        cache     par.Cache
+       versions  sync.Map
 }
 
 // Reqs returns the current module requirement graph.
@@ -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) {
        if mod == Target {
+               if modFile.Go != nil {
+                       r.versions.LoadOrStore(mod, modFile.Go.Version)
+               }
                var list []module.Version
-               list = append(list, r.buildList[1:]...)
-               return list, nil
+               return append(list, r.buildList[1:]...), nil
        }
 
        if cfg.BuildGetmode == "vendor" {
@@ -778,11 +799,10 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
                                base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
                                return nil, ErrRequire
                        }
-                       var list []module.Version
-                       for _, r := range f.Require {
-                               list = append(list, r.Mod)
+                       if f.Go != nil {
+                               r.versions.LoadOrStore(mod, f.Go.Version)
                        }
-                       return list, nil
+                       return r.modFileToList(f), nil
                }
                mod = repl
        }
@@ -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)
                return nil, ErrRequire
        }
-
-       var list []module.Version
-       for _, req := range f.Require {
-               list = append(list, req.Mod)
+       if f.Go != nil {
+               r.versions.LoadOrStore(mod, f.Go.Version)
        }
-       return list, nil
+
+       return r.modFileToList(f), nil
 }
 
 // ErrRequire is the sentinel error returned when Require encounters problems.
index 38ff22211cf4f972852e7dece2b9a2775c1e68d2..bf8840c25c401edb93814aa5589c8cd3fb76b67f 100644 (file)
@@ -320,6 +320,27 @@ func (b *Builder) needCgoHdr(a *Action) bool {
        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 (
        needBuild uint32 = 1 << iota
        needCgoHdr
@@ -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)
        }
 
+       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 {
                return err
        }
diff --git a/src/cmd/go/testdata/script/mod_go_version.txt b/src/cmd/go/testdata/script/mod_go_version.txt
new file mode 100644 (file)
index 0000000..f5706ee
--- /dev/null
@@ -0,0 +1,61 @@
+# 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