diff options
Diffstat (limited to 'src/cmd/go/internal')
-rw-r--r-- | src/cmd/go/internal/modcmd/edit.go | 2 | ||||
-rw-r--r-- | src/cmd/go/internal/modcmd/init.go | 2 | ||||
-rw-r--r-- | src/cmd/go/internal/modfetch/coderepo.go | 23 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/buildlist.go | 19 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/init.go | 39 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/modfile.go | 84 | ||||
-rw-r--r-- | src/cmd/go/internal/modload/query.go | 30 |
7 files changed, 120 insertions, 79 deletions
diff --git a/src/cmd/go/internal/modcmd/edit.go b/src/cmd/go/internal/modcmd/edit.go index e856e7c630..bb3d521092 100644 --- a/src/cmd/go/internal/modcmd/edit.go +++ b/src/cmd/go/internal/modcmd/edit.go @@ -25,7 +25,7 @@ import ( ) var cmdEdit = &base.Command{ - UsageLine: "go mod edit [editing flags] [go.mod]", + UsageLine: "go mod edit [editing flags] [-fmt|-print|-json] [go.mod]", Short: "edit go.mod from tools or scripts", Long: ` Edit provides a command-line interface for editing go.mod, diff --git a/src/cmd/go/internal/modcmd/init.go b/src/cmd/go/internal/modcmd/init.go index 73cc282d81..958c3066ac 100644 --- a/src/cmd/go/internal/modcmd/init.go +++ b/src/cmd/go/internal/modcmd/init.go @@ -13,7 +13,7 @@ import ( ) var cmdInit = &base.Command{ - UsageLine: "go mod init [module]", + UsageLine: "go mod init [module-path]", Short: "initialize new module in current directory", Long: ` Init initializes and writes a new go.mod file in the current directory, in diff --git a/src/cmd/go/internal/modfetch/coderepo.go b/src/cmd/go/internal/modfetch/coderepo.go index f817a04583..dfef9f73c2 100644 --- a/src/cmd/go/internal/modfetch/coderepo.go +++ b/src/cmd/go/internal/modfetch/coderepo.go @@ -864,22 +864,25 @@ func (r *codeRepo) GoMod(version string) (data []byte, err error) { data, err = r.code.ReadFile(rev, path.Join(dir, "go.mod"), codehost.MaxGoMod) if err != nil { if os.IsNotExist(err) { - return r.legacyGoMod(rev, dir), nil + return LegacyGoMod(r.modPath), nil } return nil, err } return data, nil } -func (r *codeRepo) legacyGoMod(rev, dir string) []byte { - // We used to try to build a go.mod reflecting pre-existing - // package management metadata files, but the conversion - // was inherently imperfect (because those files don't have - // exactly the same semantics as go.mod) and, when done - // for dependencies in the middle of a build, impossible to - // correct. So we stopped. - // Return a fake go.mod that simply declares the module path. - return []byte(fmt.Sprintf("module %s\n", modfile.AutoQuote(r.modPath))) +// LegacyGoMod generates a fake go.mod file for a module that doesn't have one. +// The go.mod file contains a module directive and nothing else: no go version, +// no requirements. +// +// We used to try to build a go.mod reflecting pre-existing +// package management metadata files, but the conversion +// was inherently imperfect (because those files don't have +// exactly the same semantics as go.mod) and, when done +// for dependencies in the middle of a build, impossible to +// correct. So we stopped. +func LegacyGoMod(modPath string) []byte { + return []byte(fmt.Sprintf("module %s\n", modfile.AutoQuote(modPath))) } func (r *codeRepo) modPrefix(rev string) string { diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go index 604a57b437..bf69567316 100644 --- a/src/cmd/go/internal/modload/buildlist.go +++ b/src/cmd/go/internal/modload/buildlist.go @@ -191,6 +191,19 @@ func (rs *Requirements) rootSelected(path string) (version string, ok bool) { return "", false } +// hasRedundantRoot returns true if the root list contains multiple requirements +// of the same module or a requirement on any version of the main module. +// Redundant requirements should be pruned, but they may influence version +// selection. +func (rs *Requirements) hasRedundantRoot() bool { + for i, m := range rs.rootModules { + if m.Path == Target.Path || (i > 0 && m.Path == rs.rootModules[i-1].Path) { + return true + } + } + return false +} + // Graph returns the graph of module requirements loaded from the current // root modules (as reported by RootModules). // @@ -882,6 +895,12 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen // and (trivially) version. if !rootsUpgraded { + if cfg.BuildMod != "mod" { + // The only changes to the root set (if any) were to remove duplicates. + // The requirements are consistent (if perhaps redundant), so keep the + // original rs to preserve its ModuleGraph. + return rs, nil + } // The root set has converged: every root going into this iteration was // already at its selected version, although we have have removed other // (redundant) roots for the same path. diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go index a8cbd9fe16..45f724d5e3 100644 --- a/src/cmd/go/internal/modload/init.go +++ b/src/cmd/go/internal/modload/init.go @@ -449,13 +449,22 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) { } setDefaultBuildMod() // possibly enable automatic vendoring - rs = requirementsFromModFile(ctx) - + rs = requirementsFromModFile() if cfg.BuildMod == "vendor" { readVendorList() checkVendorConsistency() rs.initVendor(vendorList) } + if rs.hasRedundantRoot() { + // If any module path appears more than once in the roots, we know that the + // go.mod file needs to be updated even though we have not yet loaded any + // transitive dependencies. + rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false) + if err != nil { + base.Fatalf("go: %v", err) + } + } + if index.goVersionV == "" { // TODO(#45551): Do something more principled instead of checking // cfg.CmdName directly here. @@ -530,7 +539,12 @@ func CreateModFile(ctx context.Context, modPath string) { base.Fatalf("go: %v", err) } - commitRequirements(ctx, modFileGoVersion(), requirementsFromModFile(ctx)) + rs := requirementsFromModFile() + rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false) + if err != nil { + base.Fatalf("go: %v", err) + } + commitRequirements(ctx, modFileGoVersion(), rs) // Suggest running 'go mod tidy' unless the project is empty. Even if we // imported all the correct requirements above, we're probably missing @@ -641,9 +655,8 @@ func initTarget(m module.Version) { // requirementsFromModFile returns the set of non-excluded requirements from // the global modFile. -func requirementsFromModFile(ctx context.Context) *Requirements { +func requirementsFromModFile() *Requirements { roots := make([]module.Version, 0, len(modFile.Require)) - mPathCount := map[string]int{Target.Path: 1} direct := map[string]bool{} for _, r := range modFile.Require { if index != nil && index.exclude[r.Mod] { @@ -656,28 +669,12 @@ func requirementsFromModFile(ctx context.Context) *Requirements { } roots = append(roots, r.Mod) - mPathCount[r.Mod.Path]++ if !r.Indirect { direct[r.Mod.Path] = true } } module.Sort(roots) rs := newRequirements(modDepthFromGoVersion(modFileGoVersion()), roots, direct) - - // If any module path appears more than once in the roots, we know that the - // go.mod file needs to be updated even though we have not yet loaded any - // transitive dependencies. - for _, n := range mPathCount { - if n > 1 { - var err error - rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false) - if err != nil { - base.Fatalf("go: %v", err) - } - break - } - } - return rs } diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go index d280945ea6..03e02e73b6 100644 --- a/src/cmd/go/internal/modload/modfile.go +++ b/src/cmd/go/internal/modload/modfile.go @@ -595,47 +595,14 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) { } c := rawGoModSummaryCache.Do(m, func() interface{} { summary := new(modFileSummary) - var f *modfile.File - if m.Version == "" { - // m is a replacement module with only a file path. - dir := m.Path - if !filepath.IsAbs(dir) { - dir = filepath.Join(ModRoot(), dir) - } - gomod := filepath.Join(dir, "go.mod") - var data []byte - var err error - if gomodActual, ok := fsys.OverlayPath(gomod); ok { - // Don't lock go.mod if it's part of the overlay. - // On Plan 9, locking requires chmod, and we don't want to modify any file - // in the overlay. See #44700. - data, err = os.ReadFile(gomodActual) - } else { - data, err = lockedfile.Read(gomodActual) - } - if err != nil { - return cached{nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(gomod), err))} - } - f, err = modfile.ParseLax(gomod, data, nil) - if err != nil { - return cached{nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(gomod), err))} - } - } else { - if !semver.IsValid(m.Version) { - // Disallow the broader queries supported by fetch.Lookup. - base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version) - } - - data, err := modfetch.GoMod(m.Path, m.Version) - if err != nil { - return cached{nil, err} - } - f, err = modfile.ParseLax("go.mod", data, nil) - if err != nil { - return cached{nil, module.VersionError(m, fmt.Errorf("parsing go.mod: %v", err))} - } + name, data, err := rawGoModData(m) + if err != nil { + return cached{nil, err} + } + f, err := modfile.ParseLax(name, data, nil) + if err != nil { + return cached{nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(name), err))} } - if f.Module != nil { summary.module = f.Module.Mod summary.deprecated = f.Module.Deprecated @@ -671,6 +638,43 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) { var rawGoModSummaryCache par.Cache // module.Version → rawGoModSummary result +// rawGoModData returns the content of the go.mod file for module m, ignoring +// all replacements that may apply to m. +// +// rawGoModData cannot be used on the Target module. +// +// Unlike rawGoModSummary, rawGoModData does not cache its results in memory. +// Use rawGoModSummary instead unless you specifically need these bytes. +func rawGoModData(m module.Version) (name string, data []byte, err error) { + if m.Version == "" { + // m is a replacement module with only a file path. + dir := m.Path + if !filepath.IsAbs(dir) { + dir = filepath.Join(ModRoot(), dir) + } + name = filepath.Join(dir, "go.mod") + if gomodActual, ok := fsys.OverlayPath(name); ok { + // Don't lock go.mod if it's part of the overlay. + // On Plan 9, locking requires chmod, and we don't want to modify any file + // in the overlay. See #44700. + data, err = os.ReadFile(gomodActual) + } else { + data, err = lockedfile.Read(gomodActual) + } + if err != nil { + return "", nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(name), err)) + } + } else { + if !semver.IsValid(m.Version) { + // Disallow the broader queries supported by fetch.Lookup. + base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version) + } + name = "go.mod" + data, err = modfetch.GoMod(m.Path, m.Version) + } + return name, data, err +} + // queryLatestVersionIgnoringRetractions looks up the latest version of the // module with the given path without considering retracted or excluded // versions. diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go index dda9004a9f..e737ca90fc 100644 --- a/src/cmd/go/internal/modload/query.go +++ b/src/cmd/go/internal/modload/query.go @@ -5,13 +5,13 @@ package modload import ( + "bytes" "context" "errors" "fmt" "io/fs" "os" pathpkg "path" - "path/filepath" "sort" "strings" "sync" @@ -931,14 +931,32 @@ func moduleHasRootPackage(ctx context.Context, m module.Version) (bool, error) { return ok, err } -func versionHasGoMod(ctx context.Context, m module.Version) (bool, error) { - needSum := false - root, _, err := fetch(ctx, m, needSum) +// versionHasGoMod returns whether a version has a go.mod file. +// +// versionHasGoMod fetches the go.mod file (possibly a fake) and true if it +// contains anything other than a module directive with the same path. When a +// module does not have a real go.mod file, the go command acts as if it had one +// that only contained a module directive. Normal go.mod files created after +// 1.12 at least have a go directive. +// +// This function is a heuristic, since it's possible to commit a file that would +// pass this test. However, we only need a heurstic for determining whether +// +incompatible versions may be "latest", which is what this function is used +// for. +// +// This heuristic is useful for two reasons: first, when using a proxy, +// this lets us fetch from the .mod endpoint which is much faster than the .zip +// endpoint. The .mod file is used anyway, even if the .zip file contains a +// go.mod with different content. Second, if we don't fetch the .zip, then +// we don't need to verify it in go.sum. This makes 'go list -m -u' faster +// and simpler. +func versionHasGoMod(_ context.Context, m module.Version) (bool, error) { + _, data, err := rawGoModData(m) if err != nil { return false, err } - fi, err := os.Stat(filepath.Join(root, "go.mod")) - return err == nil && !fi.IsDir(), nil + isFake := bytes.Equal(data, modfetch.LegacyGoMod(m.Path)) + return !isFake, nil } // A versionRepo is a subset of modfetch.Repo that can report information about |