go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/starlark/starlarkproto/descriptor_set.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package starlarkproto 16 17 import ( 18 "fmt" 19 "sync/atomic" 20 21 "go.starlark.net/starlark" 22 23 "google.golang.org/protobuf/types/descriptorpb" 24 ) 25 26 // DescriptorSet contains FileDescriptorProto of zero or more *.proto files, 27 // along with pointers to DescriptorSets with their imports. 28 // 29 // A descriptor set can be registered in a Loader. Doing so transitively 30 // registers all its dependencies. See Loader.AddDescriptorSet for more details. 31 // 32 // Implements starlark.Value and starlark.HasAttrs interfaces. Usage from 33 // Starlark side may look like this: 34 // 35 // load(".../wellknown_descpb.star", "wellknown_descpb") 36 // myprotos_descpb = proto.new_descriptor_set( 37 // name = "myprotos", 38 // deps = [wellknown_descpb], 39 // blob = io.read_file("myprotos.descpb"), 40 // ) 41 // myprotos_descpb.register() 42 // 43 // By default register() registers the descriptor set in the default loader, 44 // i.e. ds.register() is same as ds.register(loader=proto.default_loader()). 45 // Also note that ds.register(loader=l) is a sugar for l.add_descriptor_set(ds), 46 // so ds.register() is same as proto.default_loader().add_descriptor_set(ds), 47 // just much shorter. 48 type DescriptorSet struct { 49 name string // for debug messages and errors, can be anything 50 hash uint32 // unique (within the process) value, used by Hash() 51 52 fdps []*descriptorpb.FileDescriptorProto // files in this set 53 deps []*DescriptorSet // direct dependencies of this set 54 } 55 56 // descSetHash is used to give each instance of *DescriptorSet its own unique 57 // non-reused hash value for Hash() method. 58 var descSetHash uint32 = 10000 59 60 // NewDescriptorSet evaluates given file descriptors and their dependencies and 61 // produces new DescriptorSet if there are no unresolved imports and no 62 // duplicated files. 63 // 64 // 'fdps' should be ordered topologically (i.e. if file A imports file B, then 65 // B should precede A in 'fdps' or be somewhere among 'deps'). This is always 66 // the case when generating sets via 'protoc --descriptor_set_out=...'. 67 // 68 // Note that dependencies can only be specified when creating the descriptor 69 // set and can't be changed later. Cycles thus are impossible. 70 func NewDescriptorSet(name string, fdps []*descriptorpb.FileDescriptorProto, deps []*DescriptorSet) (*DescriptorSet, error) { 71 ds := &DescriptorSet{ 72 name: name, 73 hash: atomic.AddUint32(&descSetHash, 1), 74 fdps: append([]*descriptorpb.FileDescriptorProto(nil), fdps...), 75 deps: append([]*DescriptorSet(nil), deps...), 76 } 77 78 // Collect ALL dependencies (most nested first). Note that diamond dependency 79 // diagram is fine, but the same *.proto file registered in different sets is 80 // not fine: this will be checked below. 81 82 topo := make([]*DescriptorSet, 0, len(deps)) 83 seen := make(map[*DescriptorSet]struct{}, len(deps)) 84 85 var visitDep func(d *DescriptorSet) 86 visitDep = func(d *DescriptorSet) { 87 if _, ok := seen[d]; !ok { 88 seen[d] = struct{}{} 89 for _, dep := range d.deps { 90 visitDep(dep) 91 } 92 topo = append(topo, d) 93 } 94 } 95 visitDep(ds) 96 97 // Verify each *.proto file is described by exactly one set, and at the moment 98 // it is added (based on topological order), all its dependencies are already 99 // present. 100 101 files := map[string]*DescriptorSet{} // *.proto path => DescriptoSet it is defined in 102 for _, dep := range topo { 103 for _, fd := range dep.fdps { 104 pname := fd.GetName() 105 if ds := files[pname]; ds != nil { 106 return nil, fmt.Errorf("conflict between descriptor sets %q and %q: both define %q", dep.name, ds.name, pname) 107 } 108 for _, imp := range fd.GetDependency() { 109 if files[imp] == nil { 110 return nil, fmt.Errorf("in descriptor set %q: %q imports unknown %q", dep.name, pname, imp) 111 } 112 } 113 files[pname] = dep 114 } 115 } 116 117 return ds, nil 118 } 119 120 // Implementation of starlark.Value and starlark.HasAttrs. 121 122 // String returns str(...) representation of the set, for debug messages. 123 func (ds *DescriptorSet) String() string { return fmt.Sprintf("proto.DescriptorSet(%q)", ds.name) } 124 125 // Type returns a short string describing the value's type. 126 func (ds *DescriptorSet) Type() string { return "proto.DescriptorSet" } 127 128 // Freeze does nothing since DescriptorSet is already immutable. 129 func (ds *DescriptorSet) Freeze() {} 130 131 // Truth returns the truth value of an object. 132 func (ds *DescriptorSet) Truth() starlark.Bool { return starlark.True } 133 134 // Hash returns unique value associated with this set. 135 func (ds *DescriptorSet) Hash() (uint32, error) { return ds.hash, nil } 136 137 // AtrrNames lists available attributes. 138 func (ds *DescriptorSet) AttrNames() []string { 139 return []string{ 140 "register", 141 } 142 } 143 144 // Attr returns an attribute given its name (or nil if not present). 145 func (ds *DescriptorSet) Attr(name string) (starlark.Value, error) { 146 switch name { 147 case "register": 148 return dsRegisterBuiltin.BindReceiver(ds), nil 149 default: 150 return nil, nil 151 } 152 } 153 154 // Implementation of DescriptorSet Starlark methods. 155 156 var dsRegisterBuiltin = starlark.NewBuiltin("register", func(th *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 157 var loader starlark.Value 158 if err := starlark.UnpackArgs("register", args, kwargs, "loader?", &loader); err != nil { 159 return nil, err 160 } 161 if loader == nil || loader == starlark.None { 162 loader = DefaultLoader(th) 163 if loader == nil { 164 return nil, fmt.Errorf("register: loader is None and there's no default loader") 165 } 166 } 167 l, ok := loader.(*Loader) 168 if !ok { 169 return nil, fmt.Errorf("register: for parameter \"loader\": got %s, want proto.Loader", l.Type()) 170 } 171 return starlark.None, l.AddDescriptorSet(b.Receiver().(*DescriptorSet)) 172 })