generate.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. /* Copyright 2018 The Bazel Authors. All rights reserved.
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package golang
  13. import (
  14. "fmt"
  15. "go/build"
  16. "log"
  17. "path"
  18. "path/filepath"
  19. "sort"
  20. "strings"
  21. "sync"
  22. "github.com/bazelbuild/bazel-gazelle/internal/config"
  23. "github.com/bazelbuild/bazel-gazelle/internal/language/proto"
  24. "github.com/bazelbuild/bazel-gazelle/internal/pathtools"
  25. "github.com/bazelbuild/bazel-gazelle/internal/rule"
  26. )
  27. func (gl *goLang) GenerateRules(c *config.Config, dir, rel string, f *rule.File, subdirs, regularFiles, genFiles []string, otherEmpty, otherGen []*rule.Rule) (empty, gen []*rule.Rule) {
  28. // Extract information about proto files. We need this to exclude .pb.go
  29. // files and generate go_proto_library rules.
  30. gc := getGoConfig(c)
  31. pc := proto.GetProtoConfig(c)
  32. var protoRuleNames []string
  33. protoPackages := make(map[string]proto.Package)
  34. protoFileInfo := make(map[string]proto.FileInfo)
  35. for _, r := range otherGen {
  36. if r.Kind() != "proto_library" {
  37. continue
  38. }
  39. pkg := r.PrivateAttr(proto.PackageKey).(proto.Package)
  40. protoPackages[r.Name()] = pkg
  41. for name, info := range pkg.Files {
  42. protoFileInfo[name] = info
  43. }
  44. protoRuleNames = append(protoRuleNames, r.Name())
  45. }
  46. sort.Strings(protoRuleNames)
  47. var emptyProtoRuleNames []string
  48. for _, r := range otherEmpty {
  49. if r.Kind() == "proto_library" {
  50. emptyProtoRuleNames = append(emptyProtoRuleNames, r.Name())
  51. }
  52. }
  53. // If proto rule generation is enabled, exclude .pb.go files that correspond
  54. // to any .proto files present.
  55. if !pc.Mode.ShouldIncludePregeneratedFiles() {
  56. keep := func(f string) bool {
  57. if strings.HasSuffix(f, ".pb.go") {
  58. _, ok := protoFileInfo[strings.TrimSuffix(f, ".pb.go")+".proto"]
  59. return !ok
  60. }
  61. return true
  62. }
  63. filterFiles(&regularFiles, keep)
  64. filterFiles(&genFiles, keep)
  65. }
  66. // Split regular files into files which can determine the package name and
  67. // import path and other files.
  68. var goFiles, otherFiles []string
  69. for _, f := range regularFiles {
  70. if strings.HasSuffix(f, ".go") {
  71. goFiles = append(goFiles, f)
  72. } else {
  73. otherFiles = append(otherFiles, f)
  74. }
  75. }
  76. // Look for a subdirectory named testdata. Only treat it as data if it does
  77. // not contain a buildable package.
  78. var hasTestdata bool
  79. for _, sub := range subdirs {
  80. if sub == "testdata" {
  81. hasTestdata = !gl.goPkgRels[path.Join(rel, "testdata")]
  82. break
  83. }
  84. }
  85. // Build a set of packages from files in this directory.
  86. goPackageMap, goFilesWithUnknownPackage := buildPackages(c, dir, rel, goFiles, hasTestdata)
  87. // Select a package to generate rules for. If there is no package, create
  88. // an empty package so we can generate empty rules.
  89. var protoName string
  90. pkg, err := selectPackage(c, dir, goPackageMap)
  91. if err != nil {
  92. if _, ok := err.(*build.NoGoError); ok {
  93. if len(protoPackages) == 1 {
  94. for name, ppkg := range protoPackages {
  95. pkg = &goPackage{
  96. name: goProtoPackageName(ppkg),
  97. importPath: goProtoImportPath(gc, ppkg, rel),
  98. proto: protoTargetFromProtoPackage(name, ppkg),
  99. }
  100. protoName = name
  101. break
  102. }
  103. } else {
  104. pkg = emptyPackage(c, dir, rel)
  105. }
  106. } else {
  107. log.Print(err)
  108. }
  109. }
  110. // Try to link the selected package with a proto package.
  111. if pkg != nil {
  112. if pkg.importPath == "" {
  113. if err := pkg.inferImportPath(c); err != nil && pkg.firstGoFile() != "" {
  114. inferImportPathErrorOnce.Do(func() { log.Print(err) })
  115. }
  116. }
  117. for _, name := range protoRuleNames {
  118. ppkg := protoPackages[name]
  119. if pkg.importPath == goProtoImportPath(gc, ppkg, rel) {
  120. protoName = name
  121. pkg.proto = protoTargetFromProtoPackage(name, ppkg)
  122. break
  123. }
  124. }
  125. }
  126. // Generate rules for proto packages. These should come before the other
  127. // Go rules.
  128. g := newGenerator(c, f, rel)
  129. var rules []*rule.Rule
  130. var protoEmbed string
  131. for _, name := range protoRuleNames {
  132. ppkg := protoPackages[name]
  133. var rs []*rule.Rule
  134. if name == protoName {
  135. protoEmbed, rs = g.generateProto(pc.Mode, pkg.proto, pkg.importPath)
  136. } else {
  137. target := protoTargetFromProtoPackage(name, ppkg)
  138. importPath := goProtoImportPath(gc, ppkg, rel)
  139. _, rs = g.generateProto(pc.Mode, target, importPath)
  140. }
  141. rules = append(rules, rs...)
  142. }
  143. for _, name := range emptyProtoRuleNames {
  144. goProtoName := strings.TrimSuffix(name, "_proto") + "_go_proto"
  145. empty = append(empty, rule.NewRule("go_proto_library", goProtoName))
  146. }
  147. if pkg != nil && pc.Mode == proto.PackageMode && pkg.firstGoFile() == "" {
  148. // In proto package mode, don't generate a go_library embedding a
  149. // go_proto_library unless there are actually go files.
  150. protoEmbed = ""
  151. }
  152. // Complete the Go package and generate rules for that.
  153. if pkg != nil {
  154. // Add files with unknown packages. This happens when there are parse
  155. // or I/O errors. We should keep the file in the srcs list and let the
  156. // compiler deal with the error.
  157. cgo := pkg.haveCgo()
  158. for _, info := range goFilesWithUnknownPackage {
  159. if err := pkg.addFile(c, info, cgo); err != nil {
  160. log.Print(err)
  161. }
  162. }
  163. // Process the other static files.
  164. for _, file := range otherFiles {
  165. info := otherFileInfo(filepath.Join(dir, file))
  166. if err := pkg.addFile(c, info, cgo); err != nil {
  167. log.Print(err)
  168. }
  169. }
  170. // Process generated files. Note that generated files may have the same names
  171. // as static files. Bazel will use the generated files, but we will look at
  172. // the content of static files, assuming they will be the same.
  173. regularFileSet := make(map[string]bool)
  174. for _, f := range regularFiles {
  175. regularFileSet[f] = true
  176. }
  177. for _, f := range genFiles {
  178. if regularFileSet[f] {
  179. continue
  180. }
  181. info := fileNameInfo(filepath.Join(dir, f))
  182. if err := pkg.addFile(c, info, cgo); err != nil {
  183. log.Print(err)
  184. }
  185. }
  186. // Generate Go rules.
  187. if protoName == "" {
  188. // Empty proto rules for deletion.
  189. _, rs := g.generateProto(pc.Mode, pkg.proto, pkg.importPath)
  190. rules = append(rules, rs...)
  191. }
  192. lib := g.generateLib(pkg, protoEmbed)
  193. var libName string
  194. if !lib.IsEmpty(goKinds[lib.Kind()]) {
  195. libName = lib.Name()
  196. }
  197. rules = append(rules, lib)
  198. rules = append(rules,
  199. g.generateBin(pkg, libName),
  200. g.generateTest(pkg, libName))
  201. }
  202. for _, r := range rules {
  203. if r.IsEmpty(goKinds[r.Kind()]) {
  204. empty = append(empty, r)
  205. } else {
  206. gen = append(gen, r)
  207. }
  208. }
  209. if f != nil || len(gen) > 0 {
  210. gl.goPkgRels[rel] = true
  211. } else {
  212. for _, sub := range subdirs {
  213. if gl.goPkgRels[path.Join(rel, sub)] {
  214. gl.goPkgRels[rel] = true
  215. break
  216. }
  217. }
  218. }
  219. return empty, gen
  220. }
  221. func filterFiles(files *[]string, pred func(string) bool) {
  222. w := 0
  223. for r := 0; r < len(*files); r++ {
  224. f := (*files)[r]
  225. if pred(f) {
  226. (*files)[w] = f
  227. w++
  228. }
  229. }
  230. *files = (*files)[:w]
  231. }
  232. func buildPackages(c *config.Config, dir, rel string, goFiles []string, hasTestdata bool) (packageMap map[string]*goPackage, goFilesWithUnknownPackage []fileInfo) {
  233. // Process .go and .proto files first, since these determine the package name.
  234. packageMap = make(map[string]*goPackage)
  235. for _, f := range goFiles {
  236. path := filepath.Join(dir, f)
  237. info := goFileInfo(path, rel)
  238. if info.packageName == "" {
  239. goFilesWithUnknownPackage = append(goFilesWithUnknownPackage, info)
  240. continue
  241. }
  242. if info.packageName == "documentation" {
  243. // go/build ignores this package
  244. continue
  245. }
  246. if _, ok := packageMap[info.packageName]; !ok {
  247. packageMap[info.packageName] = &goPackage{
  248. name: info.packageName,
  249. dir: dir,
  250. rel: rel,
  251. hasTestdata: hasTestdata,
  252. }
  253. }
  254. if err := packageMap[info.packageName].addFile(c, info, false); err != nil {
  255. log.Print(err)
  256. }
  257. }
  258. return packageMap, goFilesWithUnknownPackage
  259. }
  260. var inferImportPathErrorOnce sync.Once
  261. // selectPackages selects one Go packages out of the buildable packages found
  262. // in a directory. If multiple packages are found, it returns the package
  263. // whose name matches the directory if such a package exists.
  264. func selectPackage(c *config.Config, dir string, packageMap map[string]*goPackage) (*goPackage, error) {
  265. buildablePackages := make(map[string]*goPackage)
  266. for name, pkg := range packageMap {
  267. if pkg.isBuildable(c) {
  268. buildablePackages[name] = pkg
  269. }
  270. }
  271. if len(buildablePackages) == 0 {
  272. return nil, &build.NoGoError{Dir: dir}
  273. }
  274. if len(buildablePackages) == 1 {
  275. for _, pkg := range buildablePackages {
  276. return pkg, nil
  277. }
  278. }
  279. if pkg, ok := buildablePackages[defaultPackageName(c, dir)]; ok {
  280. return pkg, nil
  281. }
  282. err := &build.MultiplePackageError{Dir: dir}
  283. for name, pkg := range buildablePackages {
  284. // Add the first file for each package for the error message.
  285. // Error() method expects these lists to be the same length. File
  286. // lists must be non-empty. These lists are only created by
  287. // buildPackage for packages with .go files present.
  288. err.Packages = append(err.Packages, name)
  289. err.Files = append(err.Files, pkg.firstGoFile())
  290. }
  291. return nil, err
  292. }
  293. func emptyPackage(c *config.Config, dir, rel string) *goPackage {
  294. pkg := &goPackage{
  295. name: defaultPackageName(c, dir),
  296. dir: dir,
  297. rel: rel,
  298. }
  299. pkg.inferImportPath(c)
  300. return pkg
  301. }
  302. func defaultPackageName(c *config.Config, rel string) string {
  303. gc := getGoConfig(c)
  304. return pathtools.RelBaseName(rel, gc.prefix, "")
  305. }
  306. // hasDefaultVisibility returns whether oldFile contains a "package" rule with
  307. // a "default_visibility" attribute. Rules generated by Gazelle should not
  308. // have their own visibility attributes if this is the case.
  309. func hasDefaultVisibility(oldFile *rule.File) bool {
  310. for _, r := range oldFile.Rules {
  311. if r.Kind() == "package" && r.Attr("default_visibility") != nil {
  312. return true
  313. }
  314. }
  315. return false
  316. }
  317. // checkInternalVisibility overrides the given visibility if the package is
  318. // internal.
  319. func checkInternalVisibility(rel, visibility string) string {
  320. if i := strings.LastIndex(rel, "/internal/"); i >= 0 {
  321. visibility = fmt.Sprintf("//%s:__subpackages__", rel[:i])
  322. } else if strings.HasPrefix(rel, "internal/") {
  323. visibility = "//:__subpackages__"
  324. }
  325. return visibility
  326. }
  327. type generator struct {
  328. c *config.Config
  329. rel string
  330. shouldSetVisibility bool
  331. }
  332. func newGenerator(c *config.Config, f *rule.File, rel string) *generator {
  333. shouldSetVisibility := f == nil || !hasDefaultVisibility(f)
  334. return &generator{c: c, rel: rel, shouldSetVisibility: shouldSetVisibility}
  335. }
  336. func (g *generator) generateProto(mode proto.Mode, target protoTarget, importPath string) (string, []*rule.Rule) {
  337. if !mode.ShouldGenerateRules() && mode != proto.LegacyMode {
  338. // Don't create or delete proto rules in this mode. Any existing rules
  339. // are likely hand-written.
  340. return "", nil
  341. }
  342. filegroupName := config.DefaultProtosName
  343. protoName := target.name
  344. if protoName == "" {
  345. importPath := inferImportPath(getGoConfig(g.c), g.rel)
  346. protoName = proto.RuleName(importPath)
  347. }
  348. goProtoName := strings.TrimSuffix(protoName, "_proto") + "_go_proto"
  349. visibility := []string{checkInternalVisibility(g.rel, "//visibility:public")}
  350. if mode == proto.LegacyMode {
  351. filegroup := rule.NewRule("filegroup", filegroupName)
  352. if target.sources.isEmpty() {
  353. return "", []*rule.Rule{filegroup}
  354. }
  355. filegroup.SetAttr("srcs", target.sources.build())
  356. if g.shouldSetVisibility {
  357. filegroup.SetAttr("visibility", visibility)
  358. }
  359. return "", []*rule.Rule{filegroup}
  360. }
  361. if target.sources.isEmpty() {
  362. return "", []*rule.Rule{
  363. rule.NewRule("filegroup", filegroupName),
  364. rule.NewRule("go_proto_library", goProtoName),
  365. }
  366. }
  367. goProtoLibrary := rule.NewRule("go_proto_library", goProtoName)
  368. goProtoLibrary.SetAttr("proto", ":"+protoName)
  369. g.setImportAttrs(goProtoLibrary, importPath)
  370. if target.hasServices {
  371. goProtoLibrary.SetAttr("compilers", []string{"@io_bazel_rules_go//proto:go_grpc"})
  372. }
  373. if g.shouldSetVisibility {
  374. goProtoLibrary.SetAttr("visibility", visibility)
  375. }
  376. goProtoLibrary.SetPrivateAttr(config.GazelleImportsKey, target.imports.build())
  377. return goProtoName, []*rule.Rule{goProtoLibrary}
  378. }
  379. func (g *generator) generateLib(pkg *goPackage, embed string) *rule.Rule {
  380. goLibrary := rule.NewRule("go_library", config.DefaultLibName)
  381. if !pkg.library.sources.hasGo() && embed == "" {
  382. return goLibrary // empty
  383. }
  384. var visibility string
  385. if pkg.isCommand() {
  386. // Libraries made for a go_binary should not be exposed to the public.
  387. visibility = "//visibility:private"
  388. } else {
  389. visibility = checkInternalVisibility(pkg.rel, "//visibility:public")
  390. }
  391. g.setCommonAttrs(goLibrary, pkg.rel, visibility, pkg.library, embed)
  392. g.setImportAttrs(goLibrary, pkg.importPath)
  393. return goLibrary
  394. }
  395. func (g *generator) generateBin(pkg *goPackage, library string) *rule.Rule {
  396. name := pathtools.RelBaseName(pkg.rel, getGoConfig(g.c).prefix, g.c.RepoRoot)
  397. goBinary := rule.NewRule("go_binary", name)
  398. if !pkg.isCommand() || pkg.binary.sources.isEmpty() && library == "" {
  399. return goBinary // empty
  400. }
  401. visibility := checkInternalVisibility(pkg.rel, "//visibility:public")
  402. g.setCommonAttrs(goBinary, pkg.rel, visibility, pkg.binary, library)
  403. return goBinary
  404. }
  405. func (g *generator) generateTest(pkg *goPackage, library string) *rule.Rule {
  406. goTest := rule.NewRule("go_test", config.DefaultTestName)
  407. if !pkg.test.sources.hasGo() {
  408. return goTest // empty
  409. }
  410. g.setCommonAttrs(goTest, pkg.rel, "", pkg.test, library)
  411. if pkg.hasTestdata {
  412. goTest.SetAttr("data", rule.GlobValue{Patterns: []string{"testdata/**"}})
  413. }
  414. return goTest
  415. }
  416. func (g *generator) setCommonAttrs(r *rule.Rule, pkgRel, visibility string, target goTarget, embed string) {
  417. if !target.sources.isEmpty() {
  418. r.SetAttr("srcs", target.sources.buildFlat())
  419. }
  420. if target.cgo {
  421. r.SetAttr("cgo", true)
  422. }
  423. if !target.clinkopts.isEmpty() {
  424. r.SetAttr("clinkopts", g.options(target.clinkopts.build(), pkgRel))
  425. }
  426. if !target.copts.isEmpty() {
  427. r.SetAttr("copts", g.options(target.copts.build(), pkgRel))
  428. }
  429. if g.shouldSetVisibility && visibility != "" {
  430. r.SetAttr("visibility", []string{visibility})
  431. }
  432. if embed != "" {
  433. r.SetAttr("embed", []string{":" + embed})
  434. }
  435. r.SetPrivateAttr(config.GazelleImportsKey, target.imports.build())
  436. }
  437. func (g *generator) setImportAttrs(r *rule.Rule, importPath string) {
  438. r.SetAttr("importpath", importPath)
  439. goConf := getGoConfig(g.c)
  440. if goConf.importMapPrefix != "" {
  441. fromPrefixRel := pathtools.TrimPrefix(g.rel, goConf.importMapPrefixRel)
  442. importMap := path.Join(goConf.importMapPrefix, fromPrefixRel)
  443. if importMap != importPath {
  444. r.SetAttr("importmap", importMap)
  445. }
  446. }
  447. }
  448. var (
  449. // shortOptPrefixes are strings that come at the beginning of an option
  450. // argument that includes a path, e.g., -Ifoo/bar.
  451. shortOptPrefixes = []string{"-I", "-L", "-F"}
  452. // longOptPrefixes are separate arguments that come before a path argument,
  453. // e.g., -iquote foo/bar.
  454. longOptPrefixes = []string{"-I", "-L", "-F", "-iquote", "-isystem"}
  455. )
  456. // options transforms package-relative paths in cgo options into repository-
  457. // root-relative paths that Bazel can understand. For example, if a cgo file
  458. // in //foo declares an include flag in its copts: "-Ibar", this method
  459. // will transform that flag into "-Ifoo/bar".
  460. func (g *generator) options(opts rule.PlatformStrings, pkgRel string) rule.PlatformStrings {
  461. fixPath := func(opt string) string {
  462. if strings.HasPrefix(opt, "/") {
  463. return opt
  464. }
  465. return path.Clean(path.Join(pkgRel, opt))
  466. }
  467. fixGroups := func(groups []string) ([]string, error) {
  468. fixedGroups := make([]string, len(groups))
  469. for i, group := range groups {
  470. opts := strings.Split(group, optSeparator)
  471. fixedOpts := make([]string, len(opts))
  472. isPath := false
  473. for j, opt := range opts {
  474. if isPath {
  475. opt = fixPath(opt)
  476. isPath = false
  477. goto next
  478. }
  479. for _, short := range shortOptPrefixes {
  480. if strings.HasPrefix(opt, short) && len(opt) > len(short) {
  481. opt = short + fixPath(opt[len(short):])
  482. goto next
  483. }
  484. }
  485. for _, long := range longOptPrefixes {
  486. if opt == long {
  487. isPath = true
  488. goto next
  489. }
  490. }
  491. next:
  492. fixedOpts[j] = escapeOption(opt)
  493. }
  494. fixedGroups[i] = strings.Join(fixedOpts, " ")
  495. }
  496. return fixedGroups, nil
  497. }
  498. opts, errs := opts.MapSlice(fixGroups)
  499. if errs != nil {
  500. log.Panicf("unexpected error when transforming options with pkg %q: %v", pkgRel, errs)
  501. }
  502. return opts
  503. }
  504. func escapeOption(opt string) string {
  505. return strings.NewReplacer(
  506. `\`, `\\`,
  507. `'`, `\'`,
  508. `"`, `\"`,
  509. ` `, `\ `,
  510. "\t", "\\\t",
  511. "\n", "\\\n",
  512. "\r", "\\\r",
  513. ).Replace(opt)
  514. }