github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/build/embed.go (about)

     1  package build
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/parser"
     8  	"go/token"
     9  	"strconv"
    10  
    11  	"github.com/visualfc/goembed"
    12  )
    13  
    14  func buildIdent(name string) string {
    15  	return fmt.Sprintf("__gopherjs_embed_%x__", name)
    16  }
    17  
    18  var embed_head = `package %v
    19  
    20  import (
    21  	"embed"
    22  	_ "unsafe"
    23  )
    24  
    25  //go:linkname __gopherjs_embed_buildFS__ embed.buildFS
    26  func __gopherjs_embed_buildFS__(list []struct {
    27  	name string
    28  	data string
    29  	hash [16]byte
    30  }) (f embed.FS)
    31  `
    32  
    33  // embedFiles generates an additional source file, which initializes all variables in the package with a go:embed directive.
    34  func embedFiles(pkg *PackageData, fset *token.FileSet, files []*ast.File) (*ast.File, error) {
    35  	if len(pkg.EmbedPatternPos) == 0 {
    36  		return nil, nil
    37  	}
    38  
    39  	ems, err := goembed.CheckEmbed(pkg.EmbedPatternPos, fset, files)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  
    44  	r := goembed.NewResolve()
    45  	for _, em := range ems {
    46  		fs, err := r.Load(pkg.Dir, fset, em)
    47  		if err != nil {
    48  			return nil, err
    49  		}
    50  		switch em.Kind {
    51  		case goembed.EmbedMaybeAlias:
    52  			// value = Type(data)
    53  			// valid alias string or []byte type used by types.check
    54  			em.Spec.Values = []ast.Expr{
    55  				&ast.CallExpr{
    56  					Fun: em.Spec.Type,
    57  					Args: []ast.Expr{
    58  						&ast.Ident{
    59  							Name:    buildIdent(fs[0].Name),
    60  							NamePos: em.Spec.Names[0].NamePos,
    61  						},
    62  					},
    63  				},
    64  			}
    65  		case goembed.EmbedBytes:
    66  			// value = []byte(data)
    67  			em.Spec.Values = []ast.Expr{
    68  				&ast.CallExpr{
    69  					Fun:  em.Spec.Type,
    70  					Args: []ast.Expr{ast.NewIdent(buildIdent(fs[0].Name))},
    71  				},
    72  			}
    73  		case goembed.EmbedString:
    74  			// value = data
    75  			em.Spec.Values = []ast.Expr{ast.NewIdent(buildIdent(fs[0].Name))}
    76  		case goembed.EmbedFiles:
    77  			// value = __gopherjs_embed_buildFS__([]struct{name string; data string; hash [16]byte}{...})
    78  			fs = goembed.BuildFS(fs)
    79  			elts := make([]ast.Expr, len(fs))
    80  			for i, f := range fs {
    81  				if len(f.Data) == 0 {
    82  					elts[i] = &ast.CompositeLit{
    83  						Elts: []ast.Expr{
    84  							&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(f.Name)},
    85  							&ast.BasicLit{Kind: token.STRING, Value: `""`},
    86  							&ast.CompositeLit{
    87  								Type: &ast.ArrayType{
    88  									Len: &ast.BasicLit{Kind: token.INT, Value: "16"},
    89  									Elt: ast.NewIdent("byte"),
    90  								},
    91  							},
    92  						},
    93  					}
    94  				} else {
    95  					var hash [16]ast.Expr
    96  					for j, v := range f.Hash {
    97  						hash[j] = &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(int(v))}
    98  					}
    99  					elts[i] = &ast.CompositeLit{
   100  						Elts: []ast.Expr{
   101  							&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(f.Name)},
   102  							ast.NewIdent(buildIdent(f.Name)),
   103  							&ast.CompositeLit{
   104  								Type: &ast.ArrayType{
   105  									Len: &ast.BasicLit{Kind: token.INT, Value: "16"},
   106  									Elt: ast.NewIdent("byte"),
   107  								},
   108  								Elts: hash[:],
   109  							},
   110  						},
   111  					}
   112  				}
   113  			}
   114  			call := &ast.CallExpr{
   115  				Fun: ast.NewIdent("__gopherjs_embed_buildFS__"),
   116  				Args: []ast.Expr{
   117  					&ast.CompositeLit{
   118  						Type: &ast.ArrayType{
   119  							Elt: &ast.StructType{
   120  								Fields: &ast.FieldList{
   121  									List: []*ast.Field{
   122  										{
   123  											Names: []*ast.Ident{ast.NewIdent("name")},
   124  											Type:  ast.NewIdent("string"),
   125  										},
   126  										{
   127  											Names: []*ast.Ident{ast.NewIdent("data")},
   128  											Type:  ast.NewIdent("string"),
   129  										},
   130  										{
   131  											Names: []*ast.Ident{ast.NewIdent("hash")},
   132  											Type: &ast.ArrayType{
   133  												Len: &ast.BasicLit{Kind: token.INT, Value: "16"},
   134  												Elt: ast.NewIdent("byte"),
   135  											},
   136  										},
   137  									},
   138  								},
   139  							},
   140  						},
   141  						Elts: elts,
   142  					},
   143  				},
   144  			}
   145  			em.Spec.Values = []ast.Expr{call}
   146  		}
   147  	}
   148  
   149  	var buf bytes.Buffer
   150  	fmt.Fprintf(&buf, embed_head, pkg.Name)
   151  	buf.WriteString("\nconst (\n")
   152  	for _, f := range r.Files() {
   153  		if len(f.Data) == 0 {
   154  			fmt.Fprintf(&buf, "\t%v = \"\"\n", buildIdent(f.Name))
   155  		} else {
   156  			fmt.Fprintf(&buf, "\t%v = \"%v\"\n", buildIdent(f.Name), goembed.BytesToHex(f.Data))
   157  		}
   158  	}
   159  	buf.WriteString(")\n\n")
   160  	f, err := parser.ParseFile(fset, "js_embed.go", buf.String(), parser.ParseComments)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  	return f, nil
   165  }
   166  
   167  func joinEmbedPatternPos(m1, m2 map[string][]token.Position) map[string][]token.Position {
   168  	if len(m1) == 0 && len(m2) == 0 {
   169  		return nil
   170  	}
   171  	m := make(map[string][]token.Position)
   172  	for k, v := range m1 {
   173  		m[k] = v
   174  	}
   175  	for k, v := range m2 {
   176  		m[k] = append(m[k], v...)
   177  	}
   178  	return m
   179  }