Skip to content

Commit 1d4369f

Browse files
author
Bryan C. Mills
committed
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/+/203820 Reviewed-by: Jay Conrod <jayconrod@google.com>
1 parent ca70ada commit 1d4369f

File tree

2 files changed

+73
-26
lines changed

2 files changed

+73
-26
lines changed

src/go/build/build.go

+59-24
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,19 @@ import (
3131

3232
// A Context specifies the supporting context for a build.
3333
type Context struct {
34-
GOARCH string // target architecture
35-
GOOS string // target operating system
36-
GOROOT string // Go root
37-
GOPATH string // Go path
34+
GOARCH string // target architecture
35+
GOOS string // target operating system
36+
GOROOT string // Go root
37+
GOPATH string // Go path
38+
39+
// WorkingDir is the caller's working directory, or the empty string to use
40+
// the current directory of the running process. In module mode, this is used
41+
// to locate the main module.
42+
//
43+
// If WorkingDir is non-empty, directories passed to Import and ImportDir must
44+
// be absolute.
45+
WorkingDir string
46+
3847
CgoEnabled bool // whether cgo files are included
3948
UseAllFiles bool // use files regardless of +build lines, file names
4049
Compiler string // compiler to assume when computing target paths
@@ -994,21 +1003,14 @@ var errNoModules = errors.New("not using modules")
9941003
func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) error {
9951004
const debugImportGo = false
9961005

997-
// To invoke the go command, we must know the source directory,
1006+
// To invoke the go command,
9981007
// we must not being doing special things like AllowBinary or IgnoreVendor,
9991008
// and all the file system callbacks must be nil (we're meant to use the local file system).
1000-
if srcDir == "" || mode&AllowBinary != 0 || mode&IgnoreVendor != 0 ||
1009+
if mode&AllowBinary != 0 || mode&IgnoreVendor != 0 ||
10011010
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) {
10021011
return errNoModules
10031012
}
10041013

1005-
// Find the absolute source directory. hasSubdir does not handle
1006-
// relative paths (and can't because the callbacks don't support this).
1007-
absSrcDir, err := filepath.Abs(srcDir)
1008-
if err != nil {
1009-
return errNoModules
1010-
}
1011-
10121014
// Predict whether module aware mode is enabled by checking the value of
10131015
// GO111MODULE and looking for a go.mod file in the source directory or
10141016
// one of its parents. Running 'go env GOMOD' in the source directory would
@@ -1021,11 +1023,28 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode)
10211023
// Maybe use modules.
10221024
}
10231025

