modfile: add support for go and toolchain lines
As part of the forward compatibility work, a new toolchain line
is being added, and go lines are allowed to specify toolchain
versions like "1.21.0" or "1.21rc1" now. (The lax RE has allowed this for quite
some time; what's new here is allowing it in the main module.)
For golang/go#57001.
Change-Id: I1dc01289381fe080644a7a391b97a65158938f39
Reviewed-on: https://go-review.googlesource.com/c/mod/+/497397
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>
diff --git a/modfile/read_test.go b/modfile/read_test.go
index 82c778d..df4f117 100644
--- a/modfile/read_test.go
+++ b/modfile/read_test.go
@@ -434,13 +434,13 @@
{desc: "empty", input: "module m\ngo \n", ok: false},
{desc: "one", input: "module m\ngo 1\n", ok: false},
{desc: "two", input: "module m\ngo 1.22\n", ok: true},
- {desc: "three", input: "module m\ngo 1.22.333", ok: false},
+ {desc: "three", input: "module m\ngo 1.22.333", ok: true},
{desc: "before", input: "module m\ngo v1.2\n", ok: false},
- {desc: "after", input: "module m\ngo 1.2rc1\n", ok: false},
+ {desc: "after", input: "module m\ngo 1.2rc1\n", ok: true},
{desc: "space", input: "module m\ngo 1.2 3.4\n", ok: false},
- {desc: "alt1", input: "module m\ngo 1.2.3\n", ok: false, laxOK: true},
- {desc: "alt2", input: "module m\ngo 1.2rc1\n", ok: false, laxOK: true},
- {desc: "alt3", input: "module m\ngo 1.2beta1\n", ok: false, laxOK: true},
+ {desc: "alt1", input: "module m\ngo 1.2.3\n", ok: true, laxOK: true},
+ {desc: "alt2", input: "module m\ngo 1.2rc1\n", ok: true, laxOK: true},
+ {desc: "alt3", input: "module m\ngo 1.2beta1\n", ok: true, laxOK: true},
{desc: "alt4", input: "module m\ngo 1.2.beta1\n", ok: false, laxOK: true},
{desc: "alt1", input: "module m\ngo v1.2.3\n", ok: false, laxOK: true},
{desc: "alt2", input: "module m\ngo v1.2rc1\n", ok: false, laxOK: true},
diff --git a/modfile/rule.go b/modfile/rule.go
index c20aef1..3306f6f 100644
--- a/modfile/rule.go
+++ b/modfile/rule.go
@@ -35,12 +35,13 @@
// 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
- Retract []*Retract
+ Module *Module
+ Go *Go
+ Toolchain *Toolchain
+ Require []*Require
+ Exclude []*Exclude
+ Replace []*Replace
+ Retract []*Retract
Syntax *FileSyntax
}
@@ -58,6 +59,12 @@
Syntax *Line
}
+// A Toolchain is the toolchain statement.
+type Toolchain struct {
+ Name string // "go1.21rc1"
+ Syntax *Line
+}
+
// An Exclude is a single exclude statement.
type Exclude struct {
Mod module.Version
@@ -296,9 +303,13 @@
return f, nil
}
-var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)
+var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`)
var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`)
+// Toolchains must be named beginning with `go1` or containing `-go1` as a substring,
+// like "go1.20.3" or "gccgo-go1.20.3". As a special case, "local" is also permitted.
+var ToolchainRE = lazyregexp.New(`^local$|(^|-)go1`)
+
func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
// If strict is false, this module is a dependency.
// We ignore all unknown directives as well as main-module-only
@@ -926,7 +937,7 @@
func (f *File) AddGoStmt(version string) error {
if !GoVersionRE.MatchString(version) {
- return fmt.Errorf("invalid language version string %q", version)
+ return fmt.Errorf("invalid language version %q", version)
}
if f.Go == nil {
var hint Expr
@@ -944,6 +955,28 @@
return nil
}
+func (f *File) AddToolchainStmt(name string) error {
+ if !ToolchainRE.MatchString(name) {
+ return fmt.Errorf("invalid toolchain name %q", name)
+ }
+ if f.Toolchain == nil {
+ var hint Expr
+ if f.Go != nil && f.Go.Syntax != nil {
+ hint = f.Go.Syntax
+ } else if f.Module != nil && f.Module.Syntax != nil {
+ hint = f.Module.Syntax
+ }
+ f.Toolchain = &Toolchain{
+ Name: name,
+ Syntax: f.Syntax.addLine(hint, "toolchain", name),
+ }
+ } else {
+ f.Toolchain.Name = name
+ f.Syntax.updateLine(f.Go.Syntax, "toolchain", name)
+ }
+ return nil
+}
+
// AddRequire sets the first require line for path to version vers,
// preserving any existing comments for that line and removing all
// other lines for path.
diff --git a/modfile/work.go b/modfile/work.go
index 0c0e521..827a01d 100644
--- a/modfile/work.go
+++ b/modfile/work.go
@@ -12,9 +12,10 @@
// A WorkFile is the parsed, interpreted form of a go.work file.
type WorkFile struct {
- Go *Go
- Use []*Use
- Replace []*Replace
+ Go *Go
+ Toolchain *Toolchain
+ Use []*Use
+ Replace []*Replace
Syntax *FileSyntax
}
@@ -109,7 +110,7 @@
func (f *WorkFile) AddGoStmt(version string) error {
if !GoVersionRE.MatchString(version) {
- return fmt.Errorf("invalid language version string %q", version)
+ return fmt.Errorf("invalid language version %q", version)
}
if f.Go == nil {
stmt := &Line{Token: []string{"go", version}}
@@ -117,7 +118,7 @@
Version: version,
Syntax: stmt,
}
- // Find the first non-comment-only block that's and add
+ // Find the first non-comment-only block and add
// the go statement before it. That will keep file comments at the top.
i := 0
for i = 0; i < len(f.Syntax.Stmt); i++ {
@@ -133,6 +134,40 @@
return nil
}
+func (f *WorkFile) AddToolchainStmt(name string) error {
+ if !ToolchainRE.MatchString(name) {
+ return fmt.Errorf("invalid toolchain name %q", name)
+ }
+ if f.Toolchain == nil {
+ stmt := &Line{Token: []string{"toolchain", name}}
+ f.Toolchain = &Toolchain{
+ Name: name,
+ Syntax: stmt,
+ }
+ // Find the go line and add the toolchain line after it.
+ // Or else find the first non-comment-only block and add
+ // the toolchain line before it. That will keep file comments at the top.
+ i := 0
+ for i = 0; i < len(f.Syntax.Stmt); i++ {
+ if line, ok := f.Syntax.Stmt[i].(*Line); ok && len(line.Token) > 0 && line.Token[0] == "go" {
+ i++
+ goto Found
+ }
+ }
+ for i = 0; i < len(f.Syntax.Stmt); i++ {
+ if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok {
+ break
+ }
+ }
+ Found:
+ f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...)
+ } else {
+ f.Toolchain.Name = name
+ f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name)
+ }
+ return nil
+}
+
func (f *WorkFile) AddUse(diskPath, modulePath string) error {
need := true
for _, d := range f.Use {