diff --git a/go/buildutil/util.go b/go/buildutil/util.go new file mode 100644 index 0000000000..f4dca6a234 --- /dev/null +++ b/go/buildutil/util.go @@ -0,0 +1,127 @@ +// Copyright 2014 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 buildutil + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "io" + "os" + "path" + "path/filepath" + "strings" +) + +// ParseFile behaves like parser.ParseFile, +// but uses the build context's virtual file system, if any. +// +// If file is not absolute (as defined by IsAbsPath), the (dir, file) +// components are joined using JoinPath; dir must be absolute. +// +// The displayPath function, if provided, is used to transform the +// filename that will be attached to the ASTs. +// +// TODO(adonovan): call this from go/loader.parseFiles when the tree thaws. +// +func ParseFile(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, file string, mode parser.Mode) (*ast.File, error) { + if !IsAbsPath(ctxt, file) { + file = JoinPath(ctxt, dir, file) + } + rd, err := OpenFile(ctxt, file) + if err != nil { + return nil, err + } + defer rd.Close() // ignore error + if displayPath != nil { + file = displayPath(file) + } + return parser.ParseFile(fset, file, rd, mode) +} + +// ContainingPackage returns the package containing filename. +// +// If filename is not absolute, it is interpreted relative to working directory dir. +// All I/O is via the build context's virtual file system, if any. +// +// The '...Files []string' fields of the resulting build.Package are not +// populated (build.FindOnly mode). +// +// TODO(adonovan): call this from oracle when the tree thaws. +// +func ContainingPackage(ctxt *build.Context, dir, filename string) (*build.Package, error) { + if !IsAbsPath(ctxt, filename) { + filename = JoinPath(ctxt, dir, filename) + } + + // We must not assume the virtualized file tree uses + // "/" always, + // `\` always, + // or os.PathSeparator (which varies by platform), + // but to make any progress, we are forced to assume that + // virtualized paths will not use `\` unless the PathSeparator + // is also `\`, thus we can rely on filepath.ToSlash for some sanity. + + dirSlash := path.Dir(filepath.ToSlash(filename)) + "/" + + // We assume that no source root (GOPATH[i] or GOROOT) contains any other. + for _, srcdir := range ctxt.SrcDirs() { + srcdirSlash := filepath.ToSlash(srcdir) + "/" + if strings.HasPrefix(dirSlash, srcdirSlash) { + importPath := dirSlash[len(srcdirSlash) : len(dirSlash)-len("/")] + return ctxt.Import(importPath, dir, build.FindOnly) + } + } + + return nil, fmt.Errorf("can't find package containing %s", filename) +} + +// -- Effective methods of virtual file system ------------------------- + +// FileExists returns true if the specified file exists, +// using the build context's virtual file system. +func FileExists(ctxt *build.Context, path string) bool { + if ctxt.OpenFile != nil { + r, err := ctxt.OpenFile(path) + if err != nil { + return false + } + r.Close() // ignore error + return true + } + _, err := os.Stat(path) + return err == nil +} + +// OpenFile behaves like os.Open, +// but uses the build context's virtual file system, if any. +func OpenFile(ctxt *build.Context, path string) (io.ReadCloser, error) { + if ctxt.OpenFile != nil { + return ctxt.OpenFile(path) + } + return os.Open(path) +} + +// IsAbsPath behaves like filepath.IsAbs, +// but uses the build context's virtual file system, if any. +func IsAbsPath(ctxt *build.Context, path string) bool { + if ctxt.IsAbsPath != nil { + return ctxt.IsAbsPath(path) + } + return filepath.IsAbs(path) +} + +// JoinPath behaves like filepath.Join, +// but uses the build context's virtual file system. +func JoinPath(ctxt *build.Context, path ...string) string { + if ctxt.JoinPath != nil { + return ctxt.JoinPath(path...) + } + return filepath.Join(path...) +} + +// TODO(adonovan): SplitPathList, IsDir, HasSubdir, ReadDir? diff --git a/go/buildutil/util_test.go b/go/buildutil/util_test.go new file mode 100644 index 0000000000..f0db26c794 --- /dev/null +++ b/go/buildutil/util_test.go @@ -0,0 +1,41 @@ +// Copyright 2014 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 buildutil_test + +import ( + "go/build" + "os" + "path/filepath" + "runtime" + "testing" + + "code.google.com/p/go.tools/go/buildutil" +) + +func TestContainingPackage(t *testing.T) { + // unvirtualized: + goroot := runtime.GOROOT() + gopath := filepath.SplitList(os.Getenv("GOPATH"))[0] + + for _, test := range [][2]string{ + {goroot + "/src/fmt/print.go", "fmt"}, + {goroot + "/src/encoding/json/foo.go", "encoding/json"}, + {goroot + "/src/encoding/missing/foo.go", "(not found)"}, + {gopath + "/src/code.google.com/p/go.tools/go/buildutil/util_test.go", + "code.google.com/p/go.tools/go/buildutil"}, + } { + file, want := test[0], test[1] + bp, err := buildutil.ContainingPackage(&build.Default, ".", file) + got := bp.ImportPath + if err != nil { + got = "(not found)" + } + if got != want { + t.Errorf("ContainingPackage(%q) = %s, want %s", file, got, want) + } + } + + // TODO(adonovan): test on virtualized GOPATH too. +}