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