Commit 1d4369fa authored by Bryan C. Mills's avatar Bryan C. Mills

go/build: use the main module's root when locating module sources

Previously, we were using srcDir, which would apply the wrong module
dependencies (including the wrong 'replace' and 'exclude' directives)
when locating an import path within a module.

Fixes #34860

Change-Id: Ie59dcc2075a7b51ba40f7cd2f62dae27bf58c9b0
Reviewed-on: https://go-review.googlesource.com/c/go/+/203820Reviewed-by: default avatarJay Conrod <jayconrod@google.com>
parent ca70ada2
...@@ -35,6 +35,15 @@ type Context struct { ...@@ -35,6 +35,15 @@ type Context struct {
GOOS string // target operating system GOOS string // target operating system
GOROOT string // Go root GOROOT string // Go root
GOPATH string // Go path GOPATH string // Go path
// WorkingDir is the caller's working directory, or the empty string to use
// the current directory of the running process. In module mode, this is used
// to locate the main module.
//
// If WorkingDir is non-empty, directories passed to Import and ImportDir must
// be absolute.
WorkingDir string
CgoEnabled bool // whether cgo files are included CgoEnabled bool // whether cgo files are included
UseAllFiles bool // use files regardless of +build lines, file names UseAllFiles bool // use files regardless of +build lines, file names
Compiler string // compiler to assume when computing target paths Compiler string // compiler to assume when computing target paths
...@@ -994,21 +1003,14 @@ var errNoModules = errors.New("not using modules") ...@@ -994,21 +1003,14 @@ var errNoModules = errors.New("not using modules")
func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) error { func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) error {
const debugImportGo = false const debugImportGo = false
// To invoke the go command, we must know the source directory, // To invoke the go command,
// we must not being doing special things like AllowBinary or IgnoreVendor, // 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). // 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 || if 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) { 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 return errNoModules
} }
// Find the absolute source directory. hasSubdir does not handle
// relative paths (and can't because the callbacks don't support this).
absSrcDir, err := filepath.Abs(srcDir)
if err != nil {
return errNoModules
}
// Predict whether module aware mode is enabled by checking the value of // Predict whether module aware mode is enabled by checking the value of
// GO111MODULE and looking for a go.mod file in the source directory or // GO111MODULE and looking for a go.mod file in the source directory or
// one of its parents. Running 'go env GOMOD' in the source directory would // one of its parents. Running 'go env GOMOD' in the source directory would
...@@ -1021,12 +1023,29 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) ...@@ -1021,12 +1023,29 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode)
// Maybe use modules. // Maybe use modules.
} }
if srcDir != "" {
var absSrcDir string
if filepath.IsAbs(srcDir) {
absSrcDir = srcDir
} else if ctxt.WorkingDir != "" {
return fmt.Errorf("go/build: WorkingDir is non-empty, so relative srcDir is not allowed: %v", srcDir)
} else {
// Find the absolute source directory. hasSubdir does not handle
// relative paths (and can't because the callbacks don't support this).
var err error
absSrcDir, err = filepath.Abs(srcDir)
if err != nil {
return errNoModules
}
}
// If the source directory is in GOROOT, then the in-process code works fine // If the source directory is in GOROOT, then the in-process code works fine
// and we should keep using it. Moreover, the 'go list' approach below doesn't // and we should keep using it. Moreover, the 'go list' approach below doesn't
// take standard-library vendoring into account and will fail. // take standard-library vendoring into account and will fail.
if _, ok := ctxt.hasSubdir(filepath.Join(ctxt.GOROOT, "src"), absSrcDir); ok { if _, ok := ctxt.hasSubdir(filepath.Join(ctxt.GOROOT, "src"), absSrcDir); ok {
return errNoModules return errNoModules
} }
}
// For efficiency, if path is a standard library package, let the usual lookup code handle it. // For efficiency, if path is a standard library package, let the usual lookup code handle it.
if ctxt.GOROOT != "" { if ctxt.GOROOT != "" {
...@@ -1039,7 +1058,24 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) ...@@ -1039,7 +1058,24 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode)
// Unless GO111MODULE=on, look to see if there is a go.mod. // Unless GO111MODULE=on, look to see if there is a go.mod.
// Since go1.13, it doesn't matter if we're inside GOPATH. // Since go1.13, it doesn't matter if we're inside GOPATH.
if go111Module != "on" { if go111Module != "on" {
parent := absSrcDir var (
parent string
err error
)
if ctxt.WorkingDir == "" {
parent, err = os.Getwd()
if err != nil {
// A nonexistent working directory can't be in a module.
return errNoModules
}
} else {
parent, err = filepath.Abs(ctxt.WorkingDir)
if err != nil {
// If the caller passed a bogus WorkingDir explicitly, that's materially
// different from not having modules enabled.
return err
}
}
for { for {
info, err := os.Stat(filepath.Join(parent, "go.mod")) info, err := os.Stat(filepath.Join(parent, "go.mod"))
if err == nil && !info.IsDir() { if err == nil && !info.IsDir() {
...@@ -1055,10 +1091,9 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) ...@@ -1055,10 +1091,9 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode)
cmd := exec.Command("go", "list", "-e", "-compiler="+ctxt.Compiler, "-tags="+strings.Join(ctxt.BuildTags, ","), "-installsuffix="+ctxt.InstallSuffix, "-f={{.Dir}}\n{{.ImportPath}}\n{{.Root}}\n{{.Goroot}}\n{{if .Error}}{{.Error}}{{end}}\n", "--", path) cmd := exec.Command("go", "list", "-e", "-compiler="+ctxt.Compiler, "-tags="+strings.Join(ctxt.BuildTags, ","), "-installsuffix="+ctxt.InstallSuffix, "-f={{.Dir}}\n{{.ImportPath}}\n{{.Root}}\n{{.Goroot}}\n{{if .Error}}{{.Error}}{{end}}\n", "--", path)
// TODO(bcmills): This is wrong if srcDir is in a vendor directory, or if if ctxt.WorkingDir != "" {
// srcDir is in some module dependency of the main module. The main module cmd.Dir = ctxt.WorkingDir
// chooses what the import paths mean: individual packages don't. }
cmd.Dir = srcDir
var stdout, stderr strings.Builder var stdout, stderr strings.Builder
cmd.Stdout = &stdout cmd.Stdout = &stdout
...@@ -1077,7 +1112,7 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) ...@@ -1077,7 +1112,7 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode)
) )
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return fmt.Errorf("go/build: importGo %s: %v\n%s\n", path, err, stderr.String()) return fmt.Errorf("go/build: go list %s: %v\n%s\n", path, err, stderr.String())
} }
f := strings.SplitN(stdout.String(), "\n", 5) f := strings.SplitN(stdout.String(), "\n", 5)
......
...@@ -320,7 +320,15 @@ func TestShellSafety(t *testing.T) { ...@@ -320,7 +320,15 @@ func TestShellSafety(t *testing.T) {
func TestImportDirNotExist(t *testing.T) { func TestImportDirNotExist(t *testing.T) {
testenv.MustHaveGoBuild(t) // really must just have source testenv.MustHaveGoBuild(t) // really must just have source
ctxt := Default ctxt := Default
ctxt.GOPATH = ""
emptyDir, err := ioutil.TempDir("", t.Name())
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(emptyDir)
ctxt.GOPATH = emptyDir
ctxt.WorkingDir = emptyDir
tests := []struct { tests := []struct {
label string label string
...@@ -451,6 +459,7 @@ func TestImportPackageOutsideModule(t *testing.T) { ...@@ -451,6 +459,7 @@ func TestImportPackageOutsideModule(t *testing.T) {
os.Setenv("GOPATH", gopath) os.Setenv("GOPATH", gopath)
ctxt := Default ctxt := Default
ctxt.GOPATH = gopath ctxt.GOPATH = gopath
ctxt.WorkingDir = filepath.Join(gopath, "src/example.com/p")
want := "cannot find module providing package" want := "cannot find module providing package"
if _, err := ctxt.Import("example.com/p", gopath, FindOnly); err == nil { if _, err := ctxt.Import("example.com/p", gopath, FindOnly); err == nil {
...@@ -507,8 +516,11 @@ func TestMissingImportErrorRepetition(t *testing.T) { ...@@ -507,8 +516,11 @@ func TestMissingImportErrorRepetition(t *testing.T) {
defer os.Setenv("GOPROXY", os.Getenv("GOPROXY")) defer os.Setenv("GOPROXY", os.Getenv("GOPROXY"))
os.Setenv("GOPROXY", "off") os.Setenv("GOPROXY", "off")
ctxt := Default
ctxt.WorkingDir = tmp
pkgPath := "example.com/hello" pkgPath := "example.com/hello"
if _, err = Import(pkgPath, tmp, FindOnly); err == nil { if _, err = ctxt.Import(pkgPath, tmp, FindOnly); err == nil {
t.Fatal("unexpected success") t.Fatal("unexpected success")
} else if n := strings.Count(err.Error(), pkgPath); n != 1 { } else if n := strings.Count(err.Error(), pkgPath); n != 1 {
t.Fatalf("package path %q appears in error %d times; should appear once\nerror: %v", pkgPath, n, err) t.Fatalf("package path %q appears in error %d times; should appear once\nerror: %v", pkgPath, n, err)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment