xref: /aosp_15_r20/external/starlark-go/lib/proto/cmd/star2proto/star2proto.go (revision 4947cdc739c985f6d86941e22894f5cefe7c9e9a)
1*4947cdc7SCole Faust// The star2proto command executes a Starlark file and prints a protocol
2*4947cdc7SCole Faust// message, which it expects to find in a module-level variable named 'result'.
3*4947cdc7SCole Faust//
4*4947cdc7SCole Faust// THIS COMMAND IS EXPERIMENTAL AND ITS INTERFACE MAY CHANGE.
5*4947cdc7SCole Faustpackage main
6*4947cdc7SCole Faust
7*4947cdc7SCole Faust// TODO(adonovan): add features to make this a useful tool for querying,
8*4947cdc7SCole Faust// converting, and building messages in proto, JSON, and YAML.
9*4947cdc7SCole Faust// - define operations for reading and writing files.
10*4947cdc7SCole Faust// - support (e.g.) querying a proto file given a '-e expr' flag.
11*4947cdc7SCole Faust//   This will need a convenient way to put the relevant descriptors in scope.
12*4947cdc7SCole Faust
13*4947cdc7SCole Faustimport (
14*4947cdc7SCole Faust	"flag"
15*4947cdc7SCole Faust	"fmt"
16*4947cdc7SCole Faust	"io/ioutil"
17*4947cdc7SCole Faust	"log"
18*4947cdc7SCole Faust	"os"
19*4947cdc7SCole Faust	"strings"
20*4947cdc7SCole Faust
21*4947cdc7SCole Faust	starlarkproto "go.starlark.net/lib/proto"
22*4947cdc7SCole Faust	"go.starlark.net/resolve"
23*4947cdc7SCole Faust	"go.starlark.net/starlark"
24*4947cdc7SCole Faust	"go.starlark.net/starlarkjson"
25*4947cdc7SCole Faust	"google.golang.org/protobuf/encoding/protojson"
26*4947cdc7SCole Faust	"google.golang.org/protobuf/encoding/prototext"
27*4947cdc7SCole Faust	"google.golang.org/protobuf/proto"
28*4947cdc7SCole Faust	"google.golang.org/protobuf/reflect/protodesc"
29*4947cdc7SCole Faust	"google.golang.org/protobuf/reflect/protoreflect"
30*4947cdc7SCole Faust	"google.golang.org/protobuf/reflect/protoregistry"
31*4947cdc7SCole Faust	"google.golang.org/protobuf/types/descriptorpb"
32*4947cdc7SCole Faust)
33*4947cdc7SCole Faust
34*4947cdc7SCole Faust// flags
35*4947cdc7SCole Faustvar (
36*4947cdc7SCole Faust	outputFlag  = flag.String("output", "text", "output format (text, wire, json)")
37*4947cdc7SCole Faust	varFlag     = flag.String("var", "result", "the variable to output")
38*4947cdc7SCole Faust	descriptors = flag.String("descriptors", "", "comma-separated list of names of files containing proto.FileDescriptorProto messages")
39*4947cdc7SCole Faust)
40*4947cdc7SCole Faust
41*4947cdc7SCole Faust// Starlark dialect flags
42*4947cdc7SCole Faustfunc init() {
43*4947cdc7SCole Faust	flag.BoolVar(&resolve.AllowFloat, "fp", true, "allow floating-point numbers")
44*4947cdc7SCole Faust	flag.BoolVar(&resolve.AllowSet, "set", resolve.AllowSet, "allow set data type")
45*4947cdc7SCole Faust	flag.BoolVar(&resolve.AllowLambda, "lambda", resolve.AllowLambda, "allow lambda expressions")
46*4947cdc7SCole Faust	flag.BoolVar(&resolve.AllowNestedDef, "nesteddef", resolve.AllowNestedDef, "allow nested def statements")
47*4947cdc7SCole Faust}
48*4947cdc7SCole Faust
49*4947cdc7SCole Faustfunc main() {
50*4947cdc7SCole Faust	log.SetPrefix("star2proto: ")
51*4947cdc7SCole Faust	log.SetFlags(0)
52*4947cdc7SCole Faust	flag.Parse()
53*4947cdc7SCole Faust	if len(flag.Args()) != 1 {
54*4947cdc7SCole Faust		fatalf("requires a single Starlark file name")
55*4947cdc7SCole Faust	}
56*4947cdc7SCole Faust	filename := flag.Args()[0]
57*4947cdc7SCole Faust
58*4947cdc7SCole Faust	// By default, use the linked-in descriptors
59*4947cdc7SCole Faust	// (very few in star2proto, e.g. descriptorpb itself).
60*4947cdc7SCole Faust	pool := protoregistry.GlobalFiles
61*4947cdc7SCole Faust
62*4947cdc7SCole Faust	// Load a user-provided FileDescriptorSet produced by a command such as:
63*4947cdc7SCole Faust	// $ protoc --descriptor_set_out=foo.fds foo.proto
64*4947cdc7SCole Faust	if *descriptors != "" {
65*4947cdc7SCole Faust		var fdset descriptorpb.FileDescriptorSet
66*4947cdc7SCole Faust		for i, filename := range strings.Split(*descriptors, ",") {
67*4947cdc7SCole Faust			data, err := ioutil.ReadFile(filename)
68*4947cdc7SCole Faust			if err != nil {
69*4947cdc7SCole Faust				log.Fatalf("--descriptors[%d]: %s", i, err)
70*4947cdc7SCole Faust			}
71*4947cdc7SCole Faust			// Accumulate into the repeated field of FileDescriptors.
72*4947cdc7SCole Faust			if err := (proto.UnmarshalOptions{Merge: true}).Unmarshal(data, &fdset); err != nil {
73*4947cdc7SCole Faust				log.Fatalf("%s does not contain a proto2.FileDescriptorSet: %v", filename, err)
74*4947cdc7SCole Faust			}
75*4947cdc7SCole Faust		}
76*4947cdc7SCole Faust
77*4947cdc7SCole Faust		files, err := protodesc.NewFiles(&fdset)
78*4947cdc7SCole Faust		if err != nil {
79*4947cdc7SCole Faust			log.Fatalf("protodesc.NewFiles: could not build FileDescriptor index: %v", err)
80*4947cdc7SCole Faust		}
81*4947cdc7SCole Faust		pool = files
82*4947cdc7SCole Faust	}
83*4947cdc7SCole Faust
84*4947cdc7SCole Faust	// Execute the Starlark file.
85*4947cdc7SCole Faust	thread := &starlark.Thread{
86*4947cdc7SCole Faust		Print: func(_ *starlark.Thread, msg string) { fmt.Println(msg) },
87*4947cdc7SCole Faust	}
88*4947cdc7SCole Faust	starlarkproto.SetPool(thread, pool)
89*4947cdc7SCole Faust	predeclared := starlark.StringDict{
90*4947cdc7SCole Faust		"proto": starlarkproto.Module,
91*4947cdc7SCole Faust		"json":  starlarkjson.Module,
92*4947cdc7SCole Faust	}
93*4947cdc7SCole Faust	globals, err := starlark.ExecFile(thread, filename, nil, predeclared)
94*4947cdc7SCole Faust	if err != nil {
95*4947cdc7SCole Faust		if evalErr, ok := err.(*starlark.EvalError); ok {
96*4947cdc7SCole Faust			fatalf("%s", evalErr.Backtrace())
97*4947cdc7SCole Faust		} else {
98*4947cdc7SCole Faust			fatalf("%s", err)
99*4947cdc7SCole Faust		}
100*4947cdc7SCole Faust	}
101*4947cdc7SCole Faust
102*4947cdc7SCole Faust	// Print the output variable as a message.
103*4947cdc7SCole Faust	// TODO(adonovan): this is clumsy.
104*4947cdc7SCole Faust	// Let the user call print(), or provide an expression on the command line.
105*4947cdc7SCole Faust	result, ok := globals[*varFlag]
106*4947cdc7SCole Faust	if !ok {
107*4947cdc7SCole Faust		fatalf("%s must define a module-level variable named %q", filename, *varFlag)
108*4947cdc7SCole Faust	}
109*4947cdc7SCole Faust	msgwrap, ok := result.(*starlarkproto.Message)
110*4947cdc7SCole Faust	if !ok {
111*4947cdc7SCole Faust		fatalf("got %s, want proto.Message, for %q", result.Type(), *varFlag)
112*4947cdc7SCole Faust	}
113*4947cdc7SCole Faust	msg := msgwrap.Message()
114*4947cdc7SCole Faust
115*4947cdc7SCole Faust	// -output
116*4947cdc7SCole Faust	var marshal func(protoreflect.ProtoMessage) ([]byte, error)
117*4947cdc7SCole Faust	switch *outputFlag {
118*4947cdc7SCole Faust	case "wire":
119*4947cdc7SCole Faust		marshal = proto.Marshal
120*4947cdc7SCole Faust
121*4947cdc7SCole Faust	case "text":
122*4947cdc7SCole Faust		marshal = prototext.MarshalOptions{Multiline: true, Indent: "\t"}.Marshal
123*4947cdc7SCole Faust
124*4947cdc7SCole Faust	case "json":
125*4947cdc7SCole Faust		marshal = protojson.MarshalOptions{Multiline: true, Indent: "\t"}.Marshal
126*4947cdc7SCole Faust
127*4947cdc7SCole Faust	default:
128*4947cdc7SCole Faust		fatalf("unsupported -output format: %s", *outputFlag)
129*4947cdc7SCole Faust	}
130*4947cdc7SCole Faust	data, err := marshal(msg)
131*4947cdc7SCole Faust	if err != nil {
132*4947cdc7SCole Faust		fatalf("%s", err)
133*4947cdc7SCole Faust	}
134*4947cdc7SCole Faust	os.Stdout.Write(data)
135*4947cdc7SCole Faust}
136*4947cdc7SCole Faust
137*4947cdc7SCole Faustfunc fatalf(format string, args ...interface{}) {
138*4947cdc7SCole Faust	fmt.Fprintf(os.Stderr, "star2proto: ")
139*4947cdc7SCole Faust	fmt.Fprintf(os.Stderr, format, args...)
140*4947cdc7SCole Faust	fmt.Fprintln(os.Stderr)
141*4947cdc7SCole Faust	os.Exit(1)
142*4947cdc7SCole Faust}
143