1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package main
16
17 import (
18 "encoding/json"
19 "errors"
20 "fmt"
21 "io/ioutil"
22 "os"
23 "path"
24 "path/filepath"
25 "runtime"
26 "sort"
27 "strings"
28 )
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 func buildEmbedcfgFile(goSrcs []fileInfo, embedSrcs, embedRootDirs []string, workDir string) (string, error) {
45
46
47
48
49 var major, minor int
50 if n, err := fmt.Sscanf(runtime.Version(), "go%d.%d", &major, &minor); n != 2 || err != nil {
51
52 } else if major < 1 || (major == 1 && minor < 16) {
53 return "", nil
54 }
55 importEmbed := false
56 haveEmbed := false
57 for _, src := range goSrcs {
58 if len(src.embeds) > 0 {
59 haveEmbed = true
60 rootDir := findInRootDirs(src.filename, embedRootDirs)
61 if rootDir == "" || strings.Contains(src.filename[len(rootDir)+1:], string(filepath.Separator)) {
62
63
64
65 return "", fmt.Errorf("%s: source files with //go:embed should be in same directory. Allowed directories are:\n\t%s",
66 src.filename,
67 strings.Join(embedRootDirs, "\n\t"))
68 }
69 }
70 for _, imp := range src.imports {
71 if imp.path == "embed" {
72 importEmbed = true
73 }
74 }
75 }
76 if !importEmbed || !haveEmbed {
77 return "", nil
78 }
79
80
81
82
83
84 root, err := buildEmbedTree(embedSrcs, embedRootDirs)
85 if err != nil {
86 return "", err
87 }
88
89
90 var embedcfg struct {
91 Patterns map[string][]string
92 Files map[string]string
93 }
94 embedcfg.Patterns = make(map[string][]string)
95 embedcfg.Files = make(map[string]string)
96 for _, src := range goSrcs {
97 for _, embed := range src.embeds {
98 matchedPaths, matchedFiles, err := resolveEmbed(embed, root)
99 if err != nil {
100 return "", err
101 }
102 embedcfg.Patterns[embed.pattern] = matchedPaths
103 for i, rel := range matchedPaths {
104 embedcfg.Files[rel] = matchedFiles[i]
105 }
106 }
107 }
108
109
110 embedcfgData, err := json.MarshalIndent(&embedcfg, "", "\t")
111 if err != nil {
112 return "", err
113 }
114 embedcfgName := filepath.Join(workDir, "embedcfg")
115 if err := ioutil.WriteFile(embedcfgName, embedcfgData, 0o666); err != nil {
116 return "", err
117 }
118 return embedcfgName, nil
119 }
120
121
122
123 func findInRootDirs(p string, rootDirs []string) string {
124 dir := filepath.Dir(p)
125 for _, rootDir := range rootDirs {
126 if rootDir == dir ||
127 (strings.HasPrefix(dir, rootDir) && len(dir) > len(rootDir)+1 && dir[len(rootDir)] == filepath.Separator) {
128 return rootDir
129 }
130 }
131 return ""
132 }
133
134
135 type embedNode struct {
136 name string
137 path string
138 children map[string]*embedNode
139 childNames []string
140 }
141
142
143
144
145
146
147 func (n *embedNode) add(rootDir, src string) error {
148
149 parent := n
150 parts := strings.Split(src, "/")
151 for _, p := range parts[:len(parts)-1] {
152 if parent.children[p] == nil {
153 parent.children[p] = &embedNode{
154 name: p,
155 children: make(map[string]*embedNode),
156 }
157 }
158 parent = parent.children[p]
159 }
160
161
162
163
164 var visit func(*embedNode, string, os.FileInfo) error
165 visit = func(parent *embedNode, path string, fi os.FileInfo) error {
166 base := filepath.Base(path)
167 if parent.children[base] == nil {
168 parent.children[base] = &embedNode{name: base, path: path}
169 }
170 if !fi.IsDir() {
171 return nil
172 }
173 node := parent.children[base]
174 node.children = make(map[string]*embedNode)
175 f, err := os.Open(path)
176 if err != nil {
177 return err
178 }
179 names, err := f.Readdirnames(0)
180 f.Close()
181 if err != nil {
182 return err
183 }
184 for _, name := range names {
185 cPath := filepath.Join(path, name)
186 cfi, err := os.Stat(cPath)
187 if err != nil {
188 return err
189 }
190 if err := visit(node, cPath, cfi); err != nil {
191 return err
192 }
193 }
194 return nil
195 }
196
197 path := filepath.Join(rootDir, src)
198 fi, err := os.Stat(path)
199 if err != nil {
200 return err
201 }
202 return visit(parent, path, fi)
203 }
204
205 func (n *embedNode) isDir() bool {
206 return n.children != nil
207 }
208
209
210
211 func (n *embedNode) get(path string) *embedNode {
212 if path == "." || path == "" {
213 return n
214 }
215 for _, part := range strings.Split(path, "/") {
216 n = n.children[part]
217 if n == nil {
218 return nil
219 }
220 }
221 return n
222 }
223
224 var errSkip = errors.New("skip")
225
226
227 func (n *embedNode) walk(fn func(rel string, n *embedNode) error) error {
228 var visit func(string, *embedNode) error
229 visit = func(rel string, node *embedNode) error {
230 err := fn(rel, node)
231 if err == errSkip {
232 return nil
233 } else if err != nil {
234 return err
235 }
236 for _, name := range node.childNames {
237 if err := visit(path.Join(rel, name), node.children[name]); err != nil && err != errSkip {
238 return err
239 }
240 }
241 return nil
242 }
243 err := visit("", n)
244 if err == errSkip {
245 return nil
246 }
247 return err
248 }
249
250
251
252
253 func buildEmbedTree(embedSrcs, embedRootDirs []string) (root *embedNode, err error) {
254 defer func() {
255 if err != nil {
256 err = fmt.Errorf("building tree of embeddable files in directories %s: %v", strings.Join(embedRootDirs, string(filepath.ListSeparator)), err)
257 }
258 }()
259
260
261 root = &embedNode{name: "", children: make(map[string]*embedNode)}
262 for _, src := range embedSrcs {
263 rootDir := findInRootDirs(src, embedRootDirs)
264 if rootDir == "" {
265
266 continue
267 }
268 rel := filepath.ToSlash(src[len(rootDir)+1:])
269 if err := root.add(rootDir, rel); err != nil {
270 return nil, err
271 }
272 }
273
274
275 var visit func(*embedNode)
276 visit = func(node *embedNode) {
277 node.childNames = make([]string, 0, len(node.children))
278 for name, child := range node.children {
279 node.childNames = append(node.childNames, name)
280 visit(child)
281 }
282 sort.Strings(node.childNames)
283 }
284 visit(root)
285
286 return root, nil
287 }
288
289
290
291 func resolveEmbed(embed fileEmbed, root *embedNode) (matchedPaths, matchedFiles []string, err error) {
292 defer func() {
293 if err != nil {
294 err = fmt.Errorf("%v: could not embed %s: %v", embed.pos, embed.pattern, err)
295 }
296 }()
297
298
299
300 pattern := embed.pattern
301 var matchAll bool
302 if strings.HasPrefix(pattern, "all:") {
303 matchAll = true
304 pattern = pattern[4:]
305 }
306
307
308 if _, err := path.Match(pattern, ""); err != nil || !validEmbedPattern(pattern) {
309 return nil, nil, fmt.Errorf("invalid pattern syntax")
310 }
311
312
313 err = root.walk(func(matchRel string, matchNode *embedNode) error {
314 if ok, _ := path.Match(pattern, matchRel); !ok {
315
316 return nil
317 }
318
319
320
321
322 for dir := matchRel; len(dir) > 1; dir = filepath.Dir(dir) {
323 if base := path.Base(matchRel); isBadEmbedName(base) {
324 what := "file"
325 if matchNode.isDir() {
326 what = "directory"
327 }
328 if dir == matchRel {
329 return fmt.Errorf("cannot embed %s %s: invalid name %s", what, matchRel, base)
330 } else {
331 return fmt.Errorf("cannot embed %s %s: in invalid directory %s", what, matchRel, base)
332 }
333 }
334 }
335
336 if !matchNode.isDir() {
337
338 matchedPaths = append(matchedPaths, matchRel)
339 matchedFiles = append(matchedFiles, matchNode.path)
340 return nil
341 }
342
343
344
345
346
347 matchTreeErr := matchNode.walk(func(childRel string, childNode *embedNode) error {
348
349
350 if childRel != "" {
351 base := path.Base(childRel)
352 if isBadEmbedName(base) || (!matchAll && (strings.HasPrefix(base, ".") || strings.HasPrefix(base, "_"))) {
353 if childNode.isDir() {
354 return errSkip
355 }
356 return nil
357 }
358 }
359 if !childNode.isDir() {
360 matchedPaths = append(matchedPaths, path.Join(matchRel, childRel))
361 matchedFiles = append(matchedFiles, childNode.path)
362 }
363 return nil
364 })
365 if matchTreeErr != nil {
366 return matchTreeErr
367 }
368 return errSkip
369 })
370 if err != nil && err != errSkip {
371 return nil, nil, err
372 }
373 if len(matchedPaths) == 0 {
374 return nil, nil, fmt.Errorf("no matching files found")
375 }
376 return matchedPaths, matchedFiles, nil
377 }
378
379 func validEmbedPattern(pattern string) bool {
380 return pattern != "." && fsValidPath(pattern)
381 }
382
383
384
385
386
387
388
389
390
391
392
393
394 func fsValidPath(name string) bool {
395 if name == "." {
396
397 return true
398 }
399
400
401 for {
402 i := 0
403 for i < len(name) && name[i] != '/' {
404 if name[i] == '\\' {
405 return false
406 }
407 i++
408 }
409 elem := name[:i]
410 if elem == "" || elem == "." || elem == ".." {
411 return false
412 }
413 if i == len(name) {
414 return true
415 }
416 name = name[i+1:]
417 }
418 }
419
420
421
422
423
424
425
426 func isBadEmbedName(name string) bool {
427 if !fsValidPath(name) {
428 return true
429 }
430 switch name {
431
432 case "":
433 return true
434
435 case ".bzr", ".hg", ".git", ".svn":
436 return true
437 }
438 return false
439 }
440
View as plain text