Skip to content

Commit

Permalink
cmd/go: only generate a go.mod file during 'go mod init'
Browse files Browse the repository at this point in the history
In the general case, we do not know the correct module path for a new
module unless we have checked its VCS tags for a major version. If we
do not know the correct path, then we should not synthesize a go.mod
file automatically from it.

On the other hand, we don't want to run VCS commands in the working
directory without an explicit request by the user to do so: 'go mod
init' can reasonably invoke a VCS command, but 'go build' should not.

Therefore, we should only create a go.mod file during 'go mod init'.

This change removes the previous behavior of synthesizing a file
automatically, and instead suggests a command that the user can opt to
run explicitly.

Updates #29433
Updates #27009
Updates #30228

Change-Id: I8c4554969db17156e97428df220b129a4d361040
Reviewed-on: https://go-review.googlesource.com/c/162699
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
  • Loading branch information
Bryan C. Mills committed Feb 15, 2019
1 parent 4c89a10 commit 65c2069
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 80 deletions.
77 changes: 41 additions & 36 deletions src/cmd/go/internal/modload/init.go
Expand Up @@ -154,7 +154,7 @@ func Init() {
die() // Don't init a module that we're just going to ignore.
}
// No automatic enabling in GOPATH.
if root, _ := FindModuleRoot(cwd, "", false); root != "" {
if root := findModuleRoot(cwd); root != "" {
cfg.GoModInGOPATH = filepath.Join(root, "go.mod")
}
return
Expand All @@ -164,7 +164,7 @@ func Init() {
// Running 'go mod init': go.mod will be created in current directory.
modRoot = cwd
} else {
modRoot, _ = FindModuleRoot(cwd, "", MustUseModules)
modRoot = findModuleRoot(cwd)
if modRoot == "" {
if !MustUseModules {
// GO111MODULE is 'auto' (or unset), and we can't find a module root.
Expand Down Expand Up @@ -302,6 +302,19 @@ func die() {
if inGOPATH && !MustUseModules {
base.Fatalf("go: modules disabled inside GOPATH/src by GO111MODULE=auto; see 'go help modules'")
}
if cwd != "" {
if dir, name := findAltConfig(cwd); dir != "" {
rel, err := filepath.Rel(cwd, dir)
if err != nil {
rel = dir
}
cdCmd := ""
if rel != "." {
cdCmd = fmt.Sprintf("cd %s && ", rel)
}
base.Fatalf("go: cannot find main module, but found %s in %s\n\tto create a module there, run:\n\t%sgo mod init", name, dir, cdCmd)
}
}
base.Fatalf("go: cannot find main module; see 'go help modules'")
}

Expand Down Expand Up @@ -330,12 +343,6 @@ func InitMod() {
gomod := filepath.Join(modRoot, "go.mod")
data, err := ioutil.ReadFile(gomod)
if err != nil {
if os.IsNotExist(err) {
legacyModInit()
modFileToBuildList()
WriteGoMod()
return
}
base.Fatalf("go: %v", err)
}

Expand All @@ -349,7 +356,7 @@ func InitMod() {

if len(f.Syntax.Stmt) == 0 || f.Module == nil {
// Empty mod file. Must add module path.
path, err := FindModulePath(modRoot)
path, err := findModulePath(modRoot)
if err != nil {
base.Fatalf("go: %v", err)
}
Expand Down Expand Up @@ -387,7 +394,7 @@ func Allowed(m module.Version) bool {

func legacyModInit() {
if modFile == nil {
path, err := FindModulePath(modRoot)
path, err := findModulePath(modRoot)
if err != nil {
base.Fatalf("go: %v", err)
}
Expand Down Expand Up @@ -454,57 +461,55 @@ var altConfigs = []string{
".git/config",
}

// Exported only for testing.
func FindModuleRoot(dir, limit string, legacyConfigOK bool) (root, file string) {
func findModuleRoot(dir string) (root string) {
dir = filepath.Clean(dir)
dir1 := dir
limit = filepath.Clean(limit)

// Look for enclosing go.mod.
for {
if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() {
return dir, "go.mod"
}
if dir == limit {
break
return dir
}
d := filepath.Dir(dir)
if d == dir {
break
}
dir = d
}
return ""
}

// Failing that, look for enclosing alternate version config.
if legacyConfigOK {
dir = dir1
for {
for _, name := range altConfigs {
if fi, err := os.Stat(filepath.Join(dir, name)); err == nil && !fi.IsDir() {
return dir, name
func findAltConfig(dir string) (root, name string) {
dir = filepath.Clean(dir)
for {
for _, name := range altConfigs {
if fi, err := os.Stat(filepath.Join(dir, name)); err == nil && !fi.IsDir() {
if rel := search.InDir(dir, cfg.BuildContext.GOROOT); rel == "." {
// Don't suggest creating a module from $GOROOT/.git/config.
return "", ""
}
return dir, name
}
if dir == limit {
break
}
d := filepath.Dir(dir)
if d == dir {
break
}
dir = d
}
d := filepath.Dir(dir)
if d == dir {
break
}
dir = d
}

return "", ""
}

// Exported only for testing.
func FindModulePath(dir string) (string, error) {
func findModulePath(dir string) (string, error) {
if CmdModModule != "" {
// Running go mod init x/y/z; return x/y/z.
return CmdModModule, nil
}

// TODO(bcmills): once we have located a plausible module path, we should
// query version control (if available) to verify that it matches the major
// version of the most recent tag.
// See https://golang.org/issue/29433 and https://golang.org/issue/27009.

// Cast about for import comments,
// first in top-level directory, then in subdirectories.
list, _ := ioutil.ReadDir(dir)
Expand Down
42 changes: 0 additions & 42 deletions src/cmd/go/internal/modload/init_test.go

This file was deleted.

2 changes: 1 addition & 1 deletion src/cmd/go/internal/modload/load.go
Expand Up @@ -111,7 +111,7 @@ func ImportPaths(patterns []string) []*search.Match {
} else {
pkg = Target.Path + suffix
}
} else if sub := search.InDir(dir, cfg.GOROOTsrc); sub != "" && !strings.Contains(sub, "@") {
} else if sub := search.InDir(dir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") {
pkg = filepath.ToSlash(sub)
} else if path := pathInModuleCache(dir); path != "" {
pkg = path
Expand Down
22 changes: 22 additions & 0 deletions src/cmd/go/testdata/script/mod_convert_dep.txt
@@ -1,9 +1,31 @@
env GO111MODULE=on

# We should not create a go.mod file unless the user ran 'go mod init' explicitly.
# However, we should suggest 'go mod init' if we can find an alternate config file.
cd $WORK/test/x
! go list .
stderr 'found Gopkg.lock in .*[/\\]test'
stderr '\s*cd \.\. && go mod init'

# The command we suggested should succeed.
cd ..
go mod init
go list -m all
stdout '^m$'

# In Plan 9, directories are automatically created in /n.
# For example, /n/Gopkg.lock always exists, but it's a directory.
# Test that we ignore directories when trying to find alternate config files.
cd $WORK/gopkgdir/x
! go list .
stderr 'cannot find main module'
! stderr 'Gopkg.lock'
! stderr 'go mod init'

-- $WORK/test/Gopkg.lock --
-- $WORK/test/x/x.go --
package x // import "m/x"
-- $WORK/gopkgdir/Gopkg.lock/README.txt --
../Gopkg.lock is a directory, not a file.
-- $WORK/gopkgdir/x/x.go --
package x // import "m/x"
15 changes: 14 additions & 1 deletion src/cmd/go/testdata/script/mod_convert_git.txt
@@ -1,10 +1,23 @@
env GO111MODULE=on

# detect root of module tree as root of enclosing git repo
# We should not create a go.mod file unless the user ran 'go mod init' explicitly.
# However, we should suggest 'go mod init' if we can find an alternate config file.
cd $WORK/test/x
! go list .
stderr 'found .git/config in .*[/\\]test'
stderr '\s*cd \.\. && go mod init'

# The command we suggested should succeed.
cd ..
go mod init
go list -m all
stdout '^m$'

# We should not suggest creating a go.mod file in $GOROOT, even though there may be a .git/config there.
cd $GOROOT
! go list .
! stderr 'go mod init'

-- $WORK/test/.git/config --
-- $WORK/test/x/x.go --
package x // import "m/x"
9 changes: 9 additions & 0 deletions src/cmd/go/testdata/script/mod_convert_glide.txt
@@ -1,6 +1,15 @@
env GO111MODULE=on

# We should not create a go.mod file unless the user ran 'go mod init' explicitly.
# However, we should suggest 'go mod init' if we can find an alternate config file.
cd $WORK/test/x
! go list .
stderr 'found glide.lock in .*[/\\]test'
stderr '\s*cd \.\. && go mod init'

# The command we suggested should succeed.
cd ..
go mod init
go list -m all
stdout '^m$'

Expand Down
9 changes: 9 additions & 0 deletions src/cmd/go/testdata/script/mod_convert_glockfile.txt
@@ -1,6 +1,15 @@
env GO111MODULE=on

# We should not create a go.mod file unless the user ran 'go mod init' explicitly.
# However, we should suggest 'go mod init' if we can find an alternate config file.
cd $WORK/test/x
! go list .
stderr 'found GLOCKFILE in .*[/\\]test'
stderr '\s*cd \.\. && go mod init'

# The command we suggested should succeed.
cd ..
go mod init
go list -m all
stdout '^m$'

Expand Down
9 changes: 9 additions & 0 deletions src/cmd/go/testdata/script/mod_convert_godeps.txt
@@ -1,6 +1,15 @@
env GO111MODULE=on

# We should not create a go.mod file unless the user ran 'go mod init' explicitly.
# However, we should suggest 'go mod init' if we can find an alternate config file.
cd $WORK/test/x
! go list .
stderr 'found Godeps/Godeps.json in .*[/\\]test'
stderr '\s*cd \.\. && go mod init'

# The command we suggested should succeed.
cd ..
go mod init
go list -m all
stdout '^m$'

Expand Down
9 changes: 9 additions & 0 deletions src/cmd/go/testdata/script/mod_convert_tsv.txt
@@ -1,6 +1,15 @@
env GO111MODULE=on

# We should not create a go.mod file unless the user ran 'go mod init' explicitly.
# However, we should suggest 'go mod init' if we can find an alternate config file.
cd $WORK/test/x
! go list .
stderr 'found dependencies.tsv in .*[/\\]test'
stderr '\s*cd \.\. && go mod init'

# The command we suggested should succeed.
cd ..
go mod init
go list -m all
stdout '^m$'

Expand Down
9 changes: 9 additions & 0 deletions src/cmd/go/testdata/script/mod_convert_vendor_conf.txt
@@ -1,6 +1,15 @@
env GO111MODULE=on

# We should not create a go.mod file unless the user ran 'go mod init' explicitly.
# However, we should suggest 'go mod init' if we can find an alternate config file.
cd $WORK/test/x
! go list .
stderr 'found vendor.conf in .*[/\\]test'
stderr '\s*cd \.\. && go mod init'

# The command we suggested should succeed.
cd ..
go mod init
go list -m all
stdout '^m$'

Expand Down
9 changes: 9 additions & 0 deletions src/cmd/go/testdata/script/mod_convert_vendor_json.txt
@@ -1,6 +1,15 @@
env GO111MODULE=on

# We should not create a go.mod file unless the user ran 'go mod init' explicitly.
# However, we should suggest 'go mod init' if we can find an alternate config file.
cd $WORK/test/x
! go list .
stderr 'found vendor/vendor.json in .*[/\\]test'
stderr '\s*cd \.\. && go mod init'

# The command we suggested should succeed.
cd ..
go mod init
go list -m all
stdout '^m$'

Expand Down
9 changes: 9 additions & 0 deletions src/cmd/go/testdata/script/mod_convert_vendor_manifest.txt
@@ -1,6 +1,15 @@
env GO111MODULE=on

# We should not create a go.mod file unless the user ran 'go mod init' explicitly.
# However, we should suggest 'go mod init' if we can find an alternate config file.
cd $WORK/test/x
! go list .
stderr 'found vendor/manifest in .*[/\\]test'
stderr '\s*cd \.\. && go mod init'

# The command we suggested should succeed.
cd ..
go mod init
go list -m all
stdout '^m$'

Expand Down
9 changes: 9 additions & 0 deletions src/cmd/go/testdata/script/mod_convert_vendor_yml.txt
@@ -1,6 +1,15 @@
env GO111MODULE=on

# We should not create a go.mod file unless the user ran 'go mod init' explicitly.
# However, we should suggest 'go mod init' if we can find an alternate config file.
cd $WORK/test/x
! go list .
stderr 'found vendor.yml in .*[/\\]test'
stderr '\s*cd \.\. && go mod init'

# The command we suggested should succeed.
cd ..
go mod init
go list -m all
stdout '^m$'

Expand Down

0 comments on commit 65c2069

Please sign in to comment.