scripts/easy_json/easy_json.go (152 lines of code) (raw):
// Copyright (c) 2023 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package main
import (
"crypto/md5"
"encoding/base64"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/pkg/errors"
"github.com/uber/zanzibar/parallelize"
"github.com/mailru/easyjson/parser"
// Reference the gen package to be friendly to vendoring tools,
// as it is an indirect dependency.
// (The temporary bootstrapping code uses it.)
"github.com/mailru/easyjson/bootstrap"
_ "github.com/mailru/easyjson/gen"
)
var allStructs = flag.Bool(
"all",
false,
"generate marshaler/unmarshalers for all structs in a file",
)
var checksumPrefix = "// Checksum : "
var prefixBytes = []byte(
"// Code generated by zanzibar\n" +
"// @generated\n",
)
const errorStr = "error generating EasyJSON for file: %v"
func generate(fname string) error {
fInfo, err := os.Stat(fname)
if err != nil {
return err
}
p := parser.Parser{AllStructs: *allStructs}
if err := p.Parse(fname, fInfo.IsDir()); err != nil {
return fmt.Errorf("Error parsing %v: %v", fname, err)
}
var outName string
if fInfo.IsDir() {
outName = filepath.Join(fname, p.PkgName+"_easyjson.go")
} else {
s := strings.TrimSuffix(fname, ".go")
if s == fname {
return errors.New("Filename must end in '.go'")
}
outName = s + "_easyjson.go"
}
g := bootstrap.Generator{
BuildTags: "",
PkgPath: p.PkgPath,
PkgName: p.PkgName,
Types: p.StructNames,
SnakeCase: false,
NoStdMarshalers: false,
OmitEmpty: false,
LeaveTemps: false,
OutName: outName,
StubsOnly: false,
NoFormat: false,
}
if err := g.Run(); err != nil {
return fmt.Errorf("Bootstrap failed: %v", err)
}
return nil
}
func getOldChecksum(easyJSONFile string) string {
oldEasyJSONBytes, err := ioutil.ReadFile(easyJSONFile)
if err == nil {
sliceStart := len(prefixBytes) + len(checksumPrefix)
sliceEnd := len(prefixBytes) + len(checksumPrefix) + 24
return string(oldEasyJSONBytes[sliceStart:sliceEnd])
}
return ""
}
func getNewChecksum(file string) string {
fileBytes, err := ioutil.ReadFile(file)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
return ""
}
checksum := md5.Sum(fileBytes)
return base64.StdEncoding.EncodeToString(checksum[:])
}
func main() {
flag.Parse()
files := flag.Args()
if len(files) == 0 {
flag.Usage()
os.Exit(1)
return
}
runner := parallelize.NewFixedBoundedRunner(len(files), true)
for _, file := range files {
f := func(file interface{}) (interface{}, error) {
return shouldGenerateEasyJSONFile(file.(string))
}
wrk := ¶llelize.SingleParamWork{Data: file, Func: f}
runner.SubmitWork(wrk)
}
result, err := runner.GetResult()
if err != nil {
os.Exit(1)
return
}
// for deterministic code gen, easy json generates differently with different input order
var sortFiles []string
for _, f := range result {
if f == nil {
continue
}
sortFiles = append(sortFiles, f.(string))
}
sort.Strings(sortFiles)
for _, f := range sortFiles {
if err := generateEasyJSONFile(f); err != nil {
err = errors.Wrapf(err, errorStr, f)
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
}
func shouldGenerateEasyJSONFile(file string) (interface{}, error) {
easyJSONFile := file[0:len(file)-3] + "_easyjson.go"
oldChecksum := getOldChecksum(easyJSONFile)
newChecksum := getNewChecksum(file)
// If we have an checksum in easyjson file check it.
if oldChecksum != "" && oldChecksum == newChecksum {
return nil, nil
}
return file, nil
}
func generateEasyJSONFile(file string) error {
newChecksum := getNewChecksum(file)
easyJSONFile := file[0:len(file)-3] + "_easyjson.go"
if err := generate(file); err != nil {
return err
}
bytes, err := ioutil.ReadFile(easyJSONFile)
if err != nil {
return err
}
checksumLine := checksumPrefix + newChecksum + "\n"
newLength := len(bytes) + len(prefixBytes) + len(checksumLine)
newBytes := make([]byte, newLength)
copy(newBytes, prefixBytes)
copy(newBytes[len(prefixBytes):], checksumLine)
copy(newBytes[len(prefixBytes)+len(checksumLine):], bytes)
err = ioutil.WriteFile(easyJSONFile, newBytes, 0644)
if err != nil {
return err
}
return nil
}