overlay.go

  1// Copyright 2016 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
  5package buildutil
  6
  7import (
  8	"bufio"
  9	"bytes"
 10	"fmt"
 11	"go/build"
 12	"io"
 13	"io/ioutil"
 14	"path/filepath"
 15	"strconv"
 16	"strings"
 17)
 18
 19// OverlayContext overlays a build.Context with additional files from
 20// a map. Files in the map take precedence over other files.
 21//
 22// In addition to plain string comparison, two file names are
 23// considered equal if their base names match and their directory
 24// components point at the same directory on the file system. That is,
 25// symbolic links are followed for directories, but not files.
 26//
 27// A common use case for OverlayContext is to allow editors to pass in
 28// a set of unsaved, modified files.
 29//
 30// Currently, only the Context.OpenFile function will respect the
 31// overlay. This may change in the future.
 32func OverlayContext(orig *build.Context, overlay map[string][]byte) *build.Context {
 33	// TODO(dominikh): Implement IsDir, HasSubdir and ReadDir
 34
 35	rc := func(data []byte) (io.ReadCloser, error) {
 36		return ioutil.NopCloser(bytes.NewBuffer(data)), nil
 37	}
 38
 39	copy := *orig // make a copy
 40	ctxt := &copy
 41	ctxt.OpenFile = func(path string) (io.ReadCloser, error) {
 42		// Fast path: names match exactly.
 43		if content, ok := overlay[path]; ok {
 44			return rc(content)
 45		}
 46
 47		// Slow path: check for same file under a different
 48		// alias, perhaps due to a symbolic link.
 49		for filename, content := range overlay {
 50			if sameFile(path, filename) {
 51				return rc(content)
 52			}
 53		}
 54
 55		return OpenFile(orig, path)
 56	}
 57	return ctxt
 58}
 59
 60// ParseOverlayArchive parses an archive containing Go files and their
 61// contents. The result is intended to be used with OverlayContext.
 62//
 63//
 64// Archive format
 65//
 66// The archive consists of a series of files. Each file consists of a
 67// name, a decimal file size and the file contents, separated by
 68// newlinews. No newline follows after the file contents.
 69func ParseOverlayArchive(archive io.Reader) (map[string][]byte, error) {
 70	overlay := make(map[string][]byte)
 71	r := bufio.NewReader(archive)
 72	for {
 73		// Read file name.
 74		filename, err := r.ReadString('\n')
 75		if err != nil {
 76			if err == io.EOF {
 77				break // OK
 78			}
 79			return nil, fmt.Errorf("reading archive file name: %v", err)
 80		}
 81		filename = filepath.Clean(strings.TrimSpace(filename))
 82
 83		// Read file size.
 84		sz, err := r.ReadString('\n')
 85		if err != nil {
 86			return nil, fmt.Errorf("reading size of archive file %s: %v", filename, err)
 87		}
 88		sz = strings.TrimSpace(sz)
 89		size, err := strconv.ParseUint(sz, 10, 32)
 90		if err != nil {
 91			return nil, fmt.Errorf("parsing size of archive file %s: %v", filename, err)
 92		}
 93
 94		// Read file content.
 95		content := make([]byte, size)
 96		if _, err := io.ReadFull(r, content); err != nil {
 97			return nil, fmt.Errorf("reading archive file %s: %v", filename, err)
 98		}
 99		overlay[filename] = content
100	}
101
102	return overlay, nil
103}