golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/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 "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 }