Bumping k8s dependencies to 1.13

This commit is contained in:
Cheng Xing
2018-11-16 14:08:25 -08:00
parent 305407125c
commit b4c0b68ec7
8002 changed files with 884099 additions and 276228 deletions

View File

@@ -3,112 +3,81 @@
// license that can be found in the LICENSE file.
/*
Package packages loads Go packages for inspection and analysis.
Package packages provides information about Go packages,
such as their path, source files, and imports.
It can optionally load, parse, and type-check the source files of a
package, and obtain type information for their dependencies either by
loading export data files produced by the Go compiler or by
recursively loading dependencies from source code.
Note: Though this package is ready for widespread use, we may make minor
breaking changes if absolutely necessary. Any such change will be
announced on golang-tools@ at least one week before it is committed. No
more breaking changes will be made after December 1, 2018.
THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
The Load function takes as input a list of patterns and return a list of Package
structs describing individual packages matched by those patterns.
The LoadMode controls the amount of detail in the loaded packages.
This package is intended to replace golang.org/x/tools/go/loader.
It provides a simpler interface to the same functionality and serves
as a foundation for analysis tools that work with 'go build',
including its support for versioned packages,
and also with alternative build systems such as Bazel and Blaze.
Load passes most patterns directly to the underlying build tool,
but all patterns with the prefix "query=", where query is a
non-empty string of letters from [a-z], are reserved and may be
interpreted as query operators.
Its primary operation is to load packages through
the Metadata, TypeCheck, and WholeProgram functions,
which accept a list of string arguments that denote
one or more packages according to the conventions
of the underlying build system.
Only two query operators are currently supported, "file" and "pattern".
For example, in a 'go build' workspace,
they may be a list of package names,
or relative directory names,
or even an ad-hoc list of source files:
The query "file=path/to/file.go" matches the package or packages enclosing
the Go source file path/to/file.go. For example "file=~/go/src/fmt/print.go"
might returns the packages "fmt" and "fmt [fmt.test]".
fmt
encoding/json
./json
a.go b.go
The query "pattern=string" causes "string" to be passed directly to
the underlying build tool. In most cases this is unnecessary,
but an application can use Load("pattern=" + x) as an escaping mechanism
to ensure that x is not interpreted as a query operator if it contains '='.
For a Bazel project, the arguments use Bazel's package notation:
A third query "name=identifier" will be added soon.
It will match packages whose package declaration contains the specified identifier.
For example, "name=rand" would match the packages "math/rand" and "crypto/rand",
and "name=main" would match all executables.
@repo//project:target
//project:target
:target
target
All other query operators are reserved for future use and currently
cause Load to report an error.
An application that loads packages can thus pass its command-line
arguments directly to the loading functions and it will integrate with the
usual conventions for that project.
The Package struct provides basic information about the package, including
The result of a call to a loading function is a set of Package
objects describing the packages denoted by the arguments.
These "initial" packages are in fact the roots of a graph of Packages,
the import graph, that includes complete transitive dependencies.
Clients may traverse the import graph by following the edges in the
Package.Imports map, which relates the import paths that appear in the
package's source files to the packages they import.
- ID, a unique identifier for the package in the returned set;
- GoFiles, the names of the package's Go source files;
- Imports, a map from source import strings to the Packages they name;
- Types, the type information for the package's exported symbols;
- Syntax, the parsed syntax trees for the package's source code; and
- TypeInfo, the result of a complete type-check of the package syntax trees.
Each package has three kinds of name: ID, PkgPath, and Name.
A package's ID is an unspecified identifier that uniquely
identifies it throughout the workspace, and thus may be used as a key in
a map of packages. Clients should not interpret this string, no matter
how intelligible it looks, as its structure varies across build systems.
A package's PkgPath is the name by which the package is known to the
compiler, linker, and runtime: it is the string returned by
reflect.Type.PkgPath or fmt.Sprintf("%T", x). The PkgPath is not
necessarily unique throughout the workspace; for example, an in-package
test has the same PkgPath as the package under test.
A package's Name is the identifier that appears in the "package"
declaration at the start of each of its source files,
and is the name declared when importing it into another file.
A package whose Name is "main" is linked as an executable.
(See the documentation for type Package for the complete list of fields
and more detailed descriptions.)
The loader's three entry points, Metadata, TypeCheck, and
WholeProgram, provide increasing levels of detail.
For example,
Metadata returns only a description of each package,
its source files and imports.
Some build systems permit build steps to generate
Go source files that are then compiled.
The Packages describing such a program report
the locations of the generated files.
The process of loading packages invokes the
underlying build system to ensure that these
files are present and up-to-date.
Load(nil, "bytes", "unicode...")
Although 'go build' does not in general allow code generation,
it does in a limited form in its support for cgo.
For a package whose source files import "C", subjecting them to cgo
preprocessing, the loader reports the location of the pure-Go source
files generated by cgo. This too may entail a partial build.
Cgo processing is disabled for Metadata queries,
or when the DisableCgo option is set.
returns four Package structs describing the standard library packages
bytes, unicode, unicode/utf16, and unicode/utf8. Note that one pattern
can match multiple packages and that a package might be matched by
multiple patterns: in general it is not possible to determine which
packages correspond to which patterns.
TypeCheck additionally loads, parses, and type-checks
the source files of the initial packages,
and exposes their syntax trees and type information.
Type information for dependencies of the initial
packages is obtained not from Go source code but from
compiler-generated export data files.
Again, loading invokes the underlying build system to
ensure that these files are present and up-to-date.
Note that the list returned by Load contains only the packages matched
by the patterns. Their dependencies can be found by walking the import
graph using the Imports fields.
WholeProgram loads complete type information about
the initial packages and all of their transitive dependencies.
The Load function can be configured by passing a pointer to a Config as
the first argument. A nil Config is equivalent to the zero Config, which
causes Load to run in LoadFiles mode, collecting minimal information.
See the documentation for type Config for details.
Example:
As noted earlier, the Config.Mode controls the amount of detail
reported about the loaded packages, with each mode returning all the data of the
previous mode with some extra added. See the documentation for type LoadMode
for details.
pkgs, err := packages.TypeCheck(nil, flag.Args()...)
if err != nil { ... }
for _, pkg := range pkgs {
...
}
Most tools should pass their command-line arguments (after any flags)
uninterpreted to the loader, so that the loader can interpret them
according to the conventions of the underlying build system.
See the Example function for typical usage.
*/
package packages // import "golang.org/x/tools/go/packages"
@@ -143,12 +112,6 @@ about one package without visiting all its dependencies too, so there is
no additional asymptotic cost to providing transitive information.
(This property might not be true of a hypothetical 5th build system.)
This package provides no parse-but-don't-typecheck operation because most tools
that need only untyped syntax (such as gofmt, goimports, and golint)
seem not to care about any files other than the ones they are directly
instructed to look at. Also, it is trivial for a client to supplement
this functionality on top of a Metadata query.
In calls to TypeCheck, all initial packages, and any package that
transitively depends on one of them, must be loaded from source.
Consider A->B->C->D->E: if A,C are initial, A,B,C must be loaded from
@@ -169,12 +132,9 @@ files were nearly always missing or stale. Now that 'go build' supports
caching, all the underlying build systems can guarantee to produce
export data in a reasonable (amortized) time.
Packages that are part of a test are marked IsTest=true.
Such packages include in-package tests, external tests,
and the test "main" packages synthesized by the build system.
The latter packages are reported as first-class packages,
avoiding the need for clients (such as go/ssa) to reinvent this
generation logic.
Test "main" packages synthesized by the build system are now reported as
first-class packages, avoiding the need for clients (such as go/ssa) to
reinvent this generation logic.
One way in which go/packages is simpler than the old loader is in its
treatment of in-package tests. In-package tests are packages that
@@ -201,15 +161,13 @@ type-check again. This two-phase approach had four major problems:
in several times in sequence as is now possible in WholeProgram mode.
(TypeCheck mode has a similar one-shot restriction for a different reason.)
Early drafts of this package supported "multi-shot" operation
in the Metadata and WholeProgram modes, although this feature is not exposed
through the API and will likely be removed.
Early drafts of this package supported "multi-shot" operation.
Although it allowed clients to make a sequence of calls (or concurrent
calls) to Load, building up the graph of Packages incrementally,
it was of marginal value: it complicated the API
(since it allowed some options to vary across calls but not others),
it complicated the implementation,
it cannot be made to work in TypeCheck mode, as explained above,
it cannot be made to work in Types mode, as explained above,
and it was less efficient than making one combined call (when this is possible).
Among the clients we have inspected, none made multiple calls to load
but could not be easily and satisfactorily modified to make only a single call.
@@ -241,42 +199,25 @@ application, but not by the metadata query, so, for example:
Questions & Tasks
- Add pass-through options for the underlying query tool:
Dir string
Environ []string
Flags []string
Do away with GOROOT and don't add GOARCH/GOOS:
they are not portable concepts.
The goal is to allow users to express themselves using the conventions
- Add GOARCH/GOOS?
They are not portable concepts, but could be made portable.
Our goal has been to allow users to express themselves using the conventions
of the underlying build system: if the build system honors GOARCH
during a build and during a metadata query, then so should
applications built atop that query mechanism.
Conversely, if the target architecture of the build is determined by
command-line flags, the application must pass the relevant
command-line flags, the application can pass the relevant
flags through to the build system using a command such as:
myapp -query_flag="--cpu=amd64" -query_flag="--os=darwin"
- Build tags: where do they fit in? How does Bazel/Blaze handle them?
- Add an 'IncludeTests bool' option to include tests among the results.
This flag is needed to avoid unnecessary dependencies (and, for vgo, downloads).
Should it include/skip implied tests? (all tests are implied in go build)
Or include/skip all tests?
However, this approach is low-level, unwieldy, and non-portable.
GOOS and GOARCH seem important enough to warrant a dedicated option.
- How should we handle partial failures such as a mixture of good and
malformed patterns, existing and non-existent packages, succesful and
malformed patterns, existing and non-existent packages, successful and
failed builds, import failures, import cycles, and so on, in a call to
Load?
- Do we need a GeneratedBy map that maps the name of each generated Go
source file in Srcs to that of the original file, if known, or "" otherwise?
Or are //line directives and "Generated" comments in those files enough?
- Support bazel/blaze, not just "go list".
- Support a "contains" query: a boolean option would cause the the
pattern words to be interpreted as filenames, and the query would
return the package(s) to which the file(s) belong.
- Support bazel, blaze, and go1.10 list, not just go1.11 list.
- Handle (and test) various partial success cases, e.g.
a mixture of good packages and:
@@ -297,15 +238,4 @@ Questions & Tasks
order. I suspect this is due to the breadth-first resolution now used
by go/types. Is that a bug? Discuss with gri.
- https://github.com/golang/go/issues/25980 causes these commands to crash:
$ GOPATH=/none ./gopackages -all all
due to:
$ GOPATH=/none go list -e -test -json all
and:
$ go list -e -test ./relative/path
- Modify stringer to use go/packages, perhaps initially under flag control.
- Bug: "gopackages fmt a.go" doesn't produce an error.
*/

34
vendor/golang.org/x/tools/go/packages/example_test.go generated vendored Normal file
View File

