github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/go/internal/modload/import.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package modload
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"go/build"
    12  	"internal/goroot"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  
    17  	"cmd/go/internal/cfg"
    18  	"cmd/go/internal/modfetch/codehost"
    19  	"cmd/go/internal/module"
    20  	"cmd/go/internal/par"
    21  	"cmd/go/internal/search"
    22  )
    23  
    24  type ImportMissingError struct {
    25  	ImportPath string
    26  	Module     module.Version
    27  }
    28  
    29  func (e *ImportMissingError) Error() string {
    30  	if e.Module.Path == "" {
    31  		return "cannot find module providing package " + e.ImportPath
    32  	}
    33  	return "missing module for import: " + e.Module.Path + "@" + e.Module.Version + " provides " + e.ImportPath
    34  }
    35  
    36  // Import finds the module and directory in the build list
    37  // containing the package with the given import path.
    38  // The answer must be unique: Import returns an error
    39  // if multiple modules attempt to provide the same package.
    40  // Import can return a module with an empty m.Path, for packages in the standard library.
    41  // Import can return an empty directory string, for fake packages like "C" and "unsafe".
    42  //
    43  // If the package cannot be found in the current build list,
    44  // Import returns an ImportMissingError as the error.
    45  // If Import can identify a module that could be added to supply the package,
    46  // the ImportMissingError records that module.
    47  func Import(path string) (m module.Version, dir string, err error) {
    48  	if strings.Contains(path, "@") {
    49  		return module.Version{}, "", fmt.Errorf("import path should not have @version")
    50  	}
    51  	if build.IsLocalImport(path) {
    52  		return module.Version{}, "", fmt.Errorf("relative import not supported")
    53  	}
    54  	if path == "C" || path == "unsafe" {
    55  		// There's no directory for import "C" or import "unsafe".
    56  		return module.Version{}, "", nil
    57  	}
    58  
    59  	// Is the package in the standard library?
    60  	if search.IsStandardImportPath(path) {
    61  		if strings.HasPrefix(path, "golang_org/") {
    62  			return module.Version{}, filepath.Join(cfg.GOROOT, "src/vendor", path), nil
    63  		}
    64  		if goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
    65  			dir := filepath.Join(cfg.GOROOT, "src", path)
    66  			return module.Version{}, dir, nil
    67  		}
    68  	}
    69  
    70  	// -mod=vendor is special.
    71  	// Everything must be in the main module or the main module's vendor directory.
    72  	if cfg.BuildMod == "vendor" {
    73  		mainDir, mainOK := dirInModule(path, Target.Path, ModRoot, true)
    74  		vendorDir, vendorOK := dirInModule(path, "", filepath.Join(ModRoot, "vendor"), false)
    75  		if mainOK && vendorOK {
    76  			return module.Version{}, "", fmt.Errorf("ambiguous import: found %s in multiple directories:\n\t%s\n\t%s", path, mainDir, vendorDir)
    77  		}
    78  		// Prefer to return main directory if there is one,
    79  		// Note that we're not checking that the package exists.
    80  		// We'll leave that for load.
    81  		if !vendorOK && mainDir != "" {
    82  			return Target, mainDir, nil
    83  		}
    84  		readVendorList()
    85  		return vendorMap[path], vendorDir, nil
    86  	}
    87  
    88  	// Check each module on the build list.
    89  	var dirs []string
    90  	var mods []module.Version
    91  	for _, m := range buildList {
    92  		if !maybeInModule(path, m.Path) {
    93  			// Avoid possibly downloading irrelevant modules.
    94  			continue
    95  		}
    96  		root, isLocal, err := fetch(m)
    97  		if err != nil {
    98  			// Report fetch error.
    99  			// Note that we don't know for sure this module is necessary,
   100  			// but it certainly _could_ provide the package, and even if we
   101  			// continue the loop and find the package in some other module,
   102  			// we need to look at this module to make sure the import is
   103  			// not ambiguous.
   104  			return module.Version{}, "", err
   105  		}
   106  		dir, ok := dirInModule(path, m.Path, root, isLocal)
   107  		if ok {
   108  			mods = append(mods, m)
   109  			dirs = append(dirs, dir)
   110  		}
   111  	}
   112  	if len(mods) == 1 {
   113  		return mods[0], dirs[0], nil
   114  	}
   115  	if len(mods) > 0 {
   116  		var buf bytes.Buffer
   117  		fmt.Fprintf(&buf, "ambiguous import: found %s in multiple modules:", path)
   118  		for i, m := range mods {
   119  			fmt.Fprintf(&buf, "\n\t%s", m.Path)
   120  			if m.Version != "" {
   121  				fmt.Fprintf(&buf, " %s", m.Version)
   122  			}
   123  			fmt.Fprintf(&buf, " (%s)", dirs[i])
   124  		}
   125  		return module.Version{}, "", errors.New(buf.String())
   126  	}
   127  
   128  	// Not on build list.
   129  
   130  	// Look up module containing the package, for addition to the build list.
   131  	// Goal is to determine the module, download it to dir, and return m, dir, ErrMissing.
   132  	if cfg.BuildMod == "readonly" {
   133  		return module.Version{}, "", fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
   134  	}
   135  
   136  	m, _, err = QueryPackage(path, "latest", Allowed)
   137  	if err != nil {
   138  		if _, ok := err.(*codehost.VCSError); ok {
   139  			return module.Version{}, "", err
   140  		}
   141  		return module.Version{}, "", &ImportMissingError{ImportPath: path}
   142  	}
   143  	return m, "", &ImportMissingError{ImportPath: path, Module: m}
   144  }
   145  
   146  // maybeInModule reports whether, syntactically,
   147  // a package with the given import path could be supplied
   148  // by a module with the given module path (mpath).
   149  func maybeInModule(path, mpath string) bool {
   150  	return mpath == path ||
   151  		len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
   152  }
   153  
   154  var haveGoModCache, haveGoFilesCache par.Cache
   155  
   156  // dirInModule locates the directory that would hold the package named by the given path,
   157  // if it were in the module with module path mpath and root mdir.
   158  // If path is syntactically not within mpath,
   159  // or if mdir is a local file tree (isLocal == true) and the directory
   160  // that would hold path is in a sub-module (covered by a go.mod below mdir),
   161  // dirInModule returns "", false.
   162  //
   163  // Otherwise, dirInModule returns the name of the directory where
   164  // Go source files would be expected, along with a boolean indicating
   165  // whether there are in fact Go source files in that directory.
   166  func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool) {
   167  	// Determine where to expect the package.
   168  	if path == mpath {
   169  		dir = mdir
   170  	} else if mpath == "" { // vendor directory
   171  		dir = filepath.Join(mdir, path)
   172  	} else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath {
   173  		dir = filepath.Join(mdir, path[len(mpath)+1:])
   174  	} else {
   175  		return "", false
   176  	}
   177  
   178  	// Check that there aren't other modules in the way.
   179  	// This check is unnecessary inside the module cache
   180  	// and important to skip in the vendor directory,
   181  	// where all the module trees have been overlaid.
   182  	// So we only check local module trees
   183  	// (the main module, and any directory trees pointed at by replace directives).
   184  	if isLocal {
   185  		for d := dir; d != mdir && len(d) > len(mdir); {
   186  			haveGoMod := haveGoModCache.Do(d, func() interface{} {
   187  				_, err := os.Stat(filepath.Join(d, "go.mod"))
   188  				return err == nil
   189  			}).(bool)
   190  
   191  			if haveGoMod {
   192  				return "", false
   193  			}
   194  			parent := filepath.Dir(d)
   195  			if parent == d {
   196  				// Break the loop, as otherwise we'd loop
   197  				// forever if d=="." and mdir=="".
   198  				break
   199  			}
   200  			d = parent
   201  		}
   202  	}
   203  
   204  	// Now committed to returning dir (not "").
   205  
   206  	// Are there Go source files in the directory?
   207  	// We don't care about build tags, not even "+build ignore".
   208  	// We're just looking for a plausible directory.
   209  	haveGoFiles = haveGoFilesCache.Do(dir, func() interface{} {
   210  		f, err := os.Open(dir)
   211  		if err != nil {
   212  			return false
   213  		}
   214  		defer f.Close()
   215  		names, _ := f.Readdirnames(-1)
   216  		for _, name := range names {
   217  			if strings.HasSuffix(name, ".go") {
   218  				info, err := os.Stat(filepath.Join(dir, name))
   219  				if err == nil && info.Mode().IsRegular() {
   220  					return true
   221  				}
   222  			}
   223  		}
   224  		return false
   225  	}).(bool)
   226  
   227  	return dir, haveGoFiles
   228  }