overlay.go raw

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