@@ -0,0 +1,34 @@
package packages_test
import (
"flag"
"fmt"
"os"
"golang.org/x/tools/go/packages"
)
// Example demonstrates how to load the packages specified on the
// command line from source syntax.
func Example() {
flag.Parse()
// Many tools pass their command-line arguments (after any flags)
// uninterpreted to packages.Load so that it can interpret them
// according to the conventions of the underlying build system.
cfg := &packages.Config{Mode: packages.LoadSyntax}
pkgs, err := packages.Load(cfg, flag.Args()...)
if err != nil {
fmt.Fprintf(os.Stderr, "load: %v\n", err)
os.Exit(1)
}
if packages.PrintErrors(pkgs) > 0 {
os.Exit(1)
}
// Print the names of the source files
// for each package listed on the command line.
for _, pkg := range pkgs {
fmt.Println(pkg.ID, pkg.GoFiles)
}
}

68
vendor/golang.org/x/tools/go/packages/external.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
// 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.
// This file enables an external tool to intercept package requests.
// If the tool is present then its results are used in preference to
// the go list command.
package packages
import (
"bytes"
"encoding/json"
"fmt"
"os/exec"
"strings"
)
// findExternalTool returns the file path of a tool that supplies
// the build system package structure, or "" if not found."
// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
// value, otherwise it searches for a binary named gopackagesdriver on the PATH.
func findExternalDriver(cfg *Config) driver {
const toolPrefix = "GOPACKAGESDRIVER="
tool := ""
for _, env := range cfg.Env {
if val := strings.TrimPrefix(env, toolPrefix); val != env {
tool = val
}
}
if tool != "" && tool == "off" {
return nil
}
if tool == "" {
var err error
tool, err = exec.LookPath("gopackagesdriver")
if err != nil {
return nil
}
}
return func(cfg *Config, words ...string) (*driverResponse, error) {
buf := new(bytes.Buffer)
fullargs := []string{
"list",
fmt.Sprintf("-test=%t", cfg.Tests),
fmt.Sprintf("-export=%t", usesExportData(cfg)),
fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports),
}
for _, f := range cfg.BuildFlags {
fullargs = append(fullargs, fmt.Sprintf("-buildflag=%v", f))
}
fullargs = append(fullargs, "--")
fullargs = append(fullargs, words...)
cmd := exec.CommandContext(cfg.Context, tool, fullargs...)
cmd.Env = cfg.Env
cmd.Dir = cfg.Dir
cmd.Stdout = buf
cmd.Stderr = new(bytes.Buffer)
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
}
var response driverResponse
if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
return nil, err
}
return &response, nil
}
}

View File

