github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/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  	"sort"
    16  	"strings"
    17  	"time"
    18  
    19  	"cmd/go/internal/cfg"
    20  	"cmd/go/internal/modfetch"
    21  	"cmd/go/internal/modfetch/codehost"
    22  	"cmd/go/internal/module"
    23  	"cmd/go/internal/par"
    24  	"cmd/go/internal/search"
    25  	"cmd/go/internal/semver"
    26  )
    27  
    28  type ImportMissingError struct {
    29  	ImportPath string
    30  	Module     module.Version
    31  }
    32  
    33  func (e *ImportMissingError) Error() string {
    34  	if e.Module.Path == "" {
    35  		return "cannot find module providing package " + e.ImportPath
    36  	}
    37  	return "missing module for import: " + e.Module.Path + "@" + e.Module.Version + " provides " + e.ImportPath
    38  }
    39  
    40  // Import finds the module and directory in the build list
    41  // containing the package with the given import path.
    42  // The answer must be unique: Import returns an error
    43  // if multiple modules attempt to provide the same package.
    44  // Import can return a module with an empty m.Path, for packages in the standard library.
    45  // Import can return an empty directory string, for fake packages like "C" and "unsafe".
    46  //
    47  // If the package cannot be found in the current build list,
    48  // Import returns an ImportMissingError as the error.
    49  // If Import can identify a module that could be added to supply the package,
    50  // the ImportMissingError records that module.
    51  func Import(path string) (m module.Version, dir string, err error) {
    52  	if strings.Contains(path, "@") {
    53  		return module.Version{}, "", fmt.Errorf("import path should not have @version")
    54  	}
    55  	if build.IsLocalImport(path) {
    56  		return module.Version{}, "", fmt.Errorf("relative import not supported")
    57  	}
    58  	if path == "C" || path == "unsafe" {
    59  		// There's no directory for import "C" or import "unsafe".
    60  		return module.Version{}, "", nil
    61  	}
    62  
    63  	// Is the package in the standard library?
    64  	if search.IsStandardImportPath(path) {
    65  		if goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
    66  			dir := filepath.Join(cfg.GOROOT, "src", path)
    67  			return module.Version{}, dir, nil
    68  		}
    69  	}
    70  
    71  	// -mod=vendor is special.
    72  	// Everything must be in the main module or the main module's vendor directory.
    73  	if cfg.BuildMod == "vendor" {
    74  		mainDir, mainOK := dirInModule(path, Target.Path, ModRoot(), true)
    75  		vendorDir, vendorOK := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false)
    76  		if mainOK && vendorOK {
    77  			return module.Version{}, "", fmt.Errorf("ambiguous import: found %s in multiple directories:\n\t%s\n\t%s", path, mainDir, vendorDir)
    78  		}
    79  		// Prefer to return main directory if there is one,
    80  		// Note that we're not checking that the package exists.
    81  		// We'll leave that for load.
    82  		if !vendorOK && mainDir != "" {
    83  			return Target, mainDir, nil
    84  		}
    85  		readVendorList()
    86  		return vendorMap[path], vendorDir, nil
    87  	}
    88  
    89  	// Check each module on the build list.
    90  	var dirs []string
    91  	var mods []module.Version
    92  	for _, m := range buildList {
    93  		if !maybeInModule(path, m.Path) {
    94  			// Avoid possibly downloading irrelevant modules.
    95  			continue
    96  		}
    97  		root, isLocal, err := fetch(m)
    98  		if err != nil {
    99  			// Report fetch error.
   100  			// Note that we don't know for sure this module is necessary,
   101  			// but it certainly _could_ provide the package, and even if we
   102  			// continue the loop and find the package in some other module,
   103  			// we need to look at this module to make sure the import is
   104  			// not ambiguous.
   105  			return module.Version{}, "", err
   106  		}
   107  		dir, ok := dirInModule(path, m.Path, root, isLocal)
   108  		if ok {
   109  			mods = append(mods, m)
   110  			dirs = append(dirs, dir)
   111  		}
   112  	}
   113  	if len(mods) == 1 {
   114  		return mods[0], dirs[0], nil
   115  	}
   116  	if len(mods) > 0 {
   117  		var buf bytes.Buffer
   118  		fmt.Fprintf(&buf, "ambiguous import: found %s in multiple modules:", path)
   119  		for i, m := range mods {
   120  			fmt.Fprintf(&buf, "\n\t%s", m.Path)
   121  			if m.Version != "" {
   122  				fmt.Fprintf(&buf, " %s", m.Version)
   123  			}
   124  			fmt.Fprintf(&buf, " (%s)", dirs[i])
   125  		}
   126  		return module.Version{}, "", errors.New(buf.String())
   127  	}
   128  
   129  	// Look up module containing the package, for addition to the build list.
   130  	// Goal is to determine the module, download it to dir, and return m, dir, ErrMissing.
   131  	if cfg.BuildMod == "readonly" {
   132  		return module.Version{}, "", fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
   133  	}
   134  
   135  	// Not on build list.
   136  	// To avoid spurious remote fetches, next try the latest replacement for each module.
   137  	// (golang.org/issue/26241)
   138  	if modFile != nil {
   139  		latest := map[string]string{} // path -> version
   140  		for _, r := range modFile.Replace {
   141  			if maybeInModule(path, r.Old.Path) {
   142  				latest[r.Old.Path] = semver.Max(r.Old.Version, latest[r.Old.Path])
   143  			}
   144  		}
   145  
   146  		mods = make([]module.Version, 0, len(latest))
   147  		for p, v := range latest {
   148  			// If the replacement didn't specify a version, synthesize a
   149  			// pseudo-version with an appropriate major version and a timestamp below
   150  			// any real timestamp. That way, if the main module is used from within
   151  			// some other module, the user will be able to upgrade the requirement to
   152  			// any real version they choose.
   153  			if v == "" {
   154  				if _, pathMajor, ok := module.SplitPathVersion(p); ok && len(pathMajor) > 0 {
   155  					v = modfetch.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
   156  				} else {
   157  					v = modfetch.PseudoVersion("v0", "", time.Time{}, "000000000000")
   158  				}
   159  			}
   160  			mods = append(mods, module.Version{Path: p, Version: v})
   161  		}
   162  
   163  		// Every module path in mods is a prefix of the import path.
   164  		// As in QueryPackage, prefer the longest prefix that satisfies the import.
   165  		sort.Slice(mods, func(i, j int) bool {
   166  			return len(mods[i].Path) > len(mods[j].Path)
   167  		})
   168  		for _, m := range mods {
   169  			root, isLocal, err := fetch(m)
   170  			if err != nil {
   171  				// Report fetch error as above.
   172  				return module.Version{}, "", err
   173  			}
   174  			_, ok := dirInModule(path, m.Path, root, isLocal)
   175  			if ok {
   176  				return m, "", &ImportMissingError{ImportPath: path, Module: m}
   177  			}
   178  		}
   179  	}
   180  
   181  	m, _, err = QueryPackage(path, "latest", Allowed)
   182  	if err != nil {
   183  		if _, ok := err.(*codehost.VCSError); ok {
   184  			return module.Version{}, "", err
   185  		}
   186  		return module.Version{}, "", &ImportMissingError{ImportPath: path}
   187  	}
   188  	return m, "", &ImportMissingError{ImportPath: path, Module: m}
   189  }
   190  
   191  // maybeInModule reports whether, syntactically,
   192  // a package with the given import path could be supplied
   193  // by a module with the given module path (mpath).
   194  func maybeInModule(path, mpath string) bool {
   195  	return mpath == path ||
   196  		len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
   197  }
   198  
   199  var haveGoModCache, haveGoFilesCache par.Cache
   200  
   201  // dirInModule locates the directory that would hold the package named by the given path,
   202  // if it were in the module with module path mpath and root mdir.
   203  // If path is syntactically not within mpath,
   204  // or if mdir is a local file tree (isLocal == true) and the directory
   205  // that would hold path is in a sub-module (covered by a go.mod below mdir),
   206  // dirInModule returns "", false.
   207  //
   208  // Otherwise, dirInModule returns the name of the directory where
   209  // Go source files would be expected, along with a boolean indicating
   210  // whether there are in fact Go source files in that directory.
   211  func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool) {
   212  	// Determine where to expect the package.
   213  	if path == mpath {
   214  		dir = mdir
   215  	} else if mpath == "" { // vendor directory
   216  		dir = filepath.Join(mdir, path)
   217  	} else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath {
   218  		dir = filepath.Join(mdir, path[len(mpath)+1:])
   219  	} else {
   220  		return "", false
   221  	}
   222  
   223  	// Check that there aren't other modules in the way.
   224  	// This check is unnecessary inside the module cache
   225  	// and important to skip in the vendor directory,
   226  	// where all the module trees have been overlaid.
   227  	// So we only check local module trees
   228  	// (the main module, and any directory trees pointed at by replace directives).
   229  	if isLocal {
   230  		for d := dir; d != mdir && len(d) > len(mdir); {
   231  			haveGoMod := haveGoModCache.Do(d, func() interface{} {
   232  				_, err := os.Stat(filepath.Join(d, "go.mod"))
   233  				return err == nil
   234  			}).(bool)
   235  
   236  			if haveGoMod {
   237  				return "", false
   238  			}
   239  			parent := filepath.Dir(d)
   240  			if parent == d {
   241  				// Break the loop, as otherwise we'd loop
   242  				// forever if d=="." and mdir=="".
   243  				break
   244  			}
   245  			d = parent
   246  		}
   247  	}
   248  
   249  	// Now committed to returning dir (not "").
   250  
   251  	// Are there Go source files in the directory?
   252  	// We don't care about build tags, not even "+build ignore".
   253  	// We're just looking for a plausible directory.
   254  	haveGoFiles = haveGoFilesCache.Do(dir, func() interface{} {
   255  		f, err := os.Open(dir)
   256  		if err != nil {
   257  			return false
   258  		}
   259  		defer f.Close()
   260  		names, _ := f.Readdirnames(-1)
   261  		for _, name := range names {
   262  			if strings.HasSuffix(name, ".go") {
   263  				info, err := os.Stat(filepath.Join(dir, name))
   264  				if err == nil && info.Mode().IsRegular() {
   265  					return true
   266  				}
   267  			}
   268  		}
   269  		return false
   270  	}).(bool)
   271  
   272  	return dir, haveGoFiles
   273  }