Dependency updates

This commit is contained in:
Stash Dev
2019-05-27 12:34:26 -07:00
parent 69917999ef
commit 4b037e1040
359 changed files with 60172 additions and 18749 deletions

View File

@@ -1,220 +0,0 @@
// Copyright 2013 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 cgo
// This file handles cgo preprocessing of files containing `import "C"`.
//
// DESIGN
//
// The approach taken is to run the cgo processor on the package's
// CgoFiles and parse the output, faking the filenames of the
// resulting ASTs so that the synthetic file containing the C types is
// called "C" (e.g. "~/go/src/net/C") and the preprocessed files
// have their original names (e.g. "~/go/src/net/cgo_unix.go"),
// not the names of the actual temporary files.
//
// The advantage of this approach is its fidelity to 'go build'. The
// downside is that the token.Position.Offset for each AST node is
// incorrect, being an offset within the temporary file. Line numbers
// should still be correct because of the //line comments.
//
// The logic of this file is mostly plundered from the 'go build'
// tool, which also invokes the cgo preprocessor.
//
//
// REJECTED ALTERNATIVE
//
// An alternative approach that we explored is to extend go/types'
// Importer mechanism to provide the identity of the importing package
// so that each time `import "C"` appears it resolves to a different
// synthetic package containing just the objects needed in that case.
// The loader would invoke cgo but parse only the cgo_types.go file
// defining the package-level objects, discarding the other files
// resulting from preprocessing.
//
// The benefit of this approach would have been that source-level
// syntax information would correspond exactly to the original cgo
// file, with no preprocessing involved, making source tools like
// godoc, guru, and eg happy. However, the approach was rejected
// due to the additional complexity it would impose on go/types. (It
// made for a beautiful demo, though.)
//
// cgo files, despite their *.go extension, are not legal Go source
// files per the specification since they may refer to unexported
// members of package "C" such as C.int. Also, a function such as
// C.getpwent has in effect two types, one matching its C type and one
// which additionally returns (errno C.int). The cgo preprocessor
// uses name mangling to distinguish these two functions in the
// processed code, but go/types would need to duplicate this logic in
// its handling of function calls, analogous to the treatment of map
// lookups in which y=m[k] and y,ok=m[k] are both legal.
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
// ProcessFiles invokes the cgo preprocessor on bp.CgoFiles, parses
// the output and returns the resulting ASTs.
//
func ProcessFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) {
tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C")
if err != nil {
return nil, err
}
defer os.RemoveAll(tmpdir)
pkgdir := bp.Dir
if DisplayPath != nil {
pkgdir = DisplayPath(pkgdir)
}
cgoFiles, cgoDisplayFiles, err := Run(bp, pkgdir, tmpdir, false)
if err != nil {
return nil, err
}
var files []*ast.File
for i := range cgoFiles {
rd, err := os.Open(cgoFiles[i])
if err != nil {
return nil, err
}
display := filepath.Join(bp.Dir, cgoDisplayFiles[i])
f, err := parser.ParseFile(fset, display, rd, mode)
rd.Close()
if err != nil {
return nil, err
}
files = append(files, f)
}
return files, nil
}
var cgoRe = regexp.MustCompile(`[/\\:]`)
// Run invokes the cgo preprocessor on bp.CgoFiles and returns two
// lists of files: the resulting processed files (in temporary
// directory tmpdir) and the corresponding names of the unprocessed files.
//
// Run is adapted from (*builder).cgo in
// $GOROOT/src/cmd/go/build.go, but these features are unsupported:
// Objective C, CGOPKGPATH, CGO_FLAGS.
//
// If useabs is set to true, absolute paths of the bp.CgoFiles will be passed in
// to the cgo preprocessor. This in turn will set the // line comments
// referring to those files to use absolute paths. This is needed for
// go/packages using the legacy go list support so it is able to find
// the original files.
func Run(bp *build.Package, pkgdir, tmpdir string, useabs bool) (files, displayFiles []string, err error) {
cgoCPPFLAGS, _, _, _ := cflags(bp, true)
_, cgoexeCFLAGS, _, _ := cflags(bp, false)
if len(bp.CgoPkgConfig) > 0 {
pcCFLAGS, err := pkgConfigFlags(bp)
if err != nil {
return nil, nil, err
}
cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...)
}
// Allows including _cgo_export.h from .[ch] files in the package.
cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir)
// _cgo_gotypes.go (displayed "C") contains the type definitions.
files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go"))
displayFiles = append(displayFiles, "C")
for _, fn := range bp.CgoFiles {
// "foo.cgo1.go" (displayed "foo.go") is the processed Go source.
f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_")
files = append(files, filepath.Join(tmpdir, f+"cgo1.go"))
displayFiles = append(displayFiles, fn)
}
var cgoflags []string
if bp.Goroot && bp.ImportPath == "runtime/cgo" {
cgoflags = append(cgoflags, "-import_runtime_cgo=false")
}
if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" {
cgoflags = append(cgoflags, "-import_syscall=false")
}
var cgoFiles []string = bp.CgoFiles
if useabs {
cgoFiles = make([]string, len(bp.CgoFiles))
for i := range cgoFiles {
cgoFiles[i] = filepath.Join(pkgdir, bp.CgoFiles[i])
}
}
args := stringList(
"go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--",
cgoCPPFLAGS, cgoexeCFLAGS, cgoFiles,
)
if false {
log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir)
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = pkgdir
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err)
}
return files, displayFiles, nil
}
// -- unmodified from 'go build' ---------------------------------------
// Return the flags to use when invoking the C or C++ compilers, or cgo.
func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) {
var defaults string
if def {
defaults = "-g -O2"
}
cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS)
cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS)
cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS)
ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS)
return
}
// envList returns the value of the given environment variable broken
// into fields, using the default value when the variable is empty.
func envList(key, def string) []string {
v := os.Getenv(key)
if v == "" {
v = def
}
return strings.Fields(v)
}
// stringList's arguments should be a sequence of string or []string values.
// stringList flattens them into a single []string.
func stringList(args ...interface{}) []string {
var x []string
for _, arg := range args {
switch arg := arg.(type) {
case []string:
x = append(x, arg...)
case string:
x = append(x, arg)
default:
panic("stringList: invalid argument")
}
}
return x
}

View File