@@ -1,43 +1,496 @@
package packages
// 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.
// This file defines the "go list" implementation of the Packages metadata query.
package packages
import (
"bytes"
"context"
"encoding/json"
"fmt"
"go/types"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"strings"
"sync"
"time"
"golang.org/x/tools/internal/gopathwalk"
"golang.org/x/tools/internal/semver"
)
// golistPackages uses the "go list" command to expand the
// pattern words and return metadata for the specified packages.
func golistPackages(ctx context.Context, gopath string, cgo, export bool, words []string) ([]*Package, error) {
// Fields must match go list;
// see $GOROOT/src/cmd/go/internal/load/pkg.go.
type jsonPackage struct {
ImportPath string
Dir string
Name string
Export string
GoFiles []string
CFiles []string
CgoFiles []string
SFiles []string
Imports []string
ImportMap map[string]string
Deps []string
TestGoFiles []string
TestImports []string
XTestGoFiles []string
XTestImports []string
ForTest string // q in a "p [q.test]" package, else ""
DepOnly bool
// debug controls verbose logging.
const debug = false
// A goTooOldError reports that the go command
// found by exec.LookPath is too old to use the new go list behavior.
type goTooOldError struct {
error
}
// goListDriver uses the go list command to interpret the patterns and produce
// the build system package structure.
// See driver for more details.
func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
var sizes types.Sizes
var sizeserr error
var sizeswg sync.WaitGroup
if cfg.Mode >= LoadTypes {
sizeswg.Add(1)
go func() {
sizes, sizeserr = getSizes(cfg)
sizeswg.Done()
}()
}
// Determine files requested in contains patterns
var containFiles []string
var packagesNamed []string
restPatterns := make([]string, 0, len(patterns))
// Extract file= and other [querytype]= patterns. Report an error if querytype
// doesn't exist.
extractQueries:
for _, pattern := range patterns {
eqidx := strings.Index(pattern, "=")
if eqidx < 0 {
restPatterns = append(restPatterns, pattern)
} else {
query, value := pattern[:eqidx], pattern[eqidx+len("="):]
switch query {
case "file":
containFiles = append(containFiles, value)
case "pattern":
restPatterns = append(restPatterns, value)
case "name":
packagesNamed = append(packagesNamed, value)
case "": // not a reserved query
restPatterns = append(restPatterns, pattern)
default:
for _, rune := range query {
if rune < 'a' || rune > 'z' { // not a reserved query
restPatterns = append(restPatterns, pattern)
continue extractQueries
}
}
// Reject all other patterns containing "="
return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern)
}
}
}
patterns = restPatterns
// TODO(matloob): Remove the definition of listfunc and just use golistPackages once go1.12 is released.
var listfunc driver
listfunc = func(cfg *Config, words ...string) (*driverResponse, error) {
response, err := golistDriverCurrent(cfg, words...)
if _, ok := err.(goTooOldError); ok {
listfunc = golistDriverFallback
return listfunc(cfg, words...)
}
listfunc = golistDriverCurrent
return response, err
}
var response *driverResponse
var err error
// see if we have any patterns to pass through to go list.
if len(restPatterns) > 0 {
response, err = listfunc(cfg, restPatterns...)
if err != nil {
return nil, err
}
} else {
response = &driverResponse{}
}
sizeswg.Wait()
if sizeserr != nil {
return nil, sizeserr
}
// types.SizesFor always returns nil or a *types.StdSizes
response.Sizes, _ = sizes.(*types.StdSizes)
if len(containFiles) == 0 && len(packagesNamed) == 0 {
return response, nil
}
seenPkgs := make(map[string]*Package) // for deduplication. different containing queries could produce same packages
for _, pkg := range response.Packages {
seenPkgs[pkg.ID] = pkg
}
addPkg := func(p *Package) {
if _, ok := seenPkgs[p.ID]; ok {
return
}
seenPkgs[p.ID] = p
response.Packages = append(response.Packages, p)
}
containsResults, err := runContainsQueries(cfg, listfunc, addPkg, containFiles)
if err != nil {
return nil, err
}
response.Roots = append(response.Roots, containsResults...)
namedResults, err := runNamedQueries(cfg, listfunc, addPkg, packagesNamed)
if err != nil {
return nil, err
}
response.Roots = append(response.Roots, namedResults...)
return response, nil
}
func runContainsQueries(cfg *Config, driver driver, addPkg func(*Package), queries []string) ([]string, error) {
var results []string
for _, query := range queries {
// TODO(matloob): Do only one query per directory.
fdir := filepath.Dir(query)
cfg.Dir = fdir
dirResponse, err := driver(cfg, ".")
if err != nil {
return nil, err
}
isRoot := make(map[string]bool, len(dirResponse.Roots))
for _, root := range dirResponse.Roots {
isRoot[root] = true
}
for _, pkg := range dirResponse.Packages {
// Add any new packages to the main set
// We don't bother to filter packages that will be dropped by the changes of roots,
// that will happen anyway during graph construction outside this function.
// Over-reporting packages is not a problem.
addPkg(pkg)
// if the package was not a root one, it cannot have the file
if !isRoot[pkg.ID] {
continue
}
for _, pkgFile := range pkg.GoFiles {
if filepath.Base(query) == filepath.Base(pkgFile) {
results = append(results, pkg.ID)
break
}
}
}
}
return results, nil
}
// modCacheRegexp splits a path in a module cache into module, module version, and package.
var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
func runNamedQueries(cfg *Config, driver driver, addPkg func(*Package), queries []string) ([]string, error) {
// calling `go env` isn't free; bail out if there's nothing to do.
if len(queries) == 0 {
return nil, nil
}
// Determine which directories are relevant to scan.
roots, modRoot, err := roots(cfg)
if err != nil {
return nil, err
}
// Scan the selected directories. Simple matches, from GOPATH/GOROOT
// or the local module, can simply be "go list"ed. Matches from the
// module cache need special treatment.
var matchesMu sync.Mutex
var simpleMatches, modCacheMatches []string
add := func(root gopathwalk.Root, dir string) {
// Walk calls this concurrently; protect the result slices.
matchesMu.Lock()
defer matchesMu.Unlock()
path := dir[len(root.Path)+1:]
if pathMatchesQueries(path, queries) {
switch root.Type {
case gopathwalk.RootModuleCache:
modCacheMatches = append(modCacheMatches, path)
case gopathwalk.RootCurrentModule:
// We'd need to read go.mod to find the full
// import path. Relative's easier.
rel, err := filepath.Rel(cfg.Dir, dir)
if err != nil {
// This ought to be impossible, since
// we found dir in the current module.
panic(err)
}
simpleMatches = append(simpleMatches, "./"+rel)
case gopathwalk.RootGOPATH, gopathwalk.RootGOROOT:
simpleMatches = append(simpleMatches, path)
}
}
}
startWalk := time.Now()
gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != "", Debug: debug})
if debug {
log.Printf("%v for walk", time.Since(startWalk))
}
// Weird special case: the top-level package in a module will be in
// whatever directory the user checked the repository out into. It's
// more reasonable for that to not match the package name. So, if there
// are any Go files in the mod root, query it just to be safe.
if modRoot != "" {
rel, err := filepath.Rel(cfg.Dir, modRoot)
if err != nil {
panic(err) // See above.
}
files, err := ioutil.ReadDir(modRoot)
for _, f := range files {
if strings.HasSuffix(f.Name(), ".go") {
simpleMatches = append(simpleMatches, rel)
break
}
}
}
var results []string
addResponse := func(r *driverResponse) {
for _, pkg := range r.Packages {
addPkg(pkg)
for _, name := range queries {
if pkg.Name == name {
results = append(results, pkg.ID)
break
}
}
}
}
if len(simpleMatches) != 0 {
resp, err := driver(cfg, simpleMatches...)
if err != nil {
return nil, err
}
addResponse(resp)
}
// Module cache matches are tricky. We want to avoid downloading new
// versions of things, so we need to use the ones present in the cache.
// go list doesn't accept version specifiers, so we have to write out a
// temporary module, and do the list in that module.
if len(modCacheMatches) != 0 {
// Collect all the matches, deduplicating by major version
// and preferring the newest.
type modInfo struct {
mod string
major string
}
mods := make(map[modInfo]string)
var imports []string
for _, modPath := range modCacheMatches {
matches := modCacheRegexp.FindStringSubmatch(modPath)
mod, ver := filepath.ToSlash(matches[1]), matches[2]
importPath := filepath.ToSlash(filepath.Join(matches[1], matches[3]))
major := semver.Major(ver)
if prevVer, ok := mods[modInfo{mod, major}]; !ok || semver.Compare(ver, prevVer) > 0 {
mods[modInfo{mod, major}] = ver
}
imports = append(imports, importPath)
}
// Build the temporary module.
var gomod bytes.Buffer
gomod.WriteString("module modquery\nrequire (\n")
for mod, version := range mods {
gomod.WriteString("\t" + mod.mod + " " + version + "\n")
}
gomod.WriteString(")\n")
tmpCfg := *cfg
// We're only trying to look at stuff in the module cache, so
// disable the network. This should speed things up, and has
// prevented errors in at least one case, #28518.
tmpCfg.Env = append(append([]string{"GOPROXY=off"}, cfg.Env...))
var err error
tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery")
if err != nil {
return nil, err
}
defer os.RemoveAll(tmpCfg.Dir)
if err := ioutil.WriteFile(filepath.Join(tmpCfg.Dir, "go.mod"), gomod.Bytes(), 0777); err != nil {
return nil, fmt.Errorf("writing go.mod for module cache query: %v", err)
}
// Run the query, using the import paths calculated from the matches above.
resp, err := driver(&tmpCfg, imports...)
if err != nil {
return nil, fmt.Errorf("querying module cache matches: %v", err)
}
addResponse(resp)
}
return results, nil
}
func getSizes(cfg *Config) (types.Sizes, error) {
stdout, err := invokeGo(cfg, "env", "GOARCH")
if err != nil {
return nil, err
}
goarch := strings.TrimSpace(stdout.String())
// Assume "gc" because SizesFor doesn't respond to other compilers.
// TODO(matloob): add support for gccgo as needed.
return types.SizesFor("gc", goarch), nil
}
// roots selects the appropriate paths to walk based on the passed-in configuration,
// particularly the environment and the presence of a go.mod in cfg.Dir's parents.
func roots(cfg *Config) ([]gopathwalk.Root, string, error) {
stdout, err := invokeGo(cfg, "env", "GOROOT", "GOPATH", "GOMOD")
if err != nil {
return nil, "", err
}
fields := strings.Split(stdout.String(), "\n")
if len(fields) != 4 || len(fields[3]) != 0 {
return nil, "", fmt.Errorf("go env returned unexpected output: %q", stdout.String())
}
goroot, gopath, gomod := fields[0], filepath.SplitList(fields[1]), fields[2]
var modDir string
if gomod != "" {
modDir = filepath.Dir(gomod)
}
var roots []gopathwalk.Root
// Always add GOROOT.
roots = append(roots, gopathwalk.Root{filepath.Join(goroot, "/src"), gopathwalk.RootGOROOT})
// If modules are enabled, scan the module dir.
if modDir != "" {
roots = append(roots, gopathwalk.Root{modDir, gopathwalk.RootCurrentModule})
}
// Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode.
for _, p := range gopath {
if modDir != "" {
roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache})
} else {
roots = append(roots, gopathwalk.Root{filepath.Join(p, "/src"), gopathwalk.RootGOPATH})
}
}
return roots, modDir, nil
}
// These functions were copied from goimports. See further documentation there.
// pathMatchesQueries is adapted from pkgIsCandidate.
// TODO: is it reasonable to do Contains here, rather than an exact match on a path component?
func pathMatchesQueries(path string, queries []string) bool {
lastTwo := lastTwoComponents(path)
for _, query := range queries {
if strings.Contains(lastTwo, query) {
return true
}
if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(query) {
lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
if strings.Contains(lastTwo, query) {
return true
}
}
}
return false
}
// lastTwoComponents returns at most the last two path components
// of v, using either / or \ as the path separator.
func lastTwoComponents(v string) string {
nslash := 0
for i := len(v) - 1; i >= 0; i-- {
if v[i] == '/' || v[i] == '\\' {
nslash++
if nslash == 2 {
return v[i:]
}
}
}
return v
}
func hasHyphenOrUpperASCII(s string) bool {
for i := 0; i < len(s); i++ {
b := s[i]
if b == '-' || ('A' <= b && b <= 'Z') {
return true
}
}
return false
}
func lowerASCIIAndRemoveHyphen(s string) (ret string) {
buf := make([]byte, 0, len(s))
for i := 0; i < len(s); i++ {
b := s[i]
switch {
case b == '-':
continue
case 'A' <= b && b <= 'Z':
buf = append(buf, b+('a'-'A'))
default:
buf = append(buf, b)
}
}
return string(buf)
}
// Fields must match go list;
// see $GOROOT/src/cmd/go/internal/load/pkg.go.
type jsonPackage struct {
ImportPath string
Dir string
Name string
Export string
GoFiles []string
CompiledGoFiles []string
CFiles []string
CgoFiles []string
CXXFiles []string
MFiles []string
HFiles []string
FFiles []string
SFiles []string
SwigFiles []string
SwigCXXFiles []string
SysoFiles []string
Imports []string
ImportMap map[string]string
Deps []string
TestGoFiles []string
TestImports []string
XTestGoFiles []string
XTestImports []string
ForTest string // q in a "p [q.test]" package, else ""
DepOnly bool
Error *jsonPackageError
}
type jsonPackageError struct {
ImportStack []string
Pos string
Err string
}
func otherFiles(p *jsonPackage) [][]string {
return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles}
}
// golistDriverCurrent uses the "go list" command to expand the
// pattern words and return metadata for the specified packages.
// dir may be "" and env may be nil, as per os/exec.Command.
func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) {
// go list uses the following identifiers in ImportPath and Imports:
//
// "p" -- importable package or main (command)
@@ -51,61 +504,81 @@ func golistPackages(ctx context.Context, gopath string, cgo, export bool, words
// Run "go list" for complete
// information on the specified packages.
buf, err := golist(ctx, gopath, cgo, export, words)
buf, err := invokeGo(cfg, golistargs(cfg, words)...)
if err != nil {
return nil, err
}
seen := make(map[string]*jsonPackage)
// Decode the JSON and convert it to Package form.
var result []*Package
var response driverResponse
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)
}
// Bad package?
if p.Name == "" {
// This could be due to:
// - no such package
// - package directory contains no Go source files
// - all package declarations are mangled
// - and possibly other things.
//
// For now, we throw it away and let later
// stages rediscover the problem, but this
// discards the error message computed by go list
// and computes a new one---by different logic:
// if only one of the package declarations is
// bad, for example, should we report an error
// in Metadata mode?
// Unless we parse and typecheck, we might not
// notice there's a problem.
//
// Perhaps we should save a map of PackageID to
// errors for such cases.
if p.ImportPath == "" {
// The documentation for go list says that “[e]rroneous packages will have
// a non-empty ImportPath”. If for some reason it comes back empty, we
// prefer to error out rather than silently discarding data or handing
// back a package without any way to refer to it.
if p.Error != nil {
return nil, Error{
Pos: p.Error.Pos,
Msg: p.Error.Err,
}
}
return nil, fmt.Errorf("package missing import path: %+v", p)
}
if old, found := seen[p.ImportPath]; found {
if !reflect.DeepEqual(p, old) {
return nil, fmt.Errorf("go list repeated package %v with different values", p.ImportPath)
}
// skip the duplicate
continue
}
seen[p.ImportPath] = p
id := p.ImportPath
pkg := &Package{
Name: p.Name,
ID: p.ImportPath,
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
OtherFiles: absJoin(p.Dir, otherFiles(p)...),
}
// Workaround for github.com/golang/go/issues/28749.
// TODO(adonovan): delete before go1.12 release.
out := pkg.CompiledGoFiles[:0]
for _, f := range pkg.CompiledGoFiles {
if strings.HasSuffix(f, ".s") {
continue
}
out = append(out, f)
}
pkg.CompiledGoFiles = out
// Extract the PkgPath from the package's ID.
pkgpath := id
if i := strings.IndexByte(id, ' '); i >= 0 {
pkgpath = id[:i]
if i := strings.IndexByte(pkg.ID, ' '); i >= 0 {
pkg.PkgPath = pkg.ID[:i]
} else {
pkg.PkgPath = pkg.ID
}
// Is this a test?
// ("foo [foo.test]" package or "foo.test" command)
isTest := p.ForTest != "" || strings.HasSuffix(pkgpath, ".test")
if pkgpath == "unsafe" {
p.GoFiles = nil // ignore fake unsafe.go file
if pkg.PkgPath == "unsafe" {
pkg.GoFiles = nil // ignore fake unsafe.go file
}
export := p.Export
if export != "" && !filepath.IsAbs(export) {
export = filepath.Join(p.Dir, export)
// Assume go list emits only absolute paths for Dir.
if p.Dir != "" && !filepath.IsAbs(p.Dir) {
log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir)
}
if p.Export != "" && !filepath.IsAbs(p.Export) {
pkg.ExportFile = filepath.Join(p.Dir, p.Export)
} else {
pkg.ExportFile = p.Export
}
// imports
@@ -116,40 +589,40 @@ func golistPackages(ctx context.Context, gopath string, cgo, export bool, words
for _, id := range p.Imports {
ids[id] = true
}
imports := make(map[string]string)
pkg.Imports = make(map[string]*Package)
for path, id := range p.ImportMap {
imports[path] = id // non-identity import
pkg.Imports[path] = &Package{ID: id} // non-identity import
delete(ids, id)
}
for id := range ids {
// Go issue 26136: go list omits imports in cgo-generated files.
if id == "C" && cgo {
imports["unsafe"] = "unsafe"
imports["syscall"] = "syscall"
if pkgpath != "runtime/cgo" {
imports["runtime/cgo"] = "runtime/cgo"
}
if id == "C" {
continue
}
imports[id] = id // identity import
pkg.Imports[id] = &Package{ID: id} // identity import
}
if !p.DepOnly {
response.Roots = append(response.Roots, pkg.ID)
}
pkg := &Package{
ID: id,
Name: p.Name,
PkgPath: pkgpath,
IsTest: isTest,
Srcs: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
OtherSrcs: absJoin(p.Dir, p.SFiles, p.CFiles),
imports: imports,
export: export,
indirect: p.DepOnly,
// Work around for pre-go.1.11 versions of go list.
// TODO(matloob): they should be handled by the fallback.
// Can we delete this?
if len(pkg.CompiledGoFiles) == 0 {
pkg.CompiledGoFiles = pkg.GoFiles
}
result = append(result, pkg)
if p.Error != nil {
pkg.Errors = append(pkg.Errors, Error{
Pos: p.Error.Pos,
Msg: p.Error.Err,
})
}
response.Packages = append(response.Packages, pkg)
}
return result, nil
return &response, nil
}
// absJoin absolutizes and flattens the lists of files.
@@ -165,70 +638,85 @@ func absJoin(dir string, fileses ...[]string) (res []string) {
return res
}
// golist returns the JSON-encoded result of a "go list args..." query.
func golist(ctx context.Context, gopath string, cgo, export bool, args []string) (*bytes.Buffer, error) {
out := new(bytes.Buffer)
if len(args) == 0 {
return out, nil
func golistargs(cfg *Config, words []string) []string {
fullargs := []string{
"list", "-e", "-json", "-compiled",
fmt.Sprintf("-test=%t", cfg.Tests),
fmt.Sprintf("-export=%t", usesExportData(cfg)),
fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports),
}
fullargs = append(fullargs, cfg.BuildFlags...)
fullargs = append(fullargs, "--")
fullargs = append(fullargs, words...)
return fullargs
}
const test = true // TODO(adonovan): expose a flag for this.
cmd := exec.CommandContext(ctx, "go", append([]string{
"list",
"-e",
fmt.Sprintf("-cgo=%t", cgo),
fmt.Sprintf("-test=%t", test),
fmt.Sprintf("-export=%t", export),
"-deps",
"-json",
"--",
}, args...)...)
cmd.Env = append(append([]string(nil), os.Environ()...), "GOPATH="+gopath)
if !cgo {
cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
// invokeGo returns the stdout of a go command invocation.
func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) {
if debug {
defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(cfg, args...)) }(time.Now())
}
cmd.Stdout = out
cmd.Stderr = new(bytes.Buffer)
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd := exec.CommandContext(cfg.Context, "go", args...)
// On darwin the cwd gets resolved to the real path, which breaks anything that
// expects the working directory to keep the original path, including the
// go command when dealing with modules.
// The Go stdlib has a special feature where if the cwd and the PWD are the
// same node then it trusts the PWD, so by setting it in the env for the child
// process we fix up all the paths returned by the go command.
cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir)
cmd.Dir = cfg.Dir
cmd.Stdout = stdout
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
exitErr, ok := err.(*exec.ExitError)
if !ok {
// Catastrophic error:
// - executable not found
// - context cancellation
return nil, fmt.Errorf("couldn't exec 'go list': %s %T", err, err)
return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
}
// Old go list?
if strings.Contains(fmt.Sprint(cmd.Stderr), "flag provided but not defined") {
return nil, fmt.Errorf("unsupported version of go list: %s: %s", exitErr, cmd.Stderr)
// Old go version?
if strings.Contains(stderr.String(), "flag provided but not defined") {
return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)}
}
// Export mode entails a build.
// If that build fails, errors appear on stderr
// (despite the -e flag) and the Export field is blank.
// Do not fail in that case.
if !export {
return nil, fmt.Errorf("go list: %s: %s", exitErr, cmd.Stderr)
if !usesExportData(cfg) {
return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr)
}
}
// Print standard error output from "go list".
// Due to the -e flag, this should be empty.
// However, in -export mode it contains build errors.
// Should go list save build errors in the Package.Error JSON field?
// See https://github.com/golang/go/issues/26319.
// If so, then we should continue to print stderr as go list
// will be silent unless something unexpected happened.
// If not, perhaps we should suppress it to reduce noise.
if stderr := fmt.Sprint(cmd.Stderr); stderr != "" {
fmt.Fprintf(os.Stderr, "go list stderr <<%s>>\n", stderr)
// As of writing, go list -export prints some non-fatal compilation
// errors to stderr, even with -e set. We would prefer that it put
// them in the Package.Error JSON (see http://golang.org/issue/26319).
// In the meantime, there's nowhere good to put them, but they can
// be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS
// is set.
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" {
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cfg, args...), stderr)
}
// debugging
if false {
fmt.Fprintln(os.Stderr, out)
fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(cfg, args...), stdout)
}
return out, nil
return stdout, nil
}
func cmdDebugStr(cfg *Config, args ...string) string {
env := make(map[string]string)
for _, kv := range cfg.Env {
split := strings.Split(kv, "=")
k, v := split[0], split[1]
env[k] = v
}
return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], args)
}

