diff --git a/src/go/build/build.go b/src/go/build/build.go index 8832ab7856..a4523a6eef 100644 --- a/src/go/build/build.go +++ b/src/go/build/build.go @@ -31,10 +31,19 @@ import ( // A Context specifies the supporting context for a build. type Context struct { - GOARCH string // target architecture - GOOS string // target operating system - GOROOT string // Go root - GOPATH string // Go path + GOARCH string // target architecture + 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,11 +1023,28 @@ func (ctxt *Context) importGo(p *Package, path, srcDir string, mode ImportMode) // Maybe use modules. } - // 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 + 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. @@ -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) diff --git a/src/go/build/build_test.go b/src/go/build/build_test.go index 7040a1b85b..1d14731983 100644 --- a/src/go/build/build_test.go +++ b/src/go/build/build_test.go @@ -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)