@@ -1,39 +0,0 @@
// Copyright 2013 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 cgo
import (
"errors"
"fmt"
"go/build"
"os/exec"
"strings"
)
// pkgConfig runs pkg-config with the specified arguments and returns the flags it prints.
func pkgConfig(mode string, pkgs []string) (flags []string, err error) {
cmd := exec.Command("pkg-config", append([]string{mode}, pkgs...)...)
out, err := cmd.CombinedOutput()
if err != nil {
s := fmt.Sprintf("%s failed: %v", strings.Join(cmd.Args, " "), err)
if len(out) > 0 {
s = fmt.Sprintf("%s: %s", s, out)
}
return nil, errors.New(s)
}
if len(out) > 0 {
flags = strings.Fields(string(out))
}
return
}
// pkgConfigFlags calls pkg-config if needed and returns the cflags
// needed to build the package.
func pkgConfigFlags(p *build.Package) (cflags []string, err error) {
if len(p.CgoPkgConfig) == 0 {
return nil, nil
}
return pkgConfig("--cflags", p.CgoPkgConfig)
}

View File

@@ -127,10 +127,10 @@ func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error)
// --- generic export data ---
// populate type map with predeclared "known" types
for index, typ := range predeclared {
for index, typ := range predeclared() {
p.typIndex[typ] = index
}
if len(p.typIndex) != len(predeclared) {
if len(p.typIndex) != len(predeclared()) {
return nil, internalError("duplicate entries in type map?")
}

View File

@@ -126,7 +126,7 @@ func BImportData(fset *token.FileSet, imports map[string]*types.Package, data []
// --- generic export data ---
// populate typList with predeclared "known" types
p.typList = append(p.typList, predeclared...)
p.typList = append(p.typList, predeclared()...)
// read package data
pkg = p.pkg()
@@ -976,50 +976,58 @@ const (
aliasTag
)
var predeclared = []types.Type{
// basic types
types.Typ[types.Bool],
types.Typ[types.Int],
types.Typ[types.Int8],
types.Typ[types.Int16],
types.Typ[types.Int32],
types.Typ[types.Int64],
types.Typ[types.Uint],
types.Typ[types.Uint8],
types.Typ[types.Uint16],
types.Typ[types.Uint32],
types.Typ[types.Uint64],
types.Typ[types.Uintptr],
types.Typ[types.Float32],
types.Typ[types.Float64],
types.Typ[types.Complex64],
types.Typ[types.Complex128],
types.Typ[types.String],
var predecl []types.Type // initialized lazily
// basic type aliases
types.Universe.Lookup("byte").Type(),
types.Universe.Lookup("rune").Type(),
func predeclared() []types.Type {
if predecl == nil {
// initialize lazily to be sure that all
// elements have been initialized before
predecl = []types.Type{ // basic types
types.Typ[types.Bool],
types.Typ[types.Int],
types.Typ[types.Int8],
types.Typ[types.Int16],
types.Typ[types.Int32],
types.Typ[types.Int64],
types.Typ[types.Uint],
types.Typ[types.Uint8],
types.Typ[types.Uint16],
types.Typ[types.Uint32],
types.Typ[types.Uint64],
types.Typ[types.Uintptr],
types.Typ[types.Float32],
types.Typ[types.Float64],
types.Typ[types.Complex64],
types.Typ[types.Complex128],
types.Typ[types.String],
// error
types.Universe.Lookup("error").Type(),
// basic type aliases
types.Universe.Lookup("byte").Type(),
types.Universe.Lookup("rune").Type(),
// untyped types
types.Typ[types.UntypedBool],
types.Typ[types.UntypedInt],
types.Typ[types.UntypedRune],
types.Typ[types.UntypedFloat],
types.Typ[types.UntypedComplex],
types.Typ[types.UntypedString],
types.Typ[types.UntypedNil],
// error
types.Universe.Lookup("error").Type(),
// package unsafe
types.Typ[types.UnsafePointer],
// untyped types
types.Typ[types.UntypedBool],
types.Typ[types.UntypedInt],
types.Typ[types.UntypedRune],
types.Typ[types.UntypedFloat],
types.Typ[types.UntypedComplex],
types.Typ[types.UntypedString],
types.Typ[types.UntypedNil],
// invalid type
types.Typ[types.Invalid], // only appears in packages with errors
// package unsafe
types.Typ[types.UnsafePointer],
// used internally by gc; never used by this package or in .a files
anyType{},
// invalid type
types.Typ[types.Invalid], // only appears in packages with errors
// used internally by gc; never used by this package or in .a files
anyType{},
}
}
return predecl
}
type anyType struct{}

View File

@@ -0,0 +1,723 @@
// Copyright 2019 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.
// Indexed binary package export.
// This file was derived from $GOROOT/src/cmd/compile/internal/gc/iexport.go;
// see that file for specification of the format.
// +build go1.11
package gcimporter
import (
"bytes"
"encoding/binary"
"go/ast"
"go/constant"
"go/token"
"go/types"
"io"
"math/big"
"reflect"
"sort"
)
// Current indexed export format version. Increase with each format change.
// 0: Go1.11 encoding
const iexportVersion = 0
// IExportData returns the binary export data for pkg.
// If no file set is provided, position info will be missing.
func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
defer func() {
if e := recover(); e != nil {
if ierr, ok := e.(internalError); ok {
err = ierr
return
}
// Not an internal error; panic again.
panic(e)
}
}()
p := iexporter{
out: bytes.NewBuffer(nil),
fset: fset,
allPkgs: map[*types.Package]bool{},
stringIndex: map[string]uint64{},
declIndex: map[types.Object]uint64{},
typIndex: map[types.Type]uint64{},
}
for i, pt := range predeclared() {
p.typIndex[pt] = uint64(i)
}
if len(p.typIndex) > predeclReserved {
panic(internalErrorf("too many predeclared types: %d > %d", len(p.typIndex), predeclReserved))
}
// Initialize work queue with exported declarations.
scope := pkg.Scope()
for _, name := range scope.Names() {
if ast.IsExported(name) {
p.pushDecl(scope.Lookup(name))
}
}
// Loop until no more work.
for !p.declTodo.empty() {
p.doDecl(p.declTodo.popHead())
}
// Append indices to data0 section.
dataLen := uint64(p.data0.Len())
w := p.newWriter()
w.writeIndex(p.declIndex, pkg)
w.flush()
// Assemble header.
var hdr intWriter
hdr.WriteByte('i')
hdr.uint64(iexportVersion)
hdr.uint64(uint64(p.strings.Len()))
hdr.uint64(dataLen)
// Flush output.
io.Copy(p.out, &hdr)
io.Copy(p.out, &p.strings)
io.Copy(p.out, &p.data0)
return p.out.Bytes(), nil
}
// writeIndex writes out an object index. mainIndex indicates whether
// we're writing out the main index, which is also read by
// non-compiler tools and includes a complete package description
// (i.e., name and height).
func (w *exportWriter) writeIndex(index map[types.Object]uint64, localpkg *types.Package) {
// Build a map from packages to objects from that package.
pkgObjs := map[*types.Package][]types.Object{}
// For the main index, make sure to include every package that
// we reference, even if we're not exporting (or reexporting)
// any symbols from it.
pkgObjs[localpkg] = nil
for pkg := range w.p.allPkgs {
pkgObjs[pkg] = nil
}
for obj := range index {
pkgObjs[obj.Pkg()] = append(pkgObjs[obj.Pkg()], obj)
}
var pkgs []*types.Package
for pkg, objs := range pkgObjs {
pkgs = append(pkgs, pkg)
sort.Slice(objs, func(i, j int) bool {
return objs[i].Name() < objs[j].Name()
})
}
sort.Slice(pkgs, func(i, j int) bool {
return pkgs[i].Path() < pkgs[j].Path()
})
w.uint64(uint64(len(pkgs)))
for _, pkg := range pkgs {
w.string(pkg.Path())
w.string(pkg.Name())
w.uint64(uint64(0)) // package height is not needed for go/types
objs := pkgObjs[pkg]
w.uint64(uint64(len(objs)))
for _, obj := range objs {
w.string(obj.Name())
w.uint64(index[obj])
}
}
}
type iexporter struct {
fset *token.FileSet
out *bytes.Buffer
// allPkgs tracks all packages that have been referenced by
// the export data, so we can ensure to include them in the
// main index.
allPkgs map[*types.Package]bool
declTodo objQueue
strings intWriter
stringIndex map[string]uint64
data0 intWriter
declIndex map[types.Object]uint64
typIndex map[types.Type]uint64
}
// stringOff returns the offset of s within the string section.
// If not already present, it's added to the end.
func (p *iexporter) stringOff(s string) uint64 {
off, ok := p.stringIndex[s]
if !ok {
off = uint64(p.strings.Len())
p.stringIndex[s] = off
p.strings.uint64(uint64(len(s)))
p.strings.WriteString(s)
}
return off
}
// pushDecl adds n to the declaration work queue, if not already present.
func (p *iexporter) pushDecl(obj types.Object) {
// Package unsafe is known to the compiler and predeclared.
assert(obj.Pkg() != types.Unsafe)
if _, ok := p.declIndex[obj]; ok {
return
}
p.declIndex[obj] = ^uint64(0) // mark n present in work queue
p.declTodo.pushTail(obj)
}
// exportWriter handles writing out individual data section chunks.
type exportWriter struct {
p *iexporter
data intWriter
currPkg *types.Package
prevFile string
prevLine int64
}
func (p *iexporter) doDecl(obj types.Object) {
w := p.newWriter()
w.setPkg(obj.Pkg(), false)
switch obj := obj.(type) {
case *types.Var:
w.tag('V')
w.pos(obj.Pos())
w.typ(obj.Type(), obj.Pkg())
case *types.Func:
sig, _ := obj.Type().(*types.Signature)
if sig.Recv() != nil {
panic(internalErrorf("unexpected method: %v", sig))
}
w.tag('F')
w.pos(obj.Pos())
w.signature(sig)
case *types.Const:
w.tag('C')
w.pos(obj.Pos())
w.value(obj.Type(), obj.Val())
case *types.TypeName:
if obj.IsAlias() {
w.tag('A')
w.pos(obj.Pos())
w.typ(obj.Type(), obj.Pkg())
break
}
// Defined type.
w.tag('T')
w.pos(obj.Pos())
underlying := obj.Type().Underlying()
w.typ(underlying, obj.Pkg())
t := obj.Type()
if types.IsInterface(t) {
break
}
named, ok := t.(*types.Named)
if !ok {
panic(internalErrorf("%s is not a defined type", t))
}
n := named.NumMethods()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
m := named.Method(i)
w.pos(m.Pos())
w.string(m.Name())
sig, _ := m.Type().(*types.Signature)
w.param(sig.Recv())
w.signature(sig)
}
default:
panic(internalErrorf("unexpected object: %v", obj))
}
p.declIndex[obj] = w.flush()
}
func (w *exportWriter) tag(tag byte) {
w.data.WriteByte(tag)
}
func (w *exportWriter) pos(pos token.Pos) {
p := w.p.fset.Position(pos)
file := p.Filename
line := int64(p.Line)
// When file is the same as the last position (common case),
// we can save a few bytes by delta encoding just the line
// number.
//
// Note: Because data objects may be read out of order (or not
// at all), we can only apply delta encoding within a single
// object. This is handled implicitly by tracking prevFile and
// prevLine as fields of exportWriter.
if file == w.prevFile {
delta := line - w.prevLine
w.int64(delta)
if delta == deltaNewFile {
w.int64(-1)
}
} else {
w.int64(deltaNewFile)
w.int64(line) // line >= 0
w.string(file)
w.prevFile = file
}
w.prevLine = line
}
func (w *exportWriter) pkg(pkg *types.Package) {
// Ensure any referenced packages are declared in the main index.
w.p.allPkgs[pkg] = true
w.string(pkg.Path())
}
func (w *exportWriter) qualifiedIdent(obj types.Object) {
// Ensure any referenced declarations are written out too.
w.p.pushDecl(obj)
w.string(obj.Name())
w.pkg(obj.Pkg())
}
func (w *exportWriter) typ(t types.Type, pkg *types.Package) {
w.data.uint64(w.p.typOff(t, pkg))
}
func (p *iexporter) newWriter() *exportWriter {
return &exportWriter{p: p}
}
func (w *exportWriter) flush() uint64 {
off := uint64(w.p.data0.Len())
io.Copy(&w.p.data0, &w.data)
return off
}
func (p *iexporter) typOff(t types.Type, pkg *types.Package) uint64 {
off, ok := p.typIndex[t]
if !ok {
w := p.newWriter()
w.doTyp(t, pkg)
off = predeclReserved + w.flush()
p.typIndex[t] = off
}
return off
}
func (w *exportWriter) startType(k itag) {
w.data.uint64(uint64(k))
}
func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
switch t := t.(type) {
case *types.Named:
w.startType(definedType)
w.qualifiedIdent(t.Obj())
case *types.Pointer:
w.startType(pointerType)
w.typ(t.Elem(), pkg)
case *types.Slice:
w.startType(sliceType)
w.typ(t.Elem(), pkg)
case *types.Array:
w.startType(arrayType)
w.uint64(uint64(t.Len()))
w.typ(t.Elem(), pkg)
case *types.Chan:
w.startType(chanType)
// 1 RecvOnly; 2 SendOnly; 3 SendRecv
var dir uint64
switch t.Dir() {
case types.RecvOnly:
dir = 1
case types.SendOnly:
dir = 2
case types.SendRecv:
dir = 3
}
w.uint64(dir)
w.typ(t.Elem(), pkg)
case *types.Map:
w.startType(mapType)
w.typ(t.Key(), pkg)
w.typ(t.Elem(), pkg)
case *types.Signature:
w.startType(signatureType)
w.setPkg(pkg, true)
w.signature(t)
case *types.Struct:
w.startType(structType)
w.setPkg(pkg, true)
n := t.NumFields()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
f := t.Field(i)
w.pos(f.Pos())
w.string(f.Name())
w.typ(f.Type(), pkg)
w.bool(f.Embedded())
w.string(t.Tag(i)) // note (or tag)
}
case *types.Interface:
w.startType(interfaceType)
w.setPkg(pkg, true)
n := t.NumEmbeddeds()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
f := t.Embedded(i)
w.pos(f.Obj().Pos())
w.typ(f.Obj().Type(), f.Obj().Pkg())
}
n = t.NumExplicitMethods()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
m := t.ExplicitMethod(i)
w.pos(m.Pos())
w.string(m.Name())
sig, _ := m.Type().(*types.Signature)
w.signature(sig)
}
default:
panic(internalErrorf("unexpected type: %v, %v", t, reflect.TypeOf(t)))
}
}
func (w *exportWriter) setPkg(pkg *types.Package, write bool) {
if write {
w.pkg(pkg)
}
w.currPkg = pkg
}
func (w *exportWriter) signature(sig *types.Signature) {
w.paramList(sig.Params())
w.paramList(sig.Results())
if sig.Params().Len() > 0 {
w.bool(sig.Variadic())
}
}
func (w *exportWriter) paramList(tup *types.Tuple) {
n := tup.Len()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
w.param(tup.At(i))
}
}
func (w *exportWriter) param(obj types.Object) {
w.pos(obj.Pos())
w.localIdent(obj)
w.typ(obj.Type(), obj.Pkg())
}
func (w *exportWriter) value(typ types.Type, v constant.Value) {
w.typ(typ, nil)
switch v.Kind() {
case constant.Bool:
w.bool(constant.BoolVal(v))
case constant.Int:
var i big.Int
if i64, exact := constant.Int64Val(v); exact {
i.SetInt64(i64)
} else if ui64, exact := constant.Uint64Val(v); exact {
i.SetUint64(ui64)
} else {
i.SetString(v.ExactString(), 10)
}
w.mpint(&i, typ)
case constant.Float:
f := constantToFloat(v)
w.mpfloat(f, typ)
case constant.Complex:
w.mpfloat(constantToFloat(constant.Real(v)), typ)
w.mpfloat(constantToFloat(constant.Imag(v)), typ)
case constant.String:
w.string(constant.StringVal(v))
case constant.Unknown:
// package contains type errors
default:
panic(internalErrorf("unexpected value %v (%T)", v, v))
}
}
// constantToFloat converts a constant.Value with kind constant.Float to a
// big.Float.
func constantToFloat(x constant.Value) *big.Float {
assert(x.Kind() == constant.Float)
// Use the same floating-point precision (512) as cmd/compile
// (see Mpprec in cmd/compile/internal/gc/mpfloat.go).
const mpprec = 512
var f big.Float
f.SetPrec(mpprec)
if v, exact := constant.Float64Val(x); exact {
// float64
f.SetFloat64(v)
} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
// TODO(gri): add big.Rat accessor to constant.Value.
n := valueToRat(num)
d := valueToRat(denom)
f.SetRat(n.Quo(n, d))
} else {
// Value too large to represent as a fraction => inaccessible.
// TODO(gri): add big.Float accessor to constant.Value.
_, ok := f.SetString(x.ExactString())
assert(ok)
}
return &f
}
// mpint exports a multi-precision integer.
//
// For unsigned types, small values are written out as a single
// byte. Larger values are written out as a length-prefixed big-endian
// byte string, where the length prefix is encoded as its complement.
// For example, bytes 0, 1, and 2 directly represent the integer
// values 0, 1, and 2; while bytes 255, 254, and 253 indicate a 1-,
// 2-, and 3-byte big-endian string follow.
//
// Encoding for signed types use the same general approach as for
// unsigned types, except small values use zig-zag encoding and the
// bottom bit of length prefix byte for large values is reserved as a
// sign bit.
//
// The exact boundary between small and large encodings varies
// according to the maximum number of bytes needed to encode a value
// of type typ. As a special case, 8-bit types are always encoded as a
// single byte.
//
// TODO(mdempsky): Is this level of complexity really worthwhile?
func (w *exportWriter) mpint(x *big.Int, typ types.Type) {
basic, ok := typ.Underlying().(*types.Basic)
if !ok {
panic(internalErrorf("unexpected type %v (%T)", typ.Underlying(), typ.Underlying()))
}
signed, maxBytes := intSize(basic)
negative := x.Sign() < 0
if !signed && negative {
panic(internalErrorf("negative unsigned integer; type %v, value %v", typ, x))
}
b := x.Bytes()
if len(b) > 0 && b[0] == 0 {
panic(internalErrorf("leading zeros"))
}
if uint(len(b)) > maxBytes {
panic(internalErrorf("bad mpint length: %d > %d (type %v, value %v)", len(b), maxBytes, typ, x))
}
maxSmall := 256 - maxBytes
if signed {
maxSmall = 256 - 2*maxBytes
}
if maxBytes == 1 {
maxSmall = 256
}
// Check if x can use small value encoding.
if len(b) <= 1 {
var ux uint
if len(b) == 1 {
ux = uint(b[0])
}
if signed {
ux <<= 1
if negative {
ux--
}
}
if ux < maxSmall {
w.data.WriteByte(byte(ux))
return
}
}
n := 256 - uint(len(b))
if signed {
n = 256 - 2*uint(len(b))
if negative {
n |= 1
}
}
if n < maxSmall || n >= 256 {
panic(internalErrorf("encoding mistake: %d, %v, %v => %d", len(b), signed, negative, n))
}
w.data.WriteByte(byte(n))
w.data.Write(b)
}
// mpfloat exports a multi-precision floating point number.
//
// The number's value is decomposed into mantissa × 2**exponent, where
// mantissa is an integer. The value is written out as mantissa (as a
// multi-precision integer) and then the exponent, except exponent is
// omitted if mantissa is zero.
func (w *exportWriter) mpfloat(f *big.Float, typ types.Type) {
if f.IsInf() {
panic("infinite constant")
}
// Break into f = mant × 2**exp, with 0.5 <= mant < 1.
var mant big.Float
exp := int64(f.MantExp(&mant))
// Scale so that mant is an integer.
prec := mant.MinPrec()
mant.SetMantExp(&mant, int(prec))
exp -= int64(prec)
manti, acc := mant.Int(nil)
if acc != big.Exact {
panic(internalErrorf("mantissa scaling failed for %f (%s)", f, acc))
}
w.mpint(manti, typ)
if manti.Sign() != 0 {
w.int64(exp)
}
}
func (w *exportWriter) bool(b bool) bool {
var x uint64
if b {
x = 1
}
w.uint64(x)
return b
}
func (w *exportWriter) int64(x int64) { w.data.int64(x) }
func (w *exportWriter) uint64(x uint64) { w.data.uint64(x) }
func (w *exportWriter) string(s string) { w.uint64(w.p.stringOff(s)) }
func (w *exportWriter) localIdent(obj types.Object) {
// Anonymous parameters.
if obj == nil {
w.string("")
return
}
name := obj.Name()
if name == "_" {
w.string("_")
return
}
w.string(name)
}
type intWriter struct {
bytes.Buffer
}
func (w *intWriter) int64(x int64) {
var buf [binary.MaxVarintLen64]byte
n := binary.PutVarint(buf[:], x)
w.Write(buf[:n])
}
func (w *intWriter) uint64(x uint64) {
var buf [binary.MaxVarintLen64]byte
n := binary.PutUvarint(buf[:], x)
w.Write(buf[:n])
}
func assert(cond bool) {
if !cond {
panic("internal error: assertion failed")
}
}
// The below is copied from go/src/cmd/compile/internal/gc/syntax.go.
// objQueue is a FIFO queue of types.Object. The zero value of objQueue is
// a ready-to-use empty queue.
type objQueue struct {
ring []types.Object
head, tail int
}
// empty returns true if q contains no Nodes.
func (q *objQueue) empty() bool {
return q.head == q.tail
}
// pushTail appends n to the tail of the queue.
func (q *objQueue) pushTail(obj types.Object) {
if len(q.ring) == 0 {
q.ring = make([]types.Object, 16)
} else if q.head+len(q.ring) == q.tail {
// Grow the ring.
nring := make([]types.Object, len(q.ring)*2)
// Copy the old elements.
part := q.ring[q.head%len(q.ring):]
if q.tail-q.head <= len(part) {
part = part[:q.tail-q.head]
copy(nring, part)
} else {
pos := copy(nring, part)
copy(nring[pos:], q.ring[:q.tail%len(q.ring)])
}
q.ring, q.head, q.tail = nring, 0, q.tail-q.head
}
q.ring[q.tail%len(q.ring)] = obj
q.tail++
}
// popHead pops a node from the head of the queue. It panics if q is empty.
func (q *objQueue) popHead() types.Object {
if q.empty() {
panic("dequeue empty")
}
obj := q.ring[q.head%len(q.ring)]
q.head++
return obj
}

