Skip to content

Commit 7e00049

Browse files
author
Bryan C. Mills
committed
cmd/go: only add a 'go' directive to the main module when the go.mod file will be written
Then, write the 'go.mod' file with that version before further processing. That way, if the command errors out due to a change in behavior, the reason for the change in behavior will be visible in the file diffs. If the 'go.mod' file cannot be written (due to -mod=readonly or -mod=vendor), assume Go 1.11 instead of the current Go release. (cmd/go has added 'go' directives automatically, including in 'go mod init', since Go 1.12.) For #44976 Change-Id: If9d4af557366f134f40ce4c5638688ba3bab8380 Reviewed-on: https://go-review.googlesource.com/c/go/+/302051 Trust: Bryan C. Mills <[email protected]> Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Jay Conrod <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent 4313c28 commit 7e00049

File tree

9 files changed

+219
-41
lines changed

9 files changed

+219
-41
lines changed

doc/go1.17.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ <h4 id="go-get"><code>go</code> <code>get</code></h4>
5555
<code>environment</code> for details.
5656
</p>
5757

58+
<h4 id="missing-go-directive"><code>go.mod</code> files missing <code>go</code> directives</h4>
59+
60+
<p><!-- golang.org/issue/44976 -->
61+
If the main module's <code>go.mod</code> file does not contain
62+
a <a href="/doc/modules/gomod-ref#go"><code>go</code> directive</a> and
63+
the <code>go</code> command cannot update the <code>go.mod</code> file, the
64+
<code>go</code> command now assumes <code>go 1.11</code> instead of the
65+
current release. (<code>go</code> <code>mod</code> <code>init</code> has added
66+
<code>go</code> directives automatically <a href="/doc/go1.12#modules">since
67+
Go 1.12</a>.)
68+
</p>
69+
5870
<h2 id="runtime">Runtime</h2>
5971

6072
<p>

src/cmd/go/internal/modload/build.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,14 @@ func moduleInfo(ctx context.Context, m module.Version, fromBuildList, listRetrac
147147
Version: m.Version,
148148
Main: true,
149149
}
150+
if v, ok := rawGoVersion.Load(Target); ok {
151+
info.GoVersion = v.(string)
152+
} else {
153+
panic("internal error: GoVersion not set for main module")
154+
}
150155
if HasModRoot() {
151156
info.Dir = ModRoot()
152157
info.GoMod = ModFilePath()
153-
if modFile.Go != nil {
154-
info.GoVersion = modFile.Go.Version
155-
}
156158
}
157159
return info
158160
}

src/cmd/go/internal/modload/init.go

Lines changed: 72 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,30 @@ import (
3535
"golang.org/x/mod/semver"
3636
)
3737

38+
// Variables set by other packages.
39+
//
40+
// TODO(#40775): See if these can be plumbed as explicit parameters.
41+
var (
42+
// RootMode determines whether a module root is needed.
43+
RootMode Root
44+
45+
// ForceUseModules may be set to force modules to be enabled when
46+
// GO111MODULE=auto or to report an error when GO111MODULE=off.
47+
ForceUseModules bool
48+
49+
allowMissingModuleImports bool
50+
)
51+
52+
// Variables set in Init.
3853
var (
3954
initialized bool
55+
modRoot string
56+
gopath string
57+
)
4058

