/* Copyright 2018 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 ( "fmt" "log" "strings" "github.com/bazelbuild/bazel-gazelle/label" bzl "github.com/bazelbuild/buildtools/build" ) // MapExprStrings applies a function to string sub-expressions within e. // An expression containing the results with the same structure as e is // returned. func MapExprStrings(e bzl.Expr, f func(string) string) bzl.Expr { if e == nil { return nil } switch expr := e.(type) { case *bzl.StringExpr: s := f(expr.Value) if s == "" { return nil } ret := *expr ret.Value = s return &ret case *bzl.ListExpr: var list []bzl.Expr for _, elem := range expr.List { elem = MapExprStrings(elem, f) if elem != nil { list = append(list, elem) } } if len(list) == 0 && len(expr.List) > 0 { return nil } ret := *expr ret.List = list return &ret case *bzl.DictExpr: var cases []*bzl.KeyValueExpr isEmpty := true for _, kv := range expr.List { value := MapExprStrings(kv.Value, f) if value != nil { cases = append(cases, &bzl.KeyValueExpr{Key: kv.Key, Value: value}) if key, ok := kv.Key.(*bzl.StringExpr); !ok || key.Value != "//conditions:default" { isEmpty = false } } } if isEmpty { return nil } ret := *expr ret.List = cases return &ret case *bzl.CallExpr: if x, ok := expr.X.(*bzl.Ident); !ok || x.Name != "select" || len(expr.List) != 1 { log.Panicf("unexpected call expression in generated imports: %#v", e) } arg := MapExprStrings(expr.List[0], f) if arg == nil { return nil } call := *expr call.List[0] = arg return &call case *bzl.BinaryExpr: x := MapExprStrings(expr.X, f) y := MapExprStrings(expr.Y, f) if x == nil { return y } if y == nil { return x } binop := *expr binop.X = x binop.Y = y return &binop default: return nil } } // FlattenExpr takes an expression that may have been generated from // PlatformStrings and returns its values in a flat, sorted, de-duplicated // list. Comments are accumulated and de-duplicated across duplicate // expressions. If the expression could not have been generted by // PlatformStrings, the expression will be returned unmodified. func FlattenExpr(e bzl.Expr) bzl.Expr { ps, err := extractPlatformStringsExprs(e) if err != nil { return e } ls := makeListSquasher() addElem := func(e bzl.Expr) bool { s, ok := e.(*bzl.StringExpr) if !ok { return false } ls.add(s) return true } addList := func(e bzl.Expr) bool { l, ok := e.(*bzl.ListExpr) if !ok { return false } for _, elem := range l.List { if !addElem(elem) { return false } } return true } addDict := func(d *bzl.DictExpr) bool { for _, kv := range d.List { if !addList(kv.Value) { return false } } return true } if ps.generic != nil { if !addList(ps.generic) { return e } } for _, d := range []*bzl.DictExpr{ps.os, ps.arch, ps.platform} { if d == nil { continue } if !addDict(d) { return e } } return ls.list() } func isScalar(e bzl.Expr) bool { switch e.(type) { case *bzl.StringExpr, *bzl.LiteralExpr, *bzl.Ident: return true default: return false } } func dictEntryKeyValue(e bzl.Expr) (string, *bzl.ListExpr, error) { kv, ok := e.(*bzl.KeyValueExpr) if !ok { return "", nil, fmt.Errorf("dict entry was not a key-value pair: %#v", e) } k, ok := kv.Key.(*bzl.StringExpr) if !ok { return "", nil, fmt.Errorf("dict key was not string: %#v", kv.Key) } v, ok := kv.Value.(*bzl.ListExpr) if !ok { return "", nil, fmt.Errorf("dict value was not list: %#v", kv.Value) } return k.Value, v, nil } func stringValue(e bzl.Expr) string { s, ok := e.(*bzl.StringExpr) if !ok { return "" } return s.Value } // platformStringsExprs is a set of sub-expressions that match the structure // of package.PlatformStrings. ExprFromValue produces expressions that // follow this structure for srcs, deps, and other attributes, so this matches // all non-scalar expressions generated by Gazelle. // // The matched expression has the form: // // [] + select({}) + select({}) + select({}) // // The four collections may appear in any order, and some or all of them may // be omitted (all fields are nil for a nil expression). type platformStringsExprs struct { generic *bzl.ListExpr os, arch, platform *bzl.DictExpr } // extractPlatformStringsExprs matches an expression and attempts to extract // sub-expressions in platformStringsExprs. The sub-expressions can then be // merged with corresponding sub-expressions. Any field in the returned // structure may be nil. An error is returned if the given expression does // not follow the pattern described by platformStringsExprs. func extractPlatformStringsExprs(expr bzl.Expr) (platformStringsExprs, error) { var ps platformStringsExprs if expr == nil { return ps, nil } // Break the expression into a sequence of expressions combined with +. var parts []bzl.Expr for { binop, ok := expr.(*bzl.BinaryExpr) if !ok { parts = append(parts, expr) break } parts = append(parts, binop.Y) expr = binop.X } // Process each part. They may be in any order. for _, part := range parts { switch part := part.(type) { case *bzl.ListExpr: if ps.generic != nil { return platformStringsExprs{}, fmt.Errorf("expression could not be matched: multiple list expressions") } ps.generic = part case *bzl.CallExpr: x, ok := part.X.(*bzl.Ident) if !ok || x.Name != "select" || len(part.List) != 1 { return platformStringsExprs{}, fmt.Errorf("expression could not be matched: callee other than select or wrong number of args") } arg, ok := part.List[0].(*bzl.DictExpr) if !ok { return platformStringsExprs{}, fmt.Errorf("expression could not be matched: select argument not dict") } var dict **bzl.DictExpr for _, kv := range arg.List { k, ok := kv.Key.(*bzl.StringExpr) if !ok { return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict keys are not all strings") } if k.Value == "//conditions:default" { continue } key, err := label.Parse(k.Value) if err != nil { return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict key is not label: %q", k.Value) } if KnownOSSet[key.Name] { dict = &ps.os break } if KnownArchSet[key.Name] { dict = &ps.arch break } osArch := strings.Split(key.Name, "_") if len(osArch) != 2 || !KnownOSSet[osArch[0]] || !KnownArchSet[osArch[1]] { return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict key contains unknown platform: %q", k.Value) } dict = &ps.platform break } if dict == nil { // We could not identify the dict because it's empty or only contains // //conditions:default. We'll call it the platform dict to avoid // dropping it. dict = &ps.platform } if *dict != nil { return platformStringsExprs{}, fmt.Errorf("expression could not be matched: multiple selects that are either os-specific, arch-specific, or platform-specific") } *dict = arg } } return ps, nil } // makePlatformStringsExpr constructs a single expression from the // sub-expressions in ps. func makePlatformStringsExpr(ps platformStringsExprs) bzl.Expr { makeSelect := func(dict *bzl.DictExpr) bzl.Expr { return &bzl.CallExpr{ X: &bzl.Ident{Name: "select"}, List: []bzl.Expr{dict}, } } forceMultiline := func(e bzl.Expr) { switch e := e.(type) { case *bzl.ListExpr: e.ForceMultiLine = true case *bzl.CallExpr: e.List[0].(*bzl.DictExpr).ForceMultiLine = true } } var parts []bzl.Expr if ps.generic != nil { parts = append(parts, ps.generic) } if ps.os != nil { parts = append(parts, makeSelect(ps.os)) } if ps.arch != nil { parts = append(parts, makeSelect(ps.arch)) } if ps.platform != nil { parts = append(parts, makeSelect(ps.platform)) } if len(parts) == 0 { return nil } if len(parts) == 1 { return parts[0] } expr := parts[0] forceMultiline(expr) for _, part := range parts[1:] { forceMultiline(part) expr = &bzl.BinaryExpr{ Op: "+", X: expr, Y: part, } } return expr }