diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index 41b3b9df1ba20c..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,37 +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 - } - 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 d197a79a67180c..61c09e6423b6c2 100644 --- a/src/cmd/go/testdata/script/mod_init_empty.txt +++ b/src/cmd/go/testdata/script/mod_init_empty.txt @@ -8,6 +8,26 @@ 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: 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: 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 +stderr '^go: creating new go.mod: module example.com/m/v2$' + +cd $GOPATH/src/gopkg.in/m +! go mod init +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 @@ -19,3 +39,12 @@ 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 +-- gopkg.in/m/main.go -- +package main 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