github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-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  	"errors"
     9  	"fmt"
    10  	"go/build"
    11  	"github.com/gagliardetto/golang-go/not-internal/goroot"
    12  	"os"
    13  	"path/filepath"
    14  	"sort"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/cfg"
    19  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/load"
    20  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/modfetch"
    21  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/par"
    22  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/search"
    23  
    24  	"golang.org/x/mod/module"
    25  	"golang.org/x/mod/semver"
    26  )
    27  
    28  type ImportMissingError struct {
    29  	Path     string
    30  	Module   module.Version
    31  	QueryErr error
    32  
    33  	// newMissingVersion is set to a newer version of Module if one is present
    34  	// in the build list. When set, we can't automatically upgrade.
    35  	newMissingVersion string
    36  }
    37  
    38  var _ load.ImportPathError = (*ImportMissingError)(nil)
    39  
    40  func (e *ImportMissingError) Error() string {
    41  	if e.Module.Path == "" {
    42  		if search.IsStandardImportPath(e.Path) {
    43  			return fmt.Sprintf("package %s is not in GOROOT (%s)", e.Path, filepath.Join(cfg.GOROOT, "src", e.Path))
    44  		}
    45  		if e.QueryErr != nil {
    46  			return fmt.Sprintf("cannot find module providing package %s: %v", e.Path, e.QueryErr)
    47  		}
    48  		return "cannot find module providing package " + e.Path
    49  	}
    50  	return fmt.Sprintf("missing module for import: %s@%s provides %s", e.Module.Path, e.Module.Version, e.Path)
    51  }
    52  
    53  func (e *ImportMissingError) Unwrap() error {
    54  	return e.QueryErr
    55  }
    56  
    57  func (e *ImportMissingError) ImportPath() string {
    58  	return e.Path
    59  }
    60  
    61  // An AmbiguousImportError indicates an import of a package found in multiple
    62  // modules in the build list, or found in both the main module and its vendor
    63  // directory.
    64  type AmbiguousImportError struct {
    65  	ImportPath string
    66  	Dirs       []string
    67  	Modules    []module.Version // Either empty or 1:1 with Dirs.
    68  }
    69  
    70  func (e *AmbiguousImportError) Error() string {
    71  	locType := "modules"
    72  	if len(e.Modules) == 0 {
    73  		locType = "directories"
    74  	}
    75  
    76  	var buf strings.Builder
    77  	fmt.Fprintf(&buf, "ambiguous import: found package %s in multiple %s:", e.ImportPath, locType)
    78  
    79  	for i, dir := range e.Dirs {
    80  		buf.WriteString("\n\t")
    81  		if i < len(e.Modules) {
    82  			m := e.Modules[i]
    83  			buf.WriteString(m.Path)
    84  			if m.Version != "" {
    85  				fmt.Fprintf(&buf, " %s", m.Version)
    86  			}
    87  			fmt.Fprintf(&buf, " (%s)", dir)
    88  		} else {
    89  			buf.WriteString(dir)
    90  		}
    91  	}
    92  
    93  	return buf.String()
    94  }
    95  
    96  // Import finds the module and directory in the build list
    97  // containing the package with the given import path.
    98  // The answer must be unique: Import returns an error
    99  // if multiple modules attempt to provide the same package.
   100  // Import can return a module with an empty m.Path, for packages in the standard library.
   101  // Import can return an empty directory string, for fake packages like "C" and "unsafe".
   102  //
   103  // If the package cannot be found in the current build list,
   104  // Import returns an ImportMissingError as the error.
   105  // If Import can identify a module that could be added to supply the package,
   106  // the ImportMissingError records that module.
   107  func Import(path string) (m module.Version, dir string, err error) {
   108  	if strings.Contains(path, "@") {
   109  		return module.Version{}, "", fmt.Errorf("import path should not have @version")
   110  	}
   111  	if build.IsLocalImport(path) {
   112  		return module.Version{}, "", fmt.Errorf("relative import not supported")
   113  	}
   114  	if path == "C" || path == "unsafe" {
   115  		// There's no directory for import "C" or import "unsafe".
   116  		return module.Version{}, "", nil
   117  	}
   118  
   119  	// Is the package in the standard library?
   120  	pathIsStd := search.IsStandardImportPath(path)
   121  	if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
   122  		if targetInGorootSrc {
   123  			if dir, ok := dirInModule(path, targetPrefix, ModRoot(), true); ok {
   124  				return Target, dir, nil
   125  			}
   126  		}
   127  		dir := filepath.Join(cfg.GOROOT, "src", path)
   128  		return module.Version{}, dir, nil
   129  	}
   130  
   131  	// -mod=vendor is special.
   132  	// Everything must be in the main module or the main module's vendor directory.
   133  	if cfg.BuildMod == "vendor" {
   134  		mainDir, mainOK := dirInModule(path, targetPrefix, ModRoot(), true)
   135  		vendorDir, vendorOK := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false)
   136  		if mainOK && vendorOK {
   137  			return module.Version{}, "", &AmbiguousImportError{ImportPath: path, Dirs: []string{mainDir, vendorDir}}
   138  		}
   139  		// Prefer to return main directory if there is one,
   140  		// Note that we're not checking that the package exists.
   141  		// We'll leave that for load.
   142  		if !vendorOK && mainDir != "" {
   143  			return Target, mainDir, nil
   144  		}
   145  		readVendorList()
   146  		return vendorPkgModule[path], vendorDir, nil
   147  	}
   148  
   149  	// Check each module on the build list.
   150  	var dirs []string
   151  	var mods []module.Version
   152  	for _, m := range buildList {
   153  		if !maybeInModule(path, m.Path) {
   154  			// Avoid possibly downloading irrelevant modules.
   155  			continue
   156  		}
   157  		root, isLocal, err := fetch(m)
   158  		if err != nil {
   159  			// Report fetch error.
   160  			// Note that we don't know for sure this module is necessary,
   161  			// but it certainly _could_ provide the package, and even if we
   162  			// continue the loop and find the package in some other module,
   163  			// we need to look at this module to make sure the import is
   164  			// not ambiguous.
   165  			return module.Version{}, "", err
   166  		}
   167  		dir, ok := dirInModule(path, m.Path, root, isLocal)
   168  		if ok {
   169  			mods = append(mods, m)
   170  			dirs = append(dirs, dir)
   171  		}
   172  	}
   173  	if len(mods) == 1 {
   174  		return mods[0], dirs[0], nil
   175  	}
   176  	if len(mods) > 0 {
   177  		return module.Version{}, "", &AmbiguousImportError{ImportPath: path, Dirs: dirs, Modules: mods}
   178  	}
   179  
   180  	// Look up module containing the package, for addition to the build list.
   181  	// Goal is to determine the module, download it to dir, and return m, dir, ErrMissing.
   182  	if cfg.BuildMod == "readonly" {
   183  		var queryErr error
   184  		if !pathIsStd {
   185  			if cfg.BuildModReason == "" {
   186  				queryErr = fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
   187  			} else {
   188  				queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
   189  			}
   190  		}
   191  		return module.Version{}, "", &ImportMissingError{Path: path, QueryErr: queryErr}
   192  	}
   193  	if modRoot == "" && !allowMissingModuleImports {
   194  		return module.Version{}, "", &ImportMissingError{
   195  			Path:     path,
   196  			QueryErr: errors.New("working directory is not part of a module"),
   197  		}
   198  	}
   199  
   200  	// Not on build list.
   201  	// To avoid spurious remote fetches, next try the latest replacement for each module.
   202  	// (golang.org/issue/26241)
   203  	if modFile != nil {
   204  		latest := map[string]string{} // path -> version
   205  		for _, r := range modFile.Replace {
   206  			if maybeInModule(path, r.Old.Path) {
   207  				// Don't use semver.Max here; need to preserve +incompatible suffix.
   208  				v := latest[r.Old.Path]
   209  				if semver.Compare(r.Old.Version, v) > 0 {
   210  					v = r.Old.Version
   211  				}
   212  				latest[r.Old.Path] = v
   213  			}
   214  		}
   215  
   216  		mods = make([]module.Version, 0, len(latest))
   217  		for p, v := range latest {
   218  			// If the replacement didn't specify a version, synthesize a
   219  			// pseudo-version with an appropriate major version and a timestamp below
   220  			// any real timestamp. That way, if the main module is used from within
   221  			// some other module, the user will be able to upgrade the requirement to
   222  			// any real version they choose.
   223  			if v == "" {
   224  				if _, pathMajor, ok := module.SplitPathVersion(p); ok && len(pathMajor) > 0 {
   225  					v = modfetch.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
   226  				} else {
   227  					v = modfetch.PseudoVersion("v0", "", time.Time{}, "000000000000")
   228  				}
   229  			}
   230  			mods = append(mods, module.Version{Path: p, Version: v})
   231  		}
   232  
   233  		// Every module path in mods is a prefix of the import path.
   234  		// As in QueryPackage, prefer the longest prefix that satisfies the import.
   235  		sort.Slice(mods, func(i, j int) bool {
   236  			return len(mods[i].Path) > len(mods[j].Path)
   237  		})
   238  		for _, m := range mods {
   239  			root, isLocal, err := fetch(m)
   240  			if err != nil {
   241  				// Report fetch error as above.
   242  				return module.Version{}, "", err
   243  			}
   244  			_, ok := dirInModule(path, m.Path, root, isLocal)
   245  			if ok {
   246  				return m, "", &ImportMissingError{Path: path, Module: m}
   247  			}
   248  		}
   249  		if len(mods) > 0 && module.CheckPath(path) != nil {
   250  			// The package path is not valid to fetch remotely,
   251  			// so it can only exist if in a replaced module,
   252  			// and we know from the above loop that it is not.
   253  			return module.Version{}, "", &PackageNotInModuleError{
   254  				Mod:         mods[0],
   255  				Query:       "latest",
   256  				Pattern:     path,
   257  				Replacement: Replacement(mods[0]),
   258  			}
   259  		}
   260  	}
   261  
   262  	if pathIsStd {
   263  		// This package isn't in the standard library, isn't in any module already
   264  		// in the build list, and isn't in any other module that the user has
   265  		// shimmed in via a "replace" directive.
   266  		// Moreover, the import path is reserved for the standard library, so
   267  		// QueryPackage cannot possibly find a module containing this package.
   268  		//
   269  		// Instead of trying QueryPackage, report an ImportMissingError immediately.
   270  		return module.Version{}, "", &ImportMissingError{Path: path}
   271  	}
   272  
   273  	fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path)
   274  
   275  	candidates, err := QueryPackage(path, "latest", Allowed)
   276  	if err != nil {
   277  		if errors.Is(err, os.ErrNotExist) {
   278  			// Return "cannot find module providing package […]" instead of whatever
   279  			// low-level error QueryPackage produced.
   280  			return module.Version{}, "", &ImportMissingError{Path: path, QueryErr: err}
   281  		} else {
   282  			return module.Version{}, "", err
   283  		}
   284  	}
   285  	m = candidates[0].Mod
   286  	newMissingVersion := ""
   287  	for _, c := range candidates {
   288  		cm := c.Mod
   289  		for _, bm := range buildList {
   290  			if bm.Path == cm.Path && semver.Compare(bm.Version, cm.Version) > 0 {
   291  				// QueryPackage proposed that we add module cm to provide the package,
   292  				// but we already depend on a newer version of that module (and we don't
   293  				// have the package).
   294  				//
   295  				// This typically happens when a package is present at the "@latest"
   296  				// version (e.g., v1.0.0) of a module, but we have a newer version
   297  				// of the same module in the build list (e.g., v1.0.1-beta), and
   298  				// the package is not present there.
   299  				m = cm
   300  				newMissingVersion = bm.Version
   301  				break
   302  			}
   303  		}
   304  	}
   305  	return m, "", &ImportMissingError{Path: path, Module: m, newMissingVersion: newMissingVersion}
   306  }
   307  
   308  // maybeInModule reports whether, syntactically,
   309  // a package with the given import path could be supplied
   310  // by a module with the given module path (mpath).
   311  func maybeInModule(path, mpath string) bool {
   312  	return mpath == path ||
   313  		len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
   314  }
   315  
   316  var haveGoModCache, haveGoFilesCache par.Cache
   317  
   318  // dirInModule locates the directory that would hold the package named by the given path,
   319  // if it were in the module with module path mpath and root mdir.
   320  // If path is syntactically not within mpath,
   321  // or if mdir is a local file tree (isLocal == true) and the directory
   322  // that would hold path is in a sub-module (covered by a go.mod below mdir),
   323  // dirInModule returns "", false.
   324  //
   325  // Otherwise, dirInModule returns the name of the directory where
   326  // Go source files would be expected, along with a boolean indicating
   327  // whether there are in fact Go source files in that directory.
   328  func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFiles bool) {
   329  	// Determine where to expect the package.
   330  	if path == mpath {
   331  		dir = mdir
   332  	} else if mpath == "" { // vendor directory
   333  		dir = filepath.Join(mdir, path)
   334  	} else if len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath {
   335  		dir = filepath.Join(mdir, path[len(mpath)+1:])
   336  	} else {
   337  		return "", false
   338  	}
   339  
   340  	// Check that there aren't other modules in the way.
   341  	// This check is unnecessary inside the module cache
   342  	// and important to skip in the vendor directory,
   343  	// where all the module trees have been overlaid.
   344  	// So we only check local module trees
   345  	// (the main module, and any directory trees pointed at by replace directives).
   346  	if isLocal {
   347  		for d := dir; d != mdir && len(d) > len(mdir); {
   348  			haveGoMod := haveGoModCache.Do(d, func() interface{} {
   349  				fi, err := os.Stat(filepath.Join(d, "go.mod"))
   350  				return err == nil && !fi.IsDir()
   351  			}).(bool)
   352  
   353  			if haveGoMod {
   354  				return "", false
   355  			}
   356  			parent := filepath.Dir(d)
   357  			if parent == d {
   358  				// Break the loop, as otherwise we'd loop
   359  				// forever if d=="." and mdir=="".
   360  				break
   361  			}
   362  			d = parent
   363  		}
   364  	}
   365  
   366  	// Now committed to returning dir (not "").
   367  
   368  	// Are there Go source files in the directory?
   369  	// We don't care about build tags, not even "+build ignore".
   370  	// We're just looking for a plausible directory.
   371  	haveGoFiles = haveGoFilesCache.Do(dir, func() interface{} {
   372  		f, err := os.Open(dir)
   373  		if err != nil {
   374  			return false
   375  		}
   376  		defer f.Close()
   377  		names, _ := f.Readdirnames(-1)
   378  		for _, name := range names {
   379  			if strings.HasSuffix(name, ".go") {
   380  				info, err := os.Stat(filepath.Join(dir, name))
   381  				if err == nil && info.Mode().IsRegular() {
   382  					return true
   383  				}
   384  			}
   385  		}
   386  		return false
   387  	}).(bool)
   388  
   389  	return dir, haveGoFiles
   390  }