github.com/wolfd/bazel-gazelle@v0.14.0/internal/language/go/package.go (about) 1 /* Copyright 2017 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 golang 17 18 import ( 19 "fmt" 20 "log" 21 "path" 22 "sort" 23 "strings" 24 25 "github.com/bazelbuild/bazel-gazelle/internal/config" 26 "github.com/bazelbuild/bazel-gazelle/internal/language/proto" 27 "github.com/bazelbuild/bazel-gazelle/internal/rule" 28 ) 29 30 // goPackage contains metadata for a set of .go and .proto files that can be 31 // used to generate Go rules. 32 type goPackage struct { 33 name, dir, rel string 34 library, binary, test goTarget 35 proto protoTarget 36 hasTestdata bool 37 importPath string 38 } 39 40 // goTarget contains information used to generate an individual Go rule 41 // (library, binary, or test). 42 type goTarget struct { 43 sources, imports, copts, clinkopts platformStringsBuilder 44 cgo bool 45 } 46 47 // protoTarget contains information used to generate a go_proto_library rule. 48 type protoTarget struct { 49 name string 50 sources platformStringsBuilder 51 imports platformStringsBuilder 52 hasServices bool 53 } 54 55 // platformStringsBuilder is used to construct rule.PlatformStrings. Bazel 56 // has some requirements for deps list (a dependency cannot appear in more 57 // than one select expression; dependencies cannot be duplicated), so we need 58 // to build these carefully. 59 type platformStringsBuilder struct { 60 strs map[string]platformStringInfo 61 } 62 63 // platformStringInfo contains information about a single string (source, 64 // import, or option). 65 type platformStringInfo struct { 66 set platformStringSet 67 oss map[string]bool 68 archs map[string]bool 69 platforms map[rule.Platform]bool 70 } 71 72 type platformStringSet int 73 74 const ( 75 genericSet platformStringSet = iota 76 osSet 77 archSet 78 platformSet 79 ) 80 81 // addFile adds the file described by "info" to a target in the package "p" if 82 // the file is buildable. 83 // 84 // "cgo" tells whether any ".go" file in the package contains cgo code. This 85 // affects whether C files are added to targets. 86 // 87 // An error is returned if a file is buildable but invalid (for example, a 88 // test .go file containing cgo code). Files that are not buildable will not 89 // be added to any target (for example, .txt files). 90 func (pkg *goPackage) addFile(c *config.Config, info fileInfo, cgo bool) error { 91 switch { 92 case info.ext == unknownExt || !cgo && (info.ext == cExt || info.ext == csExt): 93 return nil 94 case info.ext == protoExt: 95 if proto.GetProtoConfig(c).Mode == proto.LegacyMode { 96 // Only add files in legacy mode. This is used to generate a filegroup 97 // that contains all protos. In order modes, we get the .proto files 98 // from information emitted by the proto language extension. 99 pkg.proto.addFile(c, info) 100 } 101 case info.isTest: 102 if info.isCgo { 103 return fmt.Errorf("%s: use of cgo in test not supported", info.path) 104 } 105 pkg.test.addFile(c, info) 106 default: 107 pkg.library.addFile(c, info) 108 } 109 110 return nil 111 } 112 113 // isCommand returns true if the package name is "main". 114 func (pkg *goPackage) isCommand() bool { 115 return pkg.name == "main" 116 } 117 118 // isBuildable returns true if anything in the package is buildable. 119 // This is true if the package has Go code that satisfies build constraints 120 // on any platform or has proto files not in legacy mode. 121 func (pkg *goPackage) isBuildable(c *config.Config) bool { 122 return pkg.firstGoFile() != "" || !pkg.proto.sources.isEmpty() 123 } 124 125 // firstGoFile returns the name of a .go file if the package contains at least 126 // one .go file, or "" otherwise. 127 func (pkg *goPackage) firstGoFile() string { 128 goSrcs := []platformStringsBuilder{ 129 pkg.library.sources, 130 pkg.binary.sources, 131 pkg.test.sources, 132 } 133 for _, sb := range goSrcs { 134 if sb.strs != nil { 135 for s := range sb.strs { 136 if strings.HasSuffix(s, ".go") { 137 return s 138 } 139 } 140 } 141 } 142 return "" 143 } 144 145 func (pkg *goPackage) haveCgo() bool { 146 return pkg.library.cgo || pkg.binary.cgo || pkg.test.cgo 147 } 148 149 func (pkg *goPackage) inferImportPath(c *config.Config) error { 150 if pkg.importPath != "" { 151 log.Panic("importPath already set") 152 } 153 gc := getGoConfig(c) 154 if !gc.prefixSet { 155 return fmt.Errorf("%s: go prefix is not set, so importpath can't be determined for rules. Set a prefix with a '# gazelle:prefix' comment or with -go_prefix on the command line", pkg.dir) 156 } 157 pkg.importPath = inferImportPath(gc, pkg.rel) 158 159 if pkg.rel == gc.prefixRel { 160 pkg.importPath = gc.prefix 161 } else { 162 fromPrefixRel := strings.TrimPrefix(pkg.rel, gc.prefixRel+"/") 163 pkg.importPath = path.Join(gc.prefix, fromPrefixRel) 164 } 165 return nil 166 } 167 168 func inferImportPath(gc *goConfig, rel string) string { 169 if rel == gc.prefixRel { 170 return gc.prefix 171 } else { 172 fromPrefixRel := strings.TrimPrefix(rel, gc.prefixRel+"/") 173 return path.Join(gc.prefix, fromPrefixRel) 174 } 175 } 176 177 func goProtoPackageName(pkg proto.Package) string { 178 if value, ok := pkg.Options["go_package"]; ok { 179 if strings.LastIndexByte(value, '/') == -1 { 180 return value 181 } else { 182 if i := strings.LastIndexByte(value, ';'); i != -1 { 183 return value[i+1:] 184 } else { 185 return path.Base(value) 186 } 187 } 188 } 189 return strings.Replace(pkg.Name, ".", "_", -1) 190 } 191 192 func goProtoImportPath(gc *goConfig, pkg proto.Package, rel string) string { 193 if value, ok := pkg.Options["go_package"]; ok { 194 if strings.LastIndexByte(value, '/') == -1 { 195 return inferImportPath(gc, rel) 196 } else if i := strings.LastIndexByte(value, ';'); i != -1 { 197 return value[:i] 198 } else { 199 return value 200 } 201 } 202 return inferImportPath(gc, rel) 203 } 204 205 func (t *goTarget) addFile(c *config.Config, info fileInfo) { 206 t.cgo = t.cgo || info.isCgo 207 add := getPlatformStringsAddFunction(c, info, nil) 208 add(&t.sources, info.name) 209 add(&t.imports, info.imports...) 210 for _, copts := range info.copts { 211 optAdd := add 212 if len(copts.tags) > 0 { 213 optAdd = getPlatformStringsAddFunction(c, info, copts.tags) 214 } 215 optAdd(&t.copts, copts.opts) 216 } 217 for _, clinkopts := range info.clinkopts { 218 optAdd := add 219 if len(clinkopts.tags) > 0 { 220 optAdd = getPlatformStringsAddFunction(c, info, clinkopts.tags) 221 } 222 optAdd(&t.clinkopts, clinkopts.opts) 223 } 224 } 225 226 func protoTargetFromProtoPackage(name string, pkg proto.Package) protoTarget { 227 target := protoTarget{name: name} 228 for f := range pkg.Files { 229 target.sources.addGenericString(f) 230 } 231 for i := range pkg.Imports { 232 target.imports.addGenericString(i) 233 } 234 target.hasServices = pkg.HasServices 235 return target 236 } 237 238 func (t *protoTarget) addFile(c *config.Config, info fileInfo) { 239 t.sources.addGenericString(info.name) 240 for _, imp := range info.imports { 241 t.imports.addGenericString(imp) 242 } 243 t.hasServices = t.hasServices || info.hasServices 244 } 245 246 // getPlatformStringsAddFunction returns a function used to add strings to 247 // a *platformStringsBuilder under the same set of constraints. This is a 248 // performance optimization to avoid evaluating constraints repeatedly. 249 func getPlatformStringsAddFunction(c *config.Config, info fileInfo, cgoTags tagLine) func(sb *platformStringsBuilder, ss ...string) { 250 isOSSpecific, isArchSpecific := isOSArchSpecific(info, cgoTags) 251 252 switch { 253 case !isOSSpecific && !isArchSpecific: 254 if checkConstraints(c, "", "", info.goos, info.goarch, info.tags, cgoTags) { 255 return func(sb *platformStringsBuilder, ss ...string) { 256 for _, s := range ss { 257 sb.addGenericString(s) 258 } 259 } 260 } 261 262 case isOSSpecific && !isArchSpecific: 263 var osMatch []string 264 for _, os := range rule.KnownOSs { 265 if checkConstraints(c, os, "", info.goos, info.goarch, info.tags, cgoTags) { 266 osMatch = append(osMatch, os) 267 } 268 } 269 if len(osMatch) > 0 { 270 return func(sb *platformStringsBuilder, ss ...string) { 271 for _, s := range ss { 272 sb.addOSString(s, osMatch) 273 } 274 } 275 } 276 277 case !isOSSpecific && isArchSpecific: 278 var archMatch []string 279 for _, arch := range rule.KnownArchs { 280 if checkConstraints(c, "", arch, info.goos, info.goarch, info.tags, cgoTags) { 281 archMatch = append(archMatch, arch) 282 } 283 } 284 if len(archMatch) > 0 { 285 return func(sb *platformStringsBuilder, ss ...string) { 286 for _, s := range ss { 287 sb.addArchString(s, archMatch) 288 } 289 } 290 } 291 292 default: 293 var platformMatch []rule.Platform 294 for _, platform := range rule.KnownPlatforms { 295 if checkConstraints(c, platform.OS, platform.Arch, info.goos, info.goarch, info.tags, cgoTags) { 296 platformMatch = append(platformMatch, platform) 297 } 298 } 299 if len(platformMatch) > 0 { 300 return func(sb *platformStringsBuilder, ss ...string) { 301 for _, s := range ss { 302 sb.addPlatformString(s, platformMatch) 303 } 304 } 305 } 306 } 307 308 return func(_ *platformStringsBuilder, _ ...string) {} 309 } 310 311 func (sb *platformStringsBuilder) isEmpty() bool { 312 return sb.strs == nil 313 } 314 315 func (sb *platformStringsBuilder) hasGo() bool { 316 for s := range sb.strs { 317 if strings.HasSuffix(s, ".go") { 318 return true 319 } 320 } 321 return false 322 } 323 324 func (sb *platformStringsBuilder) addGenericString(s string) { 325 if sb.strs == nil { 326 sb.strs = make(map[string]platformStringInfo) 327 } 328 sb.strs[s] = platformStringInfo{set: genericSet} 329 } 330 331 func (sb *platformStringsBuilder) addOSString(s string, oss []string) { 332 if sb.strs == nil { 333 sb.strs = make(map[string]platformStringInfo) 334 } 335 si, ok := sb.strs[s] 336 if !ok { 337 si.set = osSet 338 si.oss = make(map[string]bool) 339 } 340 switch si.set { 341 case genericSet: 342 return 343 case osSet: 344 for _, os := range oss { 345 si.oss[os] = true 346 } 347 default: 348 si.convertToPlatforms() 349 for _, os := range oss { 350 for _, arch := range rule.KnownOSArchs[os] { 351 si.platforms[rule.Platform{OS: os, Arch: arch}] = true 352 } 353 } 354 } 355 sb.strs[s] = si 356 } 357 358 func (sb *platformStringsBuilder) addArchString(s string, archs []string) { 359 if sb.strs == nil { 360 sb.strs = make(map[string]platformStringInfo) 361 } 362 si, ok := sb.strs[s] 363 if !ok { 364 si.set = archSet 365 si.archs = make(map[string]bool) 366 } 367 switch si.set { 368 case genericSet: 369 return 370 case archSet: 371 for _, arch := range archs { 372 si.archs[arch] = true 373 } 374 default: 375 si.convertToPlatforms() 376 for _, arch := range archs { 377 for _, os := range rule.KnownArchOSs[arch] { 378 si.platforms[rule.Platform{OS: os, Arch: arch}] = true 379 } 380 } 381 } 382 sb.strs[s] = si 383 } 384 385 func (sb *platformStringsBuilder) addPlatformString(s string, platforms []rule.Platform) { 386 if sb.strs == nil { 387 sb.strs = make(map[string]platformStringInfo) 388 } 389 si, ok := sb.strs[s] 390 if !ok { 391 si.set = platformSet 392 si.platforms = make(map[rule.Platform]bool) 393 } 394 switch si.set { 395 case genericSet: 396 return 397 default: 398 si.convertToPlatforms() 399 for _, p := range platforms { 400 si.platforms[p] = true 401 } 402 } 403 sb.strs[s] = si 404 } 405 406 func (sb *platformStringsBuilder) build() rule.PlatformStrings { 407 var ps rule.PlatformStrings 408 for s, si := range sb.strs { 409 switch si.set { 410 case genericSet: 411 ps.Generic = append(ps.Generic, s) 412 case osSet: 413 if ps.OS == nil { 414 ps.OS = make(map[string][]string) 415 } 416 for os := range si.oss { 417 ps.OS[os] = append(ps.OS[os], s) 418 } 419 case archSet: 420 if ps.Arch == nil { 421 ps.Arch = make(map[string][]string) 422 } 423 for arch := range si.archs { 424 ps.Arch[arch] = append(ps.Arch[arch], s) 425 } 426 case platformSet: 427 if ps.Platform == nil { 428 ps.Platform = make(map[rule.Platform][]string) 429 } 430 for p := range si.platforms { 431 ps.Platform[p] = append(ps.Platform[p], s) 432 } 433 } 434 } 435 sort.Strings(ps.Generic) 436 if ps.OS != nil { 437 for _, ss := range ps.OS { 438 sort.Strings(ss) 439 } 440 } 441 if ps.Arch != nil { 442 for _, ss := range ps.Arch { 443 sort.Strings(ss) 444 } 445 } 446 if ps.Platform != nil { 447 for _, ss := range ps.Platform { 448 sort.Strings(ss) 449 } 450 } 451 return ps 452 } 453 454 func (sb *platformStringsBuilder) buildFlat() []string { 455 strs := make([]string, 0, len(sb.strs)) 456 for s := range sb.strs { 457 strs = append(strs, s) 458 } 459 sort.Strings(strs) 460 return strs 461 } 462 463 func (si *platformStringInfo) convertToPlatforms() { 464 switch si.set { 465 case genericSet: 466 log.Panic("cannot convert generic string to platforms") 467 case platformSet: 468 return 469 case osSet: 470 si.set = platformSet 471 si.platforms = make(map[rule.Platform]bool) 472 for os := range si.oss { 473 for _, arch := range rule.KnownOSArchs[os] { 474 si.platforms[rule.Platform{OS: os, Arch: arch}] = true 475 } 476 } 477 si.oss = nil 478 case archSet: 479 si.set = platformSet 480 si.platforms = make(map[rule.Platform]bool) 481 for arch := range si.archs { 482 for _, os := range rule.KnownArchOSs[arch] { 483 si.platforms[rule.Platform{OS: os, Arch: arch}] = true 484 } 485 } 486 si.archs = nil 487 } 488 }