]> Cypherpunks repositories - gostls13.git/commitdiff
go/build: use the main module's root when locating module sources
authorBryan C. Mills <bcmills@google.com>
Mon, 28 Oct 2019 15:37:24 +0000 (11:37 -0400)
committerBryan C. Mills <bcmills@google.com>
Tue, 29 Oct 2019 19:10:25 +0000 (19:10 +0000)
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>
src/go/build/build.go
src/go/build/build_test.go

index 8832ab785606610f041e39bfd0d7c87e2b5e3ed7..a4523a6eefe64b61e23a207b17e2a45be144cce9 100644 (file)
@@ -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)
index 7040a1b85b78081a6e39a371d478a6fcebdd1b95..1d14731983da61d9ed2461c5cbf9d75f86c38388 100644 (file)
@@ -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)