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