| // Copyright 2016 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package buildutil |
| |
| import ( |
| "bufio" |
| "bytes" |
| "fmt" |
| "go/build" |
| "io" |
| "io/ioutil" |
| "path/filepath" |
| "strconv" |
| "strings" |
| ) |
| |
| // OverlayContext overlays a build.Context with additional files from |
| // a map. Files in the map take precedence over other files. |
| // |
| // In addition to plain string comparison, two file names are |
| // considered equal if their base names match and their directory |
| // components point at the same directory on the file system. That is, |
| // symbolic links are followed for directories, but not files. |
| // |
| // A common use case for OverlayContext is to allow editors to pass in |
| // a set of unsaved, modified files. |
| // |
| // Currently, only the Context.OpenFile function will respect the |
| // overlay. This may change in the future. |
| func OverlayContext(orig *build.Context, overlay map[string][]byte) *build.Context { |
| // TODO(dominikh): Implement IsDir, HasSubdir and ReadDir |
| |
| rc := func(data []byte) (io.ReadCloser, error) { |
| return ioutil.NopCloser(bytes.NewBuffer(data)), nil |
| } |
| |
| copy := *orig // make a copy |
| ctxt := © |
| ctxt.OpenFile = func(path string) (io.ReadCloser, error) { |
| // Fast path: names match exactly. |
| if content, ok := overlay[path]; ok { |
| return rc(content) |
| } |
| |
| // Slow path: check for same file under a different |
| // alias, perhaps due to a symbolic link. |
| for filename, content := range overlay { |
| if sameFile(path, filename) { |
| return rc(content) |
| } |
| } |
| |
| return OpenFile(orig, path) |
| } |
| return ctxt |
| } |
| |
| // ParseOverlayArchive parses an archive containing Go files and their |
| // contents. The result is intended to be used with OverlayContext. |
| // |
| // |
| // Archive format |
| // |
| // The archive consists of a series of files. Each file consists of a |
| // name, a decimal file size and the file contents, separated by |
| // newlines. No newline follows after the file contents. |
| func ParseOverlayArchive(archive io.Reader) (map[string][]byte, error) { |
| overlay := make(map[string][]byte) |
| r := bufio.NewReader(archive) |
| for { |
| // Read file name. |
| filename, err := r.ReadString('\n') |
| if err != nil { |
| if err == io.EOF { |
| break // OK |
| } |
| return nil, fmt.Errorf("reading archive file name: %v", err) |
| } |
| filename = filepath.Clean(strings.TrimSpace(filename)) |
| |
| // Read file size. |
| sz, err := r.ReadString('\n') |
| if err != nil { |
| return nil, fmt.Errorf("reading size of archive file %s: %v", filename, err) |
| } |
| sz = strings.TrimSpace(sz) |
| size, err := strconv.ParseUint(sz, 10, 32) |
| if err != nil { |
| return nil, fmt.Errorf("parsing size of archive file %s: %v", filename, err) |
| } |
| |
| // Read file content. |
| content := make([]byte, size) |
| if _, err := io.ReadFull(r, content); err != nil { |
| return nil, fmt.Errorf("reading archive file %s: %v", filename, err) |
| } |
| overlay[filename] = content |
| } |
| |
| return overlay, nil |
| } |