1024-
// If the source directory is in GOROOT, then the in-process code works fine
1025-
// and we should keep using it. Moreover, the 'go list' approach below doesn't
1026-
// take standard-library vendoring into account and will fail.
1027-
if _, ok := ctxt.hasSubdir(filepath.Join(ctxt.GOROOT, "src"), absSrcDir); ok {
1028-
return errNoModules
1026+
if srcDir != "" {
1027+
var absSrcDir string
1028+
if filepath.IsAbs(srcDir) {
1029+
absSrcDir = srcDir
1030+
} else if ctxt.WorkingDir != "" {
1031+
return fmt.Errorf("go/build: WorkingDir is non-empty, so relative srcDir is not allowed: %v", srcDir)
1032+
} else {
1033+
// Find the absolute source directory. hasSubdir does not handle
1034+
// relative paths (and can't because the callbacks don't support this).
1035+
var err error
1036+
absSrcDir, err = filepath.Abs(srcDir)
1037+
if err != nil {
1038+
return errNoModules
1039+
}
1040+
}
1041+
1042+
// If the source directory is in GOROOT, then the in-process code works fine
1043+
// and we should keep using it. Moreover, the 'go list' approach below doesn't
1044+
// take standard-library vendoring into account and will fail.
1045+
if _, ok := ctxt.hasSubdir(filepath.Join(ctxt.GOROOT, "src"), absSrcDir); ok {
1046+
return errNoModules
1047+
}
10291048
}
10301049

10311050
// For efficiency, if path is a standard library package, let the usual lookup code handle it.
@@ -1039,7 +1058,24 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode)
10391058
// Unless GO111MODULE=on, look to see if there is a go.mod.
10401059
// Since go1.13, it doesn't matter if we're inside GOPATH.
10411060
if go111Module != "on" {
1042-
parent := absSrcDir
1061+
var (
1062+
parent string
1063+
err error
1064+
)
1065+
if ctxt.WorkingDir == "" {
1066+
parent, err = os.Getwd()
1067+
if err != nil {
1068+
// A nonexistent working directory can't be in a module.
1069+
return errNoModules
1070+
}
1071+
} else {
1072+
parent, err = filepath.Abs(ctxt.WorkingDir)
1073+
if err != nil {
1074+
// If the caller passed a bogus WorkingDir explicitly, that's materially
1075+
// different from not having modules enabled.
1076+
return err
1077+
}
1078+
}
10431079
for {
10441080
info, err := os.Stat(filepath.Join(parent, "go.mod"))
10451081
if err == nil && !info.IsDir() {
@@ -1055,10 +1091,9 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode)
10551091

10561092
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)
10571093

1058-
// TODO(bcmills): This is wrong if srcDir is in a vendor directory, or if
1059-
// srcDir is in some module dependency of the main module. The main module
1060-
// chooses what the import paths mean: individual packages don't.
1061-
cmd.Dir = srcDir
1094+
if ctxt.WorkingDir != "" {
1095+
cmd.Dir = ctxt.WorkingDir
1096+
}
10621097

10631098
var stdout, stderr strings.Builder
10641099
cmd.Stdout = &stdout
@@ -1077,7 +1112,7 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode)
10771112
)
10781113

10791114
if err := cmd.Run(); err != nil {
1080-
return fmt.Errorf("go/build: importGo %s: %v\n%s\n", path, err, stderr.String())
1115+
return fmt.Errorf("go/build: go list %s: %v\n%s\n", path, err, stderr.String())
10811116
}
10821117

10831118
f := strings.SplitN(stdout.String(), "\n", 5)

src/go/build/build_test.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,15 @@ func TestShellSafety(t *testing.T) {
320320
func TestImportDirNotExist(t *testing.T) {
321321
testenv.MustHaveGoBuild(t) // really must just have source
322322
ctxt := Default
323-
ctxt.GOPATH = ""
323+
324+
emptyDir, err := ioutil.TempDir("", t.Name())
325+
if err != nil {
326+
t.Fatal(err)
327+
}
328+
defer os.RemoveAll(emptyDir)
329+
330+
ctxt.GOPATH = emptyDir
331+
ctxt.WorkingDir = emptyDir
324332

325333
tests := []struct {
326334
label string
@@ -451,6 +459,7 @@ func TestImportPackageOutsideModule(t *testing.T) {
451459
os.Setenv("GOPATH", gopath)
452460
ctxt := Default
453461
ctxt.GOPATH = gopath
462+
ctxt.WorkingDir = filepath.Join(gopath, "src/example.com/p")
454463

455464
want := "cannot find module providing package"
456465
if _, err := ctxt.Import("example.com/p", gopath, FindOnly); err == nil {
@@ -507,8 +516,11 @@ func TestMissingImportErrorRepetition(t *testing.T) {
507516
defer os.Setenv("GOPROXY", os.Getenv("GOPROXY"))
508517
os.Setenv("GOPROXY", "off")
509518

519+
ctxt := Default
520+
ctxt.WorkingDir = tmp
521+
510522
pkgPath := "example.com/hello"
511-
if _, err = Import(pkgPath, tmp, FindOnly); err == nil {
523+
if _, err = ctxt.Import(pkgPath, tmp, FindOnly); err == nil {
512524
t.Fatal("unexpected success")
513525
} else if n := strings.Count(err.Error(), pkgPath); n != 1 {
514526
t.Fatalf("package path %q appears in error %d times; should appear once\nerror: %v", pkgPath, n, err)

0 commit comments

Comments
 (0)