github.com/please-build/go-rules/tools/please_go@v0.0.0-20240319165128-ea27d6f5caba/embed/embed.go (about) 1 // Package embed implements parsing of embed directives in Go files. 2 package embed 3 4 import ( 5 "encoding/json" 6 "fmt" 7 "go/build" 8 "io" 9 "io/fs" 10 "path" 11 "path/filepath" 12 "strings" 13 ) 14 15 // Cfg is the structure of a Go embedcfg file. 16 type Cfg struct { 17 Patterns map[string][]string 18 Files map[string]string 19 } 20 21 // WriteEmbedConfig writes the embed config to w 22 func WriteEmbedConfig(files []string, w io.Writer) error { 23 cfg, err := Parse(files) 24 if err != nil { 25 return err 26 } 27 b, err := json.Marshal(cfg) 28 if err != nil { 29 return err 30 } 31 _, err = w.Write(b) 32 return err 33 } 34 35 // Parse parses the given files and returns the embed information in them. 36 func Parse(gofiles []string) (*Cfg, error) { 37 cfg := &Cfg{ 38 Patterns: map[string][]string{}, 39 Files: map[string]string{}, 40 } 41 for _, dir := range dirs(gofiles) { 42 pkg, err := build.ImportDir(dir, build.ImportComment) 43 if err != nil { 44 return nil, err 45 } 46 // We munge all patterns together at this point, if a file is in our input sources we want to know about it regardless. 47 if err := cfg.AddPackage(pkg); err != nil { 48 return nil, err 49 } 50 } 51 return cfg, nil 52 } 53 54 // AddPackage parses a go package and adds any embed patterns to the configuration 55 func (cfg *Cfg) AddPackage(pkg *build.Package) error { 56 for _, pattern := range append(append(pkg.EmbedPatterns, pkg.TestEmbedPatterns...), pkg.XTestEmbedPatterns...) { 57 paths, err := relglob(pkg.Dir, pattern) 58 if err != nil { 59 return err 60 } 61 cfg.Patterns[pattern] = paths 62 for _, p := range paths { 63 cfg.Files[p] = path.Join(pkg.Dir, p) 64 } 65 } 66 return nil 67 } 68 69 func dirs(files []string) []string { 70 dirs := []string{} 71 seen := map[string]bool{} 72 for _, f := range files { 73 if dir := path.Dir(f); !seen[dir] { 74 dirs = append(dirs, dir) 75 seen[dir] = true 76 } 77 } 78 return dirs 79 } 80 81 func relglob(dir, pattern string) ([]string, error) { 82 // Go allows prefixing the pattern with all: which picks up files prefixed with . or _ (by default these should be ignored) 83 includeHidden := false 84 if strings.HasPrefix(pattern, "all:") { 85 pattern = strings.TrimPrefix(pattern, "all:") 86 includeHidden = true 87 } 88 89 paths, err := filepath.Glob(path.Join(dir, pattern)) 90 if err == nil && len(paths) == 0 { 91 return nil, fmt.Errorf("pattern %s: no matching paths found", pattern) 92 } 93 ret := make([]string, 0, len(paths)) 94 for _, p := range paths { 95 if err := filepath.WalkDir(p, func(path string, d fs.DirEntry, err error) error { 96 if err != nil { 97 return err 98 } else if !d.IsDir() { 99 if hidden := strings.HasPrefix(d.Name(), ".") || strings.HasPrefix(d.Name(), "_"); !hidden || includeHidden { 100 ret = append(ret, strings.TrimLeft(strings.TrimPrefix(path, dir), string(filepath.Separator))) 101 } 102 } 103 return nil 104 }); err != nil { 105 return nil, err 106 } 107 } 108 return ret, err 109 }