fakecontext.go

  1package buildutil
  2
  3import (
  4	"fmt"
  5	"go/build"
  6	"io"
  7	"io/ioutil"
  8	"os"
  9	"path"
 10	"path/filepath"
 11	"sort"
 12	"strings"
 13	"time"
 14)
 15
 16// FakeContext returns a build.Context for the fake file tree specified
 17// by pkgs, which maps package import paths to a mapping from file base
 18// names to contents.
 19//
 20// The fake Context has a GOROOT of "/go" and no GOPATH, and overrides
 21// the necessary file access methods to read from memory instead of the
 22// real file system.
 23//
 24// Unlike a real file tree, the fake one has only two levels---packages
 25// and files---so ReadDir("/go/src/") returns all packages under
 26// /go/src/ including, for instance, "math" and "math/big".
 27// ReadDir("/go/src/math/big") would return all the files in the
 28// "math/big" package.
 29//
 30func FakeContext(pkgs map[string]map[string]string) *build.Context {
 31	clean := func(filename string) string {
 32		f := path.Clean(filepath.ToSlash(filename))
 33		// Removing "/go/src" while respecting segment
 34		// boundaries has this unfortunate corner case:
 35		if f == "/go/src" {
 36			return ""
 37		}
 38		return strings.TrimPrefix(f, "/go/src/")
 39	}
 40
 41	ctxt := build.Default // copy
 42	ctxt.GOROOT = "/go"
 43	ctxt.GOPATH = ""
 44	ctxt.Compiler = "gc"
 45	ctxt.IsDir = func(dir string) bool {
 46		dir = clean(dir)
 47		if dir == "" {
 48			return true // needed by (*build.Context).SrcDirs
 49		}
 50		return pkgs[dir] != nil
 51	}
 52	ctxt.ReadDir = func(dir string) ([]os.FileInfo, error) {
 53		dir = clean(dir)
 54		var fis []os.FileInfo
 55		if dir == "" {
 56			// enumerate packages
 57			for importPath := range pkgs {
 58				fis = append(fis, fakeDirInfo(importPath))
 59			}
 60		} else {
 61			// enumerate files of package
 62			for basename := range pkgs[dir] {
 63				fis = append(fis, fakeFileInfo(basename))
 64			}
 65		}
 66		sort.Sort(byName(fis))
 67		return fis, nil
 68	}
 69	ctxt.OpenFile = func(filename string) (io.ReadCloser, error) {
 70		filename = clean(filename)
 71		dir, base := path.Split(filename)
 72		content, ok := pkgs[path.Clean(dir)][base]
 73		if !ok {
 74			return nil, fmt.Errorf("file not found: %s", filename)
 75		}
 76		return ioutil.NopCloser(strings.NewReader(content)), nil
 77	}
 78	ctxt.IsAbsPath = func(path string) bool {
 79		path = filepath.ToSlash(path)
 80		// Don't rely on the default (filepath.Path) since on
 81		// Windows, it reports virtual paths as non-absolute.
 82		return strings.HasPrefix(path, "/")
 83	}
 84	return &ctxt
 85}
 86
 87type byName []os.FileInfo
 88
 89func (s byName) Len() int           { return len(s) }
 90func (s byName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
 91func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
 92
 93type fakeFileInfo string
 94
 95func (fi fakeFileInfo) Name() string    { return string(fi) }
 96func (fakeFileInfo) Sys() interface{}   { return nil }
 97func (fakeFileInfo) ModTime() time.Time { return time.Time{} }
 98func (fakeFileInfo) IsDir() bool        { return false }
 99func (fakeFileInfo) Size() int64        { return 0 }
100func (fakeFileInfo) Mode() os.FileMode  { return 0644 }
101
102type fakeDirInfo string
103
104func (fd fakeDirInfo) Name() string    { return string(fd) }
105func (fakeDirInfo) Sys() interface{}   { return nil }
106func (fakeDirInfo) ModTime() time.Time { return time.Time{} }
107func (fakeDirInfo) IsDir() bool        { return true }
108func (fakeDirInfo) Size() int64        { return 0 }
109func (fakeDirInfo) Mode() os.FileMode  { return 0755 }