123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- /* 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.Expr
- isEmpty := true
- for _, kv := range expr.List {
- keyval, ok := kv.(*bzl.KeyValueExpr)
- if !ok {
- log.Panicf("unexpected expression in generated imports dict: %#v", kv)
- }
- value := MapExprStrings(keyval.Value, f)
- if value != nil {
- cases = append(cases, &bzl.KeyValueExpr{Key: keyval.Key, Value: value})
- if key, ok := keyval.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.(*bzl.KeyValueExpr).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 _, item := range arg.List {
- kv := item.(*bzl.KeyValueExpr) // parser guarantees this
- 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
- }
|