github.com/wolfd/bazel-gazelle@v0.14.0/internal/rule/rule.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 provides tools for editing Bazel build files. It is intended to
    17  // be a more powerful replacement for
    18  // github.com/bazelbuild/buildtools/build.Rule, adapted for Gazelle's usage. It
    19  // is language agnostic, but it may be used for language-specific rules by
    20  // providing configuration.
    21  //
    22  // File is the primary interface to this package. Rule and Load are used to
    23  // create, read, update, and delete rules. Once modifications are performed,
    24  // File.Sync() may be called to write the changes back to the original AST,
    25  // which may then be formatted and written back to a file.
    26  package rule
    27  
    28  import (
    29  	"io/ioutil"
    30  	"os"
    31  	"path/filepath"
    32  	"sort"
    33  	"strings"
    34  
    35  	bzl "github.com/bazelbuild/buildtools/build"
    36  	bt "github.com/bazelbuild/buildtools/tables"
    37  )
    38  
    39  // File provides editing functionality on top of a Skylark syntax tree. This
    40  // is the primary interface Gazelle uses for reading and updating build files.
    41  // To use, create a new file with EmptyFile or wrap a syntax tree with
    42  // LoadFile. Perform edits on Loads and Rules, then call Sync() to write
    43  // changes back to the AST.
    44  type File struct {
    45  	// File is the underlying build file syntax tree. Some editing operations
    46  	// may modify this, but editing is not complete until Sync() is called.
    47  	File *bzl.File
    48  
    49  	// Pkg is the Bazel package this build file defines.
    50  	Pkg string
    51  
    52  	// Path is the file system path to the build file (same as File.Path).
    53  	Path string
    54  
    55  	// Directives is a list of configuration directives found in top-level
    56  	// comments in the file. This should not be modified after the file is read.
    57  	Directives []Directive
    58  
    59  	// Loads is a list of load statements within the file. This should not
    60  	// be modified directly; use Load methods instead.
    61  	Loads []*Load
    62  
    63  	// Rules is a list of rules within the file (or function calls that look like
    64  	// rules). This should not be modified directly; use Rule methods instead.
    65  	Rules []*Rule
    66  }
    67  
    68  // EmptyFile creates a File wrapped around an empty syntax tree.
    69  func EmptyFile(path, pkg string) *File {
    70  	return &File{
    71  		File: &bzl.File{Path: path},
    72  		Path: path,
    73  		Pkg:  pkg,
    74  	}
    75  }
    76  
    77  // LoadFile loads a build file from disk, parses it, and scans for rules and
    78  // load statements. The syntax tree within the returned File will be modified
    79  // by editing methods.
    80  //
    81  // This function returns I/O and parse errors without modification. It's safe
    82  // to use os.IsNotExist and similar predicates.
    83  func LoadFile(path, pkg string) (*File, error) {
    84  	data, err := ioutil.ReadFile(path)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	return LoadData(path, pkg, data)
    89  }
    90  
    91  // LoadData parses a build file from a byte slice and scans it for rules and
    92  // load statements. The syntax tree within the returned File will be modified
    93  // by editing methods.
    94  func LoadData(path, pkg string, data []byte) (*File, error) {
    95  	ast, err := bzl.Parse(path, data)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	return ScanAST(pkg, ast), nil
   100  }
   101  
   102  // ScanAST creates a File wrapped around the given syntax tree. This tree
   103  // will be modified by editing methods.
   104  func ScanAST(pkg string, bzlFile *bzl.File) *File {
   105  	f := &File{
   106  		File: bzlFile,
   107  		Pkg:  pkg,
   108  		Path: bzlFile.Path,
   109  	}
   110  	for i, stmt := range f.File.Stmt {
   111  		call, ok := stmt.(*bzl.CallExpr)
   112  		if !ok {
   113  			continue
   114  		}
   115  		x, ok := call.X.(*bzl.LiteralExpr)
   116  		if !ok {
   117  			continue
   118  		}
   119  		if x.Token == "load" {
   120  			if l := loadFromExpr(i, call); l != nil {
   121  				f.Loads = append(f.Loads, l)
   122  			}
   123  		} else {
   124  			if r := ruleFromExpr(i, call); r != nil {
   125  				f.Rules = append(f.Rules, r)
   126  			}
   127  		}
   128  	}
   129  	f.Directives = ParseDirectives(bzlFile)
   130  	return f
   131  }
   132  
   133  // MatchBuildFileName looks for a file in files that has a name from names.
   134  // If there is at least one matching file, a path will be returned by joining
   135  // dir and the first matching name. If there are no matching files, the
   136  // empty string is returned.
   137  func MatchBuildFileName(dir string, names []string, files []os.FileInfo) string {
   138  	for _, name := range names {
   139  		for _, fi := range files {
   140  			if fi.Name() == name && !fi.IsDir() {
   141  				return filepath.Join(dir, name)
   142  			}
   143  		}
   144  	}
   145  	return ""
   146  }
   147  
   148  // Sync writes all changes back to the wrapped syntax tree. This should be
   149  // called after editing operations, before reading the syntax tree again.
   150  func (f *File) Sync() {
   151  	var inserts, deletes, stmts []*stmt
   152  	var r, w int
   153  	for r, w = 0, 0; r < len(f.Loads); r++ {
   154  		s := f.Loads[r]
   155  		s.sync()
   156  		if s.deleted {
   157  			deletes = append(deletes, &s.stmt)
   158  			continue
   159  		}
   160  		if s.inserted {
   161  			inserts = append(inserts, &s.stmt)
   162  			s.inserted = false
   163  		} else {
   164  			stmts = append(stmts, &s.stmt)
   165  		}
   166  		f.Loads[w] = s
   167  		w++
   168  	}
   169  	f.Loads = f.Loads[:w]
   170  	for r, w = 0, 0; r < len(f.Rules); r++ {
   171  		s := f.Rules[r]
   172  		s.sync()
   173  		if s.deleted {
   174  			deletes = append(deletes, &s.stmt)
   175  			continue
   176  		}
   177  		if s.inserted {
   178  			inserts = append(inserts, &s.stmt)
   179  			s.inserted = false
   180  		} else {
   181  			stmts = append(stmts, &s.stmt)
   182  		}
   183  		f.Rules[w] = s
   184  		w++
   185  	}
   186  	f.Rules = f.Rules[:w]
   187  	sort.Stable(byIndex(deletes))
   188  	sort.Stable(byIndex(inserts))
   189  	sort.Stable(byIndex(stmts))
   190  
   191  	oldStmt := f.File.Stmt
   192  	f.File.Stmt = make([]bzl.Expr, 0, len(oldStmt)-len(deletes)+len(inserts))
   193  	var ii, di, si int
   194  	for i, stmt := range oldStmt {
   195  		for ii < len(inserts) && inserts[ii].index == i {
   196  			inserts[ii].index = len(f.File.Stmt)
   197  			f.File.Stmt = append(f.File.Stmt, inserts[ii].call)
   198  			ii++
   199  		}
   200  		if di < len(deletes) && deletes[di].index == i {
   201  			di++
   202  			continue
   203  		}
   204  		if si < len(stmts) && stmts[si].call == stmt {
   205  			stmts[si].index = len(f.File.Stmt)
   206  			si++
   207  		}
   208  		f.File.Stmt = append(f.File.Stmt, stmt)
   209  	}
   210  	for ii < len(inserts) {
   211  		inserts[ii].index = len(f.File.Stmt)
   212  		f.File.Stmt = append(f.File.Stmt, inserts[ii].call)
   213  		ii++
   214  	}
   215  }
   216  
   217  // Format formats the build file in a form that can be written to disk.
   218  // This method calls Sync internally.
   219  func (f *File) Format() []byte {
   220  	f.Sync()
   221  	return bzl.Format(f.File)
   222  }
   223  
   224  // Save writes the build file to disk. This method calls Sync internally.
   225  func (f *File) Save(path string) error {
   226  	f.Sync()
   227  	data := bzl.Format(f.File)
   228  	return ioutil.WriteFile(path, data, 0666)
   229  }
   230  
   231  type stmt struct {
   232  	index                      int
   233  	deleted, inserted, updated bool
   234  	call                       *bzl.CallExpr
   235  }
   236  
   237  // Index returns the index for this statement within the build file. For
   238  // inserted rules, this is where the rule will be inserted (rules with the
   239  // same index will be inserted in the order Insert was called). For existing
   240  // rules, this is the index of the original statement.
   241  func (s *stmt) Index() int { return s.index }
   242  
   243  // Delete marks this statement for deletion. It will be removed from the
   244  // syntax tree when File.Sync is called.
   245  func (s *stmt) Delete() { s.deleted = true }
   246  
   247  type byIndex []*stmt
   248  
   249  func (s byIndex) Len() int {
   250  	return len(s)
   251  }
   252  
   253  func (s byIndex) Less(i, j int) bool {
   254  	return s[i].index < s[j].index
   255  }
   256  
   257  func (s byIndex) Swap(i, j int) {
   258  	s[i], s[j] = s[j], s[i]
   259  }
   260  
   261  // Load represents a load statement within a build file.
   262  type Load struct {
   263  	stmt
   264  	name    string
   265  	symbols map[string]bzl.Expr
   266  }
   267  
   268  // NewLoad creates a new, empty load statement for the given file name.
   269  func NewLoad(name string) *Load {
   270  	return &Load{
   271  		stmt: stmt{
   272  			call: &bzl.CallExpr{
   273  				X:            &bzl.LiteralExpr{Token: "load"},
   274  				List:         []bzl.Expr{&bzl.StringExpr{Value: name}},
   275  				ForceCompact: true,
   276  			},
   277  		},
   278  		name:    name,
   279  		symbols: make(map[string]bzl.Expr),
   280  	}
   281  }
   282  
   283  func loadFromExpr(index int, call *bzl.CallExpr) *Load {
   284  	l := &Load{
   285  		stmt:    stmt{index: index, call: call},
   286  		symbols: make(map[string]bzl.Expr),
   287  	}
   288  	if len(call.List) == 0 {
   289  		return nil
   290  	}
   291  	name, ok := call.List[0].(*bzl.StringExpr)
   292  	if !ok {
   293  		return nil
   294  	}
   295  	l.name = name.Value
   296  	for _, arg := range call.List[1:] {
   297  		switch arg := arg.(type) {
   298  		case *bzl.StringExpr:
   299  			l.symbols[arg.Value] = arg
   300  		case *bzl.BinaryExpr:
   301  			x, ok := arg.X.(*bzl.LiteralExpr)
   302  			if !ok {
   303  				return nil
   304  			}
   305  			if _, ok := arg.Y.(*bzl.StringExpr); !ok {
   306  				return nil
   307  			}
   308  			l.symbols[x.Token] = arg
   309  		default:
   310  			return nil
   311  		}
   312  	}
   313  	return l
   314  }
   315  
   316  // Name returns the name of the file this statement loads.
   317  func (l *Load) Name() string {
   318  	return l.name
   319  }
   320  
   321  // Symbols returns a list of symbols this statement loads.
   322  func (l *Load) Symbols() []string {
   323  	syms := make([]string, 0, len(l.symbols))
   324  	for sym := range l.symbols {
   325  		syms = append(syms, sym)
   326  	}
   327  	sort.Strings(syms)
   328  	return syms
   329  }
   330  
   331  // Has returns true if sym is loaded by this statement.
   332  func (l *Load) Has(sym string) bool {
   333  	_, ok := l.symbols[sym]
   334  	return ok
   335  }
   336  
   337  // Add inserts a new symbol into the load statement. This has no effect if
   338  // the symbol is already loaded. Symbols will be sorted, so the order
   339  // doesn't matter.
   340  func (l *Load) Add(sym string) {
   341  	if _, ok := l.symbols[sym]; !ok {
   342  		l.symbols[sym] = &bzl.StringExpr{Value: sym}
   343  		l.updated = true
   344  	}
   345  }
   346  
   347  // Remove deletes a symbol from the load statement. This has no effect if
   348  // the symbol is not loaded.
   349  func (l *Load) Remove(sym string) {
   350  	if _, ok := l.symbols[sym]; ok {
   351  		delete(l.symbols, sym)
   352  		l.updated = true
   353  	}
   354  }
   355  
   356  // IsEmpty returns whether this statement loads any symbols.
   357  func (l *Load) IsEmpty() bool {
   358  	return len(l.symbols) == 0
   359  }
   360  
   361  // Insert marks this statement for insertion at the given index. If multiple
   362  // statements are inserted at the same index, they will be inserted in the
   363  // order Insert is called.
   364  func (l *Load) Insert(f *File, index int) {
   365  	l.index = index
   366  	l.inserted = true
   367  	f.Loads = append(f.Loads, l)
   368  }
   369  
   370  func (l *Load) sync() {
   371  	if !l.updated {
   372  		return
   373  	}
   374  	l.updated = false
   375  
   376  	args := make([]*bzl.StringExpr, 0, len(l.symbols))
   377  	kwargs := make([]*bzl.BinaryExpr, 0, len(l.symbols))
   378  	for _, e := range l.symbols {
   379  		if a, ok := e.(*bzl.StringExpr); ok {
   380  			args = append(args, a)
   381  		} else {
   382  			kwargs = append(kwargs, e.(*bzl.BinaryExpr))
   383  		}
   384  	}
   385  	sort.Slice(args, func(i, j int) bool {
   386  		return args[i].Value < args[j].Value
   387  	})
   388  	sort.Slice(kwargs, func(i, j int) bool {
   389  		return kwargs[i].X.(*bzl.StringExpr).Value < kwargs[j].Y.(*bzl.StringExpr).Value
   390  	})
   391  
   392  	list := make([]bzl.Expr, 0, 1+len(l.symbols))
   393  	list = append(list, l.call.List[0])
   394  	for _, a := range args {
   395  		list = append(list, a)
   396  	}
   397  	for _, a := range kwargs {
   398  		list = append(list, a)
   399  	}
   400  	l.call.List = list
   401  	l.call.ForceCompact = len(kwargs) == 0
   402  }
   403  
   404  // Rule represents a rule statement within a build file.
   405  type Rule struct {
   406  	stmt
   407  	kind    string
   408  	args    []bzl.Expr
   409  	attrs   map[string]*bzl.BinaryExpr
   410  	private map[string]interface{}
   411  }
   412  
   413  // NewRule creates a new, empty rule with the given kind and name.
   414  func NewRule(kind, name string) *Rule {
   415  	nameAttr := &bzl.BinaryExpr{
   416  		X:  &bzl.LiteralExpr{Token: "name"},
   417  		Y:  &bzl.StringExpr{Value: name},
   418  		Op: "=",
   419  	}
   420  	r := &Rule{
   421  		stmt: stmt{
   422  			call: &bzl.CallExpr{
   423  				X:    &bzl.LiteralExpr{Token: kind},
   424  				List: []bzl.Expr{nameAttr},
   425  			},
   426  		},
   427  		kind:    kind,
   428  		attrs:   map[string]*bzl.BinaryExpr{"name": nameAttr},
   429  		private: map[string]interface{}{},
   430  	}
   431  	return r
   432  }
   433  
   434  func ruleFromExpr(index int, expr bzl.Expr) *Rule {
   435  	call, ok := expr.(*bzl.CallExpr)
   436  	if !ok {
   437  		return nil
   438  	}
   439  	x, ok := call.X.(*bzl.LiteralExpr)
   440  	if !ok {
   441  		return nil
   442  	}
   443  	kind := x.Token
   444  	var args []bzl.Expr
   445  	attrs := make(map[string]*bzl.BinaryExpr)
   446  	for _, arg := range call.List {
   447  		attr, ok := arg.(*bzl.BinaryExpr)
   448  		if ok && attr.Op == "=" {
   449  			key := attr.X.(*bzl.LiteralExpr) // required by parser
   450  			attrs[key.Token] = attr
   451  		} else {
   452  			args = append(args, arg)
   453  		}
   454  	}
   455  	return &Rule{
   456  		stmt: stmt{
   457  			index: index,
   458  			call:  call,
   459  		},
   460  		kind:    kind,
   461  		args:    args,
   462  		attrs:   attrs,
   463  		private: map[string]interface{}{},
   464  	}
   465  }
   466  
   467  // ShouldKeep returns whether the rule is marked with a "# keep" comment. Rules
   468  // that are kept should not be modified. This does not check whether
   469  // subexpressions within the rule should be kept.
   470  func (r *Rule) ShouldKeep() bool {
   471  	return ShouldKeep(r.call)
   472  }
   473  
   474  func (r *Rule) Kind() string {
   475  	return r.kind
   476  }
   477  
   478  func (r *Rule) SetKind(kind string) {
   479  	r.kind = kind
   480  	r.updated = true
   481  }
   482  
   483  func (r *Rule) Name() string {
   484  	return r.AttrString("name")
   485  }
   486  
   487  func (r *Rule) SetName(name string) {
   488  	r.SetAttr("name", name)
   489  }
   490  
   491  // AttrKeys returns a sorted list of attribute keys used in this rule.
   492  func (r *Rule) AttrKeys() []string {
   493  	keys := make([]string, 0, len(r.attrs))
   494  	for k := range r.attrs {
   495  		keys = append(keys, k)
   496  	}
   497  	sort.SliceStable(keys, func(i, j int) bool {
   498  		if cmp := bt.NamePriority[keys[i]] - bt.NamePriority[keys[j]]; cmp != 0 {
   499  			return cmp < 0
   500  		}
   501  		return keys[i] < keys[j]
   502  	})
   503  	return keys
   504  }
   505  
   506  // Attr returns the value of the named attribute. nil is returned when the
   507  // attribute is not set.
   508  func (r *Rule) Attr(key string) bzl.Expr {
   509  	attr, ok := r.attrs[key]
   510  	if !ok {
   511  		return nil
   512  	}
   513  	return attr.Y
   514  }
   515  
   516  // AttrString returns the value of the named attribute if it is a scalar string.
   517  // "" is returned if the attribute is not set or is not a string.
   518  func (r *Rule) AttrString(key string) string {
   519  	attr, ok := r.attrs[key]
   520  	if !ok {
   521  		return ""
   522  	}
   523  	str, ok := attr.Y.(*bzl.StringExpr)
   524  	if !ok {
   525  		return ""
   526  	}
   527  	return str.Value
   528  }
   529  
   530  // AttrStrings returns the string values of an attribute if it is a list.
   531  // nil is returned if the attribute is not set or is not a list. Non-string
   532  // values within the list won't be returned.
   533  func (r *Rule) AttrStrings(key string) []string {
   534  	attr, ok := r.attrs[key]
   535  	if !ok {
   536  		return nil
   537  	}
   538  	list, ok := attr.Y.(*bzl.ListExpr)
   539  	if !ok {
   540  		return nil
   541  	}
   542  	strs := make([]string, 0, len(list.List))
   543  	for _, e := range list.List {
   544  		if str, ok := e.(*bzl.StringExpr); ok {
   545  			strs = append(strs, str.Value)
   546  		}
   547  	}
   548  	return strs
   549  }
   550  
   551  // DelAttr removes the named attribute from the rule.
   552  func (r *Rule) DelAttr(key string) {
   553  	delete(r.attrs, key)
   554  	r.updated = true
   555  }
   556  
   557  // SetAttr adds or replaces the named attribute with an expression produced
   558  // by ExprFromValue.
   559  func (r *Rule) SetAttr(key string, value interface{}) {
   560  	y := ExprFromValue(value)
   561  	if attr, ok := r.attrs[key]; ok {
   562  		attr.Y = y
   563  	} else {
   564  		r.attrs[key] = &bzl.BinaryExpr{
   565  			X:  &bzl.LiteralExpr{Token: key},
   566  			Y:  y,
   567  			Op: "=",
   568  		}
   569  	}
   570  	r.updated = true
   571  }
   572  
   573  // PrivateAttrKeys returns a sorted list of private attribute names.
   574  func (r *Rule) PrivateAttrKeys() []string {
   575  	keys := make([]string, 0, len(r.private))
   576  	for k := range r.private {
   577  		keys = append(keys, k)
   578  	}
   579  	sort.Strings(keys)
   580  	return keys
   581  }
   582  
   583  // PrivateAttr return the private value associated with a key.
   584  func (r *Rule) PrivateAttr(key string) interface{} {
   585  	return r.private[key]
   586  }
   587  
   588  // SetPrivateAttr associates a value with a key. Unlike SetAttr, this value
   589  // is not converted to a build syntax tree and will not be written to a build
   590  // file.
   591  func (r *Rule) SetPrivateAttr(key string, value interface{}) {
   592  	r.private[key] = value
   593  }
   594  
   595  // Args returns positional arguments passed to a rule.
   596  func (r *Rule) Args() []bzl.Expr {
   597  	return r.args
   598  }
   599  
   600  // Insert marks this statement for insertion at the end of the file. Multiple
   601  // statements will be inserted in the order Insert is called.
   602  func (r *Rule) Insert(f *File) {
   603  	// TODO(jayconrod): should rules always be inserted at the end? Should there
   604  	// be some sort order?
   605  	r.index = len(f.File.Stmt)
   606  	r.inserted = true
   607  	f.Rules = append(f.Rules, r)
   608  }
   609  
   610  // IsEmpty returns true when the rule contains none of the attributes in attrs
   611  // for its kind. attrs should contain attributes that make the rule buildable
   612  // like srcs or deps and not descriptive attributes like name or visibility.
   613  func (r *Rule) IsEmpty(info KindInfo) bool {
   614  	if info.NonEmptyAttrs == nil {
   615  		return false
   616  	}
   617  	for k := range info.NonEmptyAttrs {
   618  		if _, ok := r.attrs[k]; ok {
   619  			return false
   620  		}
   621  	}
   622  	return true
   623  }
   624  
   625  func (r *Rule) IsEmptyOld(attrs map[string]bool) bool {
   626  	if attrs == nil {
   627  		return false
   628  	}
   629  	for k := range attrs {
   630  		if _, ok := r.attrs[k]; ok {
   631  			return false
   632  		}
   633  	}
   634  	return true
   635  }
   636  
   637  func (r *Rule) sync() {
   638  	if !r.updated {
   639  		return
   640  	}
   641  	r.updated = false
   642  
   643  	for _, k := range []string{"srcs", "deps"} {
   644  		if attr, ok := r.attrs[k]; ok {
   645  			bzl.Walk(attr.Y, sortExprLabels)
   646  		}
   647  	}
   648  
   649  	call := r.call
   650  	call.X.(*bzl.LiteralExpr).Token = r.kind
   651  
   652  	list := make([]bzl.Expr, 0, len(r.args)+len(r.attrs))
   653  	list = append(list, r.args...)
   654  	for _, attr := range r.attrs {
   655  		list = append(list, attr)
   656  	}
   657  	sortedAttrs := list[len(r.args):]
   658  	key := func(e bzl.Expr) string { return e.(*bzl.BinaryExpr).X.(*bzl.LiteralExpr).Token }
   659  	sort.SliceStable(sortedAttrs, func(i, j int) bool {
   660  		ki := key(sortedAttrs[i])
   661  		kj := key(sortedAttrs[j])
   662  		if cmp := bt.NamePriority[ki] - bt.NamePriority[kj]; cmp != 0 {
   663  			return cmp < 0
   664  		}
   665  		return ki < kj
   666  	})
   667  
   668  	r.call.List = list
   669  	r.updated = false
   670  }
   671  
   672  // ShouldKeep returns whether e is marked with a "# keep" comment. Kept
   673  // expressions should not be removed or modified.
   674  func ShouldKeep(e bzl.Expr) bool {
   675  	for _, c := range append(e.Comment().Before, e.Comment().Suffix...) {
   676  		text := strings.TrimSpace(strings.TrimPrefix(c.Token, "#"))
   677  		if text == "keep" {
   678  			return true
   679  		}
   680  	}
   681  	return false
   682  }
   683  
   684  type byAttrName []KeyValue
   685  
   686  var _ sort.Interface = byAttrName{}
   687  
   688  func (s byAttrName) Len() int {
   689  	return len(s)
   690  }
   691  
   692  func (s byAttrName) Less(i, j int) bool {
   693  	if cmp := bt.NamePriority[s[i].Key] - bt.NamePriority[s[j].Key]; cmp != 0 {
   694  		return cmp < 0
   695  	}
   696  	return s[i].Key < s[j].Key
   697  }
   698  
   699  func (s byAttrName) Swap(i, j int) {
   700  	s[i], s[j] = s[j], s[i]
   701  }