github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/rule/expr.go (about) 1 /* Copyright 2018 The Bazel Authors. All rights reserved. 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 16 package rule 17 18 import ( 19 "fmt" 20 "log" 21 "strings" 22 23 "github.com/bazelbuild/bazel-gazelle/label" 24 bzl "github.com/bazelbuild/buildtools/build" 25 ) 26 27 // MapExprStrings applies a function to string sub-expressions within e. 28 // An expression containing the results with the same structure as e is 29 // returned. 30 func MapExprStrings(e bzl.Expr, f func(string) string) bzl.Expr { 31 if e == nil { 32 return nil 33 } 34 switch expr := e.(type) { 35 case *bzl.StringExpr: 36 s := f(expr.Value) 37 if s == "" { 38 return nil 39 } 40 ret := *expr 41 ret.Value = s 42 return &ret 43 44 case *bzl.ListExpr: 45 var list []bzl.Expr 46 for _, elem := range expr.List { 47 elem = MapExprStrings(elem, f) 48 if elem != nil { 49 list = append(list, elem) 50 } 51 } 52 if len(list) == 0 && len(expr.List) > 0 { 53 return nil 54 } 55 ret := *expr 56 ret.List = list 57 return &ret 58 59 case *bzl.DictExpr: 60 var cases []*bzl.KeyValueExpr 61 isEmpty := true 62 for _, kv := range expr.List { 63 value := MapExprStrings(kv.Value, f) 64 if value != nil { 65 cases = append(cases, &bzl.KeyValueExpr{Key: kv.Key, Value: value}) 66 if key, ok := kv.Key.(*bzl.StringExpr); !ok || key.Value != "//conditions:default" { 67 isEmpty = false 68 } 69 } 70 } 71 if isEmpty { 72 return nil 73 } 74 ret := *expr 75 ret.List = cases 76 return &ret 77 78 case *bzl.CallExpr: 79 if x, ok := expr.X.(*bzl.Ident); !ok || x.Name != "select" || len(expr.List) != 1 { 80 log.Panicf("unexpected call expression in generated imports: %#v", e) 81 } 82 arg := MapExprStrings(expr.List[0], f) 83 if arg == nil { 84 return nil 85 } 86 call := *expr 87 call.List[0] = arg 88 return &call 89 90 case *bzl.BinaryExpr: 91 x := MapExprStrings(expr.X, f) 92 y := MapExprStrings(expr.Y, f) 93 if x == nil { 94 return y 95 } 96 if y == nil { 97 return x 98 } 99 binop := *expr 100 binop.X = x 101 binop.Y = y 102 return &binop 103 104 default: 105 return nil 106 } 107 } 108 109 // FlattenExpr takes an expression that may have been generated from 110 // PlatformStrings and returns its values in a flat, sorted, de-duplicated 111 // list. Comments are accumulated and de-duplicated across duplicate 112 // expressions. If the expression could not have been generted by 113 // PlatformStrings, the expression will be returned unmodified. 114 func FlattenExpr(e bzl.Expr) bzl.Expr { 115 ps, err := extractPlatformStringsExprs(e) 116 if err != nil { 117 return e 118 } 119 120 ls := makeListSquasher() 121 addElem := func(e bzl.Expr) bool { 122 s, ok := e.(*bzl.StringExpr) 123 if !ok { 124 return false 125 } 126 ls.add(s) 127 return true 128 } 129 addList := func(e bzl.Expr) bool { 130 l, ok := e.(*bzl.ListExpr) 131 if !ok { 132 return false 133 } 134 for _, elem := range l.List { 135 if !addElem(elem) { 136 return false 137 } 138 } 139 return true 140 } 141 addDict := func(d *bzl.DictExpr) bool { 142 for _, kv := range d.List { 143 if !addList(kv.Value) { 144 return false 145 } 146 } 147 return true 148 } 149 150 if ps.generic != nil { 151 if !addList(ps.generic) { 152 return e 153 } 154 } 155 for _, d := range []*bzl.DictExpr{ps.os, ps.arch, ps.platform} { 156 if d == nil { 157 continue 158 } 159 if !addDict(d) { 160 return e 161 } 162 } 163 164 return ls.list() 165 } 166 167 func isScalar(e bzl.Expr) bool { 168 switch e.(type) { 169 case *bzl.StringExpr, *bzl.LiteralExpr, *bzl.Ident: 170 return true 171 default: 172 return false 173 } 174 } 175 176 func dictEntryKeyValue(e bzl.Expr) (string, *bzl.ListExpr, error) { 177 kv, ok := e.(*bzl.KeyValueExpr) 178 if !ok { 179 return "", nil, fmt.Errorf("dict entry was not a key-value pair: %#v", e) 180 } 181 k, ok := kv.Key.(*bzl.StringExpr) 182 if !ok { 183 return "", nil, fmt.Errorf("dict key was not string: %#v", kv.Key) 184 } 185 v, ok := kv.Value.(*bzl.ListExpr) 186 if !ok { 187 return "", nil, fmt.Errorf("dict value was not list: %#v", kv.Value) 188 } 189 return k.Value, v, nil 190 } 191 192 func stringValue(e bzl.Expr) string { 193 s, ok := e.(*bzl.StringExpr) 194 if !ok { 195 return "" 196 } 197 return s.Value 198 } 199 200 // platformStringsExprs is a set of sub-expressions that match the structure 201 // of package.PlatformStrings. ExprFromValue produces expressions that 202 // follow this structure for srcs, deps, and other attributes, so this matches 203 // all non-scalar expressions generated by Gazelle. 204 // 205 // The matched expression has the form: 206 // 207 // [] + select({}) + select({}) + select({}) 208 // 209 // The four collections may appear in any order, and some or all of them may 210 // be omitted (all fields are nil for a nil expression). 211 type platformStringsExprs struct { 212 generic *bzl.ListExpr 213 os, arch, platform *bzl.DictExpr 214 } 215 216 // extractPlatformStringsExprs matches an expression and attempts to extract 217 // sub-expressions in platformStringsExprs. The sub-expressions can then be 218 // merged with corresponding sub-expressions. Any field in the returned 219 // structure may be nil. An error is returned if the given expression does 220 // not follow the pattern described by platformStringsExprs. 221 func extractPlatformStringsExprs(expr bzl.Expr) (platformStringsExprs, error) { 222 var ps platformStringsExprs 223 if expr == nil { 224 return ps, nil 225 } 226 227 // Break the expression into a sequence of expressions combined with +. 228 var parts []bzl.Expr 229 for { 230 binop, ok := expr.(*bzl.BinaryExpr) 231 if !ok { 232 parts = append(parts, expr) 233 break 234 } 235 parts = append(parts, binop.Y) 236 expr = binop.X 237 } 238 239 // Process each part. They may be in any order. 240 for _, part := range parts { 241 switch part := part.(type) { 242 case *bzl.ListExpr: 243 if ps.generic != nil { 244 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: multiple list expressions") 245 } 246 ps.generic = part 247 248 case *bzl.CallExpr: 249 x, ok := part.X.(*bzl.Ident) 250 if !ok || x.Name != "select" || len(part.List) != 1 { 251 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: callee other than select or wrong number of args") 252 } 253 arg, ok := part.List[0].(*bzl.DictExpr) 254 if !ok { 255 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: select argument not dict") 256 } 257 var dict **bzl.DictExpr 258 for _, kv := range arg.List { 259 k, ok := kv.Key.(*bzl.StringExpr) 260 if !ok { 261 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict keys are not all strings") 262 } 263 if k.Value == "//conditions:default" { 264 continue 265 } 266 key, err := label.Parse(k.Value) 267 if err != nil { 268 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict key is not label: %q", k.Value) 269 } 270 if KnownOSSet[key.Name] { 271 dict = &ps.os 272 break 273 } 274 if KnownArchSet[key.Name] { 275 dict = &ps.arch 276 break 277 } 278 osArch := strings.Split(key.Name, "_") 279 if len(osArch) != 2 || !KnownOSSet[osArch[0]] || !KnownArchSet[osArch[1]] { 280 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict key contains unknown platform: %q", k.Value) 281 } 282 dict = &ps.platform 283 break 284 } 285 if dict == nil { 286 // We could not identify the dict because it's empty or only contains 287 // //conditions:default. We'll call it the platform dict to avoid 288 // dropping it. 289 dict = &ps.platform 290 } 291 if *dict != nil { 292 return platformStringsExprs{}, fmt.Errorf("expression could not be matched: multiple selects that are either os-specific, arch-specific, or platform-specific") 293 } 294 *dict = arg 295 } 296 } 297 return ps, nil 298 } 299 300 // makePlatformStringsExpr constructs a single expression from the 301 // sub-expressions in ps. 302 func makePlatformStringsExpr(ps platformStringsExprs) bzl.Expr { 303 makeSelect := func(dict *bzl.DictExpr) bzl.Expr { 304 return &bzl.CallExpr{ 305 X: &bzl.Ident{Name: "select"}, 306 List: []bzl.Expr{dict}, 307 } 308 } 309 forceMultiline := func(e bzl.Expr) { 310 switch e := e.(type) { 311 case *bzl.ListExpr: 312 e.ForceMultiLine = true 313 case *bzl.CallExpr: 314 e.List[0].(*bzl.DictExpr).ForceMultiLine = true 315 } 316 } 317 318 var parts []bzl.Expr 319 if ps.generic != nil { 320 parts = append(parts, ps.generic) 321 } 322 if ps.os != nil { 323 parts = append(parts, makeSelect(ps.os)) 324 } 325 if ps.arch != nil { 326 parts = append(parts, makeSelect(ps.arch)) 327 } 328 if ps.platform != nil { 329 parts = append(parts, makeSelect(ps.platform)) 330 } 331 332 if len(parts) == 0 { 333 return nil 334 } 335 if len(parts) == 1 { 336 return parts[0] 337 } 338 expr := parts[0] 339 forceMultiline(expr) 340 for _, part := range parts[1:] { 341 forceMultiline(part) 342 expr = &bzl.BinaryExpr{ 343 Op: "+", 344 X: expr, 345 Y: part, 346 } 347 } 348 return expr 349 }