From e75bb92ef0ef1dd15f4f65fbf753404e87614c07 Mon Sep 17 00:00:00 2001 From: Yanchen Chen Date: Thu, 3 Apr 2025 00:11:07 +0800 Subject: [PATCH 1/5] cmd/go/internal/modload: don't infer a /v1 suffix module path --- src/cmd/go/internal/modload/init.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index 41b3b9df1ba20c..e274eec0cf76d7 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -1710,6 +1710,15 @@ func findModulePath(dir string) (string, error) { badPathErr = err break } + // Ensure the inferred path is valid. + if _, _, ok := module.SplitPathVersion(path); !ok { + if strings.HasPrefix(path, "gopkg.in/") { + badPathErr = errors.New("module paths beginning with gopkg.in/ must always have a major version suffix in the form of .vN") + } else { + badPathErr = errors.New("major version suffixes must be in the form of /vN and are only allowed for v2 or later") + } + break + } return path, nil } } @@ -1725,6 +1734,7 @@ func findModulePath(dir string) (string, error) { Example usage: 'go mod init example.com/m' to initialize a v0 or v1 module 'go mod init example.com/m/v2' to initialize a v2 module + 'go mod init gopkg.in/m.v1' to initialize a gopkg.in module Run 'go help mod init' for more information. ` From 5f0c4511af8ebbcda559688ff92b60b5b4da1cbe Mon Sep 17 00:00:00 2001 From: Yanchen Chen Date: Thu, 3 Apr 2025 20:37:58 +0800 Subject: [PATCH 2/5] cmd/go/internal/modload: remove example for gopkg.in module initialization --- src/cmd/go/internal/modload/init.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index e274eec0cf76d7..feee871fc44145 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -1734,7 +1734,6 @@ func findModulePath(dir string) (string, error) { Example usage: 'go mod init example.com/m' to initialize a v0 or v1 module 'go mod init example.com/m/v2' to initialize a v2 module - 'go mod init gopkg.in/m.v1' to initialize a gopkg.in module Run 'go help mod init' for more information. ` From 9946437fd13e3a142d0339b1e4e28c2f2460ec47 Mon Sep 17 00:00:00 2001 From: Yanchen Chen Date: Fri, 4 Apr 2025 00:01:35 +0800 Subject: [PATCH 3/5] cmd/go/testdata: add tests for 'go mod init' in v0 and v1 directories --- src/cmd/go/testdata/script/mod_init_empty.txt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/cmd/go/testdata/script/mod_init_empty.txt b/src/cmd/go/testdata/script/mod_init_empty.txt index d197a79a67180c..beb41c81c859cb 100644 --- a/src/cmd/go/testdata/script/mod_init_empty.txt +++ b/src/cmd/go/testdata/script/mod_init_empty.txt @@ -8,6 +8,22 @@ stdout '^example.com$' go list stdout '^example.com$' +# Reset $GOPATH +env GOPATH=$WORK/gopath + +# 'go mod init' should not create a go.mod file in v0 or v1 directory. +cd $GOPATH/src/example.com/m/v0 +! go mod init +stderr '(?s)^go: cannot determine module path for source directory(.*)example.com/m/v0 \(bad module path inferred from directory in GOPATH: major version suffixes must be in the form of /vN and are only allowed for v2 or later\)(.*)Example usage:(.*)''go mod init example.com/m'' to initialize a v0 or v1 module(.*)''go mod init example.com/m/v2'' to initialize a v2 module(.*)Run ''go help mod init'' for more information.$' + +cd $GOPATH/src/example.com/m/v1 +! go mod init +stderr '(?s)^go: cannot determine module path for source directory(.*)example.com/m/v1 \(bad module path inferred from directory in GOPATH: major version suffixes must be in the form of /vN and are only allowed for v2 or later\)(.*)Example usage:(.*)''go mod init example.com/m'' to initialize a v0 or v1 module(.*)''go mod init example.com/m/v2'' to initialize a v2 module(.*)Run ''go help mod init'' for more information.$' + +cd $GOPATH/src/example.com/m/v2 +go mod init +stderr '^go: creating new go.mod: module example.com/m/v2$' + -- go.mod -- module example.com @@ -19,3 +35,10 @@ func main() {} -- $WORK/invalid-gopath This is a text file, not a directory. + +-- example.com/m/v0/main.go -- +package main +-- example.com/m/v1/main.go -- +package main +-- example.com/m/v2/main.go -- +package main From 612ed5e27be4dd2285e862ac0b410f26ebd644b6 Mon Sep 17 00:00:00 2001 From: Yanchen Chen Date: Mon, 7 Apr 2025 08:56:35 +0800 Subject: [PATCH 4/5] cmd/go/testdata: make matches shorter and add gopkg.in module test case --- src/cmd/go/testdata/script/mod_init_empty.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/cmd/go/testdata/script/mod_init_empty.txt b/src/cmd/go/testdata/script/mod_init_empty.txt index beb41c81c859cb..2ae22271f03b0f 100644 --- a/src/cmd/go/testdata/script/mod_init_empty.txt +++ b/src/cmd/go/testdata/script/mod_init_empty.txt @@ -14,16 +14,20 @@ env GOPATH=$WORK/gopath # 'go mod init' should not create a go.mod file in v0 or v1 directory. cd $GOPATH/src/example.com/m/v0 ! go mod init -stderr '(?s)^go: cannot determine module path for source directory(.*)example.com/m/v0 \(bad module path inferred from directory in GOPATH: major version suffixes must be in the form of /vN and are only allowed for v2 or later\)(.*)Example usage:(.*)''go mod init example.com/m'' to initialize a v0 or v1 module(.*)''go mod init example.com/m/v2'' to initialize a v2 module(.*)Run ''go help mod init'' for more information.$' +stderr '(?s)^go: cannot determine module path for source directory (.*v0) \(bad module path inferred from directory in GOPATH: major version suffixes must be in the form of /vN and are only allowed for v2 or later\)(.*)' cd $GOPATH/src/example.com/m/v1 ! go mod init -stderr '(?s)^go: cannot determine module path for source directory(.*)example.com/m/v1 \(bad module path inferred from directory in GOPATH: major version suffixes must be in the form of /vN and are only allowed for v2 or later\)(.*)Example usage:(.*)''go mod init example.com/m'' to initialize a v0 or v1 module(.*)''go mod init example.com/m/v2'' to initialize a v2 module(.*)Run ''go help mod init'' for more information.$' +stderr '(?s)^go: cannot determine module path for source directory (.*v1) \(bad module path inferred from directory in GOPATH: major version suffixes must be in the form of /vN and are only allowed for v2 or later\)(.*)' cd $GOPATH/src/example.com/m/v2 go mod init stderr '^go: creating new go.mod: module example.com/m/v2$' +cd $GOPATH/src/gopkg.in/m +! go mod init +stderr '(?s)^go: cannot determine module path for source directory (.*m) \(bad module path inferred from directory in GOPATH: module paths beginning with gopkg.in/ must always have a major version suffix in the form of .vN\)(.*)' + -- go.mod -- module example.com @@ -42,3 +46,5 @@ package main package main -- example.com/m/v2/main.go -- package main +-- gopkg.in/m/main.go -- +package main From 1066d59d33ecfd7e55ed6e8139c4d3b341e4d428 Mon Sep 17 00:00:00 2001 From: Yanchen Chen Date: Mon, 14 Apr 2025 09:11:52 +0800 Subject: [PATCH 5/5] cmd/go/internal/modload: refactor module path error --- src/cmd/go/internal/modload/init.go | 94 +++++++++++-------- src/cmd/go/testdata/script/mod_init_empty.txt | 6 +- .../script/mod_init_invalid_major.txt | 32 +++---- src/cmd/go/testdata/script/mod_init_path.txt | 2 +- .../go/testdata/script/mod_invalid_path.txt | 2 +- 5 files changed, 75 insertions(+), 61 deletions(-) diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index feee871fc44145..a9db8bf8677433 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -1075,13 +1075,16 @@ func CreateModFile(ctx context.Context, modPath string) { base.Fatalf("go: %s already exists", modFilePath) } + modPathError := modulePathError{reason: fmt.Sprintf("invalid module path %q", modPath)} if modPath == "" { - var err error - modPath, err = findModulePath(modRoot) + inferredModPath, err := findModulePath(modRoot) if err != nil { base.Fatal(err) } - } else if err := module.CheckImportPath(modPath); err != nil { + modPath = inferredModPath + modPathError.reason = fmt.Sprintf("invalid module path %q inferred from directory in GOPATH", inferredModPath) + } + if err := module.CheckImportPath(modPath); err != nil { if pathErr, ok := err.(*module.InvalidPathError); ok { pathErr.Kind = "module" // Same as build.IsLocalPath() @@ -1090,14 +1093,18 @@ func CreateModFile(ctx context.Context, modPath string) { pathErr.Err = errors.New("is a local import path") } } - base.Fatal(err) - } else if _, _, ok := module.SplitPathVersion(modPath); !ok { + modPathError.message = err.Error() + base.Fatal(modPathError) + } + if _, _, ok := module.SplitPathVersion(modPath); !ok { if strings.HasPrefix(modPath, "gopkg.in/") { - invalidMajorVersionMsg := fmt.Errorf("module paths beginning with gopkg.in/ must always have a major version suffix in the form of .vN:\n\tgo mod init %s", suggestGopkgIn(modPath)) - base.Fatalf(`go: invalid module path "%v": %v`, modPath, invalidMajorVersionMsg) + modPathError.message = "module paths beginning with gopkg.in/ must always have a major version suffix in the form of .vN" + modPathError.suggestions = []string{fmt.Sprintf("go mod init %s", suggestGopkgIn(modPath))} + } else { + modPathError.message = "major version suffixes must be in the form of /vN and are only allowed for v2 or later" + modPathError.suggestions = []string{fmt.Sprintf("go mod init %s", suggestModulePath(modPath))} } - invalidMajorVersionMsg := fmt.Errorf("major version suffixes must be in the form of /vN and are only allowed for v2 or later:\n\tgo mod init %s", suggestModulePath(modPath)) - base.Fatalf(`go: invalid module path "%v": %v`, modPath, invalidMajorVersionMsg) + base.Fatal(modPathError) } fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath) @@ -1140,6 +1147,36 @@ func CreateModFile(ctx context.Context, modPath string) { } } +// modulePathError is an error that occurs when a module path is invalid. +// +// Format: +// go: : +// +// Example usage: +// +// +// Run 'go help mod init' for more information. +type modulePathError struct { + reason string + message string + suggestions []string +} + +func (e modulePathError) Error() string { + buf := strings.Builder{} + buf.WriteString(fmt.Sprintf("%s: %s\n", e.reason, e.message)) + if len(e.suggestions) > 0 { + buf.WriteString("\nExample usage:\n") + for _, suggestion := range e.suggestions { + buf.WriteString("\t") + buf.WriteString(suggestion) + buf.WriteString("\n") + } + } + buf.WriteString("\nRun 'go help mod init' for more information.\n") + return buf.String() +} + // fixVersion returns a modfile.VersionFixer implemented using the Query function. // // It resolves commit hashes and branch names to versions, @@ -1698,46 +1735,23 @@ func findModulePath(dir string) (string, error) { } // Look for path in GOPATH. - var badPathErr error for _, gpdir := range filepath.SplitList(cfg.BuildContext.GOPATH) { if gpdir == "" { continue } if rel := search.InDir(dir, filepath.Join(gpdir, "src")); rel != "" && rel != "." { - path := filepath.ToSlash(rel) - // gorelease will alert users publishing their modules to fix their paths. - if err := module.CheckImportPath(path); err != nil { - badPathErr = err - break - } - // Ensure the inferred path is valid. - if _, _, ok := module.SplitPathVersion(path); !ok { - if strings.HasPrefix(path, "gopkg.in/") { - badPathErr = errors.New("module paths beginning with gopkg.in/ must always have a major version suffix in the form of .vN") - } else { - badPathErr = errors.New("major version suffixes must be in the form of /vN and are only allowed for v2 or later") - } - break - } - return path, nil + return filepath.ToSlash(rel), nil } } - reason := "outside GOPATH, module path must be specified" - if badPathErr != nil { - // return a different error message if the module was in GOPATH, but - // the module path determined above would be an invalid path. - reason = fmt.Sprintf("bad module path inferred from directory in GOPATH: %v", badPathErr) + return "", modulePathError{ + reason: "cannot determine module path for source directory", + message: "outside GOPATH, module path must be specified", + suggestions: []string{ + "'go mod init example.com/m' to initialize a v0 or v1 module", + "'go mod init example.com/m/v2' to initialize a v2 module", + }, } - msg := `cannot determine module path for source directory %s (%s) - -Example usage: - 'go mod init example.com/m' to initialize a v0 or v1 module - 'go mod init example.com/m/v2' to initialize a v2 module - -Run 'go help mod init' for more information. -` - return "", fmt.Errorf(msg, dir, reason) } var ( diff --git a/src/cmd/go/testdata/script/mod_init_empty.txt b/src/cmd/go/testdata/script/mod_init_empty.txt index 2ae22271f03b0f..61c09e6423b6c2 100644 --- a/src/cmd/go/testdata/script/mod_init_empty.txt +++ b/src/cmd/go/testdata/script/mod_init_empty.txt @@ -14,11 +14,11 @@ env GOPATH=$WORK/gopath # 'go mod init' should not create a go.mod file in v0 or v1 directory. cd $GOPATH/src/example.com/m/v0 ! go mod init -stderr '(?s)^go: cannot determine module path for source directory (.*v0) \(bad module path inferred from directory in GOPATH: major version suffixes must be in the form of /vN and are only allowed for v2 or later\)(.*)' +stderr '(?s)^go: invalid module path "example.com/m/v0" inferred from directory in GOPATH: major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/m/v2(.*)' cd $GOPATH/src/example.com/m/v1 ! go mod init -stderr '(?s)^go: cannot determine module path for source directory (.*v1) \(bad module path inferred from directory in GOPATH: major version suffixes must be in the form of /vN and are only allowed for v2 or later\)(.*)' +stderr '(?s)^go: invalid module path "example.com/m/v1" inferred from directory in GOPATH: major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/m/v2(.*)' cd $GOPATH/src/example.com/m/v2 go mod init @@ -26,7 +26,7 @@ stderr '^go: creating new go.mod: module example.com/m/v2$' cd $GOPATH/src/gopkg.in/m ! go mod init -stderr '(?s)^go: cannot determine module path for source directory (.*m) \(bad module path inferred from directory in GOPATH: module paths beginning with gopkg.in/ must always have a major version suffix in the form of .vN\)(.*)' +stderr '(?s)^go: invalid module path "gopkg.in/m" inferred from directory in GOPATH: module paths beginning with gopkg.in/ must always have a major version suffix in the form of .vN(.*)' -- go.mod -- module example.com diff --git a/src/cmd/go/testdata/script/mod_init_invalid_major.txt b/src/cmd/go/testdata/script/mod_init_invalid_major.txt index ae93e70d6307ff..c422d052ad6b26 100644 --- a/src/cmd/go/testdata/script/mod_init_invalid_major.txt +++ b/src/cmd/go/testdata/script/mod_init_invalid_major.txt @@ -2,34 +2,34 @@ env GO111MODULE=on env GOFLAGS=-mod=mod ! go mod init example.com/user/repo/v0 -stderr '(?s)^go: invalid module path "example.com/user/repo/v0": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v0": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2(.*)' ! go mod init example.com/user/repo/v02 -stderr '(?s)^go: invalid module path "example.com/user/repo/v02": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v02": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2(.*)' ! go mod init example.com/user/repo/v023 -stderr '(?s)^go: invalid module path "example.com/user/repo/v023": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v23$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v023": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v23(.*)' ! go mod init example.com/user/repo/v1 -stderr '(?s)^go: invalid module path "example.com/user/repo/v1": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v1": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2(.*)' ! go mod init example.com/user/repo/v2.0 -stderr '(?s)^go: invalid module path "example.com/user/repo/v2.0": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v2.0": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2(.*)' ! go mod init example.com/user/repo/v2.1.4 -stderr '(?s)^go: invalid module path "example.com/user/repo/v2.1.4": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v2.1.4": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2(.*)' ! go mod init example.com/user/repo/v3.5 -stderr '(?s)^go: invalid module path "example.com/user/repo/v3.5": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v3$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v3.5": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v3(.*)' ! go mod init example.com/user/repo/v4.1.4 -stderr '(?s)^go: invalid module path "example.com/user/repo/v4.1.4": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v4$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v4.1.4": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v4(.*)' ! go mod init example.com/user/repo/v.2.3 -stderr '(?s)^go: invalid module path "example.com/user/repo/v.2.3": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v.2.3": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v2(.*)' ! go mod init example.com/user/repo/v.5.3 -stderr '(?s)^go: invalid module path "example.com/user/repo/v.5.3": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v5$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v.5.3": major version suffixes must be in the form of /vN and are only allowed for v2 or later(.*)go mod init example.com/user/repo/v5(.*)' ! go mod init gopkg.in/pkg stderr '(?s)^go: invalid module path "gopkg.in/pkg": module paths beginning with gopkg.in/ must always have a major version suffix in the form of .vN(.*)go mod init gopkg.in/pkg.v1$' @@ -63,20 +63,20 @@ stderr '(?s)^go: invalid module path "gopkg.in/user/pkg.v.2.3": module paths beg # module paths with a trailing dot are rejected as invalid import paths ! go mod init example.com/user/repo/v2. -stderr '(?s)^go: malformed module path "example.com/user/repo/v2.": trailing dot in path element$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v2.": malformed module path "example.com/user/repo/v2.": trailing dot in path element(.*)' ! go mod init example.com/user/repo/v2.. -stderr '(?s)^go: malformed module path "example.com/user/repo/v2..": trailing dot in path element$' +stderr '(?s)^go: invalid module path "example.com/user/repo/v2..": malformed module path "example.com/user/repo/v2..": trailing dot in path element(.*)' ! go mod init gopkg.in/user/pkg.v.2. -stderr '(?s)^go: malformed module path "gopkg.in/user/pkg.v.2.": trailing dot in path element$' +stderr '(?s)^go: invalid module path "gopkg.in/user/pkg.v.2.": malformed module path "gopkg.in/user/pkg.v.2.": trailing dot in path element(.*)' ! go mod init gopkg.in/user/pkg.v.2.. -stderr '(?s)^go: malformed module path "gopkg.in/user/pkg.v.2..": trailing dot in path element$' +stderr '(?s)^go: invalid module path "gopkg.in/user/pkg.v.2..": malformed module path "gopkg.in/user/pkg.v.2..": trailing dot in path element(.*)' # module paths with spaces are also rejected ! go mod init 'foo bar' -stderr '(?s)^go: malformed module path "foo bar": invalid char '' ''$' +stderr '(?s)^go: invalid module path "foo bar": malformed module path "foo bar": invalid char '' ''(.*)' ! go mod init 'foo bar baz' -stderr '(?s)^go: malformed module path "foo bar baz": invalid char '' ''$' +stderr '(?s)^go: invalid module path "foo bar baz": malformed module path "foo bar baz": invalid char '' ''(.*)' diff --git a/src/cmd/go/testdata/script/mod_init_path.txt b/src/cmd/go/testdata/script/mod_init_path.txt index e5fd4ddbcb92c7..bd6f2fa42ed2e5 100644 --- a/src/cmd/go/testdata/script/mod_init_path.txt +++ b/src/cmd/go/testdata/script/mod_init_path.txt @@ -1,7 +1,7 @@ env GO111MODULE=on ! go mod init . -stderr '^go: malformed module path ".": is a local import path$' +stderr '^go: invalid module path ".": malformed module path ".": is a local import path(.*)' cd x go mod init example.com/x diff --git a/src/cmd/go/testdata/script/mod_invalid_path.txt b/src/cmd/go/testdata/script/mod_invalid_path.txt index 975de5ebcabfcb..3448e850d05048 100644 --- a/src/cmd/go/testdata/script/mod_invalid_path.txt +++ b/src/cmd/go/testdata/script/mod_invalid_path.txt @@ -12,7 +12,7 @@ stderr '^go: error reading go.mod: missing module declaration. To specify the mo # but are a valid Windows file name. cd $WORK/'gopath/src/m''d' ! go mod init -stderr 'cannot determine module path' +stderr '(?s)^go: invalid module path "m''d" inferred from directory in GOPATH: malformed module path "m''d": invalid char ''\\''''(.*)' # Test that a go.mod file is rejected when its module declaration has a path that can't # possibly be a module path, because it isn't even a valid import path