View File

@@ -109,7 +109,7 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []
},
}
for i, pt := range predeclared {
for i, pt := range predeclared() {
p.typCache[uint64(i)] = pt
}
@@ -142,8 +142,12 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []
p.pkgIndex[pkg] = nameIndex
pkgList[i] = pkg
}
localpkg := pkgList[0]
var localpkg *types.Package
for _, pkg := range pkgList {
if pkg.Path() == path {
localpkg = pkg
}
}
names := make([]string, 0, len(p.pkgIndex[localpkg]))
for name := range p.pkgIndex[localpkg] {
@@ -330,6 +334,10 @@ func (r *importReader) value() (typ types.Type, val constant.Value) {
val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im))
default:
if b.Kind() == types.Invalid {
val = constant.MakeUnknown()
return
}
errorf("unexpected type %v", typ) // panics
panic("unreachable")
}

View File

@@ -14,7 +14,7 @@ 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.
Three query operators are currently supported: "file", "pattern", and "name".
Two query operators are currently supported: "file" and "pattern".
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"
@@ -25,10 +25,6 @@ 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 '='.
The query "name=identifier" matches 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.
All other query operators are reserved for future use and currently
cause Load to report an error.

View File

@@ -18,7 +18,7 @@ import (
// Driver
type driverRequest struct {
Command string `json "command"`
Command string `json:"command"`
Mode LoadMode `json:"mode"`
Env []string `json:"env"`
BuildFlags []string `json:"build_flags"`

View File

@@ -78,7 +78,7 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
var sizes types.Sizes
var sizeserr error
var sizeswg sync.WaitGroup
if cfg.Mode >= LoadTypes {
if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 {
sizeswg.Add(1)
go func() {
sizes, sizeserr = getSizes(cfg)
@@ -104,7 +104,7 @@ extractQueries:
containFiles = append(containFiles, value)
case "pattern":
restPatterns = append(restPatterns, value)
case "name":
case "iamashamedtousethedisabledqueryname":
packagesNamed = append(packagesNamed, value)
case "": // not a reserved query
restPatterns = append(restPatterns, pattern)
@@ -121,20 +121,6 @@ extractQueries:
}
}
// TODO(matloob): Remove the definition of listfunc and just use golistPackages once go1.12 is released.
var listfunc driver
var isFallback bool
listfunc = func(cfg *Config, words ...string) (*driverResponse, error) {
response, err := golistDriverCurrent(cfg, words...)
if _, ok := err.(goTooOldError); ok {
isFallback = true
listfunc = golistDriverFallback
return listfunc(cfg, words...)
}
listfunc = golistDriverCurrent
return response, err
}
response := &responseDeduper{}
var err error
@@ -142,7 +128,7 @@ extractQueries:
// patterns also requires a go list call, since it's the equivalent of
// ".".
if len(restPatterns) > 0 || len(patterns) == 0 {
dr, err := listfunc(cfg, restPatterns...)
dr, err := golistDriver(cfg, restPatterns...)
if err != nil {
return nil, err
}
@@ -161,13 +147,13 @@ extractQueries:
var containsCandidates []string
if len(containFiles) != 0 {
if err := runContainsQueries(cfg, listfunc, isFallback, response, containFiles); err != nil {
if err := runContainsQueries(cfg, golistDriver, response, containFiles); err != nil {
return nil, err
}
}
if len(packagesNamed) != 0 {
if err := runNamedQueries(cfg, listfunc, response, packagesNamed); err != nil {
if err := runNamedQueries(cfg, golistDriver, response, packagesNamed); err != nil {
return nil, err
}
}
@@ -180,12 +166,8 @@ extractQueries:
containsCandidates = append(containsCandidates, modifiedPkgs...)
containsCandidates = append(containsCandidates, needPkgs...)
}
if len(needPkgs) > 0 {
addNeededOverlayPackages(cfg, listfunc, response, needPkgs)
if err != nil {
return nil, err
}
if err := addNeededOverlayPackages(cfg, golistDriver, response, needPkgs); err != nil {
return nil, err
}
// Check candidate packages for containFiles.
if len(containFiles) > 0 {
@@ -205,6 +187,9 @@ extractQueries:
}
func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDeduper, pkgs []string) error {
if len(pkgs) == 0 {
return nil
}
dr, err := driver(cfg, pkgs...)
if err != nil {
return err
@@ -212,10 +197,15 @@ func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDedu
for _, pkg := range dr.Packages {
response.addPackage(pkg)
}
_, needPkgs, err := processGolistOverlay(cfg, response.dr)
if err != nil {
return err
}
addNeededOverlayPackages(cfg, driver, response, needPkgs)
return nil
}
func runContainsQueries(cfg *Config, driver driver, isFallback bool, response *responseDeduper, queries []string) error {
func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error {
for _, query := range queries {
// TODO(matloob): Do only one query per directory.
fdir := filepath.Dir(query)
@@ -225,11 +215,6 @@ func runContainsQueries(cfg *Config, driver driver, isFallback bool, response *r
if err != nil {
return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err)
}
if isFallback {
pattern = "."
cfg.Dir = fdir
}
dirResponse, err := driver(cfg, pattern)
if err != nil {
return err
@@ -559,10 +544,10 @@ 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) {
// golistDriver 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 golistDriver(cfg *Config, words ...string) (*driverResponse, error) {
// go list uses the following identifiers in ImportPath and Imports:
//
// "p" -- importable package or main (command)
@@ -605,7 +590,7 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error)
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)
return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath)
}
// skip the duplicate
continue
@@ -620,16 +605,25 @@ func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error)
OtherFiles: absJoin(p.Dir, otherFiles(p)...),
}
// Workaround for https://golang.org/issue/28749.
// TODO(adonovan): delete before go1.12 release.
out := pkg.CompiledGoFiles[:0]
for _, f := range pkg.CompiledGoFiles {
if strings.HasSuffix(f, ".s") {
continue
// Work around https://golang.org/issue/28749:
// cmd/go puts assembly, C, and C++ files in CompiledGoFiles.
// Filter out any elements of CompiledGoFiles that are also in OtherFiles.
// We have to keep this workaround in place until go1.12 is a distant memory.
if len(pkg.OtherFiles) > 0 {
other := make(map[string]bool, len(pkg.OtherFiles))
for _, f := range pkg.OtherFiles {
other[f] = true
}
out = append(out, f)
out := pkg.CompiledGoFiles[:0]
for _, f := range pkg.CompiledGoFiles {
if other[f] {
continue
}
out = append(out, f)
}
pkg.CompiledGoFiles = out
}
pkg.CompiledGoFiles = out
// Extract the PkgPath from the package's ID.
if i := strings.IndexByte(pkg.ID, ' '); i >= 0 {
@@ -711,14 +705,16 @@ func absJoin(dir string, fileses ...[]string) (res []string) {
}
func golistargs(cfg *Config, words []string) []string {
const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo
fullargs := []string{
"list", "-e", "-json", "-compiled",
"list", "-e", "-json",
fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypesInfo|NeedTypesSizes) != 0),
fmt.Sprintf("-test=%t", cfg.Tests),
fmt.Sprintf("-export=%t", usesExportData(cfg)),
fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports),
fmt.Sprintf("-deps=%t", cfg.Mode&NeedDeps != 0),
// go list doesn't let you pass -test and -find together,
// probably because you'd just get the TestMain.
fmt.Sprintf("-find=%t", cfg.Mode < LoadImports && !cfg.Tests),
fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0),
}
fullargs = append(fullargs, cfg.BuildFlags...)
fullargs = append(fullargs, "--")
@@ -728,9 +724,6 @@ func golistargs(cfg *Config, words []string) []string {
// 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())
}
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
cmd := exec.CommandContext(cfg.Context, "go", args...)
@@ -744,11 +737,21 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) {
cmd.Dir = cfg.Dir
cmd.Stdout = stdout
cmd.Stderr = stderr
if debug {
defer func(start time.Time) {
log.Printf("%s for %v, stderr: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr)
}(time.Now())
}
if err := cmd.Run(); err != nil {
// Check for 'go' executable not being found.
if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound)
}
exitErr, ok := err.(*exec.ExitError)
if !ok {
// Catastrophic error:
// - executable not found
// - context cancellation
return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
}
@@ -758,6 +761,22 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) {
return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)}
}
// This error only appears in stderr. See golang.org/cl/166398 for a fix in go list to show
// the error in the Err section of stdout in case -e option is provided.
// This fix is provided for backwards compatibility.
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must be .go files") {
output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
strings.Trim(stderr.String(), "\n"))
return bytes.NewBufferString(output), nil
}
// Workaround for #29280: go list -e has incorrect behavior when an ad-hoc package doesn't exist.
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no such file or directory") {
output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`,
strings.Trim(stderr.String(), "\n"))
return bytes.NewBufferString(output), nil
}
// Export mode entails a build.
// If that build fails, errors appear on stderr
// (despite the -e flag) and the Export field is blank.
@@ -777,12 +796,12 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) {
// 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)
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr)
}
// debugging
if false {
fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(cfg, args...), stdout)
fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(cmd, args...), stdout)
}
return stdout, nil
@@ -797,13 +816,17 @@ func containsGoFile(s []string) bool {
return false
}
func cmdDebugStr(cfg *Config, args ...string) string {
func cmdDebugStr(cmd *exec.Cmd, args ...string) string {
env := make(map[string]string)
for _, kv := range cfg.Env {
for _, kv := range cmd.Env {
split := strings.Split(kv, "=")
k, v := split[0], split[1]
env[k] = v
}
var quotedArgs []string
for _, arg := range args {
quotedArgs = append(quotedArgs, strconv.Quote(arg))
}
return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], args)
return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %s", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], strings.Join(quotedArgs, " "))
}

View File

@@ -1,450 +0,0 @@
// 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.11. 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
}
}
outdir = filepath.Join(tmpdir, 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
}
// Don't use a .go extension on the file, so that the tests think the file is inside GOCACHE.
// 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.
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

@@ -1,318 +0,0 @@
// 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

@@ -46,7 +46,9 @@ outer:
fileExists = true
}
}
if dirContains {
// The overlay could have included an entirely new package.
isNewPackage := extractPackage(pkg, path, contents)
if dirContains || isNewPackage {
if !fileExists {
pkg.GoFiles = append(pkg.GoFiles, path) // TODO(matloob): should the file just be added to GoFiles?
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, path)
@@ -102,3 +104,35 @@ func extractImports(filename string, contents []byte) ([]string, error) {
}
return res, nil
}
// extractPackage attempts to extract a package defined in an overlay.
//
// If the package has errors and has no Name, GoFiles, or Imports,
// then it's possible that it doesn't yet exist on disk.
func extractPackage(pkg *Package, filename string, contents []byte) bool {
// TODO(rstambler): Check the message of the actual error?
// It differs between $GOPATH and module mode.
if len(pkg.Errors) != 1 {
return false
}
if pkg.Name != "" || pkg.ExportFile != "" {
return false
}
if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 {
return false
}
if len(pkg.Imports) > 0 {
return false
}
f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset?
if err != nil {
return false
}
// TODO(rstambler): This doesn't work for main packages.
if filepath.Base(pkg.PkgPath) != f.Name.Name {
return false
}
pkg.Name = f.Name.Name
pkg.Errors = nil
return true
}

View File

@@ -19,6 +19,7 @@ import (
"log"
"os"
"path/filepath"
"strings"
"sync"
"golang.org/x/tools/go/gcexportdata"
@@ -29,32 +30,78 @@ import (
// but may be slower. Load may return more information than requested.
type LoadMode int
const (
// The following constants are used to specify which fields of the Package
// should be filled when loading is done. As a special case to provide
// backwards compatibility, a LoadMode of 0 is equivalent to LoadFiles.
// For all other LoadModes, the bits below specify which fields will be filled
// in the result packages.
// WARNING: This part of the go/packages API is EXPERIMENTAL. It might
// be changed or removed up until April 15 2019. After that date it will
// be frozen.
// TODO(matloob): Remove this comment on April 15.
// ID and Errors (if present) will always be filled.
// NeedName adds Name and PkgPath.
NeedName LoadMode = 1 << iota
// NeedFiles adds GoFiles and OtherFiles.
NeedFiles
// NeedCompiledGoFiles adds CompiledGoFiles.
NeedCompiledGoFiles
// NeedImports adds Imports. If NeedDeps is not set, the Imports field will contain
// "placeholder" Packages with only the ID set.
NeedImports
// NeedDeps adds the fields requested by the LoadMode in the packages in Imports. If NeedImports
// is not set NeedDeps has no effect.
NeedDeps
// NeedExportsFile adds ExportsFile.
NeedExportsFile
// NeedTypes adds Types, Fset, and IllTyped.
NeedTypes
// NeedSyntax adds Syntax.
NeedSyntax
// NeedTypesInfo adds TypesInfo.
NeedTypesInfo
// NeedTypesSizes adds TypesSizes.
NeedTypesSizes
)
const (
// LoadFiles finds the packages and computes their source file lists.
// Package fields: ID, Name, Errors, GoFiles, and OtherFiles.
LoadFiles LoadMode = iota
// Package fields: ID, Name, Errors, GoFiles, CompiledGoFiles, and OtherFiles.
LoadFiles = NeedName | NeedFiles | NeedCompiledGoFiles
// LoadImports adds import information for each package
// and its dependencies.
// Package fields added: Imports.
LoadImports
LoadImports = LoadFiles | NeedImports | NeedDeps
// LoadTypes adds type information for package-level
// declarations in the packages matching the patterns.
// Package fields added: Types, Fset, and IllTyped.
// Package fields added: Types, TypesSizes, Fset, and IllTyped.
// This mode uses type information provided by the build system when
// possible, and may fill in the ExportFile field.
LoadTypes
LoadTypes = LoadImports | NeedTypes | NeedTypesSizes
// LoadSyntax adds typed syntax trees for the packages matching the patterns.
// Package fields added: Syntax, and TypesInfo, for direct pattern matches only.
LoadSyntax
LoadSyntax = LoadTypes | NeedSyntax | NeedTypesInfo
// LoadAllSyntax adds typed syntax trees for the packages matching the patterns
// and all dependencies.
// Package fields added: Types, Fset, IllTyped, Syntax, and TypesInfo,
// for all packages in the import graph.
LoadAllSyntax
LoadAllSyntax = LoadSyntax
)
// A Config specifies details about how packages should be loaded.
@@ -90,7 +137,7 @@ type Config struct {
BuildFlags []string
// Fset provides source position information for syntax trees and types.
// If Fset is nil, the loader will create a new FileSet.
// If Fset is nil, Load will use a new fileset, but preserve Fset's value.
Fset *token.FileSet
// ParseFile is called to read and parse each file
@@ -371,15 +418,34 @@ type loaderPackage struct {
type loader struct {
pkgs map[string]*loaderPackage
Config
sizes types.Sizes
exportMu sync.Mutex // enforces mutual exclusion of exportdata operations
sizes types.Sizes
parseCache map[string]*parseValue
parseCacheMu sync.Mutex
exportMu sync.Mutex // enforces mutual exclusion of exportdata operations
// TODO(matloob): Add an implied mode here and use that instead of mode.
// Implied mode would contain all the fields we need the data for so we can
// get the actually requested fields. We'll zero them out before returning
// packages to the user. This will make it easier for us to get the conditions
// where we need certain modes right.
}
type parseValue struct {
f *ast.File
err error
ready chan struct{}
}
func newLoader(cfg *Config) *loader {
ld := &loader{}
ld := &loader{
parseCache: map[string]*parseValue{},
}
if cfg != nil {
ld.Config = *cfg
}
if ld.Config.Mode == 0 {
ld.Config.Mode = LoadFiles // Preserve zero behavior of Mode for backwards compatibility.
}
if ld.Config.Env == nil {
ld.Config.Env = os.Environ()
}
@@ -392,7 +458,7 @@ func newLoader(cfg *Config) *loader {
}
}
if ld.Mode >= LoadTypes {
if ld.Mode&NeedTypes != 0 {
if ld.Fset == nil {
ld.Fset = token.NewFileSet()
}
@@ -401,12 +467,8 @@ func newLoader(cfg *Config) *loader {
// because we load source if export data is missing.
if ld.ParseFile == nil {
ld.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
var isrc interface{}
if src != nil {
isrc = src
}
const mode = parser.AllErrors | parser.ParseComments
return parser.ParseFile(fset, filename, isrc, mode)
return parser.ParseFile(fset, filename, src, mode)
}
}
}
@@ -429,11 +491,9 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
rootIndex = i
}
lpkg := &loaderPackage{
Package: pkg,
needtypes: ld.Mode >= LoadAllSyntax ||
ld.Mode >= LoadTypes && rootIndex >= 0,
needsrc: ld.Mode >= LoadAllSyntax ||
ld.Mode >= LoadSyntax && rootIndex >= 0 ||
Package: pkg,
needtypes: (ld.Mode&(NeedTypes|NeedTypesInfo) != 0 && rootIndex < 0) || rootIndex >= 0,
needsrc: (ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && rootIndex < 0) || rootIndex >= 0 ||
len(ld.Overlay) > 0 || // Overlays can invalidate export data. TODO(matloob): make this check fine-grained based on dependencies on overlaid files
pkg.ExportFile == "" && pkg.PkgPath != "unsafe",
}
@@ -506,14 +566,17 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
if lpkg.needsrc {
srcPkgs = append(srcPkgs, lpkg)
}
if ld.Mode&NeedTypesSizes != 0 {
lpkg.TypesSizes = ld.sizes
}
stack = stack[:len(stack)-1] // pop
lpkg.color = black
return lpkg.needsrc
}
if ld.Mode < LoadImports {
//we do this to drop the stub import packages that we are not even going to try to resolve
if ld.Mode&(NeedImports|NeedDeps) == 0 {
// We do this to drop the stub import packages that we are not even going to try to resolve.
for _, lpkg := range initial {
lpkg.Imports = nil
}
@@ -523,17 +586,19 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
visit(lpkg)
}
}
for _, lpkg := range srcPkgs {
// Complete type information is required for the
// immediate dependencies of each source package.
for _, ipkg := range lpkg.Imports {
imp := ld.pkgs[ipkg.ID]
imp.needtypes = true
if ld.Mode&NeedDeps != 0 { // TODO(matloob): This is only the case if NeedTypes is also set, right?
for _, lpkg := range srcPkgs {
// Complete type information is required for the
// immediate dependencies of each source package.
for _, ipkg := range lpkg.Imports {
imp := ld.pkgs[ipkg.ID]
imp.needtypes = true
}
}
}
// Load type data if needed, starting at
// the initial packages (roots of the import DAG).
if ld.Mode >= LoadTypes {
if ld.Mode&NeedTypes != 0 {
var wg sync.WaitGroup
for _, lpkg := range initial {
wg.Add(1)
@@ -546,16 +611,61 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
}
result := make([]*Package, len(initial))
importPlaceholders := make(map[string]*Package)
for i, lpkg := range initial {
result[i] = lpkg.Package
}
for i := range ld.pkgs {
// Clear all unrequested fields, for extra de-Hyrum-ization.
if ld.Mode&NeedName == 0 {
ld.pkgs[i].Name = ""
ld.pkgs[i].PkgPath = ""
}
if ld.Mode&NeedFiles == 0 {
ld.pkgs[i].GoFiles = nil
ld.pkgs[i].OtherFiles = nil
}
if ld.Mode&NeedCompiledGoFiles == 0 {
ld.pkgs[i].CompiledGoFiles = nil
}
if ld.Mode&NeedImports == 0 {
ld.pkgs[i].Imports = nil
}
if ld.Mode&NeedExportsFile == 0 {
ld.pkgs[i].ExportFile = ""
}
if ld.Mode&NeedTypes == 0 {
ld.pkgs[i].Types = nil
ld.pkgs[i].Fset = nil
ld.pkgs[i].IllTyped = false
}
if ld.Mode&NeedSyntax == 0 {
ld.pkgs[i].Syntax = nil
}
if ld.Mode&NeedTypesInfo == 0 {
ld.pkgs[i].TypesInfo = nil
}
if ld.Mode&NeedTypesSizes == 0 {
ld.pkgs[i].TypesSizes = nil
}
if ld.Mode&NeedDeps == 0 {
for j, pkg := range ld.pkgs[i].Imports {
ph, ok := importPlaceholders[pkg.ID]
if !ok {
ph = &Package{ID: pkg.ID}
importPlaceholders[pkg.ID] = ph
}
ld.pkgs[i].Imports[j] = ph
}
}
}
return result, nil
}
// loadRecursive loads the specified package and its dependencies,
// recursively, in parallel, in topological order.
// It is atomic and idempotent.
// Precondition: ld.Mode >= LoadTypes.
// Precondition: ld.Mode&NeedTypes.
func (ld *loader) loadRecursive(lpkg *loaderPackage) {
lpkg.loadOnce.Do(func() {
// Load the direct dependencies, in parallel.
@@ -707,7 +817,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
// Type-check bodies of functions only in non-initial packages.
// Example: for import graph A->B->C and initial packages {A,C},
// we can ignore function bodies in B.
IgnoreFuncBodies: ld.Mode < LoadAllSyntax && !lpkg.initial,
IgnoreFuncBodies: (ld.Mode&(NeedDeps|NeedTypesInfo) == 0) && !lpkg.initial,
Error: appendError,
Sizes: ld.sizes,
@@ -760,6 +870,42 @@ func (f importerFunc) Import(path string) (*types.Package, error) { return f(pat
// the number of parallel I/O calls per process.
var ioLimit = make(chan bool, 20)
func (ld *loader) parseFile(filename string) (*ast.File, error) {
ld.parseCacheMu.Lock()
v, ok := ld.parseCache[filename]
if ok {
// cache hit
ld.parseCacheMu.Unlock()
<-v.ready
} else {
// cache miss
v = &parseValue{ready: make(chan struct{})}
ld.parseCache[filename] = v
ld.parseCacheMu.Unlock()
var src []byte
for f, contents := range ld.Config.Overlay {
if sameFile(f, filename) {
src = contents
}
}
var err error
if src == nil {
ioLimit <- true // wait
src, err = ioutil.ReadFile(filename)
<-ioLimit // signal
}
if err != nil {
v.err = err
} else {
v.f, v.err = ld.ParseFile(ld.Fset, filename, src)
}
close(v.ready)
}
return v.f, v.err
}
// parseFiles reads and parses the Go source files and returns the ASTs
// of the ones that could be at least partially parsed, along with a
// list of I/O and parse errors encountered.
@@ -780,24 +926,7 @@ func (ld *loader) parseFiles(filenames []string) ([]*ast.File, []error) {
}
wg.Add(1)
go func(i int, filename string) {
ioLimit <- true // wait
// ParseFile may return both an AST and an error.
var src []byte
for f, contents := range ld.Config.Overlay {
if sameFile(f, filename) {
src = contents
}
}
var err error
if src == nil {
src, err = ioutil.ReadFile(filename)
}
if err != nil {
parsed[i], errors[i] = nil, err
} else {
parsed[i], errors[i] = ld.ParseFile(ld.Fset, filename, src)
}
<-ioLimit // signal
parsed[i], errors[i] = ld.parseFile(filename)
wg.Done()
}(i, file)
}
@@ -838,7 +967,7 @@ func sameFile(x, y string) bool {
// overlay case implies x==y.)
return true
}
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
if strings.EqualFold(filepath.Base(x), filepath.Base(y)) { // (optimisation)
if xi, err := os.Stat(x); err == nil {
if yi, err := os.Stat(y); err == nil {
return os.SameFile(xi, yi)
@@ -951,5 +1080,5 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error
}
func usesExportData(cfg *Config) bool {
return LoadTypes <= cfg.Mode && cfg.Mode < LoadAllSyntax
return cfg.Mode&NeedExportsFile != 0 || cfg.Mode&NeedTypes != 0 && cfg.Mode&NeedTypesInfo == 0
}