Skip to content

cmd/go/internal/modload: don't infer a /v1 suffix module path #73146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
85 changes: 54 additions & 31 deletions src/cmd/go/internal/modload/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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: <reason>: <message>
//
// Example usage:
// <suggestions>
//
// 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,
Expand Down Expand Up @@ -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 (
Expand Down
29 changes: 29 additions & 0 deletions src/cmd/go/testdata/script/mod_init_empty.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
32 changes: 16 additions & 16 deletions src/cmd/go/testdata/script/mod_init_invalid_major.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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$'
Expand Down Expand Up @@ -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 '' ''(.*)'
2 changes: 1 addition & 1 deletion src/cmd/go/testdata/script/mod_init_path.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/go/testdata/script/mod_invalid_path.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down