github.com/emcfarlane/larking@v0.0.0-20220605172417-1704b45ee6c3/starlib/starlarkrule/rule.go (about) 1 // Copyright 2022 Edward McFarlane. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package starlarkrule 6 7 import ( 8 "fmt" 9 "net/url" 10 "path" 11 "regexp" 12 "sort" 13 "strings" 14 15 "github.com/emcfarlane/larking/starlib/starext" 16 "github.com/emcfarlane/larking/starlib/starlarkstruct" 17 "go.starlark.net/starlark" 18 "go.starlark.net/syntax" 19 ) 20 21 func NewModule() *starlarkstruct.Module { 22 return &starlarkstruct.Module{ 23 Name: "rule", 24 Members: starlark.StringDict{ 25 "rule": starext.MakeBuiltin("rule.rule", MakeRule), 26 "attr": NewAttrModule(), 27 "attrs": starext.MakeBuiltin("rule.attrs", MakeAttrs), 28 "DefaultInfo": DefaultInfo, 29 "ContainerInfo": ContainerInfo, 30 }, 31 } 32 } 33 34 // Label is a resource URL. 35 type Label struct { 36 url.URL 37 frozen bool 38 } 39 40 func (*Label) Type() string { return "label" } 41 func (l *Label) Truth() starlark.Bool { return l != nil } 42 func (l *Label) Hash() (uint32, error) { 43 // Hash simplified from struct hash. 44 var x uint32 = 8731 45 namehash, _ := starlark.String(l.String()).Hash() 46 x = x ^ 3*namehash 47 return x, nil 48 } 49 func (l *Label) Freeze() { l.frozen = true } 50 51 type labelAttr func(l *Label) starlark.Value 52 53 var labelAttrs = map[string]labelAttr{ 54 "scheme": func(l *Label) starlark.Value { return starlark.String(l.Scheme) }, 55 "opaque": func(l *Label) starlark.Value { return starlark.String(l.Opaque) }, 56 "user": func(l *Label) starlark.Value { return starlark.String(l.User.String()) }, 57 "host": func(l *Label) starlark.Value { return starlark.String(l.Host) }, 58 "path": func(l *Label) starlark.Value { return starlark.String(l.Path) }, 59 "query": func(l *Label) starlark.Value { return starlark.String(l.RawQuery) }, 60 "fragment": func(l *Label) starlark.Value { return starlark.String(l.Fragment) }, 61 62 // Blob type parameters. 63 "bucket": func(l *Label) starlark.Value { return starlark.String(l.BucketURL()) }, 64 "key": func(l *Label) starlark.Value { return starlark.String(l.Key()) }, 65 } 66 67 func (v *Label) Attr(name string) (starlark.Value, error) { 68 if a := labelAttrs[name]; a != nil { 69 return a(v), nil 70 } 71 return nil, nil 72 } 73 func (v *Label) AttrNames() []string { 74 names := make([]string, 0, len(labelAttrs)) 75 for name := range labelAttrs { 76 names = append(names, name) 77 } 78 sort.Strings(names) 79 return names 80 } 81 82 func (l *Label) BucketURL() string { 83 u := l.URL 84 q := u.Query() 85 q.Del("key") 86 q.Del("keyargs") 87 u.RawQuery = q.Encode() 88 return u.String() 89 } 90 func (l *Label) Key() string { return l.Query().Get("key") } 91 func (l *Label) KeyArgs() (url.Values, error) { 92 s := l.Query().Get("keyargs") 93 return url.ParseQuery(s) 94 } 95 96 // Strip keyargs to match target URL. 97 func (l *Label) CleanURL() string { 98 u := l.URL 99 q := u.Query() 100 q.Del("keyargs") 101 u.RawQuery = q.Encode() 102 return u.String() 103 } 104 105 func (x *Label) CompareSameType(op syntax.Token, _y starlark.Value, depth int) (bool, error) { 106 y := _y.(*Label) 107 switch op { 108 case syntax.EQL: 109 return x.String() == y.String(), nil 110 default: 111 return false, fmt.Errorf("unsupported comparison: %v", op) 112 } 113 114 } 115 116 // Parse accepts a full formed label or a relative URL. 117 func (x *Label) Parse(relative string) (*Label, error) { 118 u := x.URL // copy 119 120 y, err := url.Parse(relative) 121 if err != nil { 122 return nil, fmt.Errorf("invalid source: %v", err) 123 } 124 if y.Scheme != "" { 125 return &Label{*y, false}, nil 126 } 127 128 // If empty scheme take path as key. 129 q := u.Query() 130 key := q.Get("key") 131 dir, _ := path.Split(key) 132 133 key = path.Join(dir, y.Path) 134 q.Set("key", key) 135 if rq := y.RawQuery; rq != "" { 136 q.Set("keyargs", rq) 137 } 138 u.RawQuery = q.Encode() 139 140 return &Label{u, false}, nil 141 142 } 143 144 // ParseLabel creates a new Label relative to the source. 145 func ParseLabel(label string) (*Label, error) { 146 u, err := url.Parse(label) 147 if err != nil { 148 return nil, fmt.Errorf("invalid label: %v", err) 149 } 150 151 // If empty scheme take path as key. 152 if u.Scheme == "" { 153 return nil, fmt.Errorf("expected absolute URL") 154 } 155 return &Label{*u, false}, nil 156 } 157 158 func ParseRelativeLabel(source, label string) (*Label, error) { 159 l, err := ParseLabel(source) 160 if err != nil { 161 return nil, err 162 } 163 return l.Parse(label) 164 } 165 166 func MakeLabel(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 167 var name string 168 if err := starlark.UnpackPositionalArgs( 169 fnname, args, kwargs, 1, &name, 170 ); err != nil { 171 return nil, err 172 } 173 return ParseRelativeLabel(thread.Name, name) 174 } 175 176 func AsLabel(v starlark.Value) (*Label, error) { 177 l, ok := v.(*Label) 178 if !ok { 179 return nil, fmt.Errorf("expected label, got %s", v.Type()) 180 } 181 return l, nil 182 } 183 184 type Rule struct { 185 impl *starlark.Function // implementation function 186 attrs *Attrs // input attribute types 187 doc string 188 //outs map[string]*Attr // output attribute types 189 provides []*Attrs 190 191 frozen bool 192 } 193 194 // MakeRule creates a new rule instance. Accepts the following optional kwargs: 195 // "impl", "attrs" and "provides". 196 func MakeRule(_ *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 197 var ( 198 impl = new(starlark.Function) 199 attrs = new(Attrs) 200 doc string 201 provides = new(starlark.List) 202 //ins = new(starlark.Dict) 203 //outs = new(starlark.Dict) 204 ) 205 if err := starlark.UnpackArgs( 206 fnname, args, kwargs, 207 "impl", &impl, 208 "attrs?", &attrs, 209 "doc?", &doc, 210 "provides?", &provides, 211 ); err != nil { 212 return nil, err 213 } 214 215 // type checks 216 if impl.NumParams() != 1 { 217 return nil, fmt.Errorf("unexpected number of params: %d", impl.NumParams()) 218 } 219 220 if err := attrs.checkMutable("use"); err != nil { 221 return nil, err 222 } 223 keyName := "name" 224 if _, ok := attrs.osd.Get(keyName); ok { 225 return nil, fmt.Errorf("reserved %q keyword", keyName) 226 } 227 attrs.osd.Insert(keyName, &Attr{ 228 Typ: AttrTypeString, 229 Def: starlark.String(""), 230 Doc: "Name of rule", 231 Mandatory: true, 232 }) 233 234 pvds := make([]*Attrs, provides.Len()) 235 for i, n := 0, provides.Len(); i < n; i++ { 236 x := provides.Index(i) 237 attr, ok := x.(*Attrs) 238 if !ok { 239 return nil, fmt.Errorf( 240 "invalid provides[%d] %q, expected %q", 241 i, x.Type(), (&Attrs{}).Type(), 242 ) 243 } 244 pvds[i] = attr 245 } 246 247 // key=dir:target.tar.gz 248 // key=dir/target.tar.gz 249 250 return &Rule{ 251 impl: impl, 252 doc: doc, 253 attrs: attrs, 254 provides: pvds, 255 //ins: inAttrs, 256 //outs: outAttrs, 257 }, nil 258 259 } 260 261 func (r *Rule) String() string { return "rule()" } 262 func (r *Rule) Type() string { return "rule" } 263 func (r *Rule) Freeze() { r.frozen = true } 264 func (r *Rule) Truth() starlark.Bool { return starlark.Bool(!r.frozen) } 265 func (r *Rule) Hash() (uint32, error) { 266 // TODO: can a rule be hashed? 267 return 0, fmt.Errorf("unhashable type: rule") 268 } 269 func (r *Rule) Impl() *starlark.Function { return r.impl } 270 func (r *Rule) Attrs() *Attrs { return r.attrs } 271 func (r *Rule) Provides() *starlark.Set { 272 s := starlark.NewSet(len(r.provides)) 273 for _, attrs := range r.provides { 274 if err := s.Insert(attrs); err != nil { 275 panic(err) 276 } 277 } 278 return s 279 } 280 281 //func (r *Rule) Outs() AttrFields { return AttrFields{r.outs} } 282 283 const bldKey = "builder" 284 285 func setBuilder(thread *starlark.Thread, builder *Builder) { 286 thread.SetLocal(bldKey, builder) 287 } 288 func getBuilder(thread *starlark.Thread) (*Builder, error) { 289 if bld, ok := thread.Local(bldKey).(*Builder); ok { 290 return bld, nil 291 } 292 return nil, fmt.Errorf("missing builder") 293 } 294 295 // genrule( 296 // cmd = "protoc ...", 297 // deps = ["//:label"], 298 // outs = ["//"], 299 // executable = "file", 300 // ) 301 302 var isStringAlphabetic = regexp.MustCompile(`^[a-zA-Z0-9_.]*$`).MatchString 303 304 func (r *Rule) Name() string { return "rule" } 305 306 func (r *Rule) CallInternal(thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { 307 bld, err := getBuilder(thread) 308 if err != nil { 309 return nil, err 310 } 311 312 if len(args) > 0 { 313 return nil, fmt.Errorf("unexpected args") 314 } 315 316 source, err := ParseLabel(thread.Name) 317 if err != nil { 318 return nil, err 319 } 320 321 attrArgs, err := r.attrs.MakeArgs(source, kwargs) 322 if err != nil { 323 return nil, err 324 } 325 326 target, err := NewTarget(source, r, attrArgs) 327 if err != nil { 328 return nil, err 329 } 330 331 if err := bld.RegisterTarget(thread, target); err != nil { 332 return nil, err 333 } 334 return r, nil 335 } 336 337 // Target is defined by a call to a rule. 338 type Target struct { 339 label *Label 340 rule *Rule 341 args AttrArgs //starext.OrderedStringDict // attribute args 342 //frozen bool 343 } 344 345 func NewTarget( 346 source *Label, 347 rule *Rule, 348 args *AttrArgs, //*starext.OrderedStringDict, 349 ) (*Target, error) { 350 // Assert name exists. 351 const field = "name" 352 nv, err := args.Attr(field) 353 if err != nil { 354 return nil, fmt.Errorf("missing required field %q", field) 355 } 356 name, ok := starlark.AsString(nv) 357 if !ok || !isStringAlphabetic(name) { 358 return nil, fmt.Errorf("invalid field %q: %q", field, name) 359 } 360 361 l, err := source.Parse(name) 362 if err != nil { 363 return nil, err 364 } 365 366 return &Target{ 367 label: l, 368 rule: rule, 369 args: *args, 370 }, nil 371 } 372 373 func (t *Target) Clone() *Target { 374 return &Target{ 375 label: t.label, // immutable? 376 rule: t.rule, // immuatble 377 args: *t.args.Clone(), // cloned 378 } 379 } 380 381 //// TODO? 382 //func (t *Target) Hash() (uint32, error) { 383 // return 0, fmt.Errorf("unhashable type: %s", t.Type()) 384 //} 385 386 func (t *Target) String() string { 387 var buf strings.Builder 388 buf.WriteString(t.Type()) 389 buf.WriteRune('(') 390 391 buf.WriteString(t.label.String()) 392 for i, n := 0, t.args.Len(); i < n; i++ { 393 if i == 0 { 394 buf.WriteRune('?') 395 } else { 396 buf.WriteRune('&') 397 } 398 399 key, val := t.args.KeyIndex(i) 400 buf.WriteString(key) // already escaped 401 buf.WriteRune('=') 402 buf.WriteString(url.QueryEscape(val.String())) 403 } 404 405 buf.WriteRune(')') 406 return buf.String() 407 } 408 func (t *Target) Type() string { return "target" } 409 410 // SetQuery params, override args. 411 func (t *Target) SetQuery(values url.Values) error { 412 // TODO: check mutability 413 414 for key, vals := range values { 415 x, ok := t.rule.attrs.osd.Get(key) 416 if !ok { 417 return fmt.Errorf("error: unknown query param: %s", key) 418 } 419 attr := x.(*Attr) 420 421 switch attr.AttrType() { 422 case AttrTypeString: 423 if len(vals) > 1 { 424 return fmt.Errorf("error: unexpected number of params: %v", vals) 425 } 426 s := vals[0] 427 // TODO: attr validation? 428 if err := t.args.SetField(key, starlark.String(s)); err != nil { 429 panic(err) 430 } 431 432 default: 433 panic("TODO: query parsing") 434 } 435 } 436 return nil 437 } 438 439 func (t *Target) Args() *AttrArgs { return &t.args } 440 func (t *Target) Rule() *Rule { return t.rule }