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  }