package.go 13 KB

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