Commit ebe1664d authored by Russ Cox's avatar Russ Cox

go/build: replace FindTree, ScanDir, Tree, DirInfo with Import, Package

This is an API change, but one I have been promising would
happen when it was clear what the go command needed.

This is basically a complete replacement of what used to be here.

build.Tree is gone.

build.DirInfo is expanded and now called build.Package.

build.FindTree is now build.Import(package, srcDir, build.FindOnly).
The returned *Package contains information that FindTree returned,
but applicable only to a single package.

build.ScanDir is now build.ImportDir.

build.FindTree+build.ScanDir is now build.Import.

The new Import API allows specifying the source directory,
in order to resolve local imports (import "./foo") and also allows
scanning of packages outside of $GOPATH.  They will come back
with less information in the Package, but they will still work.

The old go/build API exposed both too much and too little.
This API is much closer to what the go command needs,
and it works well enough in the other places where it is
used.  Path is gone, so it can no longer be misused.  (Fixes issue 2749.)

This CL updates clients of go/build other than the go command.
The go command changes are in a separate CL, to be submitted
at the same time.

R=golang-dev, r, alex.brainman, adg
CC=golang-dev
https://golang.org/cl/5713043
parent a72b87ef
...@@ -74,16 +74,10 @@ func main() { ...@@ -74,16 +74,10 @@ func main() {
pkgs = strings.Fields(string(stds)) pkgs = strings.Fields(string(stds))
} }
tree, _, err := build.FindTree("os") // some known package
if err != nil {
log.Fatalf("failed to find tree: %v", err)
}
var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true var featureCtx = make(map[string]map[string]bool) // feature -> context name -> true
for _, context := range contexts { for _, context := range contexts {
w := NewWalker() w := NewWalker()
w.context = context w.context = context
w.tree = tree
for _, pkg := range pkgs { for _, pkg := range pkgs {
w.wantedPkg[pkg] = true w.wantedPkg[pkg] = true
...@@ -95,7 +89,7 @@ func main() { ...@@ -95,7 +89,7 @@ func main() {
strings.HasPrefix(pkg, "old/") { strings.HasPrefix(pkg, "old/") {
continue continue
} }
if !tree.HasSrc(pkg) { if fi, err := os.Stat(filepath.Join(w.root, pkg)); err != nil || !fi.IsDir() {
log.Fatalf("no source in tree for package %q", pkg) log.Fatalf("no source in tree for package %q", pkg)
} }
w.WalkPackage(pkg) w.WalkPackage(pkg)
...@@ -165,7 +159,7 @@ type pkgSymbol struct { ...@@ -165,7 +159,7 @@ type pkgSymbol struct {
type Walker struct { type Walker struct {
context *build.Context context *build.Context
tree *build.Tree root string
fset *token.FileSet fset *token.FileSet
scope []string scope []string
features map[string]bool // set features map[string]bool // set
...@@ -191,6 +185,7 @@ func NewWalker() *Walker { ...@@ -191,6 +185,7 @@ func NewWalker() *Walker {
selectorFullPkg: make(map[string]string), selectorFullPkg: make(map[string]string),
wantedPkg: make(map[string]bool), wantedPkg: make(map[string]bool),
prevConstType: make(map[pkgSymbol]string), prevConstType: make(map[pkgSymbol]string),
root: filepath.Join(build.Default.GOROOT, "src/pkg"),
} }
} }
...@@ -252,15 +247,13 @@ func (w *Walker) WalkPackage(name string) { ...@@ -252,15 +247,13 @@ func (w *Walker) WalkPackage(name string) {
defer func() { defer func() {
w.packageState[name] = loaded w.packageState[name] = loaded
}() }()
dir := filepath.Join(w.tree.SrcDir(), filepath.FromSlash(name)) dir := filepath.Join(w.root, filepath.FromSlash(name))
var info *build.DirInfo ctxt := w.context
var err error if ctxt == nil {
if ctx := w.context; ctx != nil { ctxt = &build.Default
info, err = ctx.ScanDir(dir)
} else {
info, err = build.ScanDir(dir)
} }
info, err := ctxt.ImportDir(dir, 0)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "no Go source files") { if strings.Contains(err.Error(), "no Go source files") {
return return
......
...@@ -7,7 +7,6 @@ package main ...@@ -7,7 +7,6 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"go/build"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
...@@ -36,7 +35,7 @@ func TestGolden(t *testing.T) { ...@@ -36,7 +35,7 @@ func TestGolden(t *testing.T) {
w := NewWalker() w := NewWalker()
w.wantedPkg[fi.Name()] = true w.wantedPkg[fi.Name()] = true
w.tree = &build.Tree{Path: "testdata", Goroot: true} w.root = "testdata/src/pkg"
goldenFile := filepath.Join("testdata", "src", "pkg", fi.Name(), "golden.txt") goldenFile := filepath.Join("testdata", "src", "pkg", fi.Name(), "golden.txt")
w.WalkPackage(fi.Name()) w.WalkPackage(fi.Name())
......
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"go/printer" "go/printer"
"go/token" "go/token"
"io" "io"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
...@@ -90,11 +91,11 @@ var ( ...@@ -90,11 +91,11 @@ var (
func initHandlers() { func initHandlers() {
paths := filepath.SplitList(*pkgPath) paths := filepath.SplitList(*pkgPath)
for _, t := range build.Path { gorootSrc := filepath.Join(build.Default.GOROOT, "src", "pkg")
if t.Goroot { for _, p := range build.Default.SrcDirs() {
continue if p != gorootSrc {
paths = append(paths, p)
} }
paths = append(paths, t.SrcDir())
} }
fsMap.Init(paths) fsMap.Init(paths)
...@@ -1002,11 +1003,13 @@ func fsReadDir(dir string) ([]os.FileInfo, error) { ...@@ -1002,11 +1003,13 @@ func fsReadDir(dir string) ([]os.FileInfo, error) {
return fs.ReadDir(dir) return fs.ReadDir(dir)
} }
// fsReadFile implements ReadFile for the go/build package. // fsOpenFile implements OpenFile for the go/build package.
func fsReadFile(dir, name string) (path string, data []byte, err error) { func fsOpenFile(name string) (r io.ReadCloser, err error) {
path = filepath.Join(dir, name) data, err := ReadFile(fs, name)
data, err = ReadFile(fs, path) if err != nil {
return return nil, err
}
return ioutil.NopCloser(bytes.NewReader(data)), nil
} }
func inList(name string, list []string) bool { func inList(name string, list []string) bool {
...@@ -1039,10 +1042,11 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf ...@@ -1039,10 +1042,11 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
// To use different pair, such as if we allowed the user // To use different pair, such as if we allowed the user
// to choose, set ctxt.GOOS and ctxt.GOARCH before // to choose, set ctxt.GOOS and ctxt.GOARCH before
// calling ctxt.ScanDir. // calling ctxt.ScanDir.
ctxt := build.DefaultContext ctxt := build.Default
ctxt.IsAbsPath = path.IsAbs
ctxt.ReadDir = fsReadDir ctxt.ReadDir = fsReadDir
ctxt.ReadFile = fsReadFile ctxt.OpenFile = fsOpenFile
dir, err := ctxt.ScanDir(abspath) dir, err := ctxt.ImportDir(abspath, 0)
if err == nil { if err == nil {
pkgFiles = append(dir.GoFiles, dir.CgoFiles...) pkgFiles = append(dir.GoFiles, dir.CgoFiles...)
} }
......
...@@ -388,9 +388,9 @@ func main() { ...@@ -388,9 +388,9 @@ func main() {
} }
relpath := path relpath := path
abspath := path abspath := path
if t, pkg, err := build.FindTree(path); err == nil { if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" {
relpath = pkg relpath = bp.ImportPath
abspath = filepath.Join(t.SrcDir(), pkg) abspath = bp.Dir
} else if !filepath.IsAbs(path) { } else if !filepath.IsAbs(path) {
abspath = absolutePath(path, pkgHandler.fsRoot) abspath = absolutePath(path, pkgHandler.fsRoot)
} else { } else {
......
...@@ -18,6 +18,7 @@ import ( ...@@ -18,6 +18,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"text/scanner" "text/scanner"
) )
...@@ -39,11 +40,14 @@ func findPkg(path string) (filename, id string) { ...@@ -39,11 +40,14 @@ func findPkg(path string) (filename, id string) {
switch path[0] { switch path[0] {
default: default:
// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
tree, pkg, err := build.FindTree(path) bp, _ := build.Import(path, "", build.FindOnly)
if err != nil { if bp.PkgObj == "" {
return return
} }
noext = filepath.Join(tree.PkgDir(), pkg) noext = bp.PkgObj
if strings.HasSuffix(noext, ".a") {
noext = noext[:len(noext)-2]
}
case '.': case '.':
// "./x" -> "/this/directory/x.ext", "/this/directory/x" // "./x" -> "/this/directory/x.ext", "/this/directory/x"
......
This diff is collapsed.
...@@ -5,83 +5,14 @@ ...@@ -5,83 +5,14 @@
package build package build
import ( import (
"os"
"path/filepath" "path/filepath"
"reflect"
"runtime" "runtime"
"sort"
"testing" "testing"
) )
func sortstr(x []string) []string {
sort.Strings(x)
return x
}
var buildPkgs = []struct {
dir string
info *DirInfo
}{
{
"go/build/pkgtest",
&DirInfo{
GoFiles: []string{"pkgtest.go"},
SFiles: []string{"sqrt_" + runtime.GOARCH + ".s"},
Package: "pkgtest",
Imports: []string{"bytes"},
TestImports: []string{"fmt", "pkgtest"},
TestGoFiles: sortstr([]string{"sqrt_test.go", "sqrt_" + runtime.GOARCH + "_test.go"}),
XTestGoFiles: []string{"xsqrt_test.go"},
},
},
{
"go/build/cmdtest",
&DirInfo{
GoFiles: []string{"main.go"},
Package: "main",
Imports: []string{"go/build/pkgtest"},
TestImports: []string{},
},
},
{
"go/build/cgotest",
&DirInfo{
CgoFiles: ifCgo([]string{"cgotest.go"}),
CFiles: []string{"cgotest.c"},
HFiles: []string{"cgotest.h"},
Imports: []string{"C", "unsafe"},
TestImports: []string{},
Package: "cgotest",
},
},
}
func ifCgo(x []string) []string {
if DefaultContext.CgoEnabled {
return x
}
return nil
}
func TestBuild(t *testing.T) {
for _, tt := range buildPkgs {
tree := Path[0] // Goroot
dir := filepath.Join(tree.SrcDir(), tt.dir)
info, err := ScanDir(dir)
if err != nil {
t.Errorf("ScanDir(%#q): %v", tt.dir, err)
continue
}
// Don't bother testing import positions.
tt.info.ImportPos, tt.info.TestImportPos = info.ImportPos, info.TestImportPos
if !reflect.DeepEqual(info, tt.info) {
t.Errorf("ScanDir(%#q) = %#v, want %#v\n", tt.dir, info, tt.info)
continue
}
}
}
func TestMatch(t *testing.T) { func TestMatch(t *testing.T) {
ctxt := DefaultContext ctxt := Default
what := "default" what := "default"
match := func(tag string) { match := func(tag string) {
if !ctxt.match(tag) { if !ctxt.match(tag) {
...@@ -106,3 +37,40 @@ func TestMatch(t *testing.T) { ...@@ -106,3 +37,40 @@ func TestMatch(t *testing.T) {
match(runtime.GOOS + "," + runtime.GOARCH + ",!bar") match(runtime.GOOS + "," + runtime.GOARCH + ",!bar")
nomatch(runtime.GOOS + "," + runtime.GOARCH + ",bar") nomatch(runtime.GOOS + "," + runtime.GOARCH + ",bar")
} }
func TestDotSlashImport(t *testing.T) {
p, err := ImportDir("testdata/other", 0)
if err != nil {
t.Fatal(err)
}
if len(p.Imports) != 1 || p.Imports[0] != "./file" {
t.Fatalf("testdata/other: Imports=%v, want [./file]", p.Imports)
}
p1, err := Import("./file", "testdata/other", 0)
if err != nil {
t.Fatal(err)
}
if p1.Name != "file" {
t.Fatalf("./file: Name=%q, want %q", p1.Name, "file")
}
dir := filepath.Clean("testdata/other/file") // Clean to use \ on Windows
if p1.Dir != dir {
t.Fatalf("./file: Dir=%q, want %q", p1.Name, dir)
}
}
func TestLocalDirectory(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
p, err := ImportDir(cwd, 0)
if err != nil {
t.Fatal(err)
}
if p.ImportPath != "go/build" {
t.Fatalf("ImportPath=%q, want %q", p.ImportPath, "go/build")
}
}
// Copyright 2011 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.
int
Add(int x, int y, int *sum)
{
sum = x+y;
}
// Copyright 2011 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 cgotest
/*
char* greeting = "hello, world";
*/
// #include "cgotest.h"
import "C"
import "unsafe"
var Greeting = C.GoString(C.greeting)
func DoAdd(x, y int) (sum int) {
C.Add(C.int(x), C.int(y), (*C.int)(unsafe.Pointer(&sum)))
return
}
// Copyright 2011 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.
extern int Add(int, int, int *);
// Copyright 2011 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 main
import "go/build/pkgtest"
func main() {
pkgtest.Foo()
print(int(pkgtest.Sqrt(9)))
}
// Copyright 2011 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 build
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
)
// Path is a validated list of Trees derived from $GOROOT and $GOPATH at init.
var Path []*Tree
// Tree describes a Go source tree, either $GOROOT or one from $GOPATH.
type Tree struct {
Path string
Goroot bool
}
func newTree(p string) (*Tree, error) {
if !filepath.IsAbs(p) {
return nil, errors.New("must be absolute")
}
ep, err := filepath.EvalSymlinks(p)
if err != nil {
return nil, err
}
return &Tree{Path: ep}, nil
}
// SrcDir returns the tree's package source directory.
func (t *Tree) SrcDir() string {
if t.Goroot {
return filepath.Join(t.Path, "src", "pkg")
}
return filepath.Join(t.Path, "src")
}
// PkgDir returns the tree's package object directory.
func (t *Tree) PkgDir() string {
goos, goarch := runtime.GOOS, runtime.GOARCH
if e := os.Getenv("GOOS"); e != "" {
goos = e
}
if e := os.Getenv("GOARCH"); e != "" {
goarch = e
}
return filepath.Join(t.Path, "pkg", goos+"_"+goarch)
}
// BinDir returns the tree's binary executable directory.
func (t *Tree) BinDir() string {
if t.Goroot {
if gobin := os.Getenv("GOBIN"); gobin != "" {
return filepath.Clean(gobin)
}
}
return filepath.Join(t.Path, "bin")
}
// HasSrc returns whether the given package's
// source can be found inside this Tree.
func (t *Tree) HasSrc(pkg string) bool {
fi, err := os.Stat(filepath.Join(t.SrcDir(), pkg))
if err != nil {
return false
}
return fi.IsDir()
}
// HasPkg returns whether the given package's
// object file can be found inside this Tree.
func (t *Tree) HasPkg(pkg string) bool {
fi, err := os.Stat(filepath.Join(t.PkgDir(), pkg+".a"))
if err != nil {
return false
}
return !fi.IsDir()
}
var (
ErrNotFound = errors.New("package could not be found locally")
ErrTreeNotFound = errors.New("no valid GOROOT or GOPATH could be found")
)
// FindTree takes an import or filesystem path and returns the
// tree where the package source should be and the package import path.
func FindTree(path string) (tree *Tree, pkg string, err error) {
if isLocalPath(path) {
if path, err = filepath.Abs(path); err != nil {
return
}
if path, err = filepath.EvalSymlinks(path); err != nil {
return
}
for _, t := range Path {
tpath := t.SrcDir() + string(filepath.Separator)
if !filepath.HasPrefix(path, tpath) {
continue
}
tree = t
pkg = filepath.ToSlash(path[len(tpath):])
return
}
err = fmt.Errorf("path %q not inside a GOPATH", path)
return
}
tree = defaultTree
pkg = filepath.ToSlash(path)
for _, t := range Path {
if t.HasSrc(pkg) {
tree = t
return
}
}
if tree == nil {
err = ErrTreeNotFound
} else {
err = ErrNotFound
}
return
}
var (
// argument lists used by the build's gc and ld methods
gcImportArgs []string
ldImportArgs []string
// default tree for remote packages
defaultTree *Tree
)
// set up Path: parse and validate GOROOT and GOPATH variables
func init() {
root := runtime.GOROOT()
t, err := newTree(root)
if err == nil {
t.Goroot = true
Path = []*Tree{t}
}
for _, p := range filepath.SplitList(os.Getenv("GOPATH")) {
if p == "" {
continue
}
t, err := newTree(p)
if err != nil {
continue
}
Path = append(Path, t)
gcImportArgs = append(gcImportArgs, "-I", t.PkgDir())
ldImportArgs = append(ldImportArgs, "-L", t.PkgDir())
// select first GOPATH entry as default
if defaultTree == nil {
defaultTree = t
}
}
// use GOROOT if no valid GOPATH specified
if defaultTree == nil && len(Path) > 0 {
defaultTree = Path[0]
}
}
// Copyright 2011 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 pkgtest
import "bytes"
func Foo() *bytes.Buffer {
return nil
}
func Sqrt(x float64) float64
// Copyright 2009 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.
// func Sqrt(x float64) float64
TEXT ·Sqrt(SB),7,$0
FMOVD x+0(FP),F0
FSQRT
FMOVDP F0,r+8(FP)
RET
// Copyright 2009 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.
// func Sqrt(x float64) float64
TEXT ·Sqrt(SB),7,$0
SQRTSD x+0(FP), X0
MOVSD X0, r+8(FP)
RET
// Copyright 2011 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.
// func Sqrt(x float64) float64
TEXT ·Sqrt(SB),7,$0
MOVD x+0(FP),F0
SQRTD F0,F0
MOVD F0,r+8(FP)
RET
// Copyright 2011 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 pkgtest
import "fmt"
var _ = fmt.Printf
// Copyright 2011 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 pkgtest_test
import "pkgtest"
var _ = pkgtest.Foo
...@@ -55,7 +55,7 @@ var tests = []GoodFileTest{ ...@@ -55,7 +55,7 @@ var tests = []GoodFileTest{
func TestGoodOSArch(t *testing.T) { func TestGoodOSArch(t *testing.T) {
for _, test := range tests { for _, test := range tests {
if DefaultContext.goodOSArchFile(test.name) != test.result { if Default.goodOSArchFile(test.name) != test.result {
t.Fatalf("goodOSArchFile(%q) != %v", test.name, test.result) t.Fatalf("goodOSArchFile(%q) != %v", test.name, test.result)
} }
} }
......
// Test data - not compiled.
package file
func F() {}
// Test data - not compiled.
package main
import (
"./file"
)
func main() {
file.F()
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment