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 {
GOOS string // target operating system
GOROOT string // Go root
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
UseAllFiles bool // use files regardless of +build lines, file names
Compiler string // compiler to assume when computing target paths
......@@ -994,21 +1003,14 @@ var errNoModules = errors.New("not using modules")
func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) error {
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,
// 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) {
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
// 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
......@@ -1021,12 +1023,29 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode)
// 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
// and we should keep using it. Moreover, the 'go list' approach below doesn't
// take standard-library vendoring into account and will fail.
if _, ok := ctxt.hasSubdir(filepath.Join(ctxt.GOROOT, "src"), absSrcDir); ok {
return errNoModules
}
}
// For efficiency, if path is a standard library package, let the usual lookup code handle it.
if ctxt.GOROOT != "" {
......@@ -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.
// Since go1.13, it doesn't matter if we're inside GOPATH.
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 {
info, err := os.Stat(filepath.Join(parent, "go.mod"))
if err == nil && !info.IsDir() {
......@@ -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)
// TODO(bcmills): This is wrong if srcDir is in a vendor directory, or if
// srcDir is in some module dependency of the main module. The main module
// chooses what the import paths mean: individual packages don't.
cmd.Dir = srcDir
if ctxt.WorkingDir != "" {
cmd.Dir = ctxt.WorkingDir
}
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
......@@ -1077,7 +1112,7 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode)
)
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)
......
......@@ -320,7 +320,15 @@ func TestShellSafety(t *testing.T) {
func TestImportDirNotExist(t *testing.T) {
testenv.MustHaveGoBuild(t) // really must just have source
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 {
label string
......@@ -451,6 +459,7 @@ func TestImportPackageOutsideModule(t *testing.T) {
os.Setenv("GOPATH", gopath)
ctxt := Default
ctxt.GOPATH = gopath
ctxt.WorkingDir = filepath.Join(gopath, "src/example.com/p")
want := "cannot find module providing package"
if _, err := ctxt.Import("example.com/p", gopath, FindOnly); err == nil {
......@@ -507,8 +516,11 @@ func TestMissingImportErrorRepetition(t *testing.T) {
defer os.Setenv("GOPROXY", os.Getenv("GOPROXY"))
os.Setenv("GOPROXY", "off")
ctxt := Default
ctxt.WorkingDir = tmp
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")
} 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)
......
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