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 := ©
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}