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 }