aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/go/internal/modload/init.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/go/internal/modload/init.go')
-rw-r--r--src/cmd/go/internal/modload/init.go161
1 files changed, 126 insertions, 35 deletions
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 93027c44c4..1f50dcb11c 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -368,13 +368,9 @@ func InitMod(ctx context.Context) {
modFile = f
index = indexModFile(data, f, fixed)
- if len(f.Syntax.Stmt) == 0 || f.Module == nil {
- // Empty mod file. Must add module path.
- path, err := findModulePath(modRoot)
- if err != nil {
- base.Fatalf("go: %v", err)
- }
- f.AddModuleStmt(path)
+ if f.Module == nil {
+ // No module declaration. Must add module path.
+ base.Fatalf("go: no module declaration in go.mod.\n\tRun 'go mod edit -module=example.com/mod' to specify the module path.")
}
if len(f.Syntax.Stmt) == 1 && f.Module != nil {
@@ -383,14 +379,61 @@ func InitMod(ctx context.Context) {
legacyModInit()
}
- modFileToBuildList()
+ if err := checkModulePathLax(f.Module.Mod.Path); err != nil {
+ base.Fatalf("go: %v", err)
+ }
+
setDefaultBuildMod()
+ modFileToBuildList()
if cfg.BuildMod == "vendor" {
readVendorList()
checkVendorConsistency()
}
}
+// checkModulePathLax checks that the path meets some minimum requirements
+// to avoid confusing users or the module cache. The requirements are weaker
+// than those of module.CheckPath to allow room for weakening module path
+// requirements in the future, but strong enough to help users avoid significant
+// problems.
+func checkModulePathLax(p string) error {
+ // TODO(matloob): Replace calls of this function in this CL with calls
+ // to module.CheckImportPath once it's been laxened, if it becomes laxened.
+ // See golang.org/issue/29101 for a discussion about whether to make CheckImportPath
+ // more lax or more strict.
+
+ errorf := func(format string, args ...interface{}) error {
+ return fmt.Errorf("invalid module path %q: %s", p, fmt.Sprintf(format, args...))
+ }
+
+ // Disallow shell characters " ' * < > ? ` | to avoid triggering bugs
+ // with file systems and subcommands. Disallow file path separators : and \
+ // because path separators other than / will confuse the module cache.
+ // See fileNameOK in golang.org/x/mod/module/module.go.
+ shellChars := "`" + `\"'*<>?|`
+ fsChars := `\:`
+ if i := strings.IndexAny(p, shellChars); i >= 0 {
+ return errorf("contains disallowed shell character %q", p[i])
+ }
+ if i := strings.IndexAny(p, fsChars); i >= 0 {
+ return errorf("contains disallowed path separator character %q", p[i])
+ }
+
+ // Ensure path.IsAbs and build.IsLocalImport are false, and that the path is
+ // invariant under path.Clean, also to avoid confusing the module cache.
+ if path.IsAbs(p) {
+ return errorf("is an absolute path")
+ }
+ if build.IsLocalImport(p) {
+ return errorf("is a local import path")
+ }
+ if path.Clean(p) != p {
+ return errorf("is not clean")
+ }
+
+ return nil
+}
+
// fixVersion returns a modfile.VersionFixer implemented using the Query function.
//
// It resolves commit hashes and branch names to versions,
@@ -459,7 +502,15 @@ func modFileToBuildList() {
list := []module.Version{Target}
for _, r := range modFile.Require {
- list = append(list, r.Mod)
+ if index != nil && index.exclude[r.Mod] {
+ if cfg.BuildMod == "mod" {
+ fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
+ } else {
+ fmt.Fprintf(os.Stderr, "go: ignoring requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
+ }
+ } else {
+ list = append(list, r.Mod)
+ }
}
buildList = list
}
@@ -467,46 +518,49 @@ func modFileToBuildList() {
// setDefaultBuildMod sets a default value for cfg.BuildMod
// if it is currently empty.
func setDefaultBuildMod() {
- if cfg.BuildMod != "" {
+ if cfg.BuildModExplicit {
// Don't override an explicit '-mod=' argument.
return
}
- cfg.BuildMod = "mod"
+
if cfg.CmdName == "get" || strings.HasPrefix(cfg.CmdName, "mod ") {
- // Don't set -mod implicitly for commands whose purpose is to
- // manipulate the build list.
+ // 'get' and 'go mod' commands may update go.mod automatically.
+ // TODO(jayconrod): should this narrower? Should 'go mod download' or
+ // 'go mod graph' update go.mod by default?
+ cfg.BuildMod = "mod"
return
}
if modRoot == "" {
+ cfg.BuildMod = "mod"
return
}
if fi, err := os.Stat(filepath.Join(modRoot, "vendor")); err == nil && fi.IsDir() {
modGo := "unspecified"
- if index.goVersion != "" {
- if semver.Compare("v"+index.goVersion, "v1.14") >= 0 {
+ if index.goVersionV != "" {
+ if semver.Compare(index.goVersionV, "v1.14") >= 0 {
// The Go version is at least 1.14, and a vendor directory exists.
// Set -mod=vendor by default.
cfg.BuildMod = "vendor"
cfg.BuildModReason = "Go version in go.mod is at least 1.14 and vendor directory exists."
return
} else {
- modGo = index.goVersion
+ modGo = index.goVersionV[1:]
}
}
- // Since a vendor directory exists, we have a non-trivial reason for
- // choosing -mod=mod, although it probably won't be used for anything.
- // Record the reason anyway for consistency.
- // It may be overridden if we switch to mod=readonly below.
- cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s.", modGo)
+ // Since a vendor directory exists, we should record why we didn't use it.
+ // This message won't normally be shown, but it may appear with import errors.
+ cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s, so vendor directory was not used.", modGo)
}
p := ModFilePath()
if fi, err := os.Stat(p); err == nil && !hasWritePerm(p, fi) {
cfg.BuildMod = "readonly"
cfg.BuildModReason = "go.mod file is read-only."
+ return
}
+ cfg.BuildMod = "mod"
}
func legacyModInit() {
@@ -674,16 +728,35 @@ 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 != "." {
- return filepath.ToSlash(rel), nil
+ path := filepath.ToSlash(rel)
+ // TODO(matloob): replace this with module.CheckImportPath
+ // once it's been laxened.
+ // Only checkModulePathLax here. There are some unpublishable
+ // module names that are compatible with checkModulePathLax
+ // but they already work in GOPATH so don't break users
+ // trying to do a build with modules. gorelease will alert users
+ // publishing their modules to fix their paths.
+ if err := checkModulePathLax(path); err != nil {
+ badPathErr = err
+ break
+ }
+ return path, nil
}
}
- msg := `cannot determine module path for source directory %s (outside GOPATH, module path must be specified)
+ 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)
+ }
+ 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
@@ -691,7 +764,7 @@ Example usage:
Run 'go help mod init' for more information.
`
- return "", fmt.Errorf(msg, dir)
+ return "", fmt.Errorf(msg, dir, reason)
}
var (
@@ -787,19 +860,16 @@ func WriteGoMod() {
// prefer to report a dirty go.mod over a dirty go.sum
if cfg.BuildModReason != "" {
base.Fatalf("go: updates to go.mod needed, disabled by -mod=readonly\n\t(%s)", cfg.BuildModReason)
- } else {
+ } else if cfg.BuildModExplicit {
base.Fatalf("go: updates to go.mod needed, disabled by -mod=readonly")
}
}
- // Always update go.sum, even if we didn't change go.mod: we may have
- // downloaded modules that we didn't have before.
- modfetch.WriteGoSum(keepSums())
-
if !dirty && cfg.CmdName != "mod tidy" {
// The go.mod file has the same semantic content that it had before
// (but not necessarily the same exact bytes).
- // Ignore any intervening edits.
+ // Don't write go.mod, but write go.sum in case we added or trimmed sums.
+ modfetch.WriteGoSum(keepSums(true))
return
}
@@ -810,6 +880,9 @@ func WriteGoMod() {
defer func() {
// At this point we have determined to make the go.mod file on disk equal to new.
index = indexModFile(new, modFile, false)
+
+ // Update go.sum after releasing the side lock and refreshing the index.
+ modfetch.WriteGoSum(keepSums(true))
}()
// Make a best-effort attempt to acquire the side lock, only to exclude
@@ -850,7 +923,10 @@ func WriteGoMod() {
// the last load function like ImportPaths, LoadALL, etc.). It also contains
// entries for go.mod files needed for MVS (the version of these entries
// ends with "/go.mod").
-func keepSums() map[module.Version]bool {
+//
+// If addDirect is true, the set also includes sums for modules directly
+// required by go.mod, as represented by the index, with replacements applied.
+func keepSums(addDirect bool) map[module.Version]bool {
// Walk the module graph and keep sums needed by MVS.
modkey := func(m module.Version) module.Version {
return module.Version{Path: m.Path, Version: m.Version + "/go.mod"}
@@ -862,9 +938,6 @@ func keepSums() map[module.Version]bool {
walk = func(m module.Version) {
// If we build using a replacement module, keep the sum for the replacement,
// since that's the code we'll actually use during a build.
- //
- // TODO(golang.org/issue/29182): Perhaps we should keep both sums, and the
- // sums for both sets of transitive requirements.
r := Replacement(m)
if r.Path == "" {
keep[modkey(m)] = true
@@ -894,9 +967,27 @@ func keepSums() map[module.Version]bool {
}
}
+ // Add entries for modules directly required by go.mod.
+ if addDirect {
+ for m := range index.require {
+ var kept module.Version
+ if r := Replacement(m); r.Path != "" {
+ kept = r
+ } else {
+ kept = m
+ }
+ keep[kept] = true
+ keep[module.Version{Path: kept.Path, Version: kept.Version + "/go.mod"}] = true
+ }
+ }
+
return keep
}
func TrimGoSum() {
- modfetch.TrimGoSum(keepSums())
+ // Don't retain sums for direct requirements in go.mod. When TrimGoSum is
+ // called, go.mod has not been updated, and it may contain requirements on
+ // modules deleted from the build list.
+ addDirect := false
+ modfetch.TrimGoSum(keepSums(addDirect))
}