github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/buildutil/overlay.go (about) 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 }