You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

103 lines
2.9 KiB

  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. package buildutil
  5. import (
  6. "bufio"
  7. "bytes"
  8. "fmt"
  9. "go/build"
  10. "io"
  11. "io/ioutil"
  12. "path/filepath"
  13. "strconv"
  14. "strings"
  15. )
  16. // OverlayContext overlays a build.Context with additional files from
  17. // a map. Files in the map take precedence over other files.
  18. //
  19. // In addition to plain string comparison, two file names are
  20. // considered equal if their base names match and their directory
  21. // components point at the same directory on the file system. That is,
  22. // symbolic links are followed for directories, but not files.
  23. //
  24. // A common use case for OverlayContext is to allow editors to pass in
  25. // a set of unsaved, modified files.
  26. //
  27. // Currently, only the Context.OpenFile function will respect the
  28. // overlay. This may change in the future.
  29. func OverlayContext(orig *build.Context, overlay map[string][]byte) *build.Context {
  30. // TODO(dominikh): Implement IsDir, HasSubdir and ReadDir
  31. rc := func(data []byte) (io.ReadCloser, error) {
  32. return ioutil.NopCloser(bytes.NewBuffer(data)), nil
  33. }
  34. copy := *orig // make a copy
  35. ctxt := &copy
  36. ctxt.OpenFile = func(path string) (io.ReadCloser, error) {
  37. // Fast path: names match exactly.
  38. if content, ok := overlay[path]; ok {
  39. return rc(content)
  40. }
  41. // Slow path: check for same file under a different
  42. // alias, perhaps due to a symbolic link.
  43. for filename, content := range overlay {
  44. if sameFile(path, filename) {
  45. return rc(content)
  46. }
  47. }
  48. return OpenFile(orig, path)
  49. }
  50. return ctxt
  51. }
  52. // ParseOverlayArchive parses an archive containing Go files and their
  53. // contents. The result is intended to be used with OverlayContext.
  54. //
  55. //
  56. // Archive format
  57. //
  58. // The archive consists of a series of files. Each file consists of a
  59. // name, a decimal file size and the file contents, separated by
  60. // newlinews. No newline follows after the file contents.
  61. func ParseOverlayArchive(archive io.Reader) (map[string][]byte, error) {
  62. overlay := make(map[string][]byte)
  63. r := bufio.NewReader(archive)
  64. for {
  65. // Read file name.
  66. filename, err := r.ReadString('\n')
  67. if err != nil {
  68. if err == io.EOF {
  69. break // OK
  70. }
  71. return nil, fmt.Errorf("reading archive file name: %v", err)
  72. }
  73. filename = filepath.Clean(strings.TrimSpace(filename))
  74. // Read file size.
  75. sz, err := r.ReadString('\n')
  76. if err != nil {
  77. return nil, fmt.Errorf("reading size of archive file %s: %v", filename, err)
  78. }
  79. sz = strings.TrimSpace(sz)
  80. size, err := strconv.ParseUint(sz, 10, 32)
  81. if err != nil {
  82. return nil, fmt.Errorf("parsing size of archive file %s: %v", filename, err)
  83. }
  84. // Read file content.
  85. content := make([]byte, size)
  86. if _, err := io.ReadFull(r, content); err != nil {
  87. return nil, fmt.Errorf("reading archive file %s: %v", filename, err)
  88. }
  89. overlay[filename] = content
  90. }
  91. return overlay, nil
  92. }