41-
modRoot string
42-
Target module.Version
59+
// Variables set in initTarget (during {Load,Create}ModFile).
60+
var (
61+
Target module.Version
4362

4463
// targetPrefix is the path prefix for packages in Target, without a trailing
4564
// slash. For most modules, targetPrefix is just Target.Path, but the
@@ -49,17 +68,6 @@ var (
4968
// targetInGorootSrc caches whether modRoot is within GOROOT/src.
5069
// The "std" module is special within GOROOT/src, but not otherwise.
5170
targetInGorootSrc bool
52-
53-
gopath string
54-
55-
// RootMode determines whether a module root is needed.
56-
RootMode Root
57-
58-
// ForceUseModules may be set to force modules to be enabled when
59-
// GO111MODULE=auto or to report an error when GO111MODULE=off.
60-
ForceUseModules bool
61-
62-
allowMissingModuleImports bool
6371
)
6472

6573
type Root int
@@ -362,6 +370,7 @@ func LoadModFile(ctx context.Context) {
362370
Target = module.Version{Path: "command-line-arguments"}
363371
targetPrefix = "command-line-arguments"
364372
buildList = []module.Version{Target}
373+
rawGoVersion.Store(Target, latestGoVersion())
365374
return
366375
}
367376

@@ -377,24 +386,29 @@ func LoadModFile(ctx context.Context) {
377386
// Errors returned by modfile.Parse begin with file:line.
378387
base.Fatalf("go: errors parsing go.mod:\n%s\n", err)
379388
}
380-
modFile = f
381-
index = indexModFile(data, f, fixed)
382-
383389
if f.Module == nil {
384390
// No module declaration. Must add module path.
385391
base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
386392
}
387393

394+
modFile = f
395+
initTarget(f.Module.Mod)
396+
index = indexModFile(data, f, fixed)
397+
388398
if err := checkModulePathLax(f.Module.Mod.Path); err != nil {
389399
base.Fatalf("go: %v", err)
390400
}
391401

392402
setDefaultBuildMod() // possibly enable automatic vendoring
393-
modFileToBuildList()
403+
buildList = modFileToBuildList(modFile)
394404
if cfg.BuildMod == "vendor" {
395405
readVendorList()
396406
checkVendorConsistency()
397407
}
408+
if index.goVersionV == "" && cfg.BuildMod == "mod" {
409+
addGoStmt()
410+
WriteGoMod()
411+
}
398412
}
399413

400414
// CreateModFile initializes a new module by creating a go.mod file.
@@ -427,6 +441,7 @@ func CreateModFile(ctx context.Context, modPath string) {
427441
fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath)
428442
modFile = new(modfile.File)
429443
modFile.AddModuleStmt(modPath)
444+
initTarget(modFile.Module.Mod)
430445
addGoStmt() // Add the go directive before converted module requirements.
431446

432447
convertedFrom, err := convertLegacyConfig(modPath)
@@ -437,7 +452,7 @@ func CreateModFile(ctx context.Context, modPath string) {
437452
base.Fatalf("go: %v", err)
438453
}
439454

440-
modFileToBuildList()
455+
buildList = modFileToBuildList(modFile)
441456
WriteGoMod()
442457

443458
// Suggest running 'go mod tidy' unless the project is empty. Even if we
@@ -563,19 +578,31 @@ func AllowMissingModuleImports() {
563578
allowMissingModuleImports = true
564579
}
565580

