1
0
mirror of https://github.com/golang/go synced 2024-11-07 13:46:19 -07:00

cmd/go: ignore unknown directives in dependency go.mod files

This will help with forward compatibility when we add additional
directives that only matter for the main module (or that can be
safely ignored otherwise).

Change-Id: Ida1e186fb2669b128aeb5a9a1187e2535b72b763
Reviewed-on: https://go-review.googlesource.com/125936
Reviewed-by: Bryan C. Mills <bcmills@google.com>
This commit is contained in:
Russ Cox 2018-07-24 20:21:37 -04:00
parent 9cde8048ad
commit 0090c13c91
5 changed files with 88 additions and 10 deletions

View File

@ -66,6 +66,21 @@ func testPrint(t *testing.T, in, out string) {
}
}
func TestParseLax(t *testing.T) {
badFile := []byte(`module m
surprise attack
x y (
z
)
exclude v1.2.3
replace <-!!!
`)
_, err := ParseLax("file", badFile, nil)
if err != nil {
t.Fatalf("ParseLax did not ignore irrelevant errors: %v", err)
}
}
// Test that when files in the testdata directory are parsed
// and printed and parsed again, we get the same parse tree
// both times.

View File

@ -87,7 +87,24 @@ func (f *File) AddComment(text string) {
type VersionFixer func(path, version string) (string, error)
// Parse parses the data, reported in errors as being from file,
// into a File struct. It applies fix, if non-nil, to canonicalize all module versions found.
func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
return parseToFile(file, data, fix, true)
}
// ParseLax is like Parse but ignores unknown statements.
// It is used when parsing go.mod files other than the main module,
// under the theory that most statement types we add in the future will
// only apply in the main module, like exclude and replace,
// and so we get better gradual deployments if old go commands
// simply ignore those statements when found in go.mod files
// in dependencies.
func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
return parseToFile(file, data, fix, false)
}
func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File, error) {
fs, err := parse(file, data)
if err != nil {
return nil, err
@ -100,20 +117,24 @@ func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
for _, x := range fs.Stmt {
switch x := x.(type) {
case *Line:
f.add(&errs, x, x.Token[0], x.Token[1:], fix)
f.add(&errs, x, x.Token[0], x.Token[1:], fix, strict)
case *LineBlock:
if len(x.Token) > 1 {
fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
if strict {
fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
}
continue
}
switch x.Token[0] {
default:
fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
if strict {
fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
}
continue
case "module", "require", "exclude", "replace":
for _, l := range x.Line {
f.add(&errs, l, x.Token[0], l.Token, fix)
f.add(&errs, l, x.Token[0], l.Token, fix, strict)
}
}
}
@ -125,15 +146,20 @@ func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
return f, nil
}
func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer) {
// TODO: We should pass in a flag saying whether this module is a dependency.
// If so, we should ignore all unknown directives and not attempt to parse
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
// forward compatibility if we can depend on modules that have local changes.
// 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
}
switch verb {
default:
fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb)
case "module":
if f.Module != nil {
fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line)

View File

@ -764,7 +764,7 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
return nil, ErrRequire
}
f, err := modfile.Parse(gomod, data, nil)
f, err := modfile.ParseLax(gomod, data, nil)
if err != nil {
base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
return nil, ErrRequire
@ -792,7 +792,7 @@ func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
base.Errorf("go: %s@%s: %v\n", mod.Path, mod.Version, err)
return nil, ErrRequire
}
f, err := modfile.Parse("go.mod", data, nil)
f, err := modfile.ParseLax("go.mod", data, nil)
if err != nil {
base.Errorf("go: %s@%s: parsing go.mod: %v", mod.Path, mod.Version, err)
return nil, ErrRequire

View File

@ -0,0 +1,11 @@
rsc.io/badmod v1.0.0
written by hand
-- .mod --
module rsc.io/badmod
hello world
-- .info --
{"Version":"v1.0.0"}
-- x.go --
package x

View File

@ -0,0 +1,26 @@
# Unknown lines should be ignored in dependency go.mod files.
env GO111MODULE=on
go list -m all
# ... and in replaced dependency go.mod files.
cp go.mod go.mod.usesub
go list -m all
# ... but not in the main module.
cp go.mod.bad go.mod
! go list -m all
stderr 'unknown directive: hello'
-- go.mod --
module m
require rsc.io/badmod v1.0.0
-- go.mod.bad --
module m
hello world
-- go.mod.usesub --
module m
require rsc.io/badmod v1.0.0
replace rsc.io/badmod v1.0.0 => ./sub
-- sub/go.mod --
module sub
hello world