View File

@@ -0,0 +1,450 @@
// 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, isRoot bool) {
id := p.ImportPath
if allPkgs[id] {
return
}
allPkgs[id] = true
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, true)
}
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, false)
}
for _, v := range needsTestVariant {
createTestVariants(&response, v.pkg, v.xtestPkg)
}
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) (initial []*jsonPackage, deps []string, err error) {
buf, err := invokeGo(cfg, golistArgsFallback(cfg, words)...)
if err != nil {
return nil, nil, err
}
depsSet := make(map[string]bool)
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)
}
initial = append(initial, 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 initial {
delete(depsSet, orig.ImportPath)
}
deps = make([]string, 0, len(depsSet))
for dep := range depsSet {
deps = append(deps, dep)
}
sort.Strings(deps) // ensure output is deterministic
return initial, 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)
}

View File

@@ -0,0 +1,318 @@
// 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.
// This file is largely based on the Go 1.10-era cmd/go/internal/test/test.go
// testmain generation code.
package packages
import (
"errors"
"fmt"
"go/ast"
"go/doc"
"go/parser"
"go/token"
"os"
"sort"
"strings"
"text/template"
"unicode"
"unicode/utf8"
)
// TODO(matloob): Delete this file once Go 1.12 is released.
// This file complements golist_fallback.go by providing
// support for generating testmains.
func generateTestmain(out string, testPkg, xtestPkg *Package) (extraimports, extradeps []string, err error) {
testFuncs, err := loadTestFuncs(testPkg, xtestPkg)
if err != nil {
return nil, nil, err
}
extraimports = []string{"testing", "testing/internal/testdeps"}
if testFuncs.TestMain == nil {
extraimports = append(extraimports, "os")
}
// Transitive dependencies of ("testing", "testing/internal/testdeps").
// os is part of the transitive closure so it and its transitive dependencies are
// included regardless of whether it's imported in the template below.
extradeps = []string{
"errors",
"internal/cpu",
"unsafe",
"internal/bytealg",
"internal/race",
"runtime/internal/atomic",
"runtime/internal/sys",
"runtime",
"sync/atomic",
"sync",
"io",
"unicode",
"unicode/utf8",
"bytes",
"math",
"syscall",
"time",
"internal/poll",
"internal/syscall/unix",
"internal/testlog",
"os",
"math/bits",
"strconv",
"reflect",
"fmt",
"sort",
"strings",
"flag",
"runtime/debug",
"context",
"runtime/trace",
"testing",
"bufio",
"regexp/syntax",
"regexp",
"compress/flate",
"encoding/binary",
"hash",
"hash/crc32",
"compress/gzip",
"path/filepath",
"io/ioutil",
"text/tabwriter",
"runtime/pprof",
"testing/internal/testdeps",
}
return extraimports, extradeps, writeTestmain(out, testFuncs)
}
// The following is adapted from the cmd/go testmain generation code.
// isTestFunc tells whether fn has the type of a testing function. arg
// specifies the parameter type we look for: B, M or T.
func isTestFunc(fn *ast.FuncDecl, arg string) bool {
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
fn.Type.Params.List == nil ||
len(fn.Type.Params.List) != 1 ||
len(fn.Type.Params.List[0].Names) > 1 {
return false
}
ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
if !ok {
return false
}
// We can't easily check that the type is *testing.M
// because we don't know how testing has been imported,
// but at least check that it's *M or *something.M.
// Same applies for B and T.
if name, ok := ptr.X.(*ast.Ident); ok && name.Name == arg {
return true
}
if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == arg {
return true
}
return false
}
// isTest tells whether name looks like a test (or benchmark, according to prefix).
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
// We don't want TesticularCancer.
func isTest(name, prefix string) bool {
if !strings.HasPrefix(name, prefix) {
return false
}
if len(name) == len(prefix) { // "Test" is ok
return true
}
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
}
// loadTestFuncs returns the testFuncs describing the tests that will be run.
func loadTestFuncs(ptest, pxtest *Package) (*testFuncs, error) {
t := &testFuncs{
TestPackage: ptest,
XTestPackage: pxtest,
}
for _, file := range ptest.GoFiles {
if !strings.HasSuffix(file, "_test.go") {
continue
}
if err := t.load(file, "_test", &t.ImportTest, &t.NeedTest); err != nil {
return nil, err
}
}
if pxtest != nil {
for _, file := range pxtest.GoFiles {
if err := t.load(file, "_xtest", &t.ImportXtest, &t.NeedXtest); err != nil {
return nil, err
}
}
}
return t, nil
}
// writeTestmain writes the _testmain.go file for t to the file named out.
func writeTestmain(out string, t *testFuncs) error {
f, err := os.Create(out)
if err != nil {
return err
}
defer f.Close()
if err := testmainTmpl.Execute(f, t); err != nil {
return err
}
return nil
}
type testFuncs struct {
Tests []testFunc
Benchmarks []testFunc
Examples []testFunc
TestMain *testFunc
TestPackage *Package
XTestPackage *Package
ImportTest bool
NeedTest bool
ImportXtest bool
NeedXtest bool
}
// Tested returns the name of the package being tested.
func (t *testFuncs) Tested() string {
return t.TestPackage.Name
}
type testFunc struct {
Package string // imported package name (_test or _xtest)
Name string // function name
Output string // output, for examples
Unordered bool // output is allowed to be unordered.
}
func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
var fset = token.NewFileSet()
f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
return errors.New("failed to parse test file " + filename)
}
for _, d := range f.Decls {
n, ok := d.(*ast.FuncDecl)
if !ok {
continue
}
if n.Recv != nil {
continue
}
name := n.Name.String()
switch {
case name == "TestMain":
if isTestFunc(n, "T") {
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
continue
}
err := checkTestFunc(fset, n, "M")
if err != nil {
return err
}
if t.TestMain != nil {
return errors.New("multiple definitions of TestMain")
}
t.TestMain = &testFunc{pkg, name, "", false}
*doImport, *seen = true, true
case isTest(name, "Test"):
err := checkTestFunc(fset, n, "T")
if err != nil {
return err
}
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
case isTest(name, "Benchmark"):
err := checkTestFunc(fset, n, "B")
if err != nil {
return err
}
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
*doImport, *seen = true, true
}
}
ex := doc.Examples(f)
sort.Slice(ex, func(i, j int) bool { return ex[i].Order < ex[j].Order })
for _, e := range ex {
*doImport = true // import test file whether executed or not
if e.Output == "" && !e.EmptyOutput {
// Don't run examples with no output.
continue
}
t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
*seen = true
}
return nil
}
func checkTestFunc(fset *token.FileSet, fn *ast.FuncDecl, arg string) error {
if !isTestFunc(fn, arg) {
name := fn.Name.String()
pos := fset.Position(fn.Pos())
return fmt.Errorf("%s: wrong signature for %s, must be: func %s(%s *testing.%s)", pos, name, name, strings.ToLower(arg), arg)
}
return nil
}
var testmainTmpl = template.Must(template.New("main").Parse(`
package main
import (
{{if not .TestMain}}
"os"
{{end}}
"testing"
"testing/internal/testdeps"
{{if .ImportTest}}
{{if .NeedTest}}_test{{else}}_{{end}} {{.TestPackage.PkgPath | printf "%q"}}
{{end}}
{{if .ImportXtest}}
{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.XTestPackage.PkgPath | printf "%q"}}
{{end}}
)
var tests = []testing.InternalTest{
{{range .Tests}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var benchmarks = []testing.InternalBenchmark{
{{range .Benchmarks}}
{"{{.Name}}", {{.Package}}.{{.Name}}},
{{end}}
}
var examples = []testing.InternalExample{
{{range .Examples}}
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
{{end}}
}
func init() {
testdeps.ImportPath = {{.TestPackage.PkgPath | printf "%q"}}
}
func main() {
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
{{with .TestMain}}
{{.Package}}.{{.Name}}(m)
{{else}}
os.Exit(m.Run())
{{end}}
}
`))

View File

