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 }