Source file src/cmd/compile/internal/staticdata/embed.go

     1  // Copyright 2020 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 staticdata
     6  
     7  import (
     8  	"path"
     9  	"sort"
    10  	"strings"
    11  
    12  	"cmd/compile/internal/base"
    13  	"cmd/compile/internal/ir"
    14  	"cmd/compile/internal/objw"
    15  	"cmd/compile/internal/types"
    16  	"cmd/internal/obj"
    17  )
    18  
    19  const (
    20  	embedUnknown = iota
    21  	embedBytes
    22  	embedString
    23  	embedFiles
    24  )
    25  
    26  func embedFileList(v *ir.Name, kind int) []string {
    27  	// Build list of files to store.
    28  	have := make(map[string]bool)
    29  	var list []string
    30  	for _, e := range *v.Embed {
    31  		for _, pattern := range e.Patterns {
    32  			files, ok := base.Flag.Cfg.Embed.Patterns[pattern]
    33  			if !ok {
    34  				base.ErrorfAt(e.Pos, 0, "invalid go:embed: build system did not map pattern: %s", pattern)
    35  			}
    36  			for _, file := range files {
    37  				if base.Flag.Cfg.Embed.Files[file] == "" {
    38  					base.ErrorfAt(e.Pos, 0, "invalid go:embed: build system did not map file: %s", file)
    39  					continue
    40  				}
    41  				if !have[file] {
    42  					have[file] = true
    43  					list = append(list, file)
    44  				}
    45  				if kind == embedFiles {
    46  					for dir := path.Dir(file); dir != "." && !have[dir]; dir = path.Dir(dir) {
    47  						have[dir] = true
    48  						list = append(list, dir+"/")
    49  					}
    50  				}
    51  			}
    52  		}
    53  	}
    54  	sort.Slice(list, func(i, j int) bool {
    55  		return embedFileLess(list[i], list[j])
    56  	})
    57  
    58  	if kind == embedString || kind == embedBytes {
    59  		if len(list) > 1 {
    60  			base.ErrorfAt(v.Pos(), 0, "invalid go:embed: multiple files for type %v", v.Type())
    61  			return nil
    62  		}
    63  	}
    64  
    65  	return list
    66  }
    67  
    68  // embedKind determines the kind of embedding variable.
    69  func embedKind(typ *types.Type) int {
    70  	if typ.Sym() != nil && typ.Sym().Name == "FS" && typ.Sym().Pkg.Path == "embed" {
    71  		return embedFiles
    72  	}
    73  	if typ.Kind() == types.TSTRING {
    74  		return embedString
    75  	}
    76  	if typ.IsSlice() && typ.Elem().Kind() == types.TUINT8 {
    77  		return embedBytes
    78  	}
    79  	return embedUnknown
    80  }
    81  
    82  func embedFileNameSplit(name string) (dir, elem string, isDir bool) {
    83  	name, isDir = strings.CutSuffix(name, "/")
    84  	i := strings.LastIndexByte(name, '/')
    85  	if i < 0 {
    86  		return ".", name, isDir
    87  	}
    88  	return name[:i], name[i+1:], isDir
    89  }
    90  
    91  // embedFileLess implements the sort order for a list of embedded files.
    92  // See the comment inside ../../../../embed/embed.go's Files struct for rationale.
    93  func embedFileLess(x, y string) bool {
    94  	xdir, xelem, _ := embedFileNameSplit(x)
    95  	ydir, yelem, _ := embedFileNameSplit(y)
    96  	return xdir < ydir || xdir == ydir && xelem < yelem
    97  }
    98  
    99  // WriteEmbed emits the init data for a //go:embed variable,
   100  // which is either a string, a []byte, or an embed.FS.
   101  func WriteEmbed(v *ir.Name) {
   102  	// TODO(mdempsky): User errors should be reported by the frontend.
   103  
   104  	commentPos := (*v.Embed)[0].Pos
   105  	if base.Flag.Cfg.Embed.Patterns == nil {
   106  		base.ErrorfAt(commentPos, 0, "invalid go:embed: build system did not supply embed configuration")
   107  		return
   108  	}
   109  	kind := embedKind(v.Type())
   110  	if kind == embedUnknown {
   111  		base.ErrorfAt(v.Pos(), 0, "go:embed cannot apply to var of type %v", v.Type())
   112  		return
   113  	}
   114  
   115  	files := embedFileList(v, kind)
   116  	if base.Errors() > 0 {
   117  		return
   118  	}
   119  	switch kind {
   120  	case embedString, embedBytes:
   121  		file := files[0]
   122  		fsym, size, err := fileStringSym(v.Pos(), base.Flag.Cfg.Embed.Files[file], kind == embedString, nil)
   123  		if err != nil {
   124  			base.ErrorfAt(v.Pos(), 0, "embed %s: %v", file, err)
   125  		}
   126  		sym := v.Linksym()
   127  		off := 0
   128  		off = objw.SymPtr(sym, off, fsym, 0)       // data string
   129  		off = objw.Uintptr(sym, off, uint64(size)) // len
   130  		if kind == embedBytes {
   131  			objw.Uintptr(sym, off, uint64(size)) // cap for slice
   132  		}
   133  
   134  	case embedFiles:
   135  		slicedata := v.Sym().Pkg.Lookup(v.Sym().Name + `.files`).Linksym()
   136  		off := 0
   137  		// []files pointed at by Files
   138  		off = objw.SymPtr(slicedata, off, slicedata, 3*types.PtrSize) // []file, pointing just past slice
   139  		off = objw.Uintptr(slicedata, off, uint64(len(files)))
   140  		off = objw.Uintptr(slicedata, off, uint64(len(files)))
   141  
   142  		// embed/embed.go type file is:
   143  		//	name string
   144  		//	data string
   145  		//	hash [16]byte
   146  		// Emit one of these per file in the set.
   147  		const hashSize = 16
   148  		hash := make([]byte, hashSize)
   149  		for _, file := range files {
   150  			off = objw.SymPtr(slicedata, off, StringSym(v.Pos(), file), 0) // file string
   151  			off = objw.Uintptr(slicedata, off, uint64(len(file)))
   152  			if strings.HasSuffix(file, "/") {
   153  				// entry for directory - no data
   154  				off = objw.Uintptr(slicedata, off, 0)
   155  				off = objw.Uintptr(slicedata, off, 0)
   156  				off += hashSize
   157  			} else {
   158  				fsym, size, err := fileStringSym(v.Pos(), base.Flag.Cfg.Embed.Files[file], true, hash)
   159  				if err != nil {
   160  					base.ErrorfAt(v.Pos(), 0, "embed %s: %v", file, err)
   161  				}
   162  				off = objw.SymPtr(slicedata, off, fsym, 0) // data string
   163  				off = objw.Uintptr(slicedata, off, uint64(size))
   164  				off = int(slicedata.WriteBytes(base.Ctxt, int64(off), hash))
   165  			}
   166  		}
   167  		objw.Global(slicedata, int32(off), obj.RODATA|obj.LOCAL)
   168  		sym := v.Linksym()
   169  		objw.SymPtr(sym, 0, slicedata, 0)
   170  	}
   171  }
   172  

View as plain text