Skip to content

Commit 3322f3e

Browse files
author
Jay Conrod
committed
cmd/go: forbid resolving import to modules when outside of a module
When in module mode outside of any module, 'go build' and most other commands will now report an error instead of resolving a package path to a module. Previously, most commands would attempt to find the latest version of a module providing the package. This could be very slow if many packages needed to be resolved this way. Since there is no go.mod file where module requirements can be saved, it's a repeatedly slow and confusing experience. After this change, 'go build' and other commands may still be used outside of a module on packages in std and source files (.go arguments) that only import packages in std. Listing any other package on the command line or importing a package outside std will cause an error. 'go get' is exempted from the new behavior, since it's expected that 'go get' resolves paths to modules at new versions. Updates #32027 Change-Id: Ia9d3a3b4ad738ca5423472e17818d62b96a2c959 Reviewed-on: https://go-review.googlesource.com/c/go/+/198778 Run-TryBot: Jay Conrod <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent aa09e75 commit 3322f3e

File tree

7 files changed

+130
-54
lines changed

7 files changed

+130
-54
lines changed

src/cmd/go/internal/modget/get.go

+4
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ func runGet(cmd *base.Command, args []string) {
284284
// what was requested.
285285
modload.DisallowWriteGoMod()
286286

287+
// Allow looking up modules for import paths outside of a module.
288+
// 'go get' is expected to do this, unlike other commands.
289+
modload.AllowMissingModuleImports()
290+
287291
// Parse command-line arguments and report errors. The command-line
288292
// arguments are of the form path@version or simply path, with implicit
289293
// @upgrade. path@none is "downgrade away".

src/cmd/go/internal/modload/import.go

+6
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ func Import(path string) (m module.Version, dir string, err error) {
185185
if cfg.BuildMod == "readonly" {
186186
return module.Version{}, "", fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
187187
}
188+
if modRoot == "" && !allowMissingModuleImports {
189+
return module.Version{}, "", &ImportMissingError{
190+
Path: path,
191+
QueryErr: errors.New("working directory is not part of a module"),
192+
}
193+
}
188194

189195
// Not on build list.
190196
// To avoid spurious remote fetches, next try the latest replacement for each module.

src/cmd/go/internal/modload/import_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ var importTests = []struct {
4444
func TestImport(t *testing.T) {
4545
testenv.MustHaveExternalNetwork(t)
4646
testenv.MustHaveExecPath(t, "git")
47+
defer func(old bool) {
48+
allowMissingModuleImports = old
49+
}(allowMissingModuleImports)
50+
AllowMissingModuleImports()
4751

4852
for _, tt := range importTests {
4953
t.Run(strings.ReplaceAll(tt.path, "/", "_"), func(t *testing.T) {

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

+22-19
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ var (
5757

5858
CmdModInit bool // running 'go mod init'
5959
CmdModModule string // module argument for 'go mod init'
60+
61+
allowMissingModuleImports bool
6062
)
6163

6264
// ModFile returns the parsed go.mod file.
@@ -199,28 +201,21 @@ func Init() {
199201
if modRoot == "" {
200202
// We're in module mode, but not inside a module.
201203
//
202-
// If the command is 'go get' or 'go list' and all of the args are in the
203-
// same existing module, we could use that module's download directory in
204-
// the module cache as the module root, applying any replacements and/or
205-
// exclusions specified by that module. However, that would leave us in a
206-
// strange state: we want 'go get' to be consistent with 'go list', and 'go
207-
// list' should be able to operate on multiple modules. Moreover, the 'get'
208-
// target might specify relative file paths (e.g. in the same repository) as
209-
// replacements, and we would not be able to apply those anyway: we would
210-
// need to either error out or ignore just those replacements, when a build
211-
// from an empty module could proceed without error.
204+
// Commands like 'go build', 'go run', 'go list' have no go.mod file to
205+
// read or write. They would need to find and download the latest versions
206+
// of a potentially large number of modules with no way to save version
207+
// information. We can succeed slowly (but not reproducibly), but that's
208+
// not usually a good experience.
212209
//
213-
// Instead, we'll operate as though we're in some ephemeral external module,
214-
// ignoring all replacements and exclusions uniformly.
215-
216-
// Normally we check sums using the go.sum file from the main module, but
217-
// without a main module we do not have an authoritative go.sum file.
210+
// Instead, we forbid resolving import paths to modules other than std and
211+
// cmd. Users may still build packages specified with .go files on the
212+
// command line, but they'll see an error if those files import anything
213+
// outside std.
218214
//
219-
// TODO(bcmills): In Go 1.13, check sums when outside the main module.
215+
// This can be overridden by calling AllowMissingModuleImports.
216+
// For example, 'go get' does this, since it is expected to resolve paths.
220217
//
221-
// One possible approach is to merge the go.sum files from all of the
222-
// modules we download: that doesn't protect us against bad top-level
223-
// modules, but it at least ensures consistency for transitive dependencies.
218+
// See golang.org/issue/32027.
224219
} else {
225220
modfetch.GoSumFile = filepath.Join(modRoot, "go.sum")
226221
search.SetModRoot(modRoot)
@@ -360,6 +355,14 @@ func InitMod() {
360355
}
361356
}
362357

358+
// AllowMissingModuleImports allows import paths to be resolved to modules
359+
// when there is no module root. Normally, this is forbidden because it's slow
360+
// and there's no way to make the result reproducible, but some commands
361+
// like 'go get' are expected to do this.
362+
func AllowMissingModuleImports() {
363+
allowMissingModuleImports = true
364+
}
365+
363366
// modFileToBuildList initializes buildList from the modFile.
364367
func modFileToBuildList() {
365368
Target = modFile.Module.Mod

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ go build -trimpath -o hello.exe hello.go
2121
# the current workspace or GOROOT.
2222
cd $WORK
2323
env GO111MODULE=on
24-
go build -trimpath -o fortune.exe rsc.io/fortune
25-
! grep -q $GOROOT_REGEXP fortune.exe
26-
! grep -q $WORK_REGEXP fortune.exe
24+
go get -trimpath rsc.io/fortune
25+
! grep -q $GOROOT_REGEXP $GOPATH/bin/fortune$GOEXE
26+
! grep -q $WORK_REGEXP $GOPATH/bin/fortune$GOEXE
2727

2828
# Two binaries built from identical packages in different directories
2929
# should be identical.

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

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ env GO111MODULE=on
33
! go list use.go
44
stderr 'example.com/missingpkg/deprecated: package provided by example.com/missingpkg at latest version v1.0.0 but not at required version v1.0.1-beta'
55

6+
-- go.mod --
7+
module m
8+
9+
go 1.14
10+
611
-- use.go --
712
package use
813

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

+86-32
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ go list -m
1717
stdout '^command-line-arguments$'
1818
# 'go list' in the working directory should fail even if there is a a 'package
1919
# main' present: without a main module, we do not know its package path.
20-
! go list ./foo
20+
! go list ./needmod
2121
stderr 'cannot find main module'
2222

2323
# 'go list all' lists the transitive import graph of the main module,
@@ -38,7 +38,7 @@ go list $GOROOT/src/fmt
3838
stdout '^fmt$'
3939

4040
# 'go list' should work with file arguments.
41-
go list ./foo/foo.go
41+
go list ./needmod/needmod.go
4242
stdout 'command-line-arguments'
4343

4444
# 'go list -m' with an explicit version should resolve that version.
@@ -104,7 +104,7 @@ stdout 'all modules verified'
104104
stderr 'cannot find main module'
105105
! go get -u
106106
stderr 'cannot find main module'
107-
! go get -u ./foo
107+
! go get -u ./needmod
108108
stderr 'cannot find main module'
109109

110110
# 'go get -u all' upgrades the transitive import graph of the main module,
@@ -126,35 +126,75 @@ exists $GOPATH/pkg/mod/example.com/[email protected]
126126

127127

128128
# 'go build' without arguments implicitly operates on the current directory, and should fail.
129-
cd foo
129+
cd needmod
130130
! go build
131131
stderr 'cannot find main module'
132132
cd ..
133133

134134
# 'go build' of a non-module directory should fail too.
135-
! go build ./foo
135+
! go build ./needmod
136136
stderr 'cannot find main module'
137137

138-
# However, 'go build' should succeed for standard-library packages.
138+
# 'go build' of source files should fail if they import anything outside std.
139+
! go build -n ./needmod/needmod.go
140+
stderr 'needmod[/\\]needmod.go:10:2: cannot find module providing package example.com/version: working directory is not part of a module'
141+
142+
# 'go build' of source files should succeed if they do not import anything outside std.
143+
go build -n -o ignore ./stdonly/stdonly.go
144+
145+
# 'go build' should succeed for standard-library packages.
139146
go build -n fmt
140147

141148

142-
# TODO(golang.org/issue/28992): 'go doc' should document the latest version.
143-
# For now it does not.
149+
# 'go doc' without arguments implicitly operates on the current directory, and should fail.
150+
# TODO(golang.org/issue/32027): currently, it succeeds.
151+
cd needmod
152+
go doc
153+
cd ..
154+
155+
# 'go doc' of a non-module directory should also succeed.
156+
go doc ./needmod
157+
158+
# 'go doc' should succeed for standard-library packages.
159+
go doc fmt
160+
161+
# 'go doc' should fail for a package path outside a module.
144162
! go doc example.com/version
145-
stderr 'no such package'
163+
stderr 'doc: cannot find module providing package example.com/version: working directory is not part of a module'
146164

147165
# 'go install' with a version should fail due to syntax.
148166
! go install example.com/[email protected]
149167
stderr 'can only use path@version syntax with'
150168

169+
# 'go install' should fail if a package argument must be resolved to a module.
170+
! go install example.com/printversion
171+
stderr 'cannot find module providing package example.com/printversion: working directory is not part of a module'
172+
173+
# 'go install' should fail if a source file imports a package that must be
174+
# resolved to a module.
175+
! go install ./needmod/needmod.go
176+
stderr 'needmod[/\\]needmod.go:10:2: cannot find module providing package example.com/version: working directory is not part of a module'
177+
178+
179+
# 'go run' with a verison should fail due to syntax.
180+
! go run example.com/[email protected]
181+
stderr 'can only use path@version syntax with'
182+
183+
# 'go run' should fail if a package argument must be resolved to a module.
184+
! go run example.com/printversion
185+
stderr 'cannot find module providing package example.com/printversion: working directory is not part of a module'
186+
187+
# 'go run' should fail if a source file imports a package that must be
188+
# resolved to a module.
189+
! go run ./needmod/needmod.go
190+
stderr 'needmod[/\\]needmod.go:10:2: cannot find module providing package example.com/version: working directory is not part of a module'
191+
151192

152193
# 'go fmt' should be able to format files outside of a module.
153-
go fmt foo/foo.go
194+
go fmt needmod/needmod.go
154195

155196

156197
# The remainder of the test checks dependencies by linking and running binaries.
157-
[short] stop
158198

159199
# 'go get' of a binary without a go.mod should install the requested version,
160200
# resolving outside dependencies to the latest available versions.
@@ -180,39 +220,31 @@ stdout 'path is example.com/printversion'
180220
stdout 'main is example.com/printversion v1.0.0'
181221
stdout 'using example.com/version v1.0.1'
182222

183-
# 'go install' without a version should install the latest version
184-
# using its minimal module requirements.
185-
go install example.com/printversion
186-
exec ../bin/printversion
187-
stdout 'path is example.com/printversion'
188-
stdout 'main is example.com/printversion v1.0.0'
189-
stdout 'using example.com/version v1.0.0'
190-
191-
# 'go run' should use 'main' as the effective module and import path.
192-
go run ./foo/foo.go
223+
# 'go run' should work with file arguments if they don't import anything
224+
# outside std.
225+
go run ./stdonly/stdonly.go
193226
stdout 'path is command-line-arguments$'
194227
stdout 'main is command-line-arguments \(devel\)'
195-
stdout 'using example.com/version v1.1.0'
196228

197229
# 'go generate' should work with file arguments.
198-
[exec:touch] go generate ./foo/foo.go
199-
[exec:touch] exists ./foo/gen.txt
230+
[exec:touch] go generate ./needmod/needmod.go
231+
[exec:touch] exists ./needmod/gen.txt
200232

201233
# 'go install' should work with file arguments.
202-
go install ./foo/foo.go
234+
go install ./stdonly/stdonly.go
203235

204236
# 'go test' should work with file arguments.
205-
go test -v ./foo/foo_test.go
206-
stdout 'foo was tested'
237+
go test -v ./stdonly/stdonly_test.go
238+
stdout 'stdonly was tested'
207239

208240
# 'go vet' should work with file arguments.
209-
go vet ./foo/foo.go
241+
go vet ./stdonly/stdonly.go
210242

211243

212244
-- README.txt --
213245
There is no go.mod file in the working directory.
214246

215-
-- foo/foo.go --
247+
-- needmod/needmod.go --
216248
//go:generate touch gen.txt
217249

218250
package main
@@ -237,14 +269,36 @@ func main() {
237269
}
238270
}
239271

240-
-- foo/foo_test.go --
272+
-- stdonly/stdonly.go --
273+
package main
274+
275+
import (
276+
"fmt"
277+
"os"
278+
"runtime/debug"
279+
)
280+
281+
func main() {
282+
info, ok := debug.ReadBuildInfo()
283+
if !ok {
284+
panic("missing build info")
285+
}
286+
fmt.Fprintf(os.Stdout, "path is %s\n", info.Path)
287+
fmt.Fprintf(os.Stdout, "main is %s %s\n", info.Main.Path, info.Main.Version)
288+
for _, m := range info.Deps {
289+
fmt.Fprintf(os.Stdout, "using %s %s\n", m.Path, m.Version)
290+
}
291+
}
292+
293+
-- stdonly/stdonly_test.go --
241294
package main
242295

243296
import (
244297
"fmt"
245298
"testing"
246299
)
247300

248-
func TestFoo(t *testing.T) {
249-
fmt.Println("foo was tested")
301+
func Test(t *testing.T) {
302+
fmt.Println("stdonly was tested")
250303
}
304+

0 commit comments

Comments
 (0)