github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/internal/rules/generator.go (about)

     1  /* Copyright 2016 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 rules
    17  
    18  import (
    19  	"fmt"
    20  	"log"
    21  	"path"
    22  	"strings"
    23  
    24  	"github.com/bazelbuild/bazel-gazelle/internal/config"
    25  	"github.com/bazelbuild/bazel-gazelle/internal/label"
    26  	"github.com/bazelbuild/bazel-gazelle/internal/packages"
    27  	bf "github.com/bazelbuild/buildtools/build"
    28  )
    29  
    30  // NewGenerator returns a new instance of Generator.
    31  // "oldFile" is the existing build file. May be nil.
    32  func NewGenerator(c *config.Config, l *label.Labeler, oldFile *bf.File) *Generator {
    33  	shouldSetVisibility := oldFile == nil || !hasDefaultVisibility(oldFile)
    34  	return &Generator{c: c, l: l, shouldSetVisibility: shouldSetVisibility}
    35  }
    36  
    37  // Generator generates Bazel build rules for Go build targets.
    38  type Generator struct {
    39  	c                   *config.Config
    40  	l                   *label.Labeler
    41  	shouldSetVisibility bool
    42  }
    43  
    44  // GenerateRules generates a list of rules for targets in "pkg". It also returns
    45  // a list of empty rules that may be deleted from an existing file.
    46  func (g *Generator) GenerateRules(pkg *packages.Package) (rules []bf.Expr, empty []bf.Expr, err error) {
    47  	var rs []bf.Expr
    48  
    49  	protoLibName, protoRules := g.generateProto(pkg)
    50  	rs = append(rs, protoRules...)
    51  
    52  	libName, libRule := g.generateLib(pkg, protoLibName)
    53  	rs = append(rs, libRule)
    54  
    55  	rs = append(rs,
    56  		g.generateBin(pkg, libName),
    57  		g.generateTest(pkg, libName, false),
    58  		g.generateTest(pkg, "", true))
    59  
    60  	for _, r := range rs {
    61  		if isEmpty(r) {
    62  			empty = append(empty, r)
    63  		} else {
    64  			rules = append(rules, r)
    65  		}
    66  	}
    67  
    68  	return rules, empty, nil
    69  }
    70  
    71  func (g *Generator) generateProto(pkg *packages.Package) (string, []bf.Expr) {
    72  	if g.c.ProtoMode == config.DisableProtoMode {
    73  		// Don't create or delete proto rules in this mode. Any existing rules
    74  		// are likely hand-written.
    75  		return "", nil
    76  	}
    77  
    78  	filegroupName := config.DefaultProtosName
    79  	protoName := g.l.ProtoLabel(pkg.Rel, pkg.Name).Name
    80  	goProtoName := g.l.GoProtoLabel(pkg.Rel, pkg.Name).Name
    81  
    82  	if g.c.ProtoMode == config.LegacyProtoMode {
    83  		if !pkg.Proto.HasProto() {
    84  			return "", []bf.Expr{EmptyRule("filegroup", filegroupName)}
    85  		}
    86  		attrs := []KeyValue{
    87  			{Key: "name", Value: filegroupName},
    88  			{Key: "srcs", Value: pkg.Proto.Sources},
    89  		}
    90  		if g.shouldSetVisibility {
    91  			attrs = append(attrs, KeyValue{"visibility", []string{checkInternalVisibility(pkg.Rel, "//visibility:public")}})
    92  		}
    93  		return "", []bf.Expr{NewRule("filegroup", attrs)}
    94  	}
    95  
    96  	if !pkg.Proto.HasProto() {
    97  		return "", []bf.Expr{
    98  			EmptyRule("filegroup", filegroupName),
    99  			EmptyRule("proto_library", protoName),
   100  			EmptyRule("go_proto_library", goProtoName),
   101  		}
   102  	}
   103  
   104  	var rules []bf.Expr
   105  	visibility := []string{checkInternalVisibility(pkg.Rel, "//visibility:public")}
   106  	protoAttrs := []KeyValue{
   107  		{"name", protoName},
   108  		{"srcs", pkg.Proto.Sources},
   109  	}
   110  	if g.shouldSetVisibility {
   111  		protoAttrs = append(protoAttrs, KeyValue{"visibility", visibility})
   112  	}
   113  	imports := pkg.Proto.Imports
   114  	if !imports.IsEmpty() {
   115  		protoAttrs = append(protoAttrs, KeyValue{config.GazelleImportsKey, imports})
   116  	}
   117  	rules = append(rules, NewRule("proto_library", protoAttrs))
   118  
   119  	goProtoAttrs := []KeyValue{
   120  		{"name", goProtoName},
   121  		{"proto", ":" + protoName},
   122  		{"importpath", pkg.ImportPath},
   123  	}
   124  	if pkg.Proto.HasServices {
   125  		goProtoAttrs = append(goProtoAttrs, KeyValue{"compilers", []string{"@io_bazel_rules_go//proto:go_grpc"}})
   126  	}
   127  	if g.shouldSetVisibility {
   128  		goProtoAttrs = append(goProtoAttrs, KeyValue{"visibility", visibility})
   129  	}
   130  	if !imports.IsEmpty() {
   131  		goProtoAttrs = append(goProtoAttrs, KeyValue{config.GazelleImportsKey, imports})
   132  	}
   133  	rules = append(rules, NewRule("go_proto_library", goProtoAttrs))
   134  
   135  	return goProtoName, rules
   136  }
   137  
   138  func (g *Generator) generateBin(pkg *packages.Package, library string) bf.Expr {
   139  	name := g.l.BinaryLabel(pkg.Rel).Name
   140  	if !pkg.IsCommand() || pkg.Binary.Sources.IsEmpty() && library == "" {
   141  		return EmptyRule("go_binary", name)
   142  	}
   143  	visibility := checkInternalVisibility(pkg.Rel, "//visibility:public")
   144  	attrs := g.commonAttrs(pkg.Rel, name, visibility, pkg.Binary)
   145  	if library != "" {
   146  		attrs = append(attrs, KeyValue{"embed", []string{":" + library}})
   147  	}
   148  	return NewRule("go_binary", attrs)
   149  }
   150  
   151  func (g *Generator) generateLib(pkg *packages.Package, goProtoName string) (string, *bf.CallExpr) {
   152  	name := g.l.LibraryLabel(pkg.Rel).Name
   153  	if !pkg.Library.HasGo() && goProtoName == "" {
   154  		return "", EmptyRule("go_library", name)
   155  	}
   156  	var visibility string
   157  	if pkg.IsCommand() {
   158  		// Libraries made for a go_binary should not be exposed to the public.
   159  		visibility = "//visibility:private"
   160  	} else {
   161  		visibility = checkInternalVisibility(pkg.Rel, "//visibility:public")
   162  	}
   163  
   164  	attrs := g.commonAttrs(pkg.Rel, name, visibility, pkg.Library)
   165  	attrs = append(attrs, KeyValue{"importpath", pkg.ImportPath})
   166  	if goProtoName != "" {
   167  		attrs = append(attrs, KeyValue{"embed", []string{":" + goProtoName}})
   168  	}
   169  
   170  	rule := NewRule("go_library", attrs)
   171  	return name, rule
   172  }
   173  
   174  // hasDefaultVisibility returns whether oldFile contains a "package" rule with
   175  // a "default_visibility" attribute. Rules generated by Gazelle should not
   176  // have their own visibility attributes if this is the case.
   177  func hasDefaultVisibility(oldFile *bf.File) bool {
   178  	for _, s := range oldFile.Stmt {
   179  		c, ok := s.(*bf.CallExpr)
   180  		if !ok {
   181  			continue
   182  		}
   183  		r := bf.Rule{Call: c}
   184  		if r.Kind() == "package" && r.Attr("default_visibility") != nil {
   185  			return true
   186  		}
   187  	}
   188  	return false
   189  }
   190  
   191  // checkInternalVisibility overrides the given visibility if the package is
   192  // internal.
   193  func checkInternalVisibility(rel, visibility string) string {
   194  	if i := strings.LastIndex(rel, "/internal/"); i >= 0 {
   195  		visibility = fmt.Sprintf("//%s:__subpackages__", rel[:i])
   196  	} else if strings.HasPrefix(rel, "internal/") {
   197  		visibility = "//:__subpackages__"
   198  	}
   199  	return visibility
   200  }
   201  
   202  func (g *Generator) generateTest(pkg *packages.Package, library string, isXTest bool) bf.Expr {
   203  	name := g.l.TestLabel(pkg.Rel, isXTest).Name
   204  	target := pkg.Test
   205  	if isXTest {
   206  		target = pkg.XTest
   207  	}
   208  	if !target.HasGo() {
   209  		return EmptyRule("go_test", name)
   210  	}
   211  	attrs := g.commonAttrs(pkg.Rel, name, "", target)
   212  	if library != "" {
   213  		attrs = append(attrs, KeyValue{"embed", []string{":" + library}})
   214  	}
   215  	if pkg.HasTestdata {
   216  		glob := GlobValue{Patterns: []string{"testdata/**"}}
   217  		attrs = append(attrs, KeyValue{"data", glob})
   218  	}
   219  	return NewRule("go_test", attrs)
   220  }
   221  
   222  func (g *Generator) commonAttrs(pkgRel, name, visibility string, target packages.GoTarget) []KeyValue {
   223  	attrs := []KeyValue{{"name", name}}
   224  	if !target.Sources.IsEmpty() {
   225  		attrs = append(attrs, KeyValue{"srcs", target.Sources})
   226  	}
   227  	if target.Cgo {
   228  		attrs = append(attrs, KeyValue{"cgo", true})
   229  	}
   230  	if !target.CLinkOpts.IsEmpty() {
   231  		attrs = append(attrs, KeyValue{"clinkopts", g.options(target.CLinkOpts, pkgRel)})
   232  	}
   233  	if !target.COpts.IsEmpty() {
   234  		attrs = append(attrs, KeyValue{"copts", g.options(target.COpts, pkgRel)})
   235  	}
   236  	if g.shouldSetVisibility && visibility != "" {
   237  		attrs = append(attrs, KeyValue{"visibility", []string{visibility}})
   238  	}
   239  	imports := target.Imports
   240  	if !imports.IsEmpty() {
   241  		attrs = append(attrs, KeyValue{config.GazelleImportsKey, imports})
   242  	}
   243  	return attrs
   244  }
   245  
   246  var (
   247  	// shortOptPrefixes are strings that come at the beginning of an option
   248  	// argument that includes a path, e.g., -Ifoo/bar.
   249  	shortOptPrefixes = []string{"-I", "-L", "-F"}
   250  
   251  	// longOptPrefixes are separate arguments that come before a path argument,
   252  	// e.g., -iquote foo/bar.
   253  	longOptPrefixes = []string{"-I", "-L", "-F", "-iquote", "-isystem"}
   254  )
   255  
   256  // options transforms package-relative paths in cgo options into repository-
   257  // root-relative paths that Bazel can understand. For example, if a cgo file
   258  // in //foo declares an include flag in its copts: "-Ibar", this method
   259  // will transform that flag into "-Ifoo/bar".
   260  func (g *Generator) options(opts packages.PlatformStrings, pkgRel string) packages.PlatformStrings {
   261  	fixPath := func(opt string) string {
   262  		if strings.HasPrefix(opt, "/") {
   263  			return opt
   264  		}
   265  		return path.Clean(path.Join(pkgRel, opt))
   266  	}
   267  
   268  	fixGroups := func(groups []string) ([]string, error) {
   269  		fixedGroups := make([]string, len(groups))
   270  		for i, group := range groups {
   271  			opts := strings.Split(group, packages.OptSeparator)
   272  			fixedOpts := make([]string, len(opts))
   273  			isPath := false
   274  			for j, opt := range opts {
   275  				if isPath {
   276  					opt = fixPath(opt)
   277  					isPath = false
   278  					goto next
   279  				}
   280  
   281  				for _, short := range shortOptPrefixes {
   282  					if strings.HasPrefix(opt, short) && len(opt) > len(short) {
   283  						opt = short + fixPath(opt[len(short):])
   284  						goto next
   285  					}
   286  				}
   287  
   288  				for _, long := range longOptPrefixes {
   289  					if opt == long {
   290  						isPath = true
   291  						goto next
   292  					}
   293  				}
   294  
   295  			next:
   296  				fixedOpts[j] = escapeOption(opt)
   297  			}
   298  			fixedGroups[i] = strings.Join(fixedOpts, " ")
   299  		}
   300  
   301  		return fixedGroups, nil
   302  	}
   303  
   304  	opts, errs := opts.MapSlice(fixGroups)
   305  	if errs != nil {
   306  		log.Panicf("unexpected error when transforming options with pkg %q: %v", pkgRel, errs)
   307  	}
   308  	return opts
   309  }
   310  
   311  func escapeOption(opt string) string {
   312  	return strings.NewReplacer(
   313  		`\`, `\\`,
   314  		`'`, `\'`,
   315  		`"`, `\"`,
   316  		` `, `\ `,
   317  		"\t", "\\\t",
   318  		"\n", "\\\n",
   319  		"\r", "\\\r",
   320  	).Replace(opt)
   321  }
   322  
   323  func isEmpty(r bf.Expr) bool {
   324  	c, ok := r.(*bf.CallExpr)
   325  	return ok && len(c.List) == 1 // name
   326  }