diff options
Diffstat (limited to 'src/cmd/compile/internal/staticdata/embed.go')
-rw-r--r-- | src/cmd/compile/internal/staticdata/embed.go | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/staticdata/embed.go b/src/cmd/compile/internal/staticdata/embed.go new file mode 100644 index 0000000..627c98b --- /dev/null +++ b/src/cmd/compile/internal/staticdata/embed.go @@ -0,0 +1,174 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package staticdata + +import ( + "path" + "sort" + "strings" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/objw" + "cmd/compile/internal/types" + "cmd/internal/obj" +) + +const ( + embedUnknown = iota + embedBytes + embedString + embedFiles +) + +func embedFileList(v *ir.Name, kind int) []string { + // Build list of files to store. + have := make(map[string]bool) + var list []string + for _, e := range *v.Embed { + for _, pattern := range e.Patterns { + files, ok := base.Flag.Cfg.Embed.Patterns[pattern] + if !ok { + base.ErrorfAt(e.Pos, "invalid go:embed: build system did not map pattern: %s", pattern) + } + for _, file := range files { + if base.Flag.Cfg.Embed.Files[file] == "" { + base.ErrorfAt(e.Pos, "invalid go:embed: build system did not map file: %s", file) + continue + } + if !have[file] { + have[file] = true + list = append(list, file) + } + if kind == embedFiles { + for dir := path.Dir(file); dir != "." && !have[dir]; dir = path.Dir(dir) { + have[dir] = true + list = append(list, dir+"/") + } + } + } + } + } + sort.Slice(list, func(i, j int) bool { + return embedFileLess(list[i], list[j]) + }) + + if kind == embedString || kind == embedBytes { + if len(list) > 1 { + base.ErrorfAt(v.Pos(), "invalid go:embed: multiple files for type %v", v.Type()) + return nil + } + } + + return list +} + +// embedKind determines the kind of embedding variable. +func embedKind(typ *types.Type) int { + if typ.Sym() != nil && typ.Sym().Name == "FS" && (typ.Sym().Pkg.Path == "embed" || (typ.Sym().Pkg == types.LocalPkg && base.Ctxt.Pkgpath == "embed")) { + return embedFiles + } + if typ.Kind() == types.TSTRING { + return embedString + } + if typ.IsSlice() && typ.Elem().Kind() == types.TUINT8 { + return embedBytes + } + return embedUnknown +} + +func embedFileNameSplit(name string) (dir, elem string, isDir bool) { + if name[len(name)-1] == '/' { + isDir = true + name = name[:len(name)-1] + } + i := len(name) - 1 + for i >= 0 && name[i] != '/' { + i-- + } + if i < 0 { + return ".", name, isDir + } + return name[:i], name[i+1:], isDir +} + +// embedFileLess implements the sort order for a list of embedded files. +// See the comment inside ../../../../embed/embed.go's Files struct for rationale. +func embedFileLess(x, y string) bool { + xdir, xelem, _ := embedFileNameSplit(x) + ydir, yelem, _ := embedFileNameSplit(y) + return xdir < ydir || xdir == ydir && xelem < yelem +} + +// WriteEmbed emits the init data for a //go:embed variable, +// which is either a string, a []byte, or an embed.FS. +func WriteEmbed(v *ir.Name) { + // TODO(mdempsky): User errors should be reported by the frontend. + + commentPos := (*v.Embed)[0].Pos + if base.Flag.Cfg.Embed.Patterns == nil { + base.ErrorfAt(commentPos, "invalid go:embed: build system did not supply embed configuration") + return + } + kind := embedKind(v.Type()) + if kind == embedUnknown { + base.ErrorfAt(v.Pos(), "go:embed cannot apply to var of type %v", v.Type()) + return + } + + files := embedFileList(v, kind) + switch kind { + case embedString, embedBytes: + file := files[0] + fsym, size, err := fileStringSym(v.Pos(), base.Flag.Cfg.Embed.Files[file], kind == embedString, nil) + if err != nil { + base.ErrorfAt(v.Pos(), "embed %s: %v", file, err) + } + sym := v.Linksym() + off := 0 + off = objw.SymPtr(sym, off, fsym, 0) // data string + off = objw.Uintptr(sym, off, uint64(size)) // len + if kind == embedBytes { + objw.Uintptr(sym, off, uint64(size)) // cap for slice + } + + case embedFiles: + slicedata := base.Ctxt.Lookup(`"".` + v.Sym().Name + `.files`) + off := 0 + // []files pointed at by Files + off = objw.SymPtr(slicedata, off, slicedata, 3*types.PtrSize) // []file, pointing just past slice + off = objw.Uintptr(slicedata, off, uint64(len(files))) + off = objw.Uintptr(slicedata, off, uint64(len(files))) + + // embed/embed.go type file is: + // name string + // data string + // hash [16]byte + // Emit one of these per file in the set. + const hashSize = 16 + hash := make([]byte, hashSize) + for _, file := range files { + off = objw.SymPtr(slicedata, off, StringSym(v.Pos(), file), 0) // file string + off = objw.Uintptr(slicedata, off, uint64(len(file))) + if strings.HasSuffix(file, "/") { + // entry for directory - no data + off = objw.Uintptr(slicedata, off, 0) + off = objw.Uintptr(slicedata, off, 0) + off += hashSize + } else { + fsym, size, err := fileStringSym(v.Pos(), base.Flag.Cfg.Embed.Files[file], true, hash) + if err != nil { + base.ErrorfAt(v.Pos(), "embed %s: %v", file, err) + } + off = objw.SymPtr(slicedata, off, fsym, 0) // data string + off = objw.Uintptr(slicedata, off, uint64(size)) + off = int(slicedata.WriteBytes(base.Ctxt, int64(off), hash)) + } + } + objw.Global(slicedata, int32(off), obj.RODATA|obj.LOCAL) + sym := v.Linksym() + objw.SymPtr(sym, 0, slicedata, 0) + } +} |