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