// 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
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
// 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.
// 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() {
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
)
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)
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
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 {
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)