566-
// modFileToBuildList initializes buildList from the modFile.
567-
func modFileToBuildList() {
568-
Target = modFile.Module.Mod
569-
targetPrefix = Target.Path
581+
// initTarget sets Target and associated variables according to modFile,
582+
func initTarget(m module.Version) {
583+
Target = m
584+
targetPrefix = m.Path
585+
570586
if rel := search.InDir(base.Cwd, cfg.GOROOTsrc); rel != "" {
571587
targetInGorootSrc = true
572-
if Target.Path == "std" {
588+
if m.Path == "std" {
589+
// The "std" module in GOROOT/src is the Go standard library. Unlike other
590+
// modules, the packages in the "std" module have no import-path prefix.
591+
//
592+
// Modules named "std" outside of GOROOT/src do not receive this special
593+
// treatment, so it is possible to run 'go test .' in other GOROOTs to
594+
// test individual packages using a combination of the modified package
595+
// and the ordinary standard library.
596+
// (See https://golang.org/issue/30756.)
573597
targetPrefix = ""
574598
}
575599
}
600+
}
576601

602+
// modFileToBuildList returns the list of non-excluded requirements from f.
603+
func modFileToBuildList(f *modfile.File) []module.Version {
577604
list := []module.Version{Target}
578-
for _, r := range modFile.Require {
605+
for _, r := range f.Require {
579606
if index != nil && index.exclude[r.Mod] {
580607
if cfg.BuildMod == "mod" {
581608
fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
@@ -586,7 +613,7 @@ func modFileToBuildList() {
586613
list = append(list, r.Mod)
587614
}
588615
}
589-
buildList = list
616+
return list
590617
}
591618

592619
// setDefaultBuildMod sets a default value for cfg.BuildMod if the -mod flag
@@ -650,20 +677,29 @@ func convertLegacyConfig(modPath string) (from string, err error) {
650677
return "", nil
651678
}
652679

653-
// addGoStmt adds a go directive to the go.mod file if it does not already include one.
654-
// The 'go' version added, if any, is the latest version supported by this toolchain.
680+
// addGoStmt adds a go directive to the go.mod file if it does not already
681+
// include one. The 'go' version added, if any, is the latest version supported
682+
// by this toolchain.
655683
func addGoStmt() {
656684
if modFile.Go != nil && modFile.Go.Version != "" {
657685
return
658686
}
687+
v := latestGoVersion()
688+
if err := modFile.AddGoStmt(v); err != nil {
689+
base.Fatalf("go: internal error: %v", err)
690+
}
691+
rawGoVersion.Store(Target, v)
692+
}
693+
694+
// latestGoVersion returns the latest version of the Go language supported by
695+
// this toolchain.
696+
func latestGoVersion() string {
659697
tags := build.Default.ReleaseTags
660698
version := tags[len(tags)-1]
661699
if !strings.HasPrefix(version, "go") || !modfile.GoVersionRE.MatchString(version[2:]) {
662700
base.Fatalf("go: unrecognized default version %q", version)
663701
}
664-
if err := modFile.AddGoStmt(version[2:]); err != nil {
665-
base.Fatalf("go: internal error: %v", err)
666-
}
702+
return version[2:]
667703
}
668704

669705
var altConfigs = []string{
@@ -880,10 +916,6 @@ func WriteGoMod() {
880916
return
881917
}
882918

883-
if cfg.BuildMod != "readonly" {
884-
addGoStmt()
885-
}
886-
887919
if loaded != nil {
888920
reqs := MinReqs()
889921
min, err := reqs.Required(Target)
@@ -1010,7 +1042,12 @@ func keepSums(keepBuildListZips bool) map[module.Version]bool {
10101042
}
10111043
buildList, err := mvs.BuildList(Target, reqs)
10121044
if err != nil {
1013-
panic(fmt.Sprintf("unexpected error reloading build list: %v", err))
1045+
// This call to mvs.BuildList should not fail if we have already read the
1046+
// complete build list. However, the initial “build list” initialized by
1047+
// modFileToBuildList is not complete: it contains only the explicit
1048+
// dependencies of the main module. So this call can fair if this is the
1049+
// first time we have actually loaded the real build list.
1050+
base.Fatalf("go: %v", err)
10141051
}
10151052

10161053
actualMods := make(map[string]module.Version)

src/cmd/go/internal/modload/modfile.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,13 @@ func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileInd
257257
}
258258

259259
i.goVersionV = ""
260-
if modFile.Go != nil {
260+
if modFile.Go == nil {
261+
rawGoVersion.Store(Target, "")
262+
} else {
261263
// We're going to use the semver package to compare Go versions, so go ahead
262264
// and add the "v" prefix it expects once instead of every time.
263265
i.goVersionV = "v" + modFile.Go.Version
266+
rawGoVersion.Store(Target, modFile.Go.Version)
264267
}
265268

266269
i.require = make(map[module.Version]requireMeta, len(modFile.Require))

src/cmd/go/internal/work/gc.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,18 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg
6363

6464
pkgpath := pkgPath(a)
6565
gcargs := []string{"-p", pkgpath}
66-
if p.Module != nil && p.Module.GoVersion != "" && allowedVersion(p.Module.GoVersion) {
67-
gcargs = append(gcargs, "-lang=go"+p.Module.GoVersion)
66+
if p.Module != nil {
67+
v := p.Module.GoVersion
68+
if v == "" {
69+
// We started adding a 'go' directive to the go.mod file unconditionally
70+
// as of Go 1.12, so any module that still lacks such a directive must
71+
// either have been authored before then, or have a hand-edited go.mod
72+
// file that hasn't been updated by cmd/go since that edit.
73+
v = "1.11"
74+
}
75+
if allowedVersion(v) {
76+
gcargs = append(gcargs, "-lang=go"+v)
77+
}
6878
}
6979
if p.Standard {
7080
gcargs = append(gcargs, "-std")

src/cmd/go/testdata/script/embed.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,4 @@ import _ "m"
107107
-- go.mod --
108108
module m
109109

110+
go 1.16
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
cp go.mod go.mod.orig
2+
3+
# With -mod=readonly, we should not update the go version in use.
4+
#
5+
# We started adding the go version automatically in Go 1.12, so a module without
6+
# one encountered in the wild (such as in the module cache) should assume Go
7+
# 1.11 semantics.
8+
9+
# For Go 1.11 modules, 'all' should include dependencies of tests.
10+
# (They are pruned out as of Go 1.16.)
11+
12+
go list -mod=readonly all
13+
stdout '^example.com/dep$'
14+
stdout '^example.com/testdep$'
15+
cp stdout list-1.txt
16+
cmp go.mod go.mod.orig
17+
18+
# For Go 1.11 modules, automatic vendoring should not take effect.
19+
# (That behavior was added in Go 1.14.)
20+
21+
go list all # should default to -mod=readonly, not -mod=vendor.
22+
cmp stdout list-1.txt
23+
24+
# When we set -mod=mod, the go version should be updated immediately,
25+
# narrowing the "all" pattern reported by that command.
26+
27+
go list -mod=mod all
28+
! stdout '^example.com/testdep$'
29+
cp stdout list-2.txt
30+
cmpenv go.mod go.mod.want
31+
32+
go list -mod=mod all
33+
cmp stdout list-2.txt
34+
35+
# The updated version should have been written back to go.mod, so
36+
# automatic vendoring should come into effect (and fail).
37+
! go list all
38+
stderr '^go: inconsistent vendoring'
39+
40+
cp go.mod.orig go.mod
41+
42+
# In readonly or vendor mode (not -mod=mod), the inferred Go version is 1.11.
43+
# For Go 1.11 modules, Go 1.13 features should not be enabled.
44+
45+
! go build -mod=readonly .
46+
stderr '^# example\.com/m\n\.[/\\]m\.go:5:11: underscores in numeric literals requires go1\.13 or later \(-lang was set to go1\.11; check go\.mod\)$'
47+
cmp go.mod go.mod.orig
48+
49+
50+
-- go.mod --
51+
module example.com/m
52+
53+
require example.com/dep v0.1.0
54+
55+
replace (
56+
example.com/dep v0.1.0 => ./dep
57+
example.com/testdep v0.1.0 => ./testdep
58+
)
59+
-- go.mod.want --
60+
module example.com/m
61+
62+
go $goversion
63+
64+
require example.com/dep v0.1.0
65+
66+
replace (
67+
example.com/dep v0.1.0 => ./dep
68+
example.com/testdep v0.1.0 => ./testdep
69+
)
70+
-- vendor/example.com/dep/dep.go --
71+
package dep
72+
import _ "example.com/bananas"
73+
-- vendor/modules.txt --
74+
HAHAHA this is broken.
75+
76+
-- m.go --
77+
package m
78+
79+
import _ "example.com/dep"
80+
81+
const x = 1_000
82+
83+
-- dep/go.mod --
84+
module example.com/dep
85+
86+
require example.com/testdep v0.1.0
87+
-- dep/dep.go --
88+
package dep
89+
-- dep/dep_test.go --
90+
package dep_test
91+
92+
import _ "example.com/testdep"
93+
94+
-- testdep/go.mod --
95+
module example.com/testdep
96+
-- testdep/testdep.go --
97+
package testdep

0 commit comments

Comments
 (0)