fakecontext.go raw

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