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 | "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. |
32 | func 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 | // # Archive format |
64 | // |
65 | // The archive consists of a series of files. Each file consists of a |
66 | // name, a decimal file size and the file contents, separated by |
67 | // newlines. No newline follows after the file contents. |
68 | func ParseOverlayArchive(archive io.Reader) (map[string][]byte, error) { |
69 | overlay := make(map[string][]byte) |
70 | r := bufio.NewReader(archive) |
71 | for { |
72 | // Read file name. |
73 | filename, err := r.ReadString('\n') |
74 | if err != nil { |
75 | if err == io.EOF { |
76 | break // OK |
77 | } |
78 | return nil, fmt.Errorf("reading archive file name: %v", err) |
79 | } |
80 | filename = filepath.Clean(strings.TrimSpace(filename)) |
81 | |
82 | // Read file size. |
83 | sz, err := r.ReadString('\n') |
84 | if err != nil { |
85 | return nil, fmt.Errorf("reading size of archive file %s: %v", filename, err) |
86 | } |
87 | sz = strings.TrimSpace(sz) |
88 | size, err := strconv.ParseUint(sz, 10, 32) |
89 | if err != nil { |
90 | return nil, fmt.Errorf("parsing size of archive file %s: %v", filename, err) |
91 | } |
92 | |
93 | // Read file content. |
94 | content := make([]byte, size) |
95 | if _, err := io.ReadFull(r, content); err != nil { |
96 | return nil, fmt.Errorf("reading archive file %s: %v", filename, err) |
97 | } |
98 | overlay[filename] = content |
99 | } |
100 | |
101 | return overlay, nil |
102 | } |
103 |
Members