@@ -9,6 +9,7 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"go/types"
@@ -26,16 +27,23 @@ import (
// flags
var (
depsFlag = flag.Bool("deps", false, "show dependencies too")
cgoFlag = flag.Bool("cgo", true, "process cgo files")
mode = flag.String("mode", "metadata", "mode (one of metadata, typecheck, wholeprogram)")
private = flag.Bool("private", false, "show non-exported declarations too")
depsFlag = flag.Bool("deps", false, "show dependencies too")
testFlag = flag.Bool("test", false, "include any tests implied by the patterns")
mode = flag.String("mode", "imports", "mode (one of files, imports, types, syntax, allsyntax)")
private = flag.Bool("private", false, "show non-exported declarations too")
printJSON = flag.Bool("json", false, "print package in JSON form")
cpuprofile = flag.String("cpuprofile", "", "write CPU profile to this file")
memprofile = flag.String("memprofile", "", "write memory profile to this file")
traceFlag = flag.String("trace", "", "write trace log to this file")
buildFlags stringListValue
)
func init() {
flag.Var(&buildFlags, "buildflag", "pass argument to underlying build system (may be repeated)")
}
func usage() {
fmt.Fprintln(os.Stderr, `Usage: gopackages [-deps] [-cgo] [-mode=...] [-private] package...
@@ -102,25 +110,30 @@ func main() {
}()
}
// Load, parse, and type-check the packages named on the command line.
cfg := &packages.Config{
Mode: packages.LoadSyntax,
Tests: *testFlag,
BuildFlags: buildFlags,
}
// -mode flag
load := packages.TypeCheck
switch strings.ToLower(*mode) {
case "metadata":
load = packages.Metadata
case "typecheck":
load = packages.TypeCheck
case "wholeprogram":
load = packages.WholeProgram
case "files":
cfg.Mode = packages.LoadFiles
case "imports":
cfg.Mode = packages.LoadImports
case "types":
cfg.Mode = packages.LoadTypes
case "syntax":
cfg.Mode = packages.LoadSyntax
case "allsyntax":
cfg.Mode = packages.LoadAllSyntax
default:
log.Fatalf("invalid mode: %s", *mode)
}
// Load, parse, and type-check the packages named on the command line.
opts := &packages.Options{
Error: func(error) {}, // we'll take responsibility for printing errors
DisableCgo: !*cgoFlag,
}
lpkgs, err := load(opts, flag.Args()...)
lpkgs, err := packages.Load(cfg, flag.Args()...)
if err != nil {
log.Fatal(err)
}
@@ -161,11 +174,15 @@ func main() {
}
func print(lpkg *packages.Package) {
if *printJSON {
data, _ := json.MarshalIndent(lpkg, "", "\t")
os.Stdout.Write(data)
return
}
// title
var kind string
if lpkg.IsTest {
kind = "test "
}
// TODO(matloob): If IsTest is added back print "test command" or
// "test package" for packages with IsTest == true.
if lpkg.Name == "main" {
kind += "command"
} else {
@@ -173,24 +190,23 @@ func print(lpkg *packages.Package) {
}
fmt.Printf("Go %s %q:\n", kind, lpkg.ID) // unique ID
fmt.Printf("\tpackage %s\n", lpkg.Name)
fmt.Printf("\treflect.Type.PkgPath %q\n", lpkg.PkgPath)
// characterize type info
if lpkg.Type == nil {
if lpkg.Types == nil {
fmt.Printf("\thas no exported type info\n")
} else if !lpkg.Type.Complete() {
} else if !lpkg.Types.Complete() {
fmt.Printf("\thas incomplete exported type info\n")
} else if len(lpkg.Files) == 0 {
} else if len(lpkg.Syntax) == 0 {
fmt.Printf("\thas complete exported type info\n")
} else {
fmt.Printf("\thas complete exported type info and typed ASTs\n")
}
if lpkg.Type != nil && lpkg.IllTyped && len(lpkg.Errors) == 0 {
if lpkg.Types != nil && lpkg.IllTyped && len(lpkg.Errors) == 0 {
fmt.Printf("\thas an error among its dependencies\n")
}
// source files
for _, src := range lpkg.Srcs {
for _, src := range lpkg.GoFiles {
fmt.Printf("\tfile %s\n", src)
}
@@ -216,9 +232,9 @@ func print(lpkg *packages.Package) {
}
// package members (TypeCheck or WholeProgram mode)
if lpkg.Type != nil {
qual := types.RelativeTo(lpkg.Type)
scope := lpkg.Type.Scope()
if lpkg.Types != nil {
qual := types.RelativeTo(lpkg.Types)
scope := lpkg.Types.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
if !obj.Exported() && !*private {
@@ -239,3 +255,18 @@ func print(lpkg *packages.Package) {
fmt.Println()
}
// stringListValue is a flag.Value that accumulates strings.
// e.g. --flag=one --flag=two would produce []string{"one", "two"}.
type stringListValue []string
func newStringListValue(val []string, p *[]string) *stringListValue {
*p = val
return (*stringListValue)(p)
}
func (ss *stringListValue) Get() interface{} { return []string(*ss) }
func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) }
func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
// 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.
// +build !go1.11
package packages_test
func init() {
usesOldGolist = true
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
// 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.
// +build cgo
package packages_test
import (
"golang.org/x/tools/go/packages"
"runtime"
"strings"
"testing"
)
func TestLoadImportsC(t *testing.T) {
// This test checks that when a package depends on the
// test variant of "syscall", "unsafe", or "runtime/cgo", that dependency
// is not removed when those packages are added when it imports "C".
//
// For this test to work, the external test of syscall must have a dependency
// on net, and net must import "syscall" and "C".
if runtime.GOOS == "windows" {
t.Skipf("skipping on windows; packages on windows do not satisfy conditions for test.")
}
if runtime.GOOS == "plan9" {
// See https://github.com/golang/go/issues/27100.
t.Skip(`skipping on plan9; for some reason "net [syscall.test]" is not loaded`)
}
cfg := &packages.Config{
Mode: packages.LoadImports,
Tests: true,
}
initial, err := packages.Load(cfg, "syscall", "net")
if err != nil {
t.Fatalf("failed to load imports: %v", err)
}
_, all := importGraph(initial)
for _, test := range []struct {
pattern string
wantImport string // an import to check for
}{
{"net", "syscall:syscall"},
{"net [syscall.test]", "syscall:syscall [syscall.test]"},
{"syscall_test [syscall.test]", "net:net [syscall.test]"},
} {
// Test the import paths.
pkg := all[test.pattern]
if pkg == nil {
t.Errorf("package %q not loaded", test.pattern)
continue
}
if imports := strings.Join(imports(pkg), " "); !strings.Contains(imports, test.wantImport) {
t.Errorf("package %q: got \n%s, \nwant to have %s", test.pattern, imports, test.wantImport)
}
}
}

View File

@@ -0,0 +1,328 @@
// 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 packagestest
import (
"fmt"
"go/token"
"reflect"
"regexp"
"strings"
"golang.org/x/tools/go/expect"
)
const (
markMethod = "mark"
eofIdentifier = "EOF"
)
// Expect invokes the supplied methods for all expectation notes found in
// the exported source files.
//
// All exported go source files are parsed to collect the expectation
// notes.
// See the documentation for expect.Parse for how the notes are collected
// and parsed.
//
// The methods are supplied as a map of name to function, and those functions
// will be matched against the expectations by name.
// Notes with no matching function will be skipped, and functions with no
// matching notes will not be invoked.
// If there are no registered markers yet, a special pass will be run first
// which adds any markers declared with @mark(Name, pattern) or @name. These
// call the Mark method to add the marker to the global set.
// You can register the "mark" method to override these in your own call to
// Expect. The bound Mark function is usable directly in your method map, so
// exported.Expect(map[string]interface{}{"mark": exported.Mark})
// replicates the built in behavior.
//
// Method invocation
//
// When invoking a method the expressions in the parameter list need to be
// converted to values to be passed to the method.
// There are a very limited set of types the arguments are allowed to be.
// expect.Comment : passed the Comment instance being evaluated.
// string : can be supplied either a string literal or an identifier.
// int : can only be supplied an integer literal.
// token.Pos : has a file position calculated as described below.
// token.Position : has a file position calculated as described below.
//
// Position calculation
//
// There is some extra handling when a parameter is being coerced into a
// token.Pos, token.Position or Range type argument.
//
// If the parameter is an identifier, it will be treated as the name of an
// marker to look up (as if markers were global variables).
//
// If it is a string or regular expression, then it will be passed to
// expect.MatchBefore to look up a match in the line at which it was declared.
//
// It is safe to call this repeatedly with different method sets, but it is
// not safe to call it concurrently.
func (e *Exported) Expect(methods map[string]interface{}) error {
if err := e.getNotes(); err != nil {
return err
}
if err := e.getMarkers(); err != nil {
return err
}
var err error
ms := make(map[string]method, len(methods))
for name, f := range methods {
mi := method{f: reflect.ValueOf(f)}
mi.converters = make([]converter, mi.f.Type().NumIn())
for i := 0; i < len(mi.converters); i++ {
mi.converters[i], err = e.buildConverter(mi.f.Type().In(i))
if err != nil {
return fmt.Errorf("invalid method %v: %v", name, err)
}
}
ms[name] = mi
}
for _, n := range e.notes {
if n.Args == nil {
// simple identifier form, convert to a call to mark
n = &expect.Note{
Pos: n.Pos,
Name: markMethod,
Args: []interface{}{n.Name, n.Name},
}
}
mi, ok := ms[n.Name]
if !ok {
continue
}
params := make([]reflect.Value, len(mi.converters))
args := n.Args
for i, convert := range mi.converters {
params[i], args, err = convert(n, args)
if err != nil {
return fmt.Errorf("%v: %v", e.fset.Position(n.Pos), err)
}
}
if len(args) > 0 {
return fmt.Errorf("%v: unwanted args got %+v extra", e.fset.Position(n.Pos), args)
}
//TODO: catch the error returned from the method
mi.f.Call(params)
}
return nil
}
type Range struct {
Start token.Pos
End token.Pos
}
// Mark adds a new marker to the known set.
func (e *Exported) Mark(name string, r Range) {
if e.markers == nil {
e.markers = make(map[string]Range)
}
e.markers[name] = r
}
func (e *Exported) getNotes() error {
if e.notes != nil {
return nil
}
notes := []*expect.Note{}
for _, module := range e.written {
for _, filename := range module {
if !strings.HasSuffix(filename, ".go") {
continue
}
l, err := expect.Parse(e.fset, filename, nil)
if err != nil {
return fmt.Errorf("Failed to extract expectations: %v", err)
}
notes = append(notes, l...)
}
}
e.notes = notes
return nil
}
func (e *Exported) getMarkers() error {
if e.markers != nil {
return nil
}
// set markers early so that we don't call getMarkers again from Expect
e.markers = make(map[string]Range)
return e.Expect(map[string]interface{}{
markMethod: e.Mark,
})
}
var (
noteType = reflect.TypeOf((*expect.Note)(nil))
identifierType = reflect.TypeOf(expect.Identifier(""))
posType = reflect.TypeOf(token.Pos(0))
positionType = reflect.TypeOf(token.Position{})
rangeType = reflect.TypeOf(Range{})
)
// converter converts from a marker's argument parsed from the comment to
// reflect values passed to the method during Invoke.
// It takes the args remaining, and returns the args it did not consume.
// This allows a converter to consume 0 args for well known types, or multiple
// args for compound types.
type converter func(*expect.Note, []interface{}) (reflect.Value, []interface{}, error)
// method is used to track information about Invoke methods that is expensive to
// calculate so that we can work it out once rather than per marker.
type method struct {
f reflect.Value // the reflect value of the passed in method
converters []converter // the parameter converters for the method
}
// buildConverter works out what function should be used to go from an ast expressions to a reflect
// value of the type expected by a method.
// It is called when only the target type is know, it returns converters that are flexible across
// all supported expression types for that target type.
func (e *Exported) buildConverter(pt reflect.Type) (converter, error) {
switch {
case pt == noteType:
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
return reflect.ValueOf(n), args, nil
}, nil
case pt == posType:
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
r, remains, err := e.rangeConverter(n, args)
if err != nil {
return reflect.Value{}, nil, err
}
return reflect.ValueOf(r.Start), remains, nil
}, nil
case pt == positionType:
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
r, remains, err := e.rangeConverter(n, args)
if err != nil {
return reflect.Value{}, nil, err
}
return reflect.ValueOf(e.fset.Position(r.Start)), remains, nil
}, nil
case pt == rangeType:
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
r, remains, err := e.rangeConverter(n, args)
if err != nil {
return reflect.Value{}, nil, err
}
return reflect.ValueOf(r), remains, nil
}, nil
case pt == identifierType:
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
arg := args[0]
args = args[1:]
switch arg := arg.(type) {
case expect.Identifier:
return reflect.ValueOf(arg), args, nil
default:
return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to string", arg)
}
}, nil
case pt.Kind() == reflect.String:
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
arg := args[0]
args = args[1:]
switch arg := arg.(type) {
case expect.Identifier:
return reflect.ValueOf(string(arg)), args, nil
case string:
return reflect.ValueOf(arg), args, nil
default:
return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to string", arg)
}
}, nil
case pt.Kind() == reflect.Int64:
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
arg := args[0]
args = args[1:]
switch arg := arg.(type) {
case int64:
return reflect.ValueOf(arg), args, nil
default:
return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to int", arg)
}
}, nil
case pt.Kind() == reflect.Bool:
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
arg := args[0]
args = args[1:]
b, ok := arg.(bool)
if !ok {
return reflect.Value{}, nil, fmt.Errorf("cannot convert %v to bool", arg)
}
return reflect.ValueOf(b), args, nil
}, nil
case pt.Kind() == reflect.Slice:
return func(n *expect.Note, args []interface{}) (reflect.Value, []interface{}, error) {
converter, err := e.buildConverter(pt.Elem())
if err != nil {
return reflect.Value{}, nil, err
}
result := reflect.MakeSlice(reflect.SliceOf(pt.Elem()), 0, len(args))
for range args {
value, remains, err := converter(n, args)
if err != nil {
return reflect.Value{}, nil, err
}
result = reflect.Append(result, value)
args = remains
}
return result, args, nil
}, nil
default:
return nil, fmt.Errorf("param has invalid type %v", pt)
}
}
func (e *Exported) rangeConverter(n *expect.Note, args []interface{}) (Range, []interface{}, error) {
if len(args) < 1 {
return Range{}, nil, fmt.Errorf("missing argument")
}
arg := args[0]
args = args[1:]
switch arg := arg.(type) {
case expect.Identifier:
// handle the special identifiers
switch arg {
case eofIdentifier:
// end of file identifier, look up the current file
f := e.fset.File(n.Pos)
eof := f.Pos(f.Size())
return Range{Start: eof, End: token.NoPos}, args, nil
default:
// look up an marker by name
mark, ok := e.markers[string(arg)]
if !ok {
return Range{}, nil, fmt.Errorf("cannot find marker %v", arg)
}
return mark, args, nil
}
case string:
start, end, err := expect.MatchBefore(e.fset, e.fileContents, n.Pos, arg)
if err != nil {
return Range{}, nil, err
}
if start == token.NoPos {
return Range{}, nil, fmt.Errorf("%v: pattern %s did not match", e.fset.Position(n.Pos), arg)
}
return Range{Start: start, End: end}, args, nil
case *regexp.Regexp:
start, end, err := expect.MatchBefore(e.fset, e.fileContents, n.Pos, arg)
if err != nil {
return Range{}, nil, err
}
if start == token.NoPos {
return Range{}, nil, fmt.Errorf("%v: pattern %s did not match", e.fset.Position(n.Pos), arg)
}
return Range{Start: start, End: end}, args, nil
default:
return Range{}, nil, fmt.Errorf("cannot convert %v to pos", arg)
}
}

View File

@@ -0,0 +1,66 @@
// 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 packagestest_test
import (
"go/token"
"testing"
"golang.org/x/tools/go/expect"
"golang.org/x/tools/go/packages/packagestest"
)
func TestExpect(t *testing.T) {
exported := packagestest.Export(t, packagestest.GOPATH, []packagestest.Module{{
Name: "golang.org/fake",
Files: packagestest.MustCopyFileTree("testdata"),
}})
defer exported.Cleanup()
count := 0
if err := exported.Expect(map[string]interface{}{
"check": func(src, target token.Position) {
count++
},
"boolArg": func(n *expect.Note, yes, no bool) {
if !yes {
t.Errorf("Expected boolArg first param to be true")
}
if no {
t.Errorf("Expected boolArg second param to be false")
}
},
"intArg": func(n *expect.Note, i int64) {
if i != 42 {
t.Errorf("Expected intarg to be 42")
}
},
"stringArg": func(n *expect.Note, name expect.Identifier, value string) {
if string(name) != value {
t.Errorf("Got string arg %v expected %v", value, name)
}
},
"directNote": func(n *expect.Note) {},
"range": func(r packagestest.Range) {
if r.Start == token.NoPos || r.Start == 0 {
t.Errorf("Range had no valid starting position")
}
if r.End == token.NoPos || r.End == 0 {
t.Errorf("Range had no valid ending position")
} else if r.End <= r.Start {
t.Errorf("Range ending was not greater than start")
}
},
"checkEOF": func(n *expect.Note, p token.Pos) {
if p <= n.Pos {
t.Errorf("EOF was before the checkEOF note")
}
},
}); err != nil {
t.Fatal(err)
}
if count == 0 {
t.Fatalf("No tests were run")
}
}

View File

@@ -0,0 +1,281 @@
// 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 packagestest creates temporary projects on disk for testing go tools on.
By changing the exporter used, you can create projects for multiple build
systems from the same description, and run the same tests on them in many
cases.
*/
package packagestest
import (
"flag"
"fmt"
"go/token"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"testing"
"golang.org/x/tools/go/expect"
"golang.org/x/tools/go/packages"
)
var (
skipCleanup = flag.Bool("skip-cleanup", false, "Do not delete the temporary export folders") // for debugging
)
// Module is a representation of a go module.
type Module struct {
// Name is the base name of the module as it would be in the go.mod file.
Name string
// Files is the set of source files for all packages that make up the module.
// The keys are the file fragment that follows the module name, the value can
// be a string or byte slice, in which case it is the contents of the
// file, otherwise it must be a Writer function.
Files map[string]interface{}
}
// A Writer is a function that writes out a test file.
// It is provided the name of the file to write, and may return an error if it
// cannot write the file.
// These are used as the content of the Files map in a Module.
type Writer func(filename string) error
// Exported is returned by the Export function to report the structure that was produced on disk.
type Exported struct {
// Config is a correctly configured packages.Config ready to be passed to packages.Load.
// Exactly what it will contain varies depending on the Exporter being used.
Config *packages.Config
temp string // the temporary directory that was exported to
primary string // the first non GOROOT module that was exported
written map[string]map[string]string // the full set of exported files
fset *token.FileSet // The file set used when parsing expectations
notes []*expect.Note // The list of expectations extracted from go source files
markers map[string]Range // The set of markers extracted from go source files
contents map[string][]byte
}
// Exporter implementations are responsible for converting from the generic description of some
// test data to a driver specific file layout.
type Exporter interface {
// Name reports the name of the exporter, used in logging and sub-test generation.
Name() string
// Filename reports the system filename for test data source file.
// It is given the base directory, the module the file is part of and the filename fragment to
// work from.
Filename(exported *Exported, module, fragment string) string
// Finalize is called once all files have been written to write any extra data needed and modify
// the Config to match. It is handed the full list of modules that were encountered while writing
// files.
Finalize(exported *Exported) error
}
// All is the list of known exporters.
// This is used by TestAll to run tests with all the exporters.
var All []Exporter
// TestAll invokes the testing function once for each exporter registered in
// the All global.
// Each exporter will be run as a sub-test named after the exporter being used.
func TestAll(t *testing.T, f func(*testing.T, Exporter)) {
t.Helper()
for _, e := range All {
t.Run(e.Name(), func(t *testing.T) {
t.Helper()
f(t, e)
})
}
}
// Export is called to write out a test directory from within a test function.
// It takes the exporter and the build system agnostic module descriptions, and
// uses them to build a temporary directory.
// It returns an Exported with the results of the export.
// The Exported.Config is prepared for loading from the exported data.
// You must invoke Exported.Cleanup on the returned value to clean up.
// The file deletion in the cleanup can be skipped by setting the skip-cleanup
// flag when invoking the test, allowing the temporary directory to be left for
// debugging tests.
func Export(t *testing.T, exporter Exporter, modules []Module) *Exported {
t.Helper()
dirname := strings.Replace(t.Name(), "/", "_", -1)
dirname = strings.Replace(dirname, "#", "_", -1) // duplicate subtests get a #NNN suffix.
temp, err := ioutil.TempDir("", dirname)
if err != nil {
t.Fatal(err)
}
exported := &Exported{
Config: &packages.Config{
Dir: temp,
Env: append(os.Environ(), "GOPACKAGESDRIVER=off"),
},
temp: temp,
primary: modules[0].Name,
written: map[string]map[string]string{},
fset: token.NewFileSet(),
contents: map[string][]byte{},
}
defer func() {
if t.Failed() || t.Skipped() {
exported.Cleanup()
}
}()
for _, module := range modules {
for fragment, value := range module.Files {
fullpath := exporter.Filename(exported, module.Name, filepath.FromSlash(fragment))
written, ok := exported.written[module.Name]
if !ok {
written = map[string]string{}
exported.written[module.Name] = written
}
written[fragment] = fullpath
if err := os.MkdirAll(filepath.Dir(fullpath), 0755); err != nil {
t.Fatal(err)
}
switch value := value.(type) {
case Writer:
if err := value(fullpath); err != nil {
t.Fatal(err)
}
case string:
if err := ioutil.WriteFile(fullpath, []byte(value), 0644); err != nil {
t.Fatal(err)
}
default:
t.Fatalf("Invalid type %T in files, must be string or Writer", value)
}
}
}
if err := exporter.Finalize(exported); err != nil {
t.Fatal(err)
}
return exported
}
// Script returns a Writer that writes out contents to the file and sets the
// executable bit on the created file.
// It is intended for source files that are shell scripts.
func Script(contents string) Writer {
return func(filename string) error {
return ioutil.WriteFile(filename, []byte(contents), 0755)
}
}
// Link returns a Writer that creates a hard link from the specified source to
// the required file.
// This is used to link testdata files into the generated testing tree.
func Link(source string) Writer {
return func(filename string) error {
return os.Link(source, filename)
}
}
// Symlink returns a Writer that creates a symlink from the specified source to the
// required file.
// This is used to link testdata files into the generated testing tree.
func Symlink(source string) Writer {
if !strings.HasPrefix(source, ".") {
if abspath, err := filepath.Abs(source); err == nil {
if _, err := os.Stat(source); !os.IsNotExist(err) {
source = abspath
}
}
}
return func(filename string) error {
return os.Symlink(source, filename)
}
}
// Copy returns a Writer that copies a file from the specified source to the
// required file.
// This is used to copy testdata files into the generated testing tree.
func Copy(source string) Writer {
return func(filename string) error {
stat, err := os.Stat(source)
if err != nil {
return err
}
if !stat.Mode().IsRegular() {
// cannot copy non-regular files (e.g., directories,
// symlinks, devices, etc.)
return fmt.Errorf("Cannot copy non regular file %s", source)
}
contents, err := ioutil.ReadFile(source)
if err != nil {
return err
}
return ioutil.WriteFile(filename, contents, stat.Mode())
}
}
// MustCopyFileTree returns a file set for a module based on a real directory tree.
// It scans the directory tree anchored at root and adds a Copy writer to the
// map for every file found.
// This is to enable the common case in tests where you have a full copy of the
// package in your testdata.
// This will panic if there is any kind of error trying to walk the file tree.
func MustCopyFileTree(root string) map[string]interface{} {
result := map[string]interface{}{}
if err := filepath.Walk(filepath.FromSlash(root), func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
fragment, err := filepath.Rel(root, path)
if err != nil {
return err
}
result[fragment] = Copy(path)
return nil
}); err != nil {
log.Panic(fmt.Sprintf("MustCopyFileTree failed: %v", err))
}
return result
}
// Cleanup removes the temporary directory (unless the --skip-cleanup flag was set)
// It is safe to call cleanup multiple times.
func (e *Exported) Cleanup() {
if e.temp == "" {
return
}
if *skipCleanup {
log.Printf("Skipping cleanup of temp dir: %s", e.temp)
return
}
os.RemoveAll(e.temp) // ignore errors
e.temp = ""
}
// Temp returns the temporary directory that was generated.
func (e *Exported) Temp() string {
return e.temp
}
// File returns the full path for the given module and file fragment.
func (e *Exported) File(module, fragment string) string {
if m := e.written[module]; m != nil {
return m[fragment]
}
return ""
}
func (e *Exported) fileContents(filename string) ([]byte, error) {
if content, found := e.contents[filename]; found {
return content, nil
}
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return content, nil
}

View File

@@ -0,0 +1,73 @@
// 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 packagestest_test
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"golang.org/x/tools/go/packages/packagestest"
)
var testdata = []packagestest.Module{{
Name: "golang.org/fake1",
Files: map[string]interface{}{
"a.go": packagestest.Symlink("testdata/a.go"),
"b.go": "package fake1",
},
}, {
Name: "golang.org/fake2",
Files: map[string]interface{}{
"other/a.go": "package fake2",
},
}, {
Name: "golang.org/fake2/v2",
Files: map[string]interface{}{
"other/a.go": "package fake2",
},
}}
type fileTest struct {
module, fragment, expect string
check func(t *testing.T, filename string)
}
func checkFiles(t *testing.T, exported *packagestest.Exported, tests []fileTest) {
for _, test := range tests {
expect := filepath.Join(exported.Temp(), filepath.FromSlash(test.expect))
got := exported.File(test.module, test.fragment)
if got == "" {
t.Errorf("File %v missing from the output", expect)
} else if got != expect {
t.Errorf("Got file %v, expected %v", got, expect)
}
if test.check != nil {
test.check(t, got)
}
}
}
func checkLink(expect string) func(t *testing.T, filename string) {
expect = filepath.FromSlash(expect)
return func(t *testing.T, filename string) {
if target, err := os.Readlink(filename); err != nil {
t.Errorf("Error checking link %v: %v", filename, err)
} else if target != expect {
t.Errorf("Link %v does not match, got %v expected %v", filename, target, expect)
}
}
}
func checkContent(expect string) func(t *testing.T, filename string) {
return func(t *testing.T, filename string) {
if content, err := ioutil.ReadFile(filename); err != nil {
t.Errorf("Error reading %v: %v", filename, err)
} else if string(content) != expect {
t.Errorf("Content of %v does not match, got %v expected %v", filename, string(content), expect)
}
}
}

View File

@@ -0,0 +1,74 @@
// 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 packagestest
import (
"path"
"path/filepath"
)
// GOPATH is the exporter that produces GOPATH layouts.
// Each "module" is put in it's own GOPATH entry to help test complex cases.
// Given the two files
// golang.org/repoa#a/a.go
// golang.org/repob#b/b.go
// You would get the directory layout
// /sometemporarydirectory
// ├── repoa
// │ └── src
// │ └── golang.org
// │ └── repoa
// │ └── a
// │ └── a.go
// └── repob
// └── src
// └── golang.org
// └── repob
// └── b
// └── b.go
// GOPATH would be set to
// /sometemporarydirectory/repoa;/sometemporarydirectory/repob
// and the working directory would be
// /sometemporarydirectory/repoa/src
var GOPATH = gopath{}
func init() {
All = append(All, GOPATH)
}
type gopath struct{}
func (gopath) Name() string {
return "GOPATH"
}
func (gopath) Filename(exported *Exported, module, fragment string) string {
return filepath.Join(gopathDir(exported, module), "src", module, fragment)
}
func (gopath) Finalize(exported *Exported) error {
exported.Config.Env = append(exported.Config.Env, "GO111MODULE=off")
gopath := ""
for module := range exported.written {
if gopath != "" {
gopath += string(filepath.ListSeparator)
}
dir := gopathDir(exported, module)
gopath += dir
if module == exported.primary {
exported.Config.Dir = filepath.Join(dir, "src")
}
}
exported.Config.Env = append(exported.Config.Env, "GOPATH="+gopath)
return nil
}
func gopathDir(exported *Exported, module string) string {
dir := path.Base(module)
if versionSuffixRE.MatchString(dir) {
dir = path.Base(path.Dir(module)) + "_" + dir
}
return filepath.Join(exported.temp, dir)
}

View File

@@ -0,0 +1,28 @@
// 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 packagestest_test
import (
"path/filepath"
"testing"
"golang.org/x/tools/go/packages/packagestest"
)
func TestGOPATHExport(t *testing.T) {
exported := packagestest.Export(t, packagestest.GOPATH, testdata)
defer exported.Cleanup()
// Check that the cfg contains all the right bits
var expectDir = filepath.Join(exported.Temp(), "fake1", "src")
if exported.Config.Dir != expectDir {
t.Errorf("Got working directory %v expected %v", exported.Config.Dir, expectDir)
}
checkFiles(t, exported, []fileTest{
{"golang.org/fake1", "a.go", "fake1/src/golang.org/fake1/a.go", checkLink("testdata/a.go")},
{"golang.org/fake1", "b.go", "fake1/src/golang.org/fake1/b.go", checkContent("package fake1")},
{"golang.org/fake2", "other/a.go", "fake2/src/golang.org/fake2/other/a.go", checkContent("package fake2")},
{"golang.org/fake2/v2", "other/a.go", "fake2_v2/src/golang.org/fake2/v2/other/a.go", checkContent("package fake2")},
})
}

View File

@@ -0,0 +1,210 @@
// 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 packagestest
import (
"archive/zip"
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"golang.org/x/tools/go/packages"
)
// Modules is the exporter that produces module layouts.
// Each "repository" is put in it's own module, and the module file generated
// will have replace directives for all other modules.
// Given the two files
// golang.org/repoa#a/a.go
// golang.org/repob#b/b.go
// You would get the directory layout
// /sometemporarydirectory
// ├── repoa
// │ ├── a
// │ │ └── a.go
// │ └── go.mod
// └── repob
// ├── b
// │ └── b.go
// └── go.mod
// and the working directory would be
// /sometemporarydirectory/repoa
var Modules = modules{}
type modules struct{}
func (modules) Name() string {
return "Modules"
}
func (modules) Filename(exported *Exported, module, fragment string) string {
if module == exported.primary {
return filepath.Join(primaryDir(exported), fragment)
}
return filepath.Join(moduleDir(exported, module), fragment)
}
func (modules) Finalize(exported *Exported) error {
// Write out the primary module. This module can use symlinks and
// other weird stuff, and will be the working dir for the go command.
// It depends on all the other modules.
primaryDir := primaryDir(exported)
exported.Config.Dir = primaryDir
exported.written[exported.primary]["go.mod"] = filepath.Join(primaryDir, "go.mod")
primaryGomod := "module " + exported.primary + "\nrequire (\n"
for other := range exported.written {
if other == exported.primary {
continue
}
primaryGomod += fmt.Sprintf("\t%v %v\n", other, moduleVersion(other))
}
primaryGomod += ")\n"
if err := ioutil.WriteFile(filepath.Join(primaryDir, "go.mod"), []byte(primaryGomod), 0644); err != nil {
return err
}
// Create the mod cache so we can rename it later, even if we don't need it.
if err := os.MkdirAll(modCache(exported), 0755); err != nil {
return err
}
// Write out the go.mod files for the other modules.
for module, files := range exported.written {
if module == exported.primary {
continue
}
dir := moduleDir(exported, module)
modfile := filepath.Join(dir, "go.mod")
if err := ioutil.WriteFile(modfile, []byte("module "+module+"\n"), 0644); err != nil {
return err
}
files["go.mod"] = modfile
}
// Zip up all the secondary modules into the proxy dir.
proxyDir := filepath.Join(exported.temp, "modproxy")
for module, files := range exported.written {
if module == exported.primary {
continue
}
dir := filepath.Join(proxyDir, module, "@v")
if err := writeModuleProxy(dir, module, files); err != nil {
return fmt.Errorf("creating module proxy dir for %v: %v", module, err)
}
}
// Discard the original mod cache dir, which contained the files written
// for us by Export.
if err := os.Rename(modCache(exported), modCache(exported)+".orig"); err != nil {
return err
}
exported.Config.Env = append(exported.Config.Env,
"GO111MODULE=on",
"GOPATH="+filepath.Join(exported.temp, "modcache"),
"GOPROXY=file://"+filepath.ToSlash(proxyDir))
// Run go mod download to recreate the mod cache dir with all the extra
// stuff in cache. All the files created by Export should be recreated.
if err := invokeGo(exported.Config, "mod", "download"); err != nil {
return err
}
return nil
}
// writeModuleProxy creates a directory in the proxy dir for a module.
func writeModuleProxy(dir, module string, files map[string]string) error {
ver := moduleVersion(module)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
// list file. Just the single version.
if err := ioutil.WriteFile(filepath.Join(dir, "list"), []byte(ver+"\n"), 0644); err != nil {
return err
}
// go.mod, copied from the file written in Finalize.
modContents, err := ioutil.ReadFile(files["go.mod"])
if err != nil {
return err
}
if err := ioutil.WriteFile(filepath.Join(dir, ver+".mod"), modContents, 0644); err != nil {
return err
}
// info file, just the bare bones.
infoContents := []byte(fmt.Sprintf(`{"Version": "%v", "Time":"2017-12-14T13:08:43Z"}`, ver))
if err := ioutil.WriteFile(filepath.Join(dir, ver+".info"), infoContents, 0644); err != nil {
return err
}
// zip of all the source files.
f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
z := zip.NewWriter(f)
for name, path := range files {
zf, err := z.Create(module + "@" + ver + "/" + name)
if err != nil {
return err
}
contents, err := ioutil.ReadFile(path)
if err != nil {
return err
}
if _, err := zf.Write(contents); err != nil {
return err
}
}
if err := z.Close(); err != nil {
return err
}
return nil
}
func invokeGo(cfg *packages.Config, args ...string) error {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd := exec.Command("go", args...)
cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir)
cmd.Dir = cfg.Dir
cmd.Stdout = stdout
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("go %v: %s: %s", args, err, stderr)
}
return nil
}
func modCache(exported *Exported) string {
return filepath.Join(exported.temp, "modcache/pkg/mod")
}
func primaryDir(exported *Exported) string {
return filepath.Join(exported.temp, "primarymod", path.Base(exported.primary))
}
func moduleDir(exported *Exported, module string) string {
return filepath.Join(modCache(exported), path.Dir(module), path.Base(module)+"@"+moduleVersion(module))
}
var versionSuffixRE = regexp.MustCompile(`v\d+`)
func moduleVersion(module string) string {
if versionSuffixRE.MatchString(path.Base(module)) {
return path.Base(module) + ".0.0"
}
return "v1.0.0"
}

View File

@@ -0,0 +1,7 @@
// +build go1.11
package packagestest
func init() {
All = append(All, Modules)
}

View File

@@ -0,0 +1,32 @@
// 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.
// +build go1.11
package packagestest_test
import (
"path/filepath"
"testing"
"golang.org/x/tools/go/packages/packagestest"
)
func TestModulesExport(t *testing.T) {
exported := packagestest.Export(t, packagestest.Modules, testdata)
defer exported.Cleanup()
// Check that the cfg contains all the right bits
var expectDir = filepath.Join(exported.Temp(), "primarymod/fake1")
if exported.Config.Dir != expectDir {
t.Errorf("Got working directory %v expected %v", exported.Config.Dir, expectDir)
}
checkFiles(t, exported, []fileTest{
{"golang.org/fake1", "go.mod", "primarymod/fake1/go.mod", nil},
{"golang.org/fake1", "a.go", "primarymod/fake1/a.go", checkLink("testdata/a.go")},
{"golang.org/fake1", "b.go", "primarymod/fake1/b.go", checkContent("package fake1")},
{"golang.org/fake2", "go.mod", "modcache/pkg/mod/golang.org/fake2@v1.0.0/go.mod", nil},
{"golang.org/fake2", "other/a.go", "modcache/pkg/mod/golang.org/fake2@v1.0.0/other/a.go", checkContent("package fake2")},
{"golang.org/fake2/v2", "other/a.go", "modcache/pkg/mod/golang.org/fake2/v2@v2.0.0/other/a.go", checkContent("package fake2")},
})
}

View File

@@ -0,0 +1,24 @@
// 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 fake1
// This is a test file for the behaviors in Exported.Expect.
type AThing string //@AThing,mark(StringThing, "AThing"),mark(REThing,re`.T.*g`)
type Match string //@check("Match",re`[[:upper:]]`)
//@check(AThing, StringThing)
//@check(AThing, REThing)
//@boolArg(true, false)
//@intArg(42)
//@stringArg(PlainString, "PlainString")
//@stringArg(IdentAsString,IdentAsString)
//@directNote()
//@range(AThing)
// The following test should remain at the bottom of the file
//@checkEOF(EOF)

View File

@@ -30,10 +30,14 @@ func TestStdlibMetadata(t *testing.T) {
alloc := memstats.Alloc
// Load, parse and type-check the program.
pkgs, err := packages.Metadata(nil, "std")
cfg := &packages.Config{Mode: packages.LoadAllSyntax}
pkgs, err := packages.Load(cfg, "std")
if err != nil {
t.Fatalf("failed to load metadata: %v", err)
}
if packages.PrintErrors(pkgs) > 0 {
t.Fatal("there were errors loading standard library")
}
t1 := time.Now()
runtime.GC()
@@ -42,7 +46,9 @@ func TestStdlibMetadata(t *testing.T) {
t.Logf("Loaded %d packages", len(pkgs))
numPkgs := len(pkgs)
if want := 340; numPkgs < want {
want := 150 // 186 on linux, 185 on windows.
if numPkgs < want {
t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
}
@@ -90,49 +96,41 @@ func TestCgoOption(t *testing.T) {
{"net", "cgoLookupHost", "cgo_stub.go"},
{"os/user", "current", "lookup_stubs.go"},
} {
for i := 0; i < 2; i++ { // !cgo, cgo
opts := &packages.Options{
DisableCgo: i == 0,
Error: func(error) {},
}
pkgs, err := packages.TypeCheck(opts, test.pkg)
if err != nil {
t.Errorf("Load failed: %v", err)
continue
}
pkg := pkgs[0]
obj := pkg.Type.Scope().Lookup(test.name)
if obj == nil {
t.Errorf("no object %s.%s", test.pkg, test.name)
continue
}
posn := pkg.Fset.Position(obj.Pos())
if false {
t.Logf("DisableCgo=%t, obj=%s, posn=%s", opts.DisableCgo, obj, posn)
}
cfg := &packages.Config{Mode: packages.LoadSyntax}
pkgs, err := packages.Load(cfg, test.pkg)
if err != nil {
t.Errorf("Load failed: %v", err)
continue
}
if packages.PrintErrors(pkgs) > 0 {
t.Error("there were errors loading standard library")
continue
}
pkg := pkgs[0]
obj := pkg.Types.Scope().Lookup(test.name)
if obj == nil {
t.Errorf("no object %s.%s", test.pkg, test.name)
continue
}
posn := pkg.Fset.Position(obj.Pos())
gotFile := filepath.Base(posn.Filename)
filesMatch := gotFile == test.genericFile
gotFile := filepath.Base(posn.Filename)
filesMatch := gotFile == test.genericFile
if filesMatch {
t.Errorf("!DisableCgo: %s found in %s, want native file",
obj, gotFile)
}
if !opts.DisableCgo && filesMatch {
t.Errorf("!DisableCgo: %s found in %s, want native file",
obj, gotFile)
} else if opts.DisableCgo && !filesMatch {
t.Errorf("DisableCgo: %s found in %s, want %s",
obj, gotFile, test.genericFile)
}
// Load the file and check the object is declared at the right place.
b, err := ioutil.ReadFile(posn.Filename)
if err != nil {
t.Errorf("can't read %s: %s", posn.Filename, err)
continue
}
line := string(bytes.Split(b, []byte("\n"))[posn.Line-1])
// Don't assume posn.Column is accurate.
if !strings.Contains(line, "func "+test.name) {
t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, line)
}
// Load the file and check the object is declared at the right place.
b, err := ioutil.ReadFile(posn.Filename)
if err != nil {
t.Errorf("can't read %s: %s", posn.Filename, err)
continue
}
line := string(bytes.Split(b, []byte("\n"))[posn.Line-1])
// Don't assume posn.Column is accurate.
if !strings.Contains(line, "func "+test.name) {
t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, line)
}
}
}

View File

@@ -0,0 +1,3 @@
Test data directories here were created by running go commands with GOPATH set as such:
GOPATH=......./testdata/TestNamed_ModulesDedup go get github.com/heschik/tools-testrepo/v2@v2.0.1
and then removing the vcs cache directories, which appear to be unnecessary.

View File

@@ -0,0 +1 @@
{"Version":"v1.0.0","Time":"2018-09-28T22:09:08Z"}

View File

@@ -0,0 +1 @@
module github.com/heschik/tools-testrepo

View File

@@ -0,0 +1 @@
h1:D2qc+R2eCTCyoT8WAYoExXhPBThJWmlYSfB4coWbfBE=

View File

@@ -0,0 +1 @@
{"Version":"v2.0.0","Time":"2018-09-28T22:12:08Z"}

View File

@@ -0,0 +1 @@
module github.com/heschik/tools-testrepo/v2

View File

@@ -0,0 +1 @@
h1:Ll4Bx8ZD8zg8lD4idX7CAhx/jh16o9dWC2m9SnT1qu0=

View File

@@ -0,0 +1 @@
module github.com/heschik/tools-testrepo/v2

View File

@@ -0,0 +1 @@
module github.com/heschik/tools-testrepo

View File

@@ -0,0 +1 @@
package pkg

View File

@@ -0,0 +1 @@
{"Version":"v1.0.0","Time":"2018-09-28T22:09:08Z"}

View File

@@ -0,0 +1 @@
module github.com/heschik/tools-testrepo

View File

@@ -0,0 +1 @@
h1:D2qc+R2eCTCyoT8WAYoExXhPBThJWmlYSfB4coWbfBE=

View File

@@ -0,0 +1 @@
{"Version":"v2.0.1","Time":"2018-09-28T22:12:08Z"}

View File

@@ -0,0 +1 @@
module github.com/heschik/tools-testrepo/v2

View File

@@ -0,0 +1 @@
h1:efPBVdJ45IMcA/KXBOWyOZLo1TETKCXvzrZgfY+gqZk=

View File

@@ -0,0 +1 @@
{"Version":"v2.0.2","Time":"2018-09-28T22:12:08Z"}

View File

@@ -0,0 +1 @@
module github.com/heschik/tools-testrepo/v2

View File

@@ -0,0 +1 @@
h1:vUnR/JOkfEQt/wvMqbT9G2gODHVgVD1saTJ8x2ngAck=

View File

@@ -0,0 +1 @@
module github.com/heschik/tools-testrepo/v2

View File

@@ -0,0 +1 @@
module github.com/heschik/tools-testrepo/v2

View File

@@ -0,0 +1 @@
module github.com/heschik/tools-testrepo

55
vendor/golang.org/x/tools/go/packages/visit.go generated vendored Normal file
View File

@@ -0,0 +1,55 @@
package packages
import (
"fmt"
"os"
"sort"
)
// Visit visits all the packages in the import graph whose roots are
// pkgs, calling the optional pre function the first time each package
// is encountered (preorder), and the optional post function after a
// package's dependencies have been visited (postorder).
// The boolean result of pre(pkg) determines whether
// the imports of package pkg are visited.
func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) {
seen := make(map[*Package]bool)
var visit func(*Package)
visit = func(pkg *Package) {
if !seen[pkg] {
seen[pkg] = true
if pre == nil || pre(pkg) {
paths := make([]string, 0, len(pkg.Imports))
for path := range pkg.Imports {
paths = append(paths, path)
}
sort.Strings(paths) // Imports is a map, this makes visit stable
for _, path := range paths {
visit(pkg.Imports[path])
}
}
if post != nil {
post(pkg)
}
}
}
for _, pkg := range pkgs {
visit(pkg)
}
}
// PrintErrors prints to os.Stderr the accumulated errors of all
// packages in the import graph rooted at pkgs, dependencies first.
// PrintErrors returns the number of errors printed.
func PrintErrors(pkgs []*Package) int {
var n int
Visit(pkgs, nil, func(pkg *Package) {
for _, err := range pkg.Errors {
fmt.Fprintln(os.Stderr, err)
n++
}
})
return n
}