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 }