github.com/wolfd/bazel-gazelle@v0.14.0/internal/rule/merge.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  	"errors"
    20  	"fmt"
    21  	"log"
    22  	"sort"
    23  
    24  	bzl "github.com/bazelbuild/buildtools/build"
    25  )
    26  
    27  // MergeRules copies information from src into dst, usually discarding
    28  // information in dst when they have the same attributes.
    29  //
    30  // If dst is marked with a "# keep" comment, either above the rule or as
    31  // a suffix, nothing will be changed.
    32  //
    33  // If src has an attribute that is not in dst, it will be copied into dst.
    34  //
    35  // If src and dst have the same attribute and the attribute is mergeable and the
    36  // attribute in dst is not marked with a "# keep" comment, values in the dst
    37  // attribute not marked with a "# keep" comment will be dropped, and values from
    38  // src will be copied in.
    39  //
    40  // If dst has an attribute not in src, and the attribute is mergeable and not
    41  // marked with a "# keep" comment, values in the attribute not marked with
    42  // a "# keep" comment will be dropped. If the attribute is empty afterward,
    43  // it will be deleted.
    44  func MergeRules(src, dst *Rule, mergeable map[string]bool, filename string) {
    45  	if dst.ShouldKeep() {
    46  		return
    47  	}
    48  
    49  	// Process attributes that are in dst but not in src.
    50  	for key, dstAttr := range dst.attrs {
    51  		if _, ok := src.attrs[key]; ok || !mergeable[key] || ShouldKeep(dstAttr) {
    52  			continue
    53  		}
    54  		dstValue := dstAttr.Y
    55  		if mergedValue, err := mergeExprs(nil, dstValue); err != nil {
    56  			start, end := dstValue.Span()
    57  			log.Printf("%s:%d.%d-%d.%d: could not merge expression", filename, start.Line, start.LineRune, end.Line, end.LineRune)
    58  		} else if mergedValue == nil {
    59  			dst.DelAttr(key)
    60  		} else {
    61  			dst.SetAttr(key, mergedValue)
    62  		}
    63  	}
    64  
    65  	// Merge attributes from src into dst.
    66  	for key, srcAttr := range src.attrs {
    67  		srcValue := srcAttr.Y
    68  		if dstAttr, ok := dst.attrs[key]; !ok {
    69  			dst.SetAttr(key, srcValue)
    70  		} else if mergeable[key] && !ShouldKeep(dstAttr) {
    71  			dstValue := dstAttr.Y
    72  			if mergedValue, err := mergeExprs(srcValue, dstValue); err != nil {
    73  				start, end := dstValue.Span()
    74  				log.Printf("%s:%d.%d-%d.%d: could not merge expression", filename, start.Line, start.LineRune, end.Line, end.LineRune)
    75  			} else {
    76  				dst.SetAttr(key, mergedValue)
    77  			}
    78  		}
    79  	}
    80  }
    81  
    82  // mergeExprs combines information from src and dst and returns a merged
    83  // expression. dst may be modified during this process. The returned expression
    84  // may be different from dst when a structural change is needed.
    85  //
    86  // The following kinds of expressions are recognized.
    87  //
    88  //   * nil
    89  //   * strings (can only be merged with strings)
    90  //   * lists of strings
    91  //   * a call to select with a dict argument. The dict keys must be strings,
    92  //     and the values must be lists of strings.
    93  //   * a list of strings combined with a select call using +. The list must
    94  //     be the left operand.
    95  //
    96  // An error is returned if the expressions can't be merged, for example
    97  // because they are not in one of the above formats.
    98  func mergeExprs(src, dst bzl.Expr) (bzl.Expr, error) {
    99  	if ShouldKeep(dst) {
   100  		return nil, nil
   101  	}
   102  	if src == nil && (dst == nil || isScalar(dst)) {
   103  		return nil, nil
   104  	}
   105  	if isScalar(src) {
   106  		return src, nil
   107  	}
   108  
   109  	srcExprs, err := extractPlatformStringsExprs(src)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	dstExprs, err := extractPlatformStringsExprs(dst)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	mergedExprs, err := mergePlatformStringsExprs(srcExprs, dstExprs)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	return makePlatformStringsExpr(mergedExprs), nil
   122  }
   123  
   124  func mergePlatformStringsExprs(src, dst platformStringsExprs) (platformStringsExprs, error) {
   125  	var ps platformStringsExprs
   126  	var err error
   127  	ps.generic = mergeList(src.generic, dst.generic)
   128  	if ps.os, err = mergeDict(src.os, dst.os); err != nil {
   129  		return platformStringsExprs{}, err
   130  	}
   131  	if ps.arch, err = mergeDict(src.arch, dst.arch); err != nil {
   132  		return platformStringsExprs{}, err
   133  	}
   134  	if ps.platform, err = mergeDict(src.platform, dst.platform); err != nil {
   135  		return platformStringsExprs{}, err
   136  	}
   137  	return ps, nil
   138  }
   139  
   140  func mergeList(src, dst *bzl.ListExpr) *bzl.ListExpr {
   141  	if dst == nil {
   142  		return src
   143  	}
   144  	if src == nil {
   145  		src = &bzl.ListExpr{List: []bzl.Expr{}}
   146  	}
   147  
   148  	// Build a list of strings from the src list and keep matching strings
   149  	// in the dst list. This preserves comments. Also keep anything with
   150  	// a "# keep" comment, whether or not it's in the src list.
   151  	srcSet := make(map[string]bool)
   152  	for _, v := range src.List {
   153  		if s := stringValue(v); s != "" {
   154  			srcSet[s] = true
   155  		}
   156  	}
   157  
   158  	var merged []bzl.Expr
   159  	kept := make(map[string]bool)
   160  	keepComment := false
   161  	for _, v := range dst.List {
   162  		s := stringValue(v)
   163  		if keep := ShouldKeep(v); keep || srcSet[s] {
   164  			keepComment = keepComment || keep
   165  			merged = append(merged, v)
   166  			if s != "" {
   167  				kept[s] = true
   168  			}
   169  		}
   170  	}
   171  
   172  	// Add anything in the src list that wasn't kept.
   173  	for _, v := range src.List {
   174  		if s := stringValue(v); kept[s] {
   175  			continue
   176  		}
   177  		merged = append(merged, v)
   178  	}
   179  
   180  	if len(merged) == 0 {
   181  		return nil
   182  	}
   183  	return &bzl.ListExpr{
   184  		List:           merged,
   185  		ForceMultiLine: src.ForceMultiLine || dst.ForceMultiLine || keepComment,
   186  	}
   187  }
   188  
   189  func mergeDict(src, dst *bzl.DictExpr) (*bzl.DictExpr, error) {
   190  	if dst == nil {
   191  		return src, nil
   192  	}
   193  	if src == nil {
   194  		src = &bzl.DictExpr{List: []bzl.Expr{}}
   195  	}
   196  
   197  	var entries []*dictEntry
   198  	entryMap := make(map[string]*dictEntry)
   199  
   200  	for _, kv := range dst.List {
   201  		k, v, err := dictEntryKeyValue(kv)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		if _, ok := entryMap[k]; ok {
   206  			return nil, fmt.Errorf("dst dict contains more than one case named %q", k)
   207  		}
   208  		e := &dictEntry{key: k, dstValue: v}
   209  		entries = append(entries, e)
   210  		entryMap[k] = e
   211  	}
   212  
   213  	for _, kv := range src.List {
   214  		k, v, err := dictEntryKeyValue(kv)
   215  		if err != nil {
   216  			return nil, err
   217  		}
   218  		e, ok := entryMap[k]
   219  		if !ok {
   220  			e = &dictEntry{key: k}
   221  			entries = append(entries, e)
   222  			entryMap[k] = e
   223  		}
   224  		e.srcValue = v
   225  	}
   226  
   227  	keys := make([]string, 0, len(entries))
   228  	haveDefault := false
   229  	for _, e := range entries {
   230  		e.mergedValue = mergeList(e.srcValue, e.dstValue)
   231  		if e.key == "//conditions:default" {
   232  			// Keep the default case, even if it's empty.
   233  			haveDefault = true
   234  			if e.mergedValue == nil {
   235  				e.mergedValue = &bzl.ListExpr{}
   236  			}
   237  		} else if e.mergedValue != nil {
   238  			keys = append(keys, e.key)
   239  		}
   240  	}
   241  	if len(keys) == 0 && (!haveDefault || len(entryMap["//conditions:default"].mergedValue.List) == 0) {
   242  		return nil, nil
   243  	}
   244  	sort.Strings(keys)
   245  	// Always put the default case last.
   246  	if haveDefault {
   247  		keys = append(keys, "//conditions:default")
   248  	}
   249  
   250  	mergedEntries := make([]bzl.Expr, len(keys))
   251  	for i, k := range keys {
   252  		e := entryMap[k]
   253  		mergedEntries[i] = &bzl.KeyValueExpr{
   254  			Key:   &bzl.StringExpr{Value: e.key},
   255  			Value: e.mergedValue,
   256  		}
   257  	}
   258  
   259  	return &bzl.DictExpr{List: mergedEntries, ForceMultiLine: true}, nil
   260  }
   261  
   262  type dictEntry struct {
   263  	key                             string
   264  	dstValue, srcValue, mergedValue *bzl.ListExpr
   265  }
   266  
   267  // SquashRules copies information from src into dst without discarding
   268  // information in dst. SquashRules detects duplicate elements in lists and
   269  // dictionaries, but it doesn't sort elements after squashing. If squashing
   270  // fails because the expression is not understood, an error is returned,
   271  // and neither rule is modified.
   272  func SquashRules(src, dst *Rule, filename string) error {
   273  	if dst.ShouldKeep() {
   274  		return nil
   275  	}
   276  
   277  	for key, srcAttr := range src.attrs {
   278  		srcValue := srcAttr.Y
   279  		if dstAttr, ok := dst.attrs[key]; !ok {
   280  			dst.SetAttr(key, srcValue)
   281  		} else if !ShouldKeep(dstAttr) {
   282  			dstValue := dstAttr.Y
   283  			if squashedValue, err := squashExprs(srcValue, dstValue); err != nil {
   284  				start, end := dstValue.Span()
   285  				return fmt.Errorf("%s:%d.%d-%d.%d: could not squash expression", filename, start.Line, start.LineRune, end.Line, end.LineRune)
   286  			} else {
   287  				dst.SetAttr(key, squashedValue)
   288  			}
   289  		}
   290  	}
   291  	dst.call.Comments.Before = append(dst.call.Comments.Before, src.call.Comments.Before...)
   292  	dst.call.Comments.Suffix = append(dst.call.Comments.Suffix, src.call.Comments.Suffix...)
   293  	dst.call.Comments.After = append(dst.call.Comments.After, src.call.Comments.After...)
   294  	return nil
   295  }
   296  
   297  func squashExprs(src, dst bzl.Expr) (bzl.Expr, error) {
   298  	if ShouldKeep(dst) {
   299  		return dst, nil
   300  	}
   301  	if isScalar(dst) {
   302  		// may lose src, but they should always be the same.
   303  		return dst, nil
   304  	}
   305  	srcExprs, err := extractPlatformStringsExprs(src)
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  	dstExprs, err := extractPlatformStringsExprs(dst)
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  	squashedExprs, err := squashPlatformStringsExprs(srcExprs, dstExprs)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  	return makePlatformStringsExpr(squashedExprs), nil
   318  }
   319  
   320  func squashPlatformStringsExprs(x, y platformStringsExprs) (platformStringsExprs, error) {
   321  	var ps platformStringsExprs
   322  	var err error
   323  	if ps.generic, err = squashList(x.generic, y.generic); err != nil {
   324  		return platformStringsExprs{}, err
   325  	}
   326  	if ps.os, err = squashDict(x.os, y.os); err != nil {
   327  		return platformStringsExprs{}, err
   328  	}
   329  	if ps.arch, err = squashDict(x.arch, y.arch); err != nil {
   330  		return platformStringsExprs{}, err
   331  	}
   332  	if ps.platform, err = squashDict(x.platform, y.platform); err != nil {
   333  		return platformStringsExprs{}, err
   334  	}
   335  	return ps, nil
   336  }
   337  
   338  func squashList(x, y *bzl.ListExpr) (*bzl.ListExpr, error) {
   339  	if x == nil {
   340  		return y, nil
   341  	}
   342  	if y == nil {
   343  		return x, nil
   344  	}
   345  
   346  	ls := makeListSquasher()
   347  	for _, e := range x.List {
   348  		s, ok := e.(*bzl.StringExpr)
   349  		if !ok {
   350  			return nil, errors.New("could not squash non-string")
   351  		}
   352  		ls.add(s)
   353  	}
   354  	for _, e := range y.List {
   355  		s, ok := e.(*bzl.StringExpr)
   356  		if !ok {
   357  			return nil, errors.New("could not squash non-string")
   358  		}
   359  		ls.add(s)
   360  	}
   361  	squashed := ls.list()
   362  	squashed.Comments.Before = append(x.Comments.Before, y.Comments.Before...)
   363  	squashed.Comments.Suffix = append(x.Comments.Suffix, y.Comments.Suffix...)
   364  	squashed.Comments.After = append(x.Comments.After, y.Comments.After...)
   365  	return squashed, nil
   366  }
   367  
   368  func squashDict(x, y *bzl.DictExpr) (*bzl.DictExpr, error) {
   369  	if x == nil {
   370  		return y, nil
   371  	}
   372  	if y == nil {
   373  		return x, nil
   374  	}
   375  
   376  	cases := make(map[string]*bzl.KeyValueExpr)
   377  	addCase := func(e bzl.Expr) error {
   378  		kv := e.(*bzl.KeyValueExpr)
   379  		key, ok := kv.Key.(*bzl.StringExpr)
   380  		if !ok {
   381  			return errors.New("could not squash non-string dict key")
   382  		}
   383  		if _, ok := kv.Value.(*bzl.ListExpr); !ok {
   384  			return errors.New("could not squash non-list dict value")
   385  		}
   386  		if c, ok := cases[key.Value]; ok {
   387  			if sq, err := squashList(kv.Value.(*bzl.ListExpr), c.Value.(*bzl.ListExpr)); err != nil {
   388  				return err
   389  			} else {
   390  				c.Value = sq
   391  			}
   392  		} else {
   393  			kvCopy := *kv
   394  			cases[key.Value] = &kvCopy
   395  		}
   396  		return nil
   397  	}
   398  
   399  	for _, e := range x.List {
   400  		if err := addCase(e); err != nil {
   401  			return nil, err
   402  		}
   403  	}
   404  	for _, e := range y.List {
   405  		if err := addCase(e); err != nil {
   406  			return nil, err
   407  		}
   408  	}
   409  
   410  	keys := make([]string, 0, len(cases))
   411  	haveDefault := false
   412  	for k := range cases {
   413  		if k == "//conditions:default" {
   414  			haveDefault = true
   415  			continue
   416  		}
   417  		keys = append(keys, k)
   418  	}
   419  	sort.Strings(keys)
   420  	if haveDefault {
   421  		keys = append(keys, "//conditions:default") // must be last
   422  	}
   423  
   424  	squashed := *x
   425  	squashed.Comments.Before = append(x.Comments.Before, y.Comments.Before...)
   426  	squashed.Comments.Suffix = append(x.Comments.Suffix, y.Comments.Suffix...)
   427  	squashed.Comments.After = append(x.Comments.After, y.Comments.After...)
   428  	squashed.List = make([]bzl.Expr, 0, len(cases))
   429  	for _, k := range keys {
   430  		squashed.List = append(squashed.List, cases[k])
   431  	}
   432  	return &squashed, nil
   433  }
   434  
   435  // listSquasher builds a sorted, deduplicated list of string expressions. If
   436  // a string expression is added multiple times, comments are consolidated.
   437  // The original expressions are not modified.
   438  type listSquasher struct {
   439  	unique       map[string]*bzl.StringExpr
   440  	seenComments map[elemComment]bool
   441  }
   442  
   443  type elemComment struct {
   444  	elem, com string
   445  }
   446  
   447  func makeListSquasher() listSquasher {
   448  	return listSquasher{
   449  		unique:       make(map[string]*bzl.StringExpr),
   450  		seenComments: make(map[elemComment]bool),
   451  	}
   452  }
   453  
   454  func (ls *listSquasher) add(s *bzl.StringExpr) {
   455  	sCopy, ok := ls.unique[s.Value]
   456  	if !ok {
   457  		// Make a copy of s. We may modify it when we consolidate comments from
   458  		// duplicate strings. We don't want to modify the original in case this
   459  		// function fails (due to a later failed pattern match).
   460  		sCopy = new(bzl.StringExpr)
   461  		*sCopy = *s
   462  		sCopy.Comments.Before = make([]bzl.Comment, 0, len(s.Comments.Before))
   463  		sCopy.Comments.Suffix = make([]bzl.Comment, 0, len(s.Comments.Suffix))
   464  		ls.unique[s.Value] = sCopy
   465  	}
   466  	for _, c := range s.Comment().Before {
   467  		if key := (elemComment{s.Value, c.Token}); !ls.seenComments[key] {
   468  			sCopy.Comments.Before = append(sCopy.Comments.Before, c)
   469  			ls.seenComments[key] = true
   470  		}
   471  	}
   472  	for _, c := range s.Comment().Suffix {
   473  		if key := (elemComment{s.Value, c.Token}); !ls.seenComments[key] {
   474  			sCopy.Comments.Suffix = append(sCopy.Comments.Suffix, c)
   475  			ls.seenComments[key] = true
   476  		}
   477  	}
   478  }
   479  
   480  func (ls *listSquasher) list() *bzl.ListExpr {
   481  	sortedExprs := make([]bzl.Expr, 0, len(ls.unique))
   482  	for _, e := range ls.unique {
   483  		sortedExprs = append(sortedExprs, e)
   484  	}
   485  	sort.Slice(sortedExprs, func(i, j int) bool {
   486  		return sortedExprs[i].(*bzl.StringExpr).Value < sortedExprs[j].(*bzl.StringExpr).Value
   487  	})
   488  	return &bzl.ListExpr{List: sortedExprs}
   489  }