github.com/stackb/rules_proto@v0.0.0-20240221195024-5428336c51f1/pkg/protoc/starlark_util.go (about) 1 package protoc 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "go.starlark.net/starlark" 12 "go.starlark.net/starlarkstruct" 13 ) 14 15 type errorReporter func(format string, args ...interface{}) error 16 17 type goStarlarkFunction func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) 18 19 // Symbol is the type of a Starlark constructor symbol. It prints more 20 // favorably than a starlark.String. 21 type Symbol string 22 23 func (s Symbol) String() string { return string(s) } 24 func (s Symbol) GoString() string { return string(s) } 25 func (s Symbol) Type() string { return "symbol" } 26 func (s Symbol) Freeze() {} // immutable 27 func (s Symbol) Truth() starlark.Bool { return len(s) > 0 } 28 func (s Symbol) Hash() (uint32, error) { return starlark.String(s).Hash() } 29 func (s Symbol) Len() int { return len(s) } // bytes 30 func (s Symbol) Index(i int) starlark.Value { return s[i : i+1] } 31 32 func newStringBoolDict(in map[string]bool) *starlark.Dict { 33 out := &starlark.Dict{} 34 for k, v := range in { 35 out.SetKey(starlark.String(k), starlark.Bool(v)) 36 } 37 return out 38 } 39 40 func newStringStringDict(in map[string]string) *starlark.Dict { 41 out := &starlark.Dict{} 42 for k, v := range in { 43 out.SetKey(starlark.String(k), starlark.String(v)) 44 } 45 return out 46 } 47 48 func newStringList(in []string) *starlark.List { 49 values := make([]starlark.Value, len(in)) 50 for i, v := range in { 51 values[i] = starlark.String(v) 52 } 53 return starlark.NewList(values) 54 } 55 56 func newStringListDict(in map[string]map[string]bool) *starlark.Dict { 57 out := &starlark.Dict{} 58 return out 59 } 60 61 func newPredeclared(plugins, rules map[string]*starlarkstruct.Struct) starlark.StringDict { 62 protoc := &starlarkstruct.Module{ 63 Name: "protoc", 64 Members: starlark.StringDict{ 65 "Plugin": starlark.NewBuiltin("Plugin", newStarlarkPluginFunction(plugins)), 66 "Rule": starlark.NewBuiltin("Rule", newStarlarkLanguageRuleFunction(rules)), 67 "PluginConfiguration": starlark.NewBuiltin("PluginConfiguration", newStarlarkPluginConfiguration()), 68 }, 69 } 70 71 gazelle := &starlarkstruct.Module{ 72 Name: "gazelle", 73 Members: starlark.StringDict{ 74 "Rule": starlark.NewBuiltin("Rule", newGazelleRuleFunction()), 75 "LoadInfo": starlark.NewBuiltin("LoadInfo", newGazelleLoadInfoFunction()), 76 "KindInfo": starlark.NewBuiltin("KindInfo", newGazelleKindInfoFunction()), 77 }, 78 } 79 80 return starlark.StringDict{ 81 protoc.Name: protoc, 82 gazelle.Name: gazelle, 83 "struct": starlark.NewBuiltin("struct", starlarkstruct.Make), 84 } 85 } 86 87 func resolveStarlarkFilename(workDir, filename string) (string, error) { 88 if filename == "" { 89 return "", fmt.Errorf("filename is empty") 90 } 91 if _, err := os.Stat(filepath.Join(workDir, filename)); !errors.Is(err, os.ErrNotExist) { 92 return filepath.Join(workDir, filename), nil 93 } 94 95 dirname := workDir 96 // looking for a file named 'DO_NOT_BUILD_HERE' in a parent directory. The 97 // contents of this file names the sourceRoot directory. 98 var sourceRoot string 99 for dirname != "." { 100 sourceRootFile := filepath.Join(dirname, "DO_NOT_BUILD_HERE") 101 if _, err := os.Stat(sourceRootFile); errors.Is(err, os.ErrNotExist) { 102 dirname = filepath.Dir(dirname) 103 } else { 104 data, err := ioutil.ReadFile(sourceRootFile) 105 if err != nil { 106 return "", fmt.Errorf("failed to read DO_NOT_BUILD_HERE file: %w", err) 107 } 108 sourceRoot = strings.TrimSpace(string(data)) 109 break 110 } 111 } 112 if sourceRoot == "" { 113 return "", fmt.Errorf("failed to find sourceRoot") 114 } 115 return filepath.Join(sourceRoot, filename), nil 116 } 117 118 func loadStarlarkProgram(filename string, src interface{}, predeclared starlark.StringDict, reporter func(msg string), errorReporter func(err error)) (*starlark.StringDict, *starlark.Thread, error) { 119 newErrorf := func(msg string, args ...interface{}) error { 120 err := fmt.Errorf(filename+": "+msg, args...) 121 errorReporter(err) 122 return err 123 } 124 125 _, program, err := starlark.SourceProgram(filename, src, predeclared.Has) 126 if err != nil { 127 return nil, nil, newErrorf("source program error: %v", err) 128 } 129 130 thread := new(starlark.Thread) 131 thread.Print = func(thread *starlark.Thread, msg string) { 132 reporter(msg) 133 } 134 globals, err := program.Init(thread, predeclared) 135 if err != nil { 136 return nil, nil, newErrorf("eval: %w", err) 137 } 138 139 return &globals, thread, nil 140 } 141 142 func newGazelleRuleFunction() goStarlarkFunction { 143 return func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 144 var name, kind string 145 attrs := new(starlark.Dict) 146 147 if err := starlark.UnpackArgs("Rule", args, kwargs, 148 "name", &name, 149 "kind", &kind, 150 "attrs?", &attrs, 151 ); err != nil { 152 return nil, err 153 } 154 155 value := starlarkstruct.FromStringDict( 156 Symbol("Rule"), 157 starlark.StringDict{ 158 "name": starlark.String(name), 159 "kind": starlark.String(kind), 160 "attrs": attrs, 161 }, 162 ) 163 164 return value, nil 165 } 166 } 167 168 func newGazelleLoadInfoFunction() goStarlarkFunction { 169 return func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 170 var name string 171 symbols := new(starlark.List) 172 after := new(starlark.List) 173 174 if err := starlark.UnpackArgs("LoadInfo", args, kwargs, 175 "name", &name, 176 "symbols", &symbols, 177 "after?", &after, 178 ); err != nil { 179 return nil, err 180 } 181 182 value := starlarkstruct.FromStringDict( 183 Symbol("LoadInfo"), 184 starlark.StringDict{ 185 "name": starlark.String(name), 186 "symbols": symbols, 187 "after": after, 188 }, 189 ) 190 191 return value, nil 192 } 193 } 194 195 func newGazelleKindInfoFunction() goStarlarkFunction { 196 return func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 197 var matchAny bool 198 matchAttrs := new(starlark.List) 199 nonEmptyAttrs := new(starlark.Dict) 200 substituteAttrs := new(starlark.Dict) 201 mergeableAttrs := new(starlark.Dict) 202 resolveAttrs := new(starlark.Dict) 203 204 if err := starlark.UnpackArgs("KindInfo", args, kwargs, 205 "match_any?", &matchAny, 206 "match_attrs?", &matchAttrs, 207 "non_empty_attrs?", &nonEmptyAttrs, 208 "substitute_attrs?", &substituteAttrs, 209 "mergeable_attrs?", &mergeableAttrs, 210 "resolve_attrs?", &resolveAttrs, 211 ); err != nil { 212 return nil, err 213 } 214 215 value := starlarkstruct.FromStringDict( 216 Symbol("KindInfo"), 217 starlark.StringDict{ 218 "match_any": starlark.Bool(matchAny), 219 "match_attrs": matchAttrs, 220 "non_empty_attrs": nonEmptyAttrs, 221 "substitute_attrs": substituteAttrs, 222 "mergeable_attrs": mergeableAttrs, 223 "resolve_attrs": resolveAttrs, 224 }, 225 ) 226 227 return value, nil 228 } 229 } 230 231 func structAttrStringSlice(in *starlarkstruct.Struct, name string, errorReporter errorReporter) []string { 232 value, err := in.Attr(name) 233 if err != nil { 234 errorReporter("getting struct attr %s: %w", err) 235 return nil 236 } 237 list, ok := value.(*starlark.List) 238 if !ok { 239 errorReporter("%s is not a list", name) 240 return nil 241 } 242 return stringSlice(list, errorReporter) 243 } 244 245 func stringSlice(list *starlark.List, errorReporter errorReporter) (out []string) { 246 for i := 0; i < list.Len(); i++ { 247 value := list.Index(i) 248 switch value := (value).(type) { 249 case starlark.String: 250 out = append(out, value.GoString()) 251 default: 252 errorReporter("list[%d]: expected string, got %s", i, value.Type()) 253 } 254 } 255 return 256 } 257 258 func structAttrBool(in *starlarkstruct.Struct, name string, errorReporter errorReporter) (out bool) { 259 value, err := in.Attr(name) 260 if err != nil { 261 errorReporter("getting struct attr %s: %w", err) 262 return 263 } 264 if value == nil { 265 return false 266 } 267 switch t := value.(type) { 268 case starlark.Bool: 269 out = bool(t.Truth()) 270 default: 271 errorReporter("attr %q: want bool, got %T", name, value) 272 } 273 return 274 } 275 276 func structAttrString(in *starlarkstruct.Struct, name string, errorReporter errorReporter) string { 277 value, err := in.Attr(name) 278 if err != nil { 279 errorReporter("getting struct attr %s: %w", err) 280 return "" 281 } 282 switch value := value.(type) { 283 case starlark.String: 284 return value.GoString() 285 default: 286 errorReporter("%s is not a string (%T)", name, value) 287 return "" 288 } 289 } 290 291 func structAttrMapStringBool(in *starlarkstruct.Struct, name string, errorReporter errorReporter) (out map[string]bool) { 292 value, err := in.Attr(name) 293 if err != nil { 294 if _, ok := err.(starlark.NoSuchAttrError); ok { 295 return 296 } 297 errorReporter("%v", err) 298 return 299 } 300 if value == nil { 301 return 302 } 303 dict, ok := value.(*starlark.Dict) 304 if !ok { 305 errorReporter("%v.%s: value must have type starlark.Dict (got %T)", in.Constructor(), name, value) 306 return 307 } 308 out = make(map[string]bool, dict.Len()) 309 for _, key := range dict.Keys() { 310 k, ok := key.(starlark.String) 311 if !ok { 312 errorReporter("%v.%s: dict keys must have type string (got %T)", in.Constructor(), name, key) 313 return 314 } 315 if value, ok, err := dict.Get(key); ok && err == nil { 316 b, ok := value.(starlark.Bool) 317 if !ok { 318 errorReporter("%v.%s: dict value for %q must have type bool (got %T)", in.Constructor(), name, k.GoString(), value) 319 return 320 } 321 out[k.GoString()] = bool(b.Truth()) 322 } 323 } 324 return 325 }