gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/go/packages/golist_fallback.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 packages 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "go/build" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "sort" 16 "strings" 17 18 "golang.org/x/tools/go/internal/cgo" 19 ) 20 21 // TODO(matloob): Delete this file once Go 1.12 is released. 22 23 // This file provides backwards compatibility support for 24 // loading for versions of Go earlier than 1.11. This support is meant to 25 // assist with migration to the Package API until there's 26 // widespread adoption of these newer Go versions. 27 // This support will be removed once Go 1.12 is released 28 // in Q1 2019. 29 30 func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error) { 31 // Turn absolute paths into GOROOT and GOPATH-relative paths to provide to go list. 32 // This will have surprising behavior if GOROOT or GOPATH contain multiple packages with the same 33 // path and a user provides an absolute path to a directory that's shadowed by an earlier 34 // directory in GOROOT or GOPATH with the same package path. 35 words = cleanAbsPaths(cfg, words) 36 37 original, deps, err := getDeps(cfg, words...) 38 if err != nil { 39 return nil, err 40 } 41 42 var tmpdir string // used for generated cgo files 43 var needsTestVariant []struct { 44 pkg, xtestPkg *Package 45 } 46 47 var response driverResponse 48 allPkgs := make(map[string]bool) 49 addPackage := func(p *jsonPackage, isRoot bool) { 50 id := p.ImportPath 51 52 if allPkgs[id] { 53 return 54 } 55 allPkgs[id] = true 56 57 pkgpath := id 58 59 if pkgpath == "unsafe" { 60 p.GoFiles = nil // ignore fake unsafe.go file 61 } 62 63 importMap := func(importlist []string) map[string]*Package { 64 importMap := make(map[string]*Package) 65 for _, id := range importlist { 66 67 if id == "C" { 68 for _, path := range []string{"unsafe", "syscall", "runtime/cgo"} { 69 if pkgpath != path && importMap[path] == nil { 70 importMap[path] = &Package{ID: path} 71 } 72 } 73 continue 74 } 75 importMap[vendorlessPath(id)] = &Package{ID: id} 76 } 77 return importMap 78 } 79 compiledGoFiles := absJoin(p.Dir, p.GoFiles) 80 // Use a function to simplify control flow. It's just a bunch of gotos. 81 var cgoErrors []error 82 var outdir string 83 getOutdir := func() (string, error) { 84 if outdir != "" { 85 return outdir, nil 86 } 87 if tmpdir == "" { 88 if tmpdir, err = ioutil.TempDir("", "gopackages"); err != nil { 89 return "", err 90 } 91 } 92 outdir = filepath.Join(tmpdir, strings.Replace(p.ImportPath, "/", "_", -1)) 93 if err := os.MkdirAll(outdir, 0755); err != nil { 94 outdir = "" 95 return "", err 96 } 97 return outdir, nil 98 } 99 processCgo := func() bool { 100 // Suppress any cgo errors. Any relevant errors will show up in typechecking. 101 // TODO(matloob): Skip running cgo if Mode < LoadTypes. 102 outdir, err := getOutdir() 103 if err != nil { 104 cgoErrors = append(cgoErrors, err) 105 return false 106 } 107 files, _, err := runCgo(p.Dir, outdir, cfg.Env) 108 if err != nil { 109 cgoErrors = append(cgoErrors, err) 110 return false 111 } 112 compiledGoFiles = append(compiledGoFiles, files...) 113 return true 114 } 115 if len(p.CgoFiles) == 0 || !processCgo() { 116 compiledGoFiles = append(compiledGoFiles, absJoin(p.Dir, p.CgoFiles)...) // Punt to typechecker. 117 } 118 if isRoot { 119 response.Roots = append(response.Roots, id) 120 } 121 pkg := &Package{ 122 ID: id, 123 Name: p.Name, 124 GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), 125 CompiledGoFiles: compiledGoFiles, 126 OtherFiles: absJoin(p.Dir, otherFiles(p)...), 127 PkgPath: pkgpath, 128 Imports: importMap(p.Imports), 129 // TODO(matloob): set errors on the Package to cgoErrors 130 } 131 if p.Error != nil { 132 pkg.Errors = append(pkg.Errors, Error{ 133 Pos: p.Error.Pos, 134 Msg: p.Error.Err, 135 }) 136 } 137 response.Packages = append(response.Packages, pkg) 138 if cfg.Tests && isRoot { 139 testID := fmt.Sprintf("%s [%s.test]", id, id) 140 if len(p.TestGoFiles) > 0 || len(p.XTestGoFiles) > 0 { 141 response.Roots = append(response.Roots, testID) 142 testPkg := &Package{ 143 ID: testID, 144 Name: p.Name, 145 GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles, p.TestGoFiles), 146 CompiledGoFiles: append(compiledGoFiles, absJoin(p.Dir, p.TestGoFiles)...), 147 OtherFiles: absJoin(p.Dir, otherFiles(p)...), 148 PkgPath: pkgpath, 149 Imports: importMap(append(p.Imports, p.TestImports...)), 150 // TODO(matloob): set errors on the Package to cgoErrors 151 } 152 response.Packages = append(response.Packages, testPkg) 153 var xtestPkg *Package 154 if len(p.XTestGoFiles) > 0 { 155 xtestID := fmt.Sprintf("%s_test [%s.test]", id, id) 156 response.Roots = append(response.Roots, xtestID) 157 // Generate test variants for all packages q where a path exists 158 // such that xtestPkg -> ... -> q -> ... -> p (where p is the package under test) 159 // and rewrite all import map entries of p to point to testPkg (the test variant of 160 // p), and of each q to point to the test variant of that q. 161 xtestPkg = &Package{ 162 ID: xtestID, 163 Name: p.Name + "_test", 164 GoFiles: absJoin(p.Dir, p.XTestGoFiles), 165 CompiledGoFiles: absJoin(p.Dir, p.XTestGoFiles), 166 PkgPath: pkgpath + "_test", 167 Imports: importMap(p.XTestImports), 168 } 169 // Add to list of packages we need to rewrite imports for to refer to test variants. 170 // We may need to create a test variant of a package that hasn't been loaded yet, so 171 // the test variants need to be created later. 172 needsTestVariant = append(needsTestVariant, struct{ pkg, xtestPkg *Package }{pkg, xtestPkg}) 173 response.Packages = append(response.Packages, xtestPkg) 174 } 175 // testmain package 176 testmainID := id + ".test" 177 response.Roots = append(response.Roots, testmainID) 178 imports := map[string]*Package{} 179 imports[testPkg.PkgPath] = &Package{ID: testPkg.ID} 180 if xtestPkg != nil { 181 imports[xtestPkg.PkgPath] = &Package{ID: xtestPkg.ID} 182 } 183 testmainPkg := &Package{ 184 ID: testmainID, 185 Name: "main", 186 PkgPath: testmainID, 187 Imports: imports, 188 } 189 response.Packages = append(response.Packages, testmainPkg) 190 outdir, err := getOutdir() 191 if err != nil { 192 testmainPkg.Errors = append(testmainPkg.Errors, Error{ 193 Pos: "-", 194 Msg: fmt.Sprintf("failed to generate testmain: %v", err), 195 Kind: ListError, 196 }) 197 return 198 } 199 // Don't use a .go extension on the file, so that the tests think the file is inside GOCACHE. 200 // This allows the same test to test the pre- and post-Go 1.11 go list logic because the Go 1.11 201 // go list generates test mains in the cache, and the test code knows not to rely on paths in the 202 // cache to stay stable. 203 testmain := filepath.Join(outdir, "testmain-go") 204 extraimports, extradeps, err := generateTestmain(testmain, testPkg, xtestPkg) 205 if err != nil { 206 testmainPkg.Errors = append(testmainPkg.Errors, Error{ 207 Pos: "-", 208 Msg: fmt.Sprintf("failed to generate testmain: %v", err), 209 Kind: ListError, 210 }) 211 } 212 deps = append(deps, extradeps...) 213 for _, imp := range extraimports { // testing, testing/internal/testdeps, and maybe os 214 imports[imp] = &Package{ID: imp} 215 } 216 testmainPkg.GoFiles = []string{testmain} 217 testmainPkg.CompiledGoFiles = []string{testmain} 218 } 219 } 220 } 221 222 for _, pkg := range original { 223 addPackage(pkg, true) 224 } 225 if cfg.Mode < LoadImports || len(deps) == 0 { 226 return &response, nil 227 } 228 229 buf, err := invokeGo(cfg, golistArgsFallback(cfg, deps)...) 230 if err != nil { 231 return nil, err 232 } 233 234 // Decode the JSON and convert it to Package form. 235 for dec := json.NewDecoder(buf); dec.More(); { 236 p := new(jsonPackage) 237 if err := dec.Decode(p); err != nil { 238 return nil, fmt.Errorf("JSON decoding failed: %v", err) 239 } 240 241 addPackage(p, false) 242 } 243 244 for _, v := range needsTestVariant { 245 createTestVariants(&response, v.pkg, v.xtestPkg) 246 } 247 248 return &response, nil 249 } 250 251 func createTestVariants(response *driverResponse, pkgUnderTest, xtestPkg *Package) { 252 allPkgs := make(map[string]*Package) 253 for _, pkg := range response.Packages { 254 allPkgs[pkg.ID] = pkg 255 } 256 needsTestVariant := make(map[string]bool) 257 needsTestVariant[pkgUnderTest.ID] = true 258 var needsVariantRec func(p *Package) bool 259 needsVariantRec = func(p *Package) bool { 260 if needsTestVariant[p.ID] { 261 return true 262 } 263 for _, imp := range p.Imports { 264 if needsVariantRec(allPkgs[imp.ID]) { 265 // Don't break because we want to make sure all dependencies 266 // have been processed, and all required test variants of our dependencies 267 // exist. 268 needsTestVariant[p.ID] = true 269 } 270 } 271 if !needsTestVariant[p.ID] { 272 return false 273 } 274 // Create a clone of the package. It will share the same strings and lists of source files, 275 // but that's okay. It's only necessary for the Imports map to have a separate identity. 276 testVariant := *p 277 testVariant.ID = fmt.Sprintf("%s [%s.test]", p.ID, pkgUnderTest.ID) 278 testVariant.Imports = make(map[string]*Package) 279 for imp, pkg := range p.Imports { 280 testVariant.Imports[imp] = pkg 281 if needsTestVariant[pkg.ID] { 282 testVariant.Imports[imp] = &Package{ID: fmt.Sprintf("%s [%s.test]", pkg.ID, pkgUnderTest.ID)} 283 } 284 } 285 response.Packages = append(response.Packages, &testVariant) 286 return needsTestVariant[p.ID] 287 } 288 // finally, update the xtest package's imports 289 for imp, pkg := range xtestPkg.Imports { 290 if allPkgs[pkg.ID] == nil { 291 fmt.Printf("for %s: package %s doesn't exist\n", xtestPkg.ID, pkg.ID) 292 } 293 if needsVariantRec(allPkgs[pkg.ID]) { 294 xtestPkg.Imports[imp] = &Package{ID: fmt.Sprintf("%s [%s.test]", pkg.ID, pkgUnderTest.ID)} 295 } 296 } 297 } 298 299 // cleanAbsPaths replaces all absolute paths with GOPATH- and GOROOT-relative 300 // paths. If an absolute path is not GOPATH- or GOROOT- relative, it is left as an 301 // absolute path so an error can be returned later. 302 func cleanAbsPaths(cfg *Config, words []string) []string { 303 var searchpaths []string 304 var cleaned = make([]string, len(words)) 305 for i := range cleaned { 306 cleaned[i] = words[i] 307 // Ignore relative directory paths (they must already be goroot-relative) and Go source files 308 // (absolute source files are already allowed for ad-hoc packages). 309 // TODO(matloob): Can there be non-.go files in ad-hoc packages. 310 if !filepath.IsAbs(cleaned[i]) || strings.HasSuffix(cleaned[i], ".go") { 311 continue 312 } 313 // otherwise, it's an absolute path. Search GOPATH and GOROOT to find it. 314 if searchpaths == nil { 315 cmd := exec.Command("go", "env", "GOPATH", "GOROOT") 316 cmd.Env = cfg.Env 317 out, err := cmd.Output() 318 if err != nil { 319 searchpaths = []string{} 320 continue // suppress the error, it will show up again when running go list 321 } 322 lines := strings.Split(string(out), "\n") 323 if len(lines) != 3 || lines[0] == "" || lines[1] == "" || lines[2] != "" { 324 continue // suppress error 325 } 326 // first line is GOPATH 327 for _, path := range filepath.SplitList(lines[0]) { 328 searchpaths = append(searchpaths, filepath.Join(path, "src")) 329 } 330 // second line is GOROOT 331 searchpaths = append(searchpaths, filepath.Join(lines[1], "src")) 332 } 333 for _, sp := range searchpaths { 334 if strings.HasPrefix(cleaned[i], sp) { 335 cleaned[i] = strings.TrimPrefix(cleaned[i], sp) 336 cleaned[i] = strings.TrimLeft(cleaned[i], string(filepath.Separator)) 337 } 338 } 339 } 340 return cleaned 341 } 342 343 // vendorlessPath returns the devendorized version of the import path ipath. 344 // For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b". 345 // Copied from golang.org/x/tools/imports/fix.go. 346 func vendorlessPath(ipath string) string { 347 // Devendorize for use in import statement. 348 if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 { 349 return ipath[i+len("/vendor/"):] 350 } 351 if strings.HasPrefix(ipath, "vendor/") { 352 return ipath[len("vendor/"):] 353 } 354 return ipath 355 } 356 357 // getDeps runs an initial go list to determine all the dependency packages. 358 func getDeps(cfg *Config, words ...string) (initial []*jsonPackage, deps []string, err error) { 359 buf, err := invokeGo(cfg, golistArgsFallback(cfg, words)...) 360 if err != nil { 361 return nil, nil, err 362 } 363 364 depsSet := make(map[string]bool) 365 var testImports []string 366 367 // Extract deps from the JSON. 368 for dec := json.NewDecoder(buf); dec.More(); { 369 p := new(jsonPackage) 370 if err := dec.Decode(p); err != nil { 371 return nil, nil, fmt.Errorf("JSON decoding failed: %v", err) 372 } 373 374 initial = append(initial, p) 375 for _, dep := range p.Deps { 376 depsSet[dep] = true 377 } 378 if cfg.Tests { 379 // collect the additional imports of the test packages. 380 pkgTestImports := append(p.TestImports, p.XTestImports...) 381 for _, imp := range pkgTestImports { 382 if depsSet[imp] { 383 continue 384 } 385 depsSet[imp] = true 386 testImports = append(testImports, imp) 387 } 388 } 389 } 390 // Get the deps of the packages imported by tests. 391 if len(testImports) > 0 { 392 buf, err = invokeGo(cfg, golistArgsFallback(cfg, testImports)...) 393 if err != nil { 394 return nil, nil, err 395 } 396 // Extract deps from the JSON. 397 for dec := json.NewDecoder(buf); dec.More(); { 398 p := new(jsonPackage) 399 if err := dec.Decode(p); err != nil { 400 return nil, nil, fmt.Errorf("JSON decoding failed: %v", err) 401 } 402 for _, dep := range p.Deps { 403 depsSet[dep] = true 404 } 405 } 406 } 407 408 for _, orig := range initial { 409 delete(depsSet, orig.ImportPath) 410 } 411 412 deps = make([]string, 0, len(depsSet)) 413 for dep := range depsSet { 414 deps = append(deps, dep) 415 } 416 sort.Strings(deps) // ensure output is deterministic 417 return initial, deps, nil 418 } 419 420 func golistArgsFallback(cfg *Config, words []string) []string { 421 fullargs := []string{"list", "-e", "-json"} 422 fullargs = append(fullargs, cfg.BuildFlags...) 423 fullargs = append(fullargs, "--") 424 fullargs = append(fullargs, words...) 425 return fullargs 426 } 427 428 func runCgo(pkgdir, tmpdir string, env []string) (files, displayfiles []string, err error) { 429 // Use go/build to open cgo files and determine the cgo flags, etc, from them. 430 // This is tricky so it's best to avoid reimplementing as much as we can, and 431 // we plan to delete this support once Go 1.12 is released anyways. 432 // TODO(matloob): This isn't completely correct because we're using the Default 433 // context. Perhaps we should more accurately fill in the context. 434 bp, err := build.ImportDir(pkgdir, build.ImportMode(0)) 435 if err != nil { 436 return nil, nil, err 437 } 438 for _, ev := range env { 439 if v := strings.TrimPrefix(ev, "CGO_CPPFLAGS"); v != ev { 440 bp.CgoCPPFLAGS = append(bp.CgoCPPFLAGS, strings.Fields(v)...) 441 } else if v := strings.TrimPrefix(ev, "CGO_CFLAGS"); v != ev { 442 bp.CgoCFLAGS = append(bp.CgoCFLAGS, strings.Fields(v)...) 443 } else if v := strings.TrimPrefix(ev, "CGO_CXXFLAGS"); v != ev { 444 bp.CgoCXXFLAGS = append(bp.CgoCXXFLAGS, strings.Fields(v)...) 445 } else if v := strings.TrimPrefix(ev, "CGO_LDFLAGS"); v != ev { 446 bp.CgoLDFLAGS = append(bp.CgoLDFLAGS, strings.Fields(v)...) 447 } 448 } 449 return cgo.Run(bp, pkgdir, tmpdir, true) 450 }