| /* Copyright 2026 The Bazel Authors. All rights reserved. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package rule |
| |
| import ( |
| "log" |
| "reflect" |
| "sort" |
| "strconv" |
| |
| bzl "github.com/bazelbuild/buildtools/build" |
| ) |
| |
| // KeyValue represents a key-value pair. This gets converted into a |
| // rule attribute, i.e., a Skylark keyword argument. |
| type KeyValue struct { |
| Key string |
| Value interface{} |
| } |
| |
| // GlobValue represents a Bazel glob expression. |
| type GlobValue struct { |
| Patterns []string |
| Excludes []string |
| AllowEmpty bool |
| } |
| |
| var _ BzlExprValue = (*GlobValue)(nil) |
| |
| func (g GlobValue) BzlExpr() bzl.Expr { |
| patternsValue := ExprFromValue(g.Patterns) |
| globArgs := []bzl.Expr{patternsValue} |
| if len(g.Excludes) > 0 { |
| excludesValue := ExprFromValue(g.Excludes) |
| globArgs = append(globArgs, &bzl.AssignExpr{ |
| LHS: &bzl.Ident{Name: "exclude"}, |
| Op: "=", |
| RHS: excludesValue, |
| }) |
| } |
| |
| if g.AllowEmpty { |
| globArgs = append(globArgs, &bzl.AssignExpr{ |
| LHS: &bzl.Ident{Name: "allow_empty"}, |
| Op: "=", |
| RHS: ExprFromValue(true), |
| }) |
| } |
| |
| return &bzl.CallExpr{ |
| X: &bzl.Ident{Name: "glob"}, |
| List: globArgs, |
| } |
| } |
| |
| // ParseGlobExpr detects whether the given expression is a call to the glob |
| // function. If it is, ParseGlobExpr returns the glob's patterns and excludes |
| // (if they are literal strings) and true. If not, ParseGlobExpr returns false. |
| func ParseGlobExpr(e bzl.Expr) (GlobValue, bool) { |
| call, ok := e.(*bzl.CallExpr) |
| if !ok { |
| return GlobValue{}, false |
| } |
| callee, ok := call.X.(*bzl.Ident) |
| if !ok || callee.Name != "glob" { |
| return GlobValue{}, false |
| } |
| var glob GlobValue |
| parseStringsList := func(list *bzl.ListExpr) []string { |
| parsed := make([]string, 0, len(list.List)) |
| for _, e := range list.List { |
| if str, ok := e.(*bzl.StringExpr); ok { |
| parsed = append(parsed, str.Value) |
| } |
| } |
| return parsed |
| } |
| |
| // Positional arguments needs to be placed before named arguments, otherwise these are ambigious |
| allowPositionalArgs := true |
| for i, arg := range call.List { |
| if list, ok := arg.(*bzl.ListExpr); ok && allowPositionalArgs { |
| switch i { |
| case 0: |
| glob.Patterns = parseStringsList(list) |
| case 1: |
| glob.Excludes = parseStringsList(list) |
| // Last handled positional argument, no need to visit more |
| return glob, true |
| } |
| continue |
| } |
| |
| kv, ok := arg.(*bzl.AssignExpr) |
| if !ok { |
| continue |
| } |
| allowPositionalArgs = false |
| key, ok := kv.LHS.(*bzl.Ident) |
| if !ok { |
| continue |
| } |
| |
| if key.Name == "allow_empty" { |
| if ident, ok := kv.RHS.(*bzl.Ident); ok && ident.Name == "True" { |
| glob.AllowEmpty = true |
| } |
| continue |
| } |
| |
| list, ok := kv.RHS.(*bzl.ListExpr) |
| if !ok { |
| continue |
| } |
| switch key.Name { |
| case "exclude": |
| glob.Excludes = parseStringsList(list) |
| case "include": |
| glob.Patterns = parseStringsList(list) |
| } |
| } |
| return glob, true |
| } |
| |
| // BzlExprValue is implemented by types that have custom translations |
| // to Starlark values. |
| type BzlExprValue interface { |
| BzlExpr() bzl.Expr |
| } |
| |
| // Merger is implemented by types that can merge their data into an |
| // existing Starlark expression. |
| // |
| // When Merge is invoked, it is responsible for returning a Starlark expression that contains the |
| // result of merging its data into the previously-existing expression provided as other. |
| // Note that other can be nil, if no previous attr with this name existed. |
| type Merger interface { |
| Merge(other bzl.Expr) bzl.Expr |
| } |
| |
| type SortedStrings []string |
| |
| var _ BzlExprValue = SortedStrings(nil) |
| var _ Merger = SortedStrings(nil) |
| |
| func (s SortedStrings) BzlExpr() bzl.Expr { |
| list := make([]bzl.Expr, len(s)) |
| for i, v := range s { |
| list[i] = &bzl.StringExpr{Value: v} |
| } |
| listExpr := &bzl.ListExpr{List: list} |
| sortExprLabels(listExpr, []bzl.Expr{}) |
| return listExpr |
| } |
| |
| func (s SortedStrings) Merge(other bzl.Expr) bzl.Expr { |
| if other == nil { |
| return s.BzlExpr() |
| } |
| merged := MergeList(s.BzlExpr(), other) |
| sortExprLabels(merged, []bzl.Expr{}) |
| return merged |
| } |
| |
| type UnsortedStrings []string |
| |
| var _ Merger = UnsortedStrings(nil) |
| |
| func (s UnsortedStrings) Merge(other bzl.Expr) bzl.Expr { |
| if other == nil { |
| return ExprFromValue(s) |
| } |
| return MergeList(ExprFromValue(s), other) |
| } |
| |
| // SelectStringListValue is a value that can be translated to a Bazel |
| // select expression that picks a string list based on a string condition. |
| type SelectStringListValue map[string][]string |
| |
| var _ BzlExprValue = SelectStringListValue(nil) |
| |
| func (s SelectStringListValue) BzlExpr() bzl.Expr { |
| defaultKey := "//conditions:default" |
| keys := make([]string, 0, len(s)) |
| haveDefaultKey := false |
| for key := range s { |
| if key == defaultKey { |
| haveDefaultKey = true |
| } else { |
| keys = append(keys, key) |
| } |
| } |
| sort.Strings(keys) |
| if haveDefaultKey { |
| keys = append(keys, defaultKey) |
| } |
| |
| args := make([]*bzl.KeyValueExpr, 0, len(s)) |
| for _, key := range keys { |
| value := ExprFromValue(s[key]).(*bzl.ListExpr) |
| if key != defaultKey && len(value.List) > 0 { |
| value.ForceMultiLine = true |
| } |
| args = append(args, &bzl.KeyValueExpr{ |
| Key: &bzl.StringExpr{Value: key}, |
| Value: value, |
| }) |
| } |
| sel := &bzl.CallExpr{ |
| X: &bzl.Ident{Name: "select"}, |
| List: []bzl.Expr{&bzl.DictExpr{List: args, ForceMultiLine: true}}, |
| } |
| return sel |
| } |
| |
| // ExprFromValue converts a value into an expression that can be written into |
| // a Bazel build file. The following types of values can be converted: |
| // |
| // - bools, integers, floats, strings. |
| // - labels (converted to strings). |
| // - slices, arrays (converted to lists). |
| // - maps (converted to select expressions; keys must be rules in |
| // @io_bazel_rules_go//go/platform). |
| // - GlobValue (converted to glob expressions). |
| // - PlatformStrings (converted to a concatenation of a list and selects). |
| // |
| // Converting unsupported types will cause a panic. |
| func ExprFromValue(val interface{}) bzl.Expr { |
| if e, ok := val.(bzl.Expr); ok { |
| return e |
| } |
| if be, ok := val.(BzlExprValue); ok { |
| return be.BzlExpr() |
| } |
| |
| // Fast paths for common types to avoid reflection overhead. |
| switch v := val.(type) { |
| // primitives |
| case string: |
| return &bzl.StringExpr{Value: v} |
| case bool: |
| if v { |
| return &bzl.Ident{Name: "True"} |
| } |
| return &bzl.Ident{Name: "False"} |
| case int: |
| return intLiteralExpr(int64(v)) |
| case int8: |
| return intLiteralExpr(int64(v)) |
| case int16: |
| return intLiteralExpr(int64(v)) |
| case int32: |
| return intLiteralExpr(int64(v)) |
| case int64: |
| return intLiteralExpr(v) |
| case uint: |
| return uintLiteralExpr(uint64(v)) |
| case uint8: |
| return uintLiteralExpr(uint64(v)) |
| case uint16: |
| return uintLiteralExpr(uint64(v)) |
| case uint32: |
| return uintLiteralExpr(uint64(v)) |
| case uint64: |
| return uintLiteralExpr(v) |
| case float32: |
| return floatLiteralExpr(float64(v)) |
| case float64: |
| return floatLiteralExpr(v) |
| |
| // common types of slices |
| case []string: |
| return stringSliceToExpr(v) |
| case []interface{}: |
| return interfaceSliceToExpr(v) |
| } |
| |
| // Fallback to reflection for less common types |
| rv := reflect.ValueOf(val) |
| switch rv.Kind() { |
| case reflect.Slice, reflect.Array: |
| list := make([]bzl.Expr, 0, rv.Len()) |
| for i := 0; i < rv.Len(); i++ { |
| elem := ExprFromValue(rv.Index(i).Interface()) |
| list = append(list, elem) |
| } |
| return &bzl.ListExpr{List: list} |
| |
| case reflect.Map: |
| rkeys := rv.MapKeys() |
| sort.Sort(byString(rkeys)) |
| args := make([]*bzl.KeyValueExpr, len(rkeys)) |
| for i, rk := range rkeys { |
| k := &bzl.StringExpr{Value: mapKeyString(rk)} |
| v := ExprFromValue(rv.MapIndex(rk).Interface()) |
| if l, ok := v.(*bzl.ListExpr); ok { |
| l.ForceMultiLine = true |
| } |
| args[i] = &bzl.KeyValueExpr{Key: k, Value: v} |
| } |
| return &bzl.DictExpr{List: args, ForceMultiLine: true} |
| } |
| |
| log.Panicf("type not supported: %T", val) |
| return nil |
| } |
| |
| func intLiteralExpr(v int64) bzl.Expr { |
| return &bzl.LiteralExpr{Token: strconv.FormatInt(v, 10)} |
| } |
| |
| func uintLiteralExpr(v uint64) bzl.Expr { |
| return &bzl.LiteralExpr{Token: strconv.FormatUint(v, 10)} |
| } |
| |
| func floatLiteralExpr(v float64) bzl.Expr { |
| return &bzl.LiteralExpr{Token: strconv.FormatFloat(v, 'g', -1, 64)} |
| } |
| |
| func stringSliceToExpr(strs []string) *bzl.ListExpr { |
| list := make([]bzl.Expr, len(strs)) |
| for i, s := range strs { |
| list[i] = &bzl.StringExpr{Value: s} |
| } |
| return &bzl.ListExpr{List: list} |
| } |
| |
| func interfaceSliceToExpr(vals []interface{}) *bzl.ListExpr { |
| list := make([]bzl.Expr, len(vals)) |
| for i, v := range vals { |
| list[i] = ExprFromValue(v) |
| } |
| return &bzl.ListExpr{List: list} |
| } |
| |
| func mapKeyString(k reflect.Value) string { |
| switch s := k.Interface().(type) { |
| case string: |
| return s |
| default: |
| log.Panicf("unexpected map key: %v", k) |
| return "" |
| } |
| } |
| |
| type byString []reflect.Value |
| |
| var _ sort.Interface = (*byString)(nil) |
| |
| func (s byString) Len() int { |
| return len(s) |
| } |
| |
| func (s byString) Less(i, j int) bool { |
| return mapKeyString(s[i]) < mapKeyString(s[j]) |
| } |
| |
| func (s byString) Swap(i, j int) { |
| s[i], s[j] = s[j], s[i] |
| } |