// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package packages import ( "encoding/json" "fmt" "go/build" "io/ioutil" "os" "os/exec" "path/filepath" "sort" "strings" "golang.org/x/tools/go/internal/cgo" ) // TODO(matloob): Delete this file once Go 1.12 is released. // This file provides backwards compatibility support for // loading for versions of Go earlier than 1.10.4. This support is meant to // assist with migration to the Package API until there's // widespread adoption of these newer Go versions. // This support will be removed once Go 1.12 is released // in Q1 2019. func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error) { // Turn absolute paths into GOROOT and GOPATH-relative paths to provide to go list. // This will have surprising behavior if GOROOT or GOPATH contain multiple packages with the same // path and a user provides an absolute path to a directory that's shadowed by an earlier // directory in GOROOT or GOPATH with the same package path. words = cleanAbsPaths(cfg, words) original, deps, err := getDeps(cfg, words...) if err != nil { return nil, err } var tmpdir string // used for generated cgo files var needsTestVariant []struct { pkg, xtestPkg *Package } var response driverResponse allPkgs := make(map[string]bool) addPackage := func(p *jsonPackage) { id := p.ImportPath if allPkgs[id] { return } allPkgs[id] = true isRoot := original[id] != nil pkgpath := id if pkgpath == "unsafe" { p.GoFiles = nil // ignore fake unsafe.go file } importMap := func(importlist []string) map[string]*Package { importMap := make(map[string]*Package) for _, id := range importlist { if id == "C" { for _, path := range []string{"unsafe", "syscall", "runtime/cgo"} { if pkgpath != path && importMap[path] == nil { importMap[path] = &Package{ID: path} } } continue } importMap[vendorlessPath(id)] = &Package{ID: id} } return importMap } compiledGoFiles := absJoin(p.Dir, p.GoFiles) // Use a function to simplify control flow. It's just a bunch of gotos. var cgoErrors []error var outdir string getOutdir := func() (string, error) { if outdir != "" { return outdir, nil } if tmpdir == "" { if tmpdir, err = ioutil.TempDir("", "gopackages"); err != nil { return "", err } } // Add a "go-build" component to the path to make the tests think the files are in the cache. // This allows the same test to test the pre- and post-Go 1.11 go list logic because the Go 1.11 // go list generates test mains in the cache, and the test code knows not to rely on paths in the // cache to stay stable. outdir = filepath.Join(tmpdir, "go-build", strings.Replace(p.ImportPath, "/", "_", -1)) if err := os.MkdirAll(outdir, 0755); err != nil { outdir = "" return "", err } return outdir, nil } processCgo := func() bool { // Suppress any cgo errors. Any relevant errors will show up in typechecking. // TODO(matloob): Skip running cgo if Mode < LoadTypes. outdir, err := getOutdir() if err != nil { cgoErrors = append(cgoErrors, err) return false } files, _, err := runCgo(p.Dir, outdir, cfg.Env) if err != nil { cgoErrors = append(cgoErrors, err) return false } compiledGoFiles = append(compiledGoFiles, files...) return true } if len(p.CgoFiles) == 0 || !processCgo() { compiledGoFiles = append(compiledGoFiles, absJoin(p.Dir, p.CgoFiles)...) // Punt to typechecker. } if isRoot { response.Roots = append(response.Roots, id) } pkg := &Package{ ID: id, Name: p.Name, GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), CompiledGoFiles: compiledGoFiles, OtherFiles: absJoin(p.Dir, otherFiles(p)...), PkgPath: pkgpath, Imports: importMap(p.Imports), // TODO(matloob): set errors on the Package to cgoErrors } if p.Error != nil { pkg.Errors = append(pkg.Errors, Error{ Pos: p.Error.Pos, Msg: p.Error.Err, }) } response.Packages = append(response.Packages, pkg) if cfg.Tests && isRoot { testID := fmt.Sprintf("%s [%s.test]", id, id) if len(p.TestGoFiles) > 0 || len(p.XTestGoFiles) > 0 { response.Roots = append(response.Roots, testID) testPkg := &Package{ ID: testID, Name: p.Name, GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles, p.TestGoFiles), CompiledGoFiles: append(compiledGoFiles, absJoin(p.Dir, p.TestGoFiles)...), OtherFiles: absJoin(p.Dir, otherFiles(p)...), PkgPath: pkgpath, Imports: importMap(append(p.Imports, p.TestImports...)), // TODO(matloob): set errors on the Package to cgoErrors } response.Packages = append(response.Packages, testPkg) var xtestPkg *Package if len(p.XTestGoFiles) > 0 { xtestID := fmt.Sprintf("%s_test [%s.test]", id, id) response.Roots = append(response.Roots, xtestID) // Generate test variants for all packages q where a path exists // such that xtestPkg -> ... -> q -> ... -> p (where p is the package under test) // and rewrite all import map entries of p to point to testPkg (the test variant of // p), and of each q to point to the test variant of that q. xtestPkg = &Package{ ID: xtestID, Name: p.Name + "_test", GoFiles: absJoin(p.Dir, p.XTestGoFiles), CompiledGoFiles: absJoin(p.Dir, p.XTestGoFiles), PkgPath: pkgpath + "_test", Imports: importMap(p.XTestImports), } // Add to list of packages we need to rewrite imports for to refer to test variants. // We may need to create a test variant of a package that hasn't been loaded yet, so // the test variants need to be created later. needsTestVariant = append(needsTestVariant, struct{ pkg, xtestPkg *Package }{pkg, xtestPkg}) response.Packages = append(response.Packages, xtestPkg) } // testmain package testmainID := id + ".test" response.Roots = append(response.Roots, testmainID) imports := map[string]*Package{} imports[testPkg.PkgPath] = &Package{ID: testPkg.ID} if xtestPkg != nil { imports[xtestPkg.PkgPath] = &Package{ID: xtestPkg.ID} } testmainPkg := &Package{ ID: testmainID, Name: "main", PkgPath: testmainID, Imports: imports, } response.Packages = append(response.Packages, testmainPkg) outdir, err := getOutdir() if err != nil { testmainPkg.Errors = append(testmainPkg.Errors, Error{ Pos: "-", Msg: fmt.Sprintf("failed to generate testmain: %v", err), Kind: ListError, }) return } testmain := filepath.Join(outdir, "testmain.go") extraimports, extradeps, err := generateTestmain(testmain, testPkg, xtestPkg) if err != nil { testmainPkg.Errors = append(testmainPkg.Errors, Error{ Pos: "-", Msg: fmt.Sprintf("failed to generate testmain: %v", err), Kind: ListError, }) } deps = append(deps, extradeps...) for _, imp := range extraimports { // testing, testing/internal/testdeps, and maybe os imports[imp] = &Package{ID: imp} } testmainPkg.GoFiles = []string{testmain} testmainPkg.CompiledGoFiles = []string{testmain} } } } for _, pkg := range original { addPackage(pkg) } if cfg.Mode < LoadImports || len(deps) == 0 { return &response, nil } buf, err := invokeGo(cfg, golistArgsFallback(cfg, deps)...) if err != nil { return nil, err } // Decode the JSON and convert it to Package form. for dec := json.NewDecoder(buf); dec.More(); { p := new(jsonPackage) if err := dec.Decode(p); err != nil { return nil, fmt.Errorf("JSON decoding failed: %v", err) } addPackage(p) } for _, v := range needsTestVariant { createTestVariants(&response, v.pkg, v.xtestPkg) } // TODO(matloob): Is this the right ordering? sort.SliceStable(response.Packages, func(i, j int) bool { return response.Packages[i].PkgPath < response.Packages[j].PkgPath }) return &response, nil } func createTestVariants(response *driverResponse, pkgUnderTest, xtestPkg *Package) { allPkgs := make(map[string]*Package) for _, pkg := range response.Packages { allPkgs[pkg.ID] = pkg } needsTestVariant := make(map[string]bool) needsTestVariant[pkgUnderTest.ID] = true var needsVariantRec func(p *Package) bool needsVariantRec = func(p *Package) bool { if needsTestVariant[p.ID] { return true } for _, imp := range p.Imports { if needsVariantRec(allPkgs[imp.ID]) { // Don't break because we want to make sure all dependencies // have been processed, and all required test variants of our dependencies // exist. needsTestVariant[p.ID] = true } } if !needsTestVariant[p.ID] { return false } // Create a clone of the package. It will share the same strings and lists of source files, // but that's okay. It's only necessary for the Imports map to have a separate identity. testVariant := *p testVariant.ID = fmt.Sprintf("%s [%s.test]", p.ID, pkgUnderTest.ID) testVariant.Imports = make(map[string]*Package) for imp, pkg := range p.Imports { testVariant.Imports[imp] = pkg if needsTestVariant[pkg.ID] { testVariant.Imports[imp] = &Package{ID: fmt.Sprintf("%s [%s.test]", pkg.ID, pkgUnderTest.ID)} } } response.Packages = append(response.Packages, &testVariant) return needsTestVariant[p.ID] } // finally, update the xtest package's imports for imp, pkg := range xtestPkg.Imports { if allPkgs[pkg.ID] == nil { fmt.Printf("for %s: package %s doesn't exist\n", xtestPkg.ID, pkg.ID) } if needsVariantRec(allPkgs[pkg.ID]) { xtestPkg.Imports[imp] = &Package{ID: fmt.Sprintf("%s [%s.test]", pkg.ID, pkgUnderTest.ID)} } } } // cleanAbsPaths replaces all absolute paths with GOPATH- and GOROOT-relative // paths. If an absolute path is not GOPATH- or GOROOT- relative, it is left as an // absolute path so an error can be returned later. func cleanAbsPaths(cfg *Config, words []string) []string { var searchpaths []string var cleaned = make([]string, len(words)) for i := range cleaned { cleaned[i] = words[i] // Ignore relative directory paths (they must already be goroot-relative) and Go source files // (absolute source files are already allowed for ad-hoc packages). // TODO(matloob): Can there be non-.go files in ad-hoc packages. if !filepath.IsAbs(cleaned[i]) || strings.HasSuffix(cleaned[i], ".go") { continue } // otherwise, it's an absolute path. Search GOPATH and GOROOT to find it. if searchpaths == nil { cmd := exec.Command("go", "env", "GOPATH", "GOROOT") cmd.Env = cfg.Env out, err := cmd.Output() if err != nil { searchpaths = []string{} continue // suppress the error, it will show up again when running go list } lines := strings.Split(string(out), "\n") if len(lines) != 3 || lines[0] == "" || lines[1] == "" || lines[2] != "" { continue // suppress error } // first line is GOPATH for _, path := range filepath.SplitList(lines[0]) { searchpaths = append(searchpaths, filepath.Join(path, "src")) } // second line is GOROOT searchpaths = append(searchpaths, filepath.Join(lines[1], "src")) } for _, sp := range searchpaths { if strings.HasPrefix(cleaned[i], sp) { cleaned[i] = strings.TrimPrefix(cleaned[i], sp) cleaned[i] = strings.TrimLeft(cleaned[i], string(filepath.Separator)) } } } return cleaned } // vendorlessPath returns the devendorized version of the import path ipath. // For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b". // Copied from golang.org/x/tools/imports/fix.go. func vendorlessPath(ipath string) string { // Devendorize for use in import statement. if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 { return ipath[i+len("/vendor/"):] } if strings.HasPrefix(ipath, "vendor/") { return ipath[len("vendor/"):] } return ipath } // getDeps runs an initial go list to determine all the dependency packages. func getDeps(cfg *Config, words ...string) (originalSet map[string]*jsonPackage, deps []string, err error) { buf, err := invokeGo(cfg, golistArgsFallback(cfg, words)...) if err != nil { return nil, nil, err } depsSet := make(map[string]bool) originalSet = make(map[string]*jsonPackage) var testImports []string // Extract deps from the JSON. for dec := json.NewDecoder(buf); dec.More(); { p := new(jsonPackage) if err := dec.Decode(p); err != nil { return nil, nil, fmt.Errorf("JSON decoding failed: %v", err) } originalSet[p.ImportPath] = p for _, dep := range p.Deps { depsSet[dep] = true } if cfg.Tests { // collect the additional imports of the test packages. pkgTestImports := append(p.TestImports, p.XTestImports...) for _, imp := range pkgTestImports { if depsSet[imp] { continue } depsSet[imp] = true testImports = append(testImports, imp) } } } // Get the deps of the packages imported by tests. if len(testImports) > 0 { buf, err = invokeGo(cfg, golistArgsFallback(cfg, testImports)...) if err != nil { return nil, nil, err } // Extract deps from the JSON. for dec := json.NewDecoder(buf); dec.More(); { p := new(jsonPackage) if err := dec.Decode(p); err != nil { return nil, nil, fmt.Errorf("JSON decoding failed: %v", err) } for _, dep := range p.Deps { depsSet[dep] = true } } } for orig := range originalSet { delete(depsSet, orig) } deps = make([]string, 0, len(depsSet)) for dep := range depsSet { deps = append(deps, dep) } sort.Strings(deps) // ensure output is deterministic return originalSet, deps, nil } func golistArgsFallback(cfg *Config, words []string) []string { fullargs := []string{"list", "-e", "-json"} fullargs = append(fullargs, cfg.BuildFlags...) fullargs = append(fullargs, "--") fullargs = append(fullargs, words...) return fullargs } func runCgo(pkgdir, tmpdir string, env []string) (files, displayfiles []string, err error) { // Use go/build to open cgo files and determine the cgo flags, etc, from them. // This is tricky so it's best to avoid reimplementing as much as we can, and // we plan to delete this support once Go 1.12 is released anyways. // TODO(matloob): This isn't completely correct because we're using the Default // context. Perhaps we should more accurately fill in the context. bp, err := build.ImportDir(pkgdir, build.ImportMode(0)) if err != nil { return nil, nil, err } for _, ev := range env { if v := strings.TrimPrefix(ev, "CGO_CPPFLAGS"); v != ev { bp.CgoCPPFLAGS = append(bp.CgoCPPFLAGS, strings.Fields(v)...) } else if v := strings.TrimPrefix(ev, "CGO_CFLAGS"); v != ev { bp.CgoCFLAGS = append(bp.CgoCFLAGS, strings.Fields(v)...) } else if v := strings.TrimPrefix(ev, "CGO_CXXFLAGS"); v != ev { bp.CgoCXXFLAGS = append(bp.CgoCXXFLAGS, strings.Fields(v)...) } else if v := strings.TrimPrefix(ev, "CGO_LDFLAGS"); v != ev { bp.CgoLDFLAGS = append(bp.CgoLDFLAGS, strings.Fields(v)...) } } return cgo.Run(bp, pkgdir, tmpdir, true) }