Russ Cox would like Michael Matloob, Bryan C. Mills and Ian Cottrell to review this change.
go/build: invoke go command to find modules during Import, Context.Import
The introduction of modules has broken (intentionally) the rule
that the source code for a package x/y/z is in GOPATH/src/x/y/z
(or GOROOT/src/x/y/z). This breaks the code in go/build.Import,
which uses that rule to find the directory for a package.
In the long term, the fix is to move programs that load packages
off of go/build and onto golang.org/x/tools/go/packages, which
we hope will eventually become go/packages. That code invokes
the go command to learn what it needs to know about where
packages are.
In the short term, though, there are lots of programs that use go/build
and will not be able to find code in module dependencies.
To help those programs, go/build now runs the go command to
ask where a package's source code can be found, if it sees that
modules are in use. (If modules are not in use, it falls back to the
usual lookup code and does not invoke the go command, so that
existing uses are unaffected and not slowed down.)
Helps #24661.
Fixes #26504.
Change-Id: I0dac68854cf5011005c3b2272810245d81b7cc5a
---
M src/cmd/go/go_test.go
M src/cmd/go/internal/cfg/cfg.go
M src/cmd/go/script_test.go
A src/cmd/go/testdata/script/mod_gobuild_import.txt
M src/go/build/build.go
5 files changed, 205 insertions(+), 5 deletions(-)
diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go
index 983698c..7fadd0b 100644
--- a/src/cmd/go/go_test.go
+++ b/src/cmd/go/go_test.go
@@ -105,6 +105,7 @@
var testGo string
var testTmpDir string
+var testBin string
// The TestMain function creates a go command for testing purposes and
// deletes it after the tests have been run.
@@ -133,7 +134,11 @@
}
if canRun {
- testGo = filepath.Join(testTmpDir, "testgo"+exeSuffix)
+ testBin = filepath.Join(testTmpDir, "testbin")
+ if err := os.Mkdir(testBin, 0777); err != nil {
+ log.Fatal(err)
+ }
+ testGo = filepath.Join(testBin, "go"+exeSuffix)
args := []string{"build", "-tags", "testgo", "-o", testGo}
if race.Enabled {
args = append(args, "-race")
diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go
index 9dd90ee..c7746b6 100644
--- a/src/cmd/go/internal/cfg/cfg.go
+++ b/src/cmd/go/internal/cfg/cfg.go
@@ -20,7 +20,7 @@
var (
BuildA bool // -a flag
BuildBuildmode string // -buildmode flag
- BuildContext = build.Default
+ BuildContext = defaultContext()
BuildGetmode string // -getmode flag
BuildI bool // -i flag
BuildLinkshared bool // -linkshared flag
@@ -43,6 +43,12 @@
DebugActiongraph string // -debug-actiongraph flag (undocumented, unstable)
)
+func defaultContext() build.Context {
+ ctxt := build.Default
+ ctxt.JoinPath = filepath.Join // back door to say "do not use go command"
+ return ctxt
+}
+
func init() {
BuildToolchainCompiler = func() string { return "missing-compiler" }
BuildToolchainLinker = func() string { return "missing-linker" }
diff --git a/src/cmd/go/script_test.go b/src/cmd/go/script_test.go
index d8bcd07..263e26f 100644
--- a/src/cmd/go/script_test.go
+++ b/src/cmd/go/script_test.go
@@ -85,7 +85,7 @@
ts.cd = filepath.Join(ts.workdir, "gopath/src")
ts.env = []string{
"WORK=" + ts.workdir, // must be first for ts.abbrev
- "PATH=" + os.Getenv("PATH"),
+ "PATH=" + testBin + string(filepath.ListSeparator) + os.Getenv("PATH"),
homeEnvName() + "=/no-home",
"GOARCH=" + runtime.GOARCH,
"GOCACHE=" + testGOCACHE,
@@ -702,7 +702,7 @@
// exec runs the given command line (an actual subprocess, not simulated)
// in ts.cd with environment ts.env and then returns collected standard output and standard error.
func (ts *testScript) exec(command string, args ...string) (stdout, stderr string, err error) {
- cmd := exec.Command(testGo, args...)
+ cmd := exec.Command(command, args...)
cmd.Dir = ts.cd
cmd.Env = append(ts.env, "PWD="+ts.cd)
var stdoutBuf, stderrBuf strings.Builder
diff --git a/src/cmd/go/testdata/script/mod_gobuild_import.txt b/src/cmd/go/testdata/script/mod_gobuild_import.txt
new file mode 100644
index 0000000..9d7fd2a
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_gobuild_import.txt
@@ -0,0 +1,67 @@
+# go/build's Import should find modules by invoking the go command
+
+go build -o $WORK/testimport.exe ./testimport
+
+# GO111MODULE=off
+env GO111MODULE=off
+! exec $WORK/testimport.exe x/y/z/w
+
+# GO111MODULE=auto in GOPATH/src
+env GO111MODULE=
+! exec $WORK/testimport.exe x/y/z/w
+env GO111MODULE=auto
+! exec $WORK/testimport.exe x/y/z/w
+
+# GO111MODULE=auto outside GOPATH/src
+cd $GOPATH/other
+env GO111MODULE=
+exec $WORK/testimport.exe other/x/y/z/w
+stdout w2.go
+
+env GO111MODULE=auto
+exec $WORK/testimport.exe other/x/y/z/w
+stdout w2.go
+
+# GO111MODULE=on outside GOPATH/src
+env GO111MODULE=on
+exec $WORK/testimport.exe other/x/y/z/w
+stdout w2.go
+
+# GO111MODULE=on in GOPATH/src
+cd $GOPATH/src
+exec $WORK/testimport.exe x/y/z/w
+stdout w1.go
+
+-- go.mod --
+module x/y/z
+
+-- z.go --
+package z
+
+-- w/w1.go --
+package w
+
+-- testimport/x.go --
+package main
+
+import (
+ "fmt"
+ "go/build"
+ "log"
+ "os"
+ "strings"
+)
+
+func main() {
+ p, err := build.Import(os.Args[1], ".", 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s\n%s\n", p.Dir, strings.Join(p.GoFiles, " "))
+}
+
+-- $GOPATH/other/go.mod --
+module other/x/y
+
+-- $GOPATH/other/z/w/w2.go --
+package w
diff --git a/src/go/build/build.go b/src/go/build/build.go
index b19df28..da57f01 100644
--- a/src/go/build/build.go
+++ b/src/go/build/build.go
@@ -16,6 +16,7 @@
"io/ioutil"
"log"
"os"
+ "os/exec"
pathpkg "path"
"path/filepath"
"runtime"
@@ -277,6 +278,8 @@
return ""
}
+var defaultReleaseTags []string
+
func defaultContext() Context {
var c Context
@@ -297,6 +300,8 @@
c.ReleaseTags = append(c.ReleaseTags, "go1."+strconv.Itoa(i))
}
+ defaultReleaseTags = append([]string{}, c.ReleaseTags...) // own own private copy
+
env := os.Getenv("CGO_ENABLED")
if env == "" {
env = defaultCGO_ENABLED
@@ -583,13 +588,17 @@
return p, fmt.Errorf("import %q: cannot import absolute path", path)
}
+ gopath := ctxt.gopath()
+ if ctxt.importGo(p, path, srcDir, mode, gopath) {
+ goto Found
+ }
+
// tried records the location of unsuccessful package lookups
var tried struct {
vendor []string
goroot string
gopath []string
}
- gopath := ctxt.gopath()
// Vendor directories get first chance to satisfy import.
if mode&IgnoreVendor == 0 && srcDir != "" {
@@ -930,6 +939,119 @@
return p, pkgerr
}
+// importGo checks whether it can use the go command to find the directory for path.
+// If using the go command is appopriate, importGo tries it, and if that succeeds,
+// then importGo returns true. Otherwise it returns false.
+// Using the go command lets build.Import and build.Context.Import find code
+// in Go modules. In the long term we want tools to use go/packages (currently golang.org/x/tools/go/packages),
+// which will also use the go command.
+// Invoking the go command here is not very efficient in that it computes information
+// about the requested package and all dependencies and then only reports about the requested package.
+// Then we reinvoke it for every dependency. But this is still better than not working at all.
+// See golang.org/issue/26504.
+func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode, gopath []string) bool {
+ const debugImportGo = false
+
+ // To invoke the go command, we must know the source directory,
+ // we must not being doing special things like AllowBinary or IgnoreVendor,
+ // and all the file system callbacks must be nil (we're meant to use the local file system).
+ if srcDir == "" || mode&AllowBinary != 0 || mode&IgnoreVendor != 0 ||
+ ctxt.JoinPath != nil || ctxt.SplitPathList != nil || ctxt.IsAbsPath != nil || ctxt.IsDir != nil || ctxt.HasSubdir != nil || ctxt.ReadDir != nil || ctxt.OpenFile != nil || !equal(ctxt.ReleaseTags, defaultReleaseTags) {
+ return false
+ }
+
+ // If modules are not enabled, then the in-process code works fine and we should keep using it.
+ switch os.Getenv("GO111MODULE") {
+ case "off":
+ return false
+ case "on":
+ // ok
+ default: // "", "auto", anything else
+ // Automatic mode: no module use in $GOPATH/src.
+ for _, root := range gopath {
+ sub, ok := ctxt.hasSubdir(root, srcDir)
+ if ok && strings.HasPrefix(sub, "src/") {
+ return false
+ }
+ }
+ }
+
+ // For efficiency, if path is a standard library package, let the usual lookup code handle it.
+ if ctxt.GOROOT != "" {
+ dir := ctxt.joinPath(ctxt.GOROOT, "src", path)
+ if ctxt.isDir(dir) {
+ return false
+ }
+ }
+
+ // Look to see if there is a go.mod.
+ abs, err := filepath.Abs(srcDir)
+ if err != nil {
+ return false
+ }
+ for {
+ info, err := os.Stat(filepath.Join(abs, "go.mod"))
+ if err == nil && !info.IsDir() {
+ break
+ }
+ d := filepath.Dir(abs)
+ if len(d) >= len(abs) {
+ return false // reached top of file system, no go.mod
+ }
+ }
+
+ cmd := exec.Command("go", "list", "-compiler="+ctxt.Compiler, "-tags="+strings.Join(ctxt.BuildTags, ","), "-installsuffix="+ctxt.InstallSuffix, "-f={{.Dir}}\n{{.ImportPath}}\n{{.Root}}\n{{.Goroot}}\n", path)
+ cmd.Dir = srcDir
+ var stdout, stderr strings.Builder
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+
+ cgo := "0"
+ if ctxt.CgoEnabled {
+ cgo = "1"
+ }
+ cmd.Env = append(os.Environ(),
+ "GOOS="+ctxt.GOOS,
+ "GOARCH="+ctxt.GOARCH,
+ "GOROOT="+ctxt.GOROOT,
+ "GOPATH="+ctxt.GOPATH,
+ "CGO_ENABLED="+cgo,
+ )
+
+ if err := cmd.Run(); err != nil {
+ if debugImportGo {
+ fmt.Fprintf(os.Stderr, "go/build: importGo %s: %v\n%s\n", path, err, stderr.String())
+ }
+ return false
+ }
+
+ f := strings.Split(stdout.String(), "\n")
+ if len(f) != 5 || f[4] != "" {
+ if debugImportGo {
+ fmt.Fprintf(os.Stderr, "go/build: importGo %s: unexpected output:\n%s\n", path, stdout.String())
+ }
+ return false
+ }
+
+ p.Dir = f[0]
+ p.ImportPath = f[1]
+ p.Root = f[2]
+ p.Goroot = f[3] == "true"
+ return true
+}
+
+func equal(x, y []string) bool {
+ if len(x) != len(y) {
+ return false
+ }
+ for i, xi := range x {
+ if xi != y[i] {
+ return false
+ }
+ }
+ return true
+}
+
// hasGoFiles reports whether dir contains any files with names ending in .go.
// For a vendor check we must exclude directories that contain no .go files.
// Otherwise it is not possible to vendor just a/b/c and still import the
To view, visit change 125296. To unsubscribe, or for help writing mail filters, visit settings.
TryBots beginning. Status page: https://farmer.golang.org/try?commit=d5bf8386
Build is still in progress...
This change failed on linux-amd64:
See https://storage.googleapis.com/go-build-log/d5bf8386/linux-amd64_59f82805.log
Consult https://build.golang.org/ to see whether it's a new failure. Other builds still in progress; subsequent failure notices suppressed until final report.
8 of 19 TryBots failed:
Failed on linux-amd64: https://storage.googleapis.com/go-build-log/d5bf8386/linux-amd64_59f82805.log
Failed on linux-386: https://storage.googleapis.com/go-build-log/d5bf8386/linux-386_e5cfdba0.log
Failed on freebsd-amd64-11_1: https://storage.googleapis.com/go-build-log/d5bf8386/freebsd-amd64-11_1_f785a71c.log
Failed on windows-386-2008: https://storage.googleapis.com/go-build-log/d5bf8386/windows-386-2008_fc4d9e21.log
Failed on linux-amd64-race: https://storage.googleapis.com/go-build-log/d5bf8386/linux-amd64-race_80fca1c1.log
Failed on openbsd-amd64-62: https://storage.googleapis.com/go-build-log/d5bf8386/openbsd-amd64-62_ba49b4c6.log
Failed on windows-amd64-2016: https://storage.googleapis.com/go-build-log/d5bf8386/windows-amd64-2016_0de16291.log
Failed on misc-vet-vetall: https://storage.googleapis.com/go-build-log/d5bf8386/misc-vet-vetall_aaa9bf08.log
Consult https://build.golang.org/ to see whether they are new failures.
Patch set 1:TryBot-Result -1
Uploaded patch set 2: Patch Set 1 was rebased.
Build is still in progress...
This change failed on linux-amd64:
See https://storage.googleapis.com/go-build-log/949c4211/linux-amd64_60dfd211.log
Consult https://build.golang.org/ to see whether it's a new failure. Other builds still in progress; subsequent failure notices suppressed until final report.
To view, visit change 125296. To unsubscribe, or for help writing mail filters, visit settings.
Russ Cox uploaded patch set #3 to this change.
5 files changed, 207 insertions(+), 5 deletions(-)
To view, visit change 125296. To unsubscribe, or for help writing mail filters, visit settings.
Uploaded patch set 3.
TryBots are happy.
Patch set 3:TryBot-Result +1
Patch set 3:Code-Review +2
4 comments:
Patch Set #3, Line 141: testGo = filepath.Join(testBin, "go"+exeSuffix)
Now that the binary is no longer named “testgo”, should we remove that special case¹ from modload.Init?
(Also, be sure to re-run all of the non-short go command tests, if any, that may have been depending on that special case.)
File src/cmd/go/testdata/script/mod_gobuild_import.txt:
Patch Set #3, Line 57: os.Args[1], "."
A test with a less-trivial srcDir might be helpful, in case there are bugs in mapping the output from the go tool.
Patch Set #3, Line 592: gopath
A comment would be helpful here: given that we're invoking a method on ctxt, why do we need to pass it its own gopath?
Patch Set #3, Line 998: if len(d) >= len(abs) {
Ironically, this is very nearly the loop I suggested in this comment thread:
https://go-review.googlesource.com/c/go/+/109340/3/src/cmd/go/internal/load/pkg.go#741
To view, visit change 125296. To unsubscribe, or for help writing mail filters, visit settings.
Uploaded patch set 4: Patch Set 3 was rebased.
TryBots are happy.
Patch set 4:TryBot-Result +1
Patch set 4:Code-Review +2
2 comments:
File src/cmd/go/testdata/script/mod_gobuild_import.txt:
# GO111MODULE=on in GOPATH/src
cd $GOPATH/src
exec $WORK/testimport.exe x/y/z/w
stdout w1.go
shouldn't there be a case for (GO111MODULE=auto, cwd=z, exec $WORK/testimport.exe x/y/z/w)?
Based on the implementation, if this works, that case should too, but it seems strange that it's missing given that it's likely the case users care most about...
it's a little hard to parse this comment because of the repetition of the word 'own'
To view, visit change 125296. To unsubscribe, or for help writing mail filters, visit settings.
github.com/kisielk/gotool is a fairly commonly used package amongst CLI tools (GopherJS et al https://godoc.org/github.com/kisielk/gotool?importers). That package will need a Go 1.11 implementation to work nicely with modules.
It would seem to make sense for that Go 1.11 implementation to call *go/build.Context.Import in order to do this.
But with multiple patterns we would end up os/exec-ing multiple times.
Would it therefore make sense to introduce a "plural" version of *Context.Import for Go 1.11?
func (ctxt *Context) ImportPkgs(paths []string, srcDir string, mode ImportMode) ([]*Package, error)
Alternatively, and maybe this is the better thing to do in any case, we introduce some level of caching in *Context (caching that is a function of *Context and srcDir), with some sort of reset method. But this feels like a bigger (too big a) change for Go 1.11?
1 comment:
Patch Set #4, Line 1026: return false
Don't we need to return an error here (and below)? Because if we are in module mode (as opposed to GOPATH) we need an error that is specific to go list. If we return false here we get an error message relating to "cannot find ... in GOROOT, GOPATH". Which is not very helpful and misleading given we are in module mode.
To view, visit change 125296. To unsubscribe, or for help writing mail filters, visit settings.
Patch Set 4:
github.com/kisielk/gotool is a fairly commonly used package amongst CLI tools (GopherJS et al https://godoc.org/github.com/kisielk/gotool?importers). That package will need a Go 1.11 implementation to work nicely with modules.
It would seem to make sense for that Go 1.11 implementation to call *go/build.Context.Import in order to do this.
But with multiple patterns we would end up os/exec-ing multiple times.
Would it therefore make sense to introduce a "plural" version of *Context.Import for Go 1.11?
func (ctxt *Context) ImportPkgs(paths []string, srcDir string, mode ImportMode) ([]*Package, error)
Alternatively, and maybe this is the better thing to do in any case, we introduce some level of caching in *Context (caching that is a function of *Context and srcDir), with some sort of reset method. But this feels like a bigger (too big a) change for Go 1.11?
The Go 1.11 implementation of github.com/kisielk/gotool should probably be a very small wrapper around golang.org/x/tools/go/packages.
Eventually (probably Go 1.12) golang.org/x/tools/go/packages will become go/packages and then we'll deprecate use of go/build (but leave it with this implementation).
Uploaded patch set 5.
(7 comments)
7 comments:
Patch Set #3, Line 141: testGo = filepath.Join(testBin, "go"+exeSuffix)
Now that the binary is no longer named “testgo”, should we remove that special case¹ from modload. […]
Done
File src/cmd/go/testdata/script/mod_gobuild_import.txt:
A test with a less-trivial srcDir might be helpful, in case there are bugs in mapping the output fro […]
Done, although everything the go command returns is an absolute path so it shouldn't matter.
File src/cmd/go/testdata/script/mod_gobuild_import.txt:
env GO111MODULE=on
exec $WORK/testimport.exe other/x/y/z/w .
stdout w2.go
# GO111MODUL
shouldn't there be a case for (GO111MODULE=auto, cwd=z, exec $WORK/testimport.exe x/y/z/w)? […]
Done although I don't know why users would care so much (it correctly fails).
Patch Set #3, Line 592: mode,
A comment would be helpful here: given that we're invoking a method on ctxt, why do we need to pass […]
Done
Patch Set #3, Line 998: if err == nil && !info.IsDir() {
Ironically, this is very nearly the loop I suggested in this comment thread: […]
Almost, and maybe I was channeling you, but note that the loop in that CL must stop at a particular directory, so it's easy and bad to be off-by-one. Here I'm really just concerned that the loop terminates (doesn't run forever). I don't actually care whether it looks at /go.mod or not (looks like it does), so the off-by-one is not as important. :-)
it's a little hard to parse this comment because of the repetition of the word 'own'
It's supposed to say "our own".
Patch Set #4, Line 1026: if err := cmd.Run(); err != nil {
Don't we need to return an error here (and below)? Because if we are in module mode (as opposed to G […]
Good point, thanks. Done.
To view, visit change 125296. To unsubscribe, or for help writing mail filters, visit settings.
Russ Cox uploaded patch set #5 to this change.
go/build: invoke go command to find modules during Import, Context.Import
The introduction of modules has broken (intentionally) the rule
that the source code for a package x/y/z is in GOPATH/src/x/y/z
(or GOROOT/src/x/y/z). This breaks the code in go/build.Import,
which uses that rule to find the directory for a package.
In the long term, the fix is to move programs that load packages
off of go/build and onto golang.org/x/tools/go/packages, which
we hope will eventually become go/packages. That code invokes
the go command to learn what it needs to know about where
packages are.
In the short term, though, there are lots of programs that use go/build
and will not be able to find code in module dependencies.
To help those programs, go/build now runs the go command to
ask where a package's source code can be found, if it sees that
modules are in use. (If modules are not in use, it falls back to the
usual lookup code and does not invoke the go command, so that
existing uses are unaffected and not slowed down.)
Helps #24661.
Fixes #26504.
Change-Id: I0dac68854cf5011005c3b2272810245d81b7cc5a
---
M src/cmd/go/go_test.go
M src/cmd/go/internal/cfg/cfg.go
M src/cmd/go/internal/modload/init.go
M src/cmd/go/script_test.go
A src/cmd/go/testdata/script/mod_gobuild_import.txt
M src/go/build/build.go
6 files changed, 211 insertions(+), 11 deletions(-)
To view, visit change 125296. To unsubscribe, or for help writing mail filters, visit settings.
Russ Cox merged this change.
go/build: invoke go command to find modules during Import, Context.Import
The introduction of modules has broken (intentionally) the rule
that the source code for a package x/y/z is in GOPATH/src/x/y/z
(or GOROOT/src/x/y/z). This breaks the code in go/build.Import,
which uses that rule to find the directory for a package.
In the long term, the fix is to move programs that load packages
off of go/build and onto golang.org/x/tools/go/packages, which
we hope will eventually become go/packages. That code invokes
the go command to learn what it needs to know about where
packages are.
In the short term, though, there are lots of programs that use go/build
and will not be able to find code in module dependencies.
To help those programs, go/build now runs the go command to
ask where a package's source code can be found, if it sees that
modules are in use. (If modules are not in use, it falls back to the
usual lookup code and does not invoke the go command, so that
existing uses are unaffected and not slowed down.)
Helps #24661.
Fixes #26504.
Change-Id: I0dac68854cf5011005c3b2272810245d81b7cc5a
Reviewed-on: https://go-review.googlesource.com/125296
Reviewed-by: Michael Matloob <mat...@golang.org>
Reviewed-by: Bryan C. Mills <bcm...@google.com>
---
M src/cmd/go/go_test.go
M src/cmd/go/internal/cfg/cfg.go
M src/cmd/go/internal/modload/init.go
M src/cmd/go/script_test.go
A src/cmd/go/testdata/script/mod_gobuild_import.txt
M src/go/build/build.go
6 files changed, 211 insertions(+), 11 deletions(-)
diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go
index b894284..adf17b8 100644
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 759b5a7..7838af2 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -94,12 +94,6 @@
}
}
- // If this is testgo - the test binary during cmd/go tests -
- // then do not let it look for a go.mod unless GO111MODULE has an explicit setting or this is 'go mod -init'.
- if base := filepath.Base(os.Args[0]); (base == "testgo" || base == "testgo.exe") && env == "" && !CmdModInit {
- return
- }
-
// Disable any prompting for passwords by Git.
// Only has an effect for 2.3.0 or later, but avoiding
// the prompt in earlier versions is just too hard.
diff --git a/src/cmd/go/script_test.go b/src/cmd/go/script_test.go
index d8bcd07..263e26f 100644
--- a/src/cmd/go/script_test.go
+++ b/src/cmd/go/script_test.go
@@ -85,7 +85,7 @@
ts.cd = filepath.Join(ts.workdir, "gopath/src")
ts.env = []string{
"WORK=" + ts.workdir, // must be first for ts.abbrev
- "PATH=" + os.Getenv("PATH"),
+ "PATH=" + testBin + string(filepath.ListSeparator) + os.Getenv("PATH"),
homeEnvName() + "=/no-home",
"GOARCH=" + runtime.GOARCH,
"GOCACHE=" + testGOCACHE,
@@ -702,7 +702,7 @@
// exec runs the given command line (an actual subprocess, not simulated)
// in ts.cd with environment ts.env and then returns collected standard output and standard error.
func (ts *testScript) exec(command string, args ...string) (stdout, stderr string, err error) {
- cmd := exec.Command(testGo, args...)
+ cmd := exec.Command(command, args...)
cmd.Dir = ts.cd
cmd.Env = append(ts.env, "PWD="+ts.cd)
var stdoutBuf, stderrBuf strings.Builder
diff --git a/src/cmd/go/testdata/script/mod_gobuild_import.txt b/src/cmd/go/testdata/script/mod_gobuild_import.txt
new file mode 100644
index 0000000..932b8b6
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_gobuild_import.txt
@@ -0,0 +1,74 @@
+# go/build's Import should find modules by invoking the go command
+
+go build -o $WORK/testimport.exe ./testimport
+
+# GO111MODULE=off
+env GO111MODULE=off
+! exec $WORK/testimport.exe x/y/z/w .
+
+# GO111MODULE=auto in GOPATH/src
+env GO111MODULE=
+! exec $WORK/testimport.exe x/y/z/w .
+env GO111MODULE=auto
+! exec $WORK/testimport.exe x/y/z/w .
+
+# GO111MODULE=auto outside GOPATH/src
+cd $GOPATH/other
+env GO111MODULE=
+exec $WORK/testimport.exe other/x/y/z/w .
+stdout w2.go
+
+! exec $WORK/testimport.exe x/y/z/w .
+stderr 'cannot find module providing package x/y/z/w'
+
+cd z
+env GO111MODULE=auto
+exec $WORK/testimport.exe other/x/y/z/w .
+stdout w2.go
+
+# GO111MODULE=on outside GOPATH/src
+env GO111MODULE=on
+exec $WORK/testimport.exe other/x/y/z/w .
+stdout w2.go
+
+# GO111MODULE=on in GOPATH/src
+cd $GOPATH/src
+exec $WORK/testimport.exe x/y/z/w .
+stdout w1.go
+cd w
+exec $WORK/testimport.exe x/y/z/w ..
+stdout w1.go
+
+-- go.mod --
+module x/y/z
+
+-- z.go --
+package z
+
+-- w/w1.go --
+package w
+
+-- testimport/x.go --
+package main
+
+import (
+ "fmt"
+ "go/build"
+ "log"
+ "os"
+ "strings"
+)
+
+func main() {
+ p, err := build.Import(os.Args[1], os.Args[2], 0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s\n%s\n", p.Dir, strings.Join(p.GoFiles, " "))
+}
+
+-- $GOPATH/other/go.mod --
+module other/x/y
+
+-- $GOPATH/other/z/w/w2.go --
+package w
diff --git a/src/go/build/build.go b/src/go/build/build.go
index b19df28..0ed5b82 100644
--- a/src/go/build/build.go
+++ b/src/go/build/build.go
@@ -16,6 +16,7 @@
"io/ioutil"
"log"
"os"
+ "os/exec"
pathpkg "path"
"path/filepath"
"runtime"
@@ -277,6 +278,8 @@
return ""
}
+var defaultReleaseTags []string
+
func defaultContext() Context {
var c Context
@@ -297,6 +300,8 @@
c.ReleaseTags = append(c.ReleaseTags, "go1."+strconv.Itoa(i))
}
+ defaultReleaseTags = append([]string{}, c.ReleaseTags...) // our own private copy
+
env := os.Getenv("CGO_ENABLED")
if env == "" {
env = defaultCGO_ENABLED
@@ -583,13 +588,19 @@
return p, fmt.Errorf("import %q: cannot import absolute path", path)
}
+ gopath := ctxt.gopath() // needed by both importGo and below; avoid computing twice
+ if err := ctxt.importGo(p, path, srcDir, mode, gopath); err == nil {
+ goto Found
+ } else if err != errNoModules {
+ return p, err
+ }
+
// tried records the location of unsuccessful package lookups
var tried struct {
vendor []string
goroot string
gopath []string
}
- gopath := ctxt.gopath()
// Vendor directories get first chance to satisfy import.
if mode&IgnoreVendor == 0 && srcDir != "" {
@@ -930,6 +941,116 @@
return p, pkgerr
}
+var errNoModules = errors.New("not using modules")
+
+// importGo checks whether it can use the go command to find the directory for path.
+// If using the go command is not appopriate, importGo returns errNoModules.
+// Otherwise, importGo tries using the go command and reports whether that succeeded.
+// Using the go command lets build.Import and build.Context.Import find code
+// in Go modules. In the long term we want tools to use go/packages (currently golang.org/x/tools/go/packages),
+// which will also use the go command.
+// Invoking the go command here is not very efficient in that it computes information
+// about the requested package and all dependencies and then only reports about the requested package.
+// Then we reinvoke it for every dependency. But this is still better than not working at all.
+// See golang.org/issue/26504.
+func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode, gopath []string) error {
+ const debugImportGo = false
+
+ // To invoke the go command, we must know the source directory,
+ // we must not being doing special things like AllowBinary or IgnoreVendor,
+ // and all the file system callbacks must be nil (we're meant to use the local file system).
+ if srcDir == "" || mode&AllowBinary != 0 || mode&IgnoreVendor != 0 ||
+ ctxt.JoinPath != nil || ctxt.SplitPathList != nil || ctxt.IsAbsPath != nil || ctxt.IsDir != nil || ctxt.HasSubdir != nil || ctxt.ReadDir != nil || ctxt.OpenFile != nil || !equal(ctxt.ReleaseTags, defaultReleaseTags) {
+ return errNoModules
+ }
+
+ // If modules are not enabled, then the in-process code works fine and we should keep using it.
+ switch os.Getenv("GO111MODULE") {
+ case "off":
+ return errNoModules
+ case "on":
+ // ok
+ default: // "", "auto", anything else
+ // Automatic mode: no module use in $GOPATH/src.
+ for _, root := range gopath {
+ sub, ok := ctxt.hasSubdir(root, srcDir)
+ if ok && strings.HasPrefix(sub, "src/") {
+ return errNoModules
+ }
+ }
+ }
+
+ // For efficiency, if path is a standard library package, let the usual lookup code handle it.
+ if ctxt.GOROOT != "" {
+ dir := ctxt.joinPath(ctxt.GOROOT, "src", path)
+ if ctxt.isDir(dir) {
+ return errNoModules
+ }
+ }
+
+ // Look to see if there is a go.mod.
+ abs, err := filepath.Abs(srcDir)
+ if err != nil {
+ return errNoModules
+ }
+ for {
+ info, err := os.Stat(filepath.Join(abs, "go.mod"))
+ if err == nil && !info.IsDir() {
+ break
+ }
+ d := filepath.Dir(abs)
+ if len(d) >= len(abs) {
+ return errNoModules // reached top of file system, no go.mod
+ }
+ abs = d
+ }
+
+ cmd := exec.Command("go", "list", "-compiler="+ctxt.Compiler, "-tags="+strings.Join(ctxt.BuildTags, ","), "-installsuffix="+ctxt.InstallSuffix, "-f={{.Dir}}\n{{.ImportPath}}\n{{.Root}}\n{{.Goroot}}\n", path)
+ cmd.Dir = srcDir
+ var stdout, stderr strings.Builder
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+
+ cgo := "0"
+ if ctxt.CgoEnabled {
+ cgo = "1"
+ }
+ cmd.Env = append(os.Environ(),
+ "GOOS="+ctxt.GOOS,
+ "GOARCH="+ctxt.GOARCH,
+ "GOROOT="+ctxt.GOROOT,
+ "GOPATH="+ctxt.GOPATH,
+ "CGO_ENABLED="+cgo,
+ )
+
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("go/build: importGo %s: %v\n%s\n", path, err, stderr.String())
+ }
+
+ f := strings.Split(stdout.String(), "\n")
+ if len(f) != 5 || f[4] != "" {
+ return fmt.Errorf("go/build: importGo %s: unexpected output:\n%s\n", path, stdout.String())
+ }
+
+ p.Dir = f[0]
+ p.ImportPath = f[1]
+ p.Root = f[2]
+ p.Goroot = f[3] == "true"
+ return nil
+}
+
+func equal(x, y []string) bool {
+ if len(x) != len(y) {
+ return false
+ }
+ for i, xi := range x {
+ if xi != y[i] {
+ return false
+ }
+ }
+ return true
+}
+
// hasGoFiles reports whether dir contains any files with names ending in .go.
// For a vendor check we must exclude directories that contain no .go files.
// Otherwise it is not possible to vendor just a/b/c and still import the
To view, visit change 125296. To unsubscribe, or for help writing mail filters, visit settings.