xref: /aosp_15_r20/external/bazelbuild-rules_go/go/tools/builders/embed.go (revision 9bb1b549b6a84214c53be0924760be030e66b93a)
1*9bb1b549SSpandan Das// Copyright 2017 The Bazel Authors. All rights reserved.
2*9bb1b549SSpandan Das//
3*9bb1b549SSpandan Das// Licensed under the Apache License, Version 2.0 (the "License");
4*9bb1b549SSpandan Das// you may not use this file except in compliance with the License.
5*9bb1b549SSpandan Das// You may obtain a copy of the License at
6*9bb1b549SSpandan Das//
7*9bb1b549SSpandan Das//    http://www.apache.org/licenses/LICENSE-2.0
8*9bb1b549SSpandan Das//
9*9bb1b549SSpandan Das// Unless required by applicable law or agreed to in writing, software
10*9bb1b549SSpandan Das// distributed under the License is distributed on an "AS IS" BASIS,
11*9bb1b549SSpandan Das// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*9bb1b549SSpandan Das// See the License for the specific language governing permissions and
13*9bb1b549SSpandan Das// limitations under the License.
14*9bb1b549SSpandan Das
15*9bb1b549SSpandan Das// embed generates a .go file from the contents of a list of data files. It is
16*9bb1b549SSpandan Das// invoked by go_embed_data as an action.
17*9bb1b549SSpandan Daspackage main
18*9bb1b549SSpandan Das
19*9bb1b549SSpandan Dasimport (
20*9bb1b549SSpandan Das	"archive/tar"
21*9bb1b549SSpandan Das	"archive/zip"
22*9bb1b549SSpandan Das	"bufio"
23*9bb1b549SSpandan Das	"errors"
24*9bb1b549SSpandan Das	"flag"
25*9bb1b549SSpandan Das	"fmt"
26*9bb1b549SSpandan Das	"io"
27*9bb1b549SSpandan Das	"log"
28*9bb1b549SSpandan Das	"os"
29*9bb1b549SSpandan Das	"path"
30*9bb1b549SSpandan Das	"path/filepath"
31*9bb1b549SSpandan Das	"strconv"
32*9bb1b549SSpandan Das	"strings"
33*9bb1b549SSpandan Das	"text/template"
34*9bb1b549SSpandan Das	"unicode/utf8"
35*9bb1b549SSpandan Das)
36*9bb1b549SSpandan Das
37*9bb1b549SSpandan Dasvar headerTpl = template.Must(template.New("embed").Parse(`// Generated by go_embed_data for {{.Label}}. DO NOT EDIT.
38*9bb1b549SSpandan Das
39*9bb1b549SSpandan Daspackage {{.Package}}
40*9bb1b549SSpandan Das
41*9bb1b549SSpandan Das`))
42*9bb1b549SSpandan Das
43*9bb1b549SSpandan Dasvar multiFooterTpl = template.Must(template.New("embed").Parse(`
44*9bb1b549SSpandan Dasvar {{.Var}} = map[string]{{.Type}}{
45*9bb1b549SSpandan Das{{- range $i, $f := .FoundSources}}
46*9bb1b549SSpandan Das	{{$.Key $f}}: {{$.Var}}_{{$i}},
47*9bb1b549SSpandan Das{{- end}}
48*9bb1b549SSpandan Das}
49*9bb1b549SSpandan Das
50*9bb1b549SSpandan Das`))
51*9bb1b549SSpandan Das
52*9bb1b549SSpandan Dasfunc main() {
53*9bb1b549SSpandan Das	log.SetPrefix("embed: ")
54*9bb1b549SSpandan Das	log.SetFlags(0) // don't print timestamps
55*9bb1b549SSpandan Das	if err := run(os.Args); err != nil {
56*9bb1b549SSpandan Das		log.Fatal(err)
57*9bb1b549SSpandan Das	}
58*9bb1b549SSpandan Das}
59*9bb1b549SSpandan Das
60*9bb1b549SSpandan Dastype configuration struct {
61*9bb1b549SSpandan Das	Label, Package, Var      string
62*9bb1b549SSpandan Das	Multi                    bool
63*9bb1b549SSpandan Das	sources                  []string
64*9bb1b549SSpandan Das	FoundSources             []string
65*9bb1b549SSpandan Das	out, workspace           string
66*9bb1b549SSpandan Das	flatten, unpack, strData bool
67*9bb1b549SSpandan Das}
68*9bb1b549SSpandan Das
69*9bb1b549SSpandan Dasfunc (c *configuration) Type() string {
70*9bb1b549SSpandan Das	if c.strData {
71*9bb1b549SSpandan Das		return "string"
72*9bb1b549SSpandan Das	} else {
73*9bb1b549SSpandan Das		return "[]byte"
74*9bb1b549SSpandan Das	}
75*9bb1b549SSpandan Das}
76*9bb1b549SSpandan Das
77*9bb1b549SSpandan Dasfunc (c *configuration) Key(filename string) string {
78*9bb1b549SSpandan Das	workspacePrefix := "external/" + c.workspace + "/"
79*9bb1b549SSpandan Das	key := filepath.FromSlash(strings.TrimPrefix(filename, workspacePrefix))
80*9bb1b549SSpandan Das	if c.flatten {
81*9bb1b549SSpandan Das		key = path.Base(filename)
82*9bb1b549SSpandan Das	}
83*9bb1b549SSpandan Das	return strconv.Quote(key)
84*9bb1b549SSpandan Das}
85*9bb1b549SSpandan Das
86*9bb1b549SSpandan Dasfunc run(args []string) error {
87*9bb1b549SSpandan Das	c, err := newConfiguration(args)
88*9bb1b549SSpandan Das	if err != nil {
89*9bb1b549SSpandan Das		return err
90*9bb1b549SSpandan Das	}
91*9bb1b549SSpandan Das
92*9bb1b549SSpandan Das	f, err := os.Create(c.out)
93*9bb1b549SSpandan Das	if err != nil {
94*9bb1b549SSpandan Das		return err
95*9bb1b549SSpandan Das	}
96*9bb1b549SSpandan Das	defer f.Close()
97*9bb1b549SSpandan Das	w := bufio.NewWriter(f)
98*9bb1b549SSpandan Das	defer w.Flush()
99*9bb1b549SSpandan Das
100*9bb1b549SSpandan Das	if err := headerTpl.Execute(w, c); err != nil {
101*9bb1b549SSpandan Das		return err
102*9bb1b549SSpandan Das	}
103*9bb1b549SSpandan Das
104*9bb1b549SSpandan Das	if c.Multi {
105*9bb1b549SSpandan Das		return embedMultipleFiles(c, w)
106*9bb1b549SSpandan Das	}
107*9bb1b549SSpandan Das	return embedSingleFile(c, w)
108*9bb1b549SSpandan Das}
109*9bb1b549SSpandan Das
110*9bb1b549SSpandan Dasfunc newConfiguration(args []string) (*configuration, error) {
111*9bb1b549SSpandan Das	var c configuration
112*9bb1b549SSpandan Das	flags := flag.NewFlagSet("embed", flag.ExitOnError)
113*9bb1b549SSpandan Das	flags.StringVar(&c.Label, "label", "", "Label of the rule being executed (required)")
114*9bb1b549SSpandan Das	flags.StringVar(&c.Package, "package", "", "Go package name (required)")
115*9bb1b549SSpandan Das	flags.StringVar(&c.Var, "var", "", "Variable name (required)")
116*9bb1b549SSpandan Das	flags.BoolVar(&c.Multi, "multi", false, "Whether the variable is a map or a single value")
117*9bb1b549SSpandan Das	flags.StringVar(&c.out, "out", "", "Go file to generate (required)")
118*9bb1b549SSpandan Das	flags.StringVar(&c.workspace, "workspace", "", "Name of the workspace (required)")
119*9bb1b549SSpandan Das	flags.BoolVar(&c.flatten, "flatten", false, "Whether to access files by base name")
120*9bb1b549SSpandan Das	flags.BoolVar(&c.strData, "string", false, "Whether to store contents as strings")
121*9bb1b549SSpandan Das	flags.BoolVar(&c.unpack, "unpack", false, "Whether to treat files as archives to unpack.")
122*9bb1b549SSpandan Das	flags.Parse(args[1:])
123*9bb1b549SSpandan Das	if c.Label == "" {
124*9bb1b549SSpandan Das		return nil, errors.New("error: -label option not provided")
125*9bb1b549SSpandan Das	}
126*9bb1b549SSpandan Das	if c.Package == "" {
127*9bb1b549SSpandan Das		return nil, errors.New("error: -package option not provided")
128*9bb1b549SSpandan Das	}
129*9bb1b549SSpandan Das	if c.Var == "" {
130*9bb1b549SSpandan Das		return nil, errors.New("error: -var option not provided")
131*9bb1b549SSpandan Das	}
132*9bb1b549SSpandan Das	if c.out == "" {
133*9bb1b549SSpandan Das		return nil, errors.New("error: -out option not provided")
134*9bb1b549SSpandan Das	}
135*9bb1b549SSpandan Das	if c.workspace == "" {
136*9bb1b549SSpandan Das		return nil, errors.New("error: -workspace option not provided")
137*9bb1b549SSpandan Das	}
138*9bb1b549SSpandan Das	c.sources = flags.Args()
139*9bb1b549SSpandan Das	if !c.Multi && len(c.sources) != 1 {
140*9bb1b549SSpandan Das		return nil, fmt.Errorf("error: -multi flag not given, so want exactly one source; got %d", len(c.sources))
141*9bb1b549SSpandan Das	}
142*9bb1b549SSpandan Das	if c.unpack {
143*9bb1b549SSpandan Das		if !c.Multi {
144*9bb1b549SSpandan Das			return nil, errors.New("error: -multi flag is required for -unpack mode.")
145*9bb1b549SSpandan Das		}
146*9bb1b549SSpandan Das		for _, src := range c.sources {
147*9bb1b549SSpandan Das			if ext := filepath.Ext(src); ext != ".zip" && ext != ".tar" {
148*9bb1b549SSpandan Das				return nil, fmt.Errorf("error: -unpack flag expects .zip or .tar extension (got %q)", ext)
149*9bb1b549SSpandan Das			}
150*9bb1b549SSpandan Das		}
151*9bb1b549SSpandan Das	}
152*9bb1b549SSpandan Das	return &c, nil
153*9bb1b549SSpandan Das}
154*9bb1b549SSpandan Das
155*9bb1b549SSpandan Dasfunc embedSingleFile(c *configuration, w io.Writer) error {
156*9bb1b549SSpandan Das	dataBegin, dataEnd := "\"", "\"\n"
157*9bb1b549SSpandan Das	if !c.strData {
158*9bb1b549SSpandan Das		dataBegin, dataEnd = "[]byte(\"", "\")\n"
159*9bb1b549SSpandan Das	}
160*9bb1b549SSpandan Das
161*9bb1b549SSpandan Das	if _, err := fmt.Fprintf(w, "var %s = %s", c.Var, dataBegin); err != nil {
162*9bb1b549SSpandan Das		return err
163*9bb1b549SSpandan Das	}
164*9bb1b549SSpandan Das	if err := embedFileContents(w, c.sources[0]); err != nil {
165*9bb1b549SSpandan Das		return err
166*9bb1b549SSpandan Das	}
167*9bb1b549SSpandan Das	_, err := fmt.Fprint(w, dataEnd)
168*9bb1b549SSpandan Das	return err
169*9bb1b549SSpandan Das}
170*9bb1b549SSpandan Das
171*9bb1b549SSpandan Dasfunc embedMultipleFiles(c *configuration, w io.Writer) error {
172*9bb1b549SSpandan Das	dataBegin, dataEnd := "\"", "\"\n"
173*9bb1b549SSpandan Das	if !c.strData {
174*9bb1b549SSpandan Das		dataBegin, dataEnd = "[]byte(\"", "\")\n"
175*9bb1b549SSpandan Das	}
176*9bb1b549SSpandan Das
177*9bb1b549SSpandan Das	if _, err := fmt.Fprint(w, "var (\n"); err != nil {
178*9bb1b549SSpandan Das		return err
179*9bb1b549SSpandan Das	}
180*9bb1b549SSpandan Das	if err := findSources(c, func(i int, f io.Reader) error {
181*9bb1b549SSpandan Das		if _, err := fmt.Fprintf(w, "\t%s_%d = %s", c.Var, i, dataBegin); err != nil {
182*9bb1b549SSpandan Das			return err
183*9bb1b549SSpandan Das		}
184*9bb1b549SSpandan Das		if _, err := io.Copy(&escapeWriter{w}, f); err != nil {
185*9bb1b549SSpandan Das			return err
186*9bb1b549SSpandan Das		}
187*9bb1b549SSpandan Das		if _, err := fmt.Fprint(w, dataEnd); err != nil {
188*9bb1b549SSpandan Das			return err
189*9bb1b549SSpandan Das		}
190*9bb1b549SSpandan Das		return nil
191*9bb1b549SSpandan Das	}); err != nil {
192*9bb1b549SSpandan Das		return err
193*9bb1b549SSpandan Das	}
194*9bb1b549SSpandan Das	if _, err := fmt.Fprint(w, ")\n"); err != nil {
195*9bb1b549SSpandan Das		return err
196*9bb1b549SSpandan Das	}
197*9bb1b549SSpandan Das	if err := multiFooterTpl.Execute(w, c); err != nil {
198*9bb1b549SSpandan Das		return err
199*9bb1b549SSpandan Das	}
200*9bb1b549SSpandan Das	return nil
201*9bb1b549SSpandan Das}
202*9bb1b549SSpandan Das
203*9bb1b549SSpandan Dasfunc findSources(c *configuration, cb func(i int, f io.Reader) error) error {
204*9bb1b549SSpandan Das	if c.unpack {
205*9bb1b549SSpandan Das		for _, filename := range c.sources {
206*9bb1b549SSpandan Das			ext := filepath.Ext(filename)
207*9bb1b549SSpandan Das			if ext == ".zip" {
208*9bb1b549SSpandan Das				if err := findZipSources(c, filename, cb); err != nil {
209*9bb1b549SSpandan Das					return err
210*9bb1b549SSpandan Das				}
211*9bb1b549SSpandan Das			} else if ext == ".tar" {
212*9bb1b549SSpandan Das				if err := findTarSources(c, filename, cb); err != nil {
213*9bb1b549SSpandan Das					return err
214*9bb1b549SSpandan Das				}
215*9bb1b549SSpandan Das			} else {
216*9bb1b549SSpandan Das				panic("unknown archive extension: " + ext)
217*9bb1b549SSpandan Das			}
218*9bb1b549SSpandan Das		}
219*9bb1b549SSpandan Das		return nil
220*9bb1b549SSpandan Das	}
221*9bb1b549SSpandan Das	for _, filename := range c.sources {
222*9bb1b549SSpandan Das		f, err := os.Open(filename)
223*9bb1b549SSpandan Das		if err != nil {
224*9bb1b549SSpandan Das			return err
225*9bb1b549SSpandan Das		}
226*9bb1b549SSpandan Das		err = cb(len(c.FoundSources), bufio.NewReader(f))
227*9bb1b549SSpandan Das		f.Close()
228*9bb1b549SSpandan Das		if err != nil {
229*9bb1b549SSpandan Das			return err
230*9bb1b549SSpandan Das		}
231*9bb1b549SSpandan Das		c.FoundSources = append(c.FoundSources, filename)
232*9bb1b549SSpandan Das	}
233*9bb1b549SSpandan Das	return nil
234*9bb1b549SSpandan Das}
235*9bb1b549SSpandan Das
236*9bb1b549SSpandan Dasfunc findZipSources(c *configuration, filename string, cb func(i int, f io.Reader) error) error {
237*9bb1b549SSpandan Das	r, err := zip.OpenReader(filename)
238*9bb1b549SSpandan Das	if err != nil {
239*9bb1b549SSpandan Das		return err
240*9bb1b549SSpandan Das	}
241*9bb1b549SSpandan Das	defer r.Close()
242*9bb1b549SSpandan Das	for _, file := range r.File {
243*9bb1b549SSpandan Das		f, err := file.Open()
244*9bb1b549SSpandan Das		if err != nil {
245*9bb1b549SSpandan Das			return err
246*9bb1b549SSpandan Das		}
247*9bb1b549SSpandan Das		err = cb(len(c.FoundSources), f)
248*9bb1b549SSpandan Das		f.Close()
249*9bb1b549SSpandan Das		if err != nil {
250*9bb1b549SSpandan Das			return err
251*9bb1b549SSpandan Das		}
252*9bb1b549SSpandan Das		c.FoundSources = append(c.FoundSources, file.Name)
253*9bb1b549SSpandan Das	}
254*9bb1b549SSpandan Das	return nil
255*9bb1b549SSpandan Das}
256*9bb1b549SSpandan Das
257*9bb1b549SSpandan Dasfunc findTarSources(c *configuration, filename string, cb func(i int, f io.Reader) error) error {
258*9bb1b549SSpandan Das	tf, err := os.Open(filename)
259*9bb1b549SSpandan Das	if err != nil {
260*9bb1b549SSpandan Das		return err
261*9bb1b549SSpandan Das	}
262*9bb1b549SSpandan Das	defer tf.Close()
263*9bb1b549SSpandan Das	reader := tar.NewReader(bufio.NewReader(tf))
264*9bb1b549SSpandan Das	for {
265*9bb1b549SSpandan Das		h, err := reader.Next()
266*9bb1b549SSpandan Das		if err == io.EOF {
267*9bb1b549SSpandan Das			return nil
268*9bb1b549SSpandan Das		}
269*9bb1b549SSpandan Das		if err != nil {
270*9bb1b549SSpandan Das			return err
271*9bb1b549SSpandan Das		}
272*9bb1b549SSpandan Das		if h.Typeflag != tar.TypeReg {
273*9bb1b549SSpandan Das			continue
274*9bb1b549SSpandan Das		}
275*9bb1b549SSpandan Das		if err := cb(len(c.FoundSources), &io.LimitedReader{
276*9bb1b549SSpandan Das			R: reader,
277*9bb1b549SSpandan Das			N: h.Size,
278*9bb1b549SSpandan Das		}); err != nil {
279*9bb1b549SSpandan Das			return err
280*9bb1b549SSpandan Das		}
281*9bb1b549SSpandan Das		c.FoundSources = append(c.FoundSources, h.Name)
282*9bb1b549SSpandan Das	}
283*9bb1b549SSpandan Das}
284*9bb1b549SSpandan Das
285*9bb1b549SSpandan Dasfunc embedFileContents(w io.Writer, filename string) error {
286*9bb1b549SSpandan Das	f, err := os.Open(filename)
287*9bb1b549SSpandan Das	if err != nil {
288*9bb1b549SSpandan Das		return err
289*9bb1b549SSpandan Das	}
290*9bb1b549SSpandan Das	defer f.Close()
291*9bb1b549SSpandan Das
292*9bb1b549SSpandan Das	_, err = io.Copy(&escapeWriter{w}, bufio.NewReader(f))
293*9bb1b549SSpandan Das	return err
294*9bb1b549SSpandan Das}
295*9bb1b549SSpandan Das
296*9bb1b549SSpandan Dastype escapeWriter struct {
297*9bb1b549SSpandan Das	w io.Writer
298*9bb1b549SSpandan Das}
299*9bb1b549SSpandan Das
300*9bb1b549SSpandan Dasfunc (w *escapeWriter) Write(data []byte) (n int, err error) {
301*9bb1b549SSpandan Das	n = len(data)
302*9bb1b549SSpandan Das
303*9bb1b549SSpandan Das	for err == nil && len(data) > 0 {
304*9bb1b549SSpandan Das		// https://golang.org/ref/spec#String_literals: "Within the quotes, any
305*9bb1b549SSpandan Das		// character may appear except newline and unescaped double quote. The
306*9bb1b549SSpandan Das		// text between the quotes forms the value of the literal, with backslash
307*9bb1b549SSpandan Das		// escapes interpreted as they are in rune literals […]."
308*9bb1b549SSpandan Das		switch b := data[0]; b {
309*9bb1b549SSpandan Das		case '\\':
310*9bb1b549SSpandan Das			_, err = w.w.Write([]byte(`\\`))
311*9bb1b549SSpandan Das		case '"':
312*9bb1b549SSpandan Das			_, err = w.w.Write([]byte(`\"`))
313*9bb1b549SSpandan Das		case '\n':
314*9bb1b549SSpandan Das			_, err = w.w.Write([]byte(`\n`))
315*9bb1b549SSpandan Das
316*9bb1b549SSpandan Das		case '\x00':
317*9bb1b549SSpandan Das			// https://golang.org/ref/spec#Source_code_representation: "Implementation
318*9bb1b549SSpandan Das			// restriction: For compatibility with other tools, a compiler may
319*9bb1b549SSpandan Das			// disallow the NUL character (U+0000) in the source text."
320*9bb1b549SSpandan Das			_, err = w.w.Write([]byte(`\x00`))
321*9bb1b549SSpandan Das
322*9bb1b549SSpandan Das		default:
323*9bb1b549SSpandan Das			// https://golang.org/ref/spec#Source_code_representation: "Implementation
324*9bb1b549SSpandan Das			// restriction: […] A byte order mark may be disallowed anywhere else in
325*9bb1b549SSpandan Das			// the source."
326*9bb1b549SSpandan Das			const byteOrderMark = '\uFEFF'
327*9bb1b549SSpandan Das
328*9bb1b549SSpandan Das			if r, size := utf8.DecodeRune(data); r != utf8.RuneError && r != byteOrderMark {
329*9bb1b549SSpandan Das				_, err = w.w.Write(data[:size])
330*9bb1b549SSpandan Das				data = data[size:]
331*9bb1b549SSpandan Das				continue
332*9bb1b549SSpandan Das			}
333*9bb1b549SSpandan Das
334*9bb1b549SSpandan Das			_, err = fmt.Fprintf(w.w, `\x%02x`, b)
335*9bb1b549SSpandan Das		}
336*9bb1b549SSpandan Das		data = data[1:]
337*9bb1b549SSpandan Das	}
338*9bb1b549SSpandan Das
339*9bb1b549SSpandan Das	return n - len(data), err
340*9bb1b549SSpandan Das}
341