fix-update.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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 main
  13. import (
  14. "bytes"
  15. "flag"
  16. "fmt"
  17. "io/ioutil"
  18. "log"
  19. "os"
  20. "path/filepath"
  21. "sort"
  22. "strings"
  23. "github.com/bazelbuild/bazel-gazelle/config"
  24. gzflag "github.com/bazelbuild/bazel-gazelle/flag"
  25. "github.com/bazelbuild/bazel-gazelle/label"
  26. "github.com/bazelbuild/bazel-gazelle/language"
  27. "github.com/bazelbuild/bazel-gazelle/merger"
  28. "github.com/bazelbuild/bazel-gazelle/repo"
  29. "github.com/bazelbuild/bazel-gazelle/resolve"
  30. "github.com/bazelbuild/bazel-gazelle/rule"
  31. "github.com/bazelbuild/bazel-gazelle/walk"
  32. )
  33. // updateConfig holds configuration information needed to run the fix and
  34. // update commands. This includes everything in config.Config, but it also
  35. // includes some additional fields that aren't relevant to other packages.
  36. type updateConfig struct {
  37. dirs []string
  38. emit emitFunc
  39. repos []repo.Repo
  40. workspaceFiles []*rule.File
  41. walkMode walk.Mode
  42. patchPath string
  43. patchBuffer bytes.Buffer
  44. }
  45. type emitFunc func(c *config.Config, f *rule.File) error
  46. var modeFromName = map[string]emitFunc{
  47. "print": printFile,
  48. "fix": fixFile,
  49. "diff": diffFile,
  50. }
  51. const updateName = "_update"
  52. func getUpdateConfig(c *config.Config) *updateConfig {
  53. return c.Exts[updateName].(*updateConfig)
  54. }
  55. type updateConfigurer struct {
  56. mode string
  57. recursive bool
  58. knownImports []string
  59. repoConfigPath string
  60. }
  61. func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
  62. uc := &updateConfig{}
  63. c.Exts[updateName] = uc
  64. c.ShouldFix = cmd == "fix"
  65. fs.StringVar(&ucr.mode, "mode", "fix", "print: prints all of the updated BUILD files\n\tfix: rewrites all of the BUILD files in place\n\tdiff: computes the rewrite but then just does a diff")
  66. fs.BoolVar(&ucr.recursive, "r", true, "when true, gazelle will update subdirectories recursively")
  67. fs.StringVar(&uc.patchPath, "patch", "", "when set with -mode=diff, gazelle will write to a file instead of stdout")
  68. fs.Var(&gzflag.MultiFlag{Values: &ucr.knownImports}, "known_import", "import path for which external resolution is skipped (can specify multiple times)")
  69. fs.StringVar(&ucr.repoConfigPath, "repo_config", "", "file where Gazelle should load repository configuration. Defaults to WORKSPACE.")
  70. }
  71. func (ucr *updateConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
  72. uc := getUpdateConfig(c)
  73. var ok bool
  74. uc.emit, ok = modeFromName[ucr.mode]
  75. if !ok {
  76. return fmt.Errorf("unrecognized emit mode: %q", ucr.mode)
  77. }
  78. if uc.patchPath != "" && ucr.mode != "diff" {
  79. return fmt.Errorf("-patch set but -mode is %s, not diff", ucr.mode)
  80. }
  81. dirs := fs.Args()
  82. if len(dirs) == 0 {
  83. dirs = []string{"."}
  84. }
  85. uc.dirs = make([]string, len(dirs))
  86. for i := range dirs {
  87. dir, err := filepath.Abs(dirs[i])
  88. if err != nil {
  89. return fmt.Errorf("%s: failed to find absolute path: %v", dirs[i], err)
  90. }
  91. dir, err = filepath.EvalSymlinks(dir)
  92. if err != nil {
  93. return fmt.Errorf("%s: failed to resolve symlinks: %v", dirs[i], err)
  94. }
  95. if !isDescendingDir(dir, c.RepoRoot) {
  96. return fmt.Errorf("dir %q is not a subdirectory of repo root %q", dir, c.RepoRoot)
  97. }
  98. uc.dirs[i] = dir
  99. }
  100. if ucr.recursive {
  101. uc.walkMode = walk.VisitAllUpdateSubdirsMode
  102. } else if c.IndexLibraries {
  103. uc.walkMode = walk.VisitAllUpdateDirsMode
  104. } else {
  105. uc.walkMode = walk.UpdateDirsMode
  106. }
  107. // Load the repo configuration file (WORKSPACE by default) to find out
  108. // names and prefixes of other go_repositories. This affects external
  109. // dependency resolution for Go.
  110. // TODO(jayconrod): Go-specific code should be moved to language/go.
  111. if ucr.repoConfigPath == "" {
  112. ucr.repoConfigPath = filepath.Join(c.RepoRoot, "WORKSPACE")
  113. }
  114. repoConfigFile, err := rule.LoadWorkspaceFile(ucr.repoConfigPath, "")
  115. if err != nil && !os.IsNotExist(err) {
  116. return err
  117. } else if err == nil {
  118. c.Repos, _, err = repo.ListRepositories(repoConfigFile)
  119. if err != nil {
  120. return err
  121. }
  122. }
  123. for _, imp := range ucr.knownImports {
  124. uc.repos = append(uc.repos, repo.Repo{
  125. Name: label.ImportPathToBazelRepoName(imp),
  126. GoPrefix: imp,
  127. })
  128. }
  129. for _, r := range c.Repos {
  130. if r.Kind() == "go_repository" {
  131. uc.repos = append(uc.repos, repo.Repo{
  132. Name: r.Name(),
  133. GoPrefix: r.AttrString("importpath"),
  134. })
  135. }
  136. }
  137. // If the repo configuration file is not WORKSPACE, also load WORKSPACE
  138. // and any declared macro files so we can apply fixes.
  139. workspacePath := filepath.Join(c.RepoRoot, "WORKSPACE")
  140. var workspace *rule.File
  141. if ucr.repoConfigPath == workspacePath {
  142. workspace = repoConfigFile
  143. } else {
  144. workspace, err = rule.LoadWorkspaceFile(workspacePath, "")
  145. if err != nil && !os.IsNotExist(err) {
  146. return err
  147. }
  148. }
  149. if workspace != nil {
  150. c.RepoName = findWorkspaceName(workspace)
  151. _, repoFileMap, err := repo.ListRepositories(workspace)
  152. if err != nil {
  153. return err
  154. }
  155. seen := make(map[*rule.File]bool)
  156. for _, f := range repoFileMap {
  157. if !seen[f] {
  158. uc.workspaceFiles = append(uc.workspaceFiles, f)
  159. seen[f] = true
  160. }
  161. }
  162. sort.Slice(uc.workspaceFiles, func(i, j int) bool {
  163. return uc.workspaceFiles[i].Path < uc.workspaceFiles[j].Path
  164. })
  165. }
  166. return nil
  167. }
  168. func (ucr *updateConfigurer) KnownDirectives() []string { return nil }
  169. func (ucr *updateConfigurer) Configure(c *config.Config, rel string, f *rule.File) {}
  170. // visitRecord stores information about about a directory visited with
  171. // packages.Walk.
  172. type visitRecord struct {
  173. // pkgRel is the slash-separated path to the visited directory, relative to
  174. // the repository root. "" for the repository root itself.
  175. pkgRel string
  176. // c is the configuration for the directory with directives applied.
  177. c *config.Config
  178. // rules is a list of generated Go rules.
  179. rules []*rule.Rule
  180. // imports contains opaque import information for each rule in rules.
  181. imports []interface{}
  182. // empty is a list of empty Go rules that may be deleted.
  183. empty []*rule.Rule
  184. // file is the build file being processed.
  185. file *rule.File
  186. // mappedKinds are mapped kinds used during this visit.
  187. mappedKinds []config.MappedKind
  188. mappedKindInfo map[string]rule.KindInfo
  189. }
  190. type byPkgRel []visitRecord
  191. func (vs byPkgRel) Len() int { return len(vs) }
  192. func (vs byPkgRel) Less(i, j int) bool { return vs[i].pkgRel < vs[j].pkgRel }
  193. func (vs byPkgRel) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
  194. var genericLoads = []rule.LoadInfo{
  195. {
  196. Name: "@bazel_gazelle//:def.bzl",
  197. Symbols: []string{"gazelle"},
  198. },
  199. }
  200. func runFixUpdate(cmd command, args []string) (err error) {
  201. cexts := make([]config.Configurer, 0, len(languages)+3)
  202. cexts = append(cexts,
  203. &config.CommonConfigurer{},
  204. &updateConfigurer{},
  205. &walk.Configurer{},
  206. &resolve.Configurer{})
  207. mrslv := newMetaResolver()
  208. kinds := make(map[string]rule.KindInfo)
  209. loads := genericLoads
  210. for _, lang := range languages {
  211. cexts = append(cexts, lang)
  212. for kind, info := range lang.Kinds() {
  213. mrslv.AddBuiltin(kind, lang)
  214. kinds[kind] = info
  215. }
  216. loads = append(loads, lang.Loads()...)
  217. }
  218. ruleIndex := resolve.NewRuleIndex(mrslv.Resolver)
  219. c, err := newFixUpdateConfiguration(cmd, args, cexts)
  220. if err != nil {
  221. return err
  222. }
  223. if err := fixRepoFiles(c, loads); err != nil {
  224. return err
  225. }
  226. if cmd == fixCmd {
  227. // Only check the version when "fix" is run. Generated build files
  228. // frequently work with older version of rules_go, and we don't want to
  229. // nag too much since there's no way to disable this warning.
  230. checkRulesGoVersion(c.RepoRoot)
  231. }
  232. // Visit all directories in the repository.
  233. var visits []visitRecord
  234. uc := getUpdateConfig(c)
  235. walk.Walk(c, cexts, uc.dirs, uc.walkMode, func(dir, rel string, c *config.Config, update bool, f *rule.File, subdirs, regularFiles, genFiles []string) {
  236. // If this file is ignored or if Gazelle was not asked to update this
  237. // directory, just index the build file and move on.
  238. if !update {
  239. if c.IndexLibraries && f != nil {
  240. for _, r := range f.Rules {
  241. ruleIndex.AddRule(c, r, f)
  242. }
  243. }
  244. return
  245. }
  246. // Fix any problems in the file.
  247. if f != nil {
  248. for _, l := range languages {
  249. l.Fix(c, f)
  250. }
  251. }
  252. // Generate rules.
  253. var empty, gen []*rule.Rule
  254. var imports []interface{}
  255. for _, l := range languages {
  256. res := l.GenerateRules(language.GenerateArgs{
  257. Config: c,
  258. Dir: dir,
  259. Rel: rel,
  260. File: f,
  261. Subdirs: subdirs,
  262. RegularFiles: regularFiles,
  263. GenFiles: genFiles,
  264. OtherEmpty: empty,
  265. OtherGen: gen})
  266. if len(res.Gen) != len(res.Imports) {
  267. log.Panicf("%s: language %s generated %d rules but returned %d imports", rel, l.Name(), len(res.Gen), len(res.Imports))
  268. }
  269. empty = append(empty, res.Empty...)
  270. gen = append(gen, res.Gen...)
  271. imports = append(imports, res.Imports...)
  272. }
  273. if f == nil && len(gen) == 0 {
  274. return
  275. }
  276. // Apply and record relevant kind mappings.
  277. var (
  278. mappedKinds []config.MappedKind
  279. mappedKindInfo = make(map[string]rule.KindInfo)
  280. )
  281. for _, r := range gen {
  282. if repl, ok := c.KindMap[r.Kind()]; ok {
  283. mappedKindInfo[repl.KindName] = kinds[r.Kind()]
  284. mappedKinds = append(mappedKinds, repl)
  285. mrslv.MappedKind(rel, repl)
  286. r.SetKind(repl.KindName)
  287. }
  288. }
  289. // Insert or merge rules into the build file.
  290. if f == nil {
  291. f = rule.EmptyFile(filepath.Join(dir, c.DefaultBuildFileName()), rel)
  292. for _, r := range gen {
  293. r.Insert(f)
  294. }
  295. } else {
  296. merger.MergeFile(f, empty, gen, merger.PreResolve,
  297. unionKindInfoMaps(kinds, mappedKindInfo))
  298. }
  299. visits = append(visits, visitRecord{
  300. pkgRel: rel,
  301. c: c,
  302. rules: gen,
  303. imports: imports,
  304. empty: empty,
  305. file: f,
  306. mappedKinds: mappedKinds,
  307. mappedKindInfo: mappedKindInfo,
  308. })
  309. // Add library rules to the dependency resolution table.
  310. if c.IndexLibraries {
  311. for _, r := range f.Rules {
  312. ruleIndex.AddRule(c, r, f)
  313. }
  314. }
  315. })
  316. // Finish building the index for dependency resolution.
  317. ruleIndex.Finish()
  318. // Resolve dependencies.
  319. rc, cleanupRc := repo.NewRemoteCache(uc.repos)
  320. defer func() {
  321. if cerr := cleanupRc(); err == nil && cerr != nil {
  322. err = cerr
  323. }
  324. }()
  325. for _, v := range visits {
  326. for i, r := range v.rules {
  327. from := label.New(c.RepoName, v.pkgRel, r.Name())
  328. mrslv.Resolver(r, v.pkgRel).Resolve(v.c, ruleIndex, rc, r, v.imports[i], from)
  329. }
  330. merger.MergeFile(v.file, v.empty, v.rules, merger.PostResolve,
  331. unionKindInfoMaps(kinds, v.mappedKindInfo))
  332. }
  333. // Emit merged files.
  334. var exit error
  335. for _, v := range visits {
  336. merger.FixLoads(v.file, applyKindMappings(v.mappedKinds, loads))
  337. if err := uc.emit(v.c, v.file); err != nil {
  338. if err == exitError {
  339. exit = err
  340. } else {
  341. log.Print(err)
  342. }
  343. }
  344. }
  345. if uc.patchPath != "" {
  346. if err := ioutil.WriteFile(uc.patchPath, uc.patchBuffer.Bytes(), 0666); err != nil {
  347. return err
  348. }
  349. }
  350. return exit
  351. }
  352. func newFixUpdateConfiguration(cmd command, args []string, cexts []config.Configurer) (*config.Config, error) {
  353. c := config.New()
  354. fs := flag.NewFlagSet("gazelle", flag.ContinueOnError)
  355. // Flag will call this on any parse error. Don't print usage unless
  356. // -h or -help were passed explicitly.
  357. fs.Usage = func() {}
  358. for _, cext := range cexts {
  359. cext.RegisterFlags(fs, cmd.String(), c)
  360. }
  361. if err := fs.Parse(args); err != nil {
  362. if err == flag.ErrHelp {
  363. fixUpdateUsage(fs)
  364. return nil, err
  365. }
  366. // flag already prints the error; don't print it again.
  367. log.Fatal("Try -help for more information.")
  368. }
  369. for _, cext := range cexts {
  370. if err := cext.CheckFlags(fs, c); err != nil {
  371. return nil, err
  372. }
  373. }
  374. return c, nil
  375. }
  376. func fixUpdateUsage(fs *flag.FlagSet) {
  377. fmt.Fprint(os.Stderr, `usage: gazelle [fix|update] [flags...] [package-dirs...]
  378. The update command creates new build files and update existing BUILD files
  379. when needed.
  380. The fix command also creates and updates build files, and in addition, it may
  381. make potentially breaking updates to usage of rules. For example, it may
  382. delete obsolete rules or rename existing rules.
  383. There are several output modes which can be selected with the -mode flag. The
  384. output mode determines what Gazelle does with updated BUILD files.
  385. fix (default) - write updated BUILD files back to disk.
  386. print - print updated BUILD files to stdout.
  387. diff - diff updated BUILD files against existing files in unified format.
  388. Gazelle accepts a list of paths to Go package directories to process (defaults
  389. to the working directory if none are given). It recursively traverses
  390. subdirectories. All directories must be under the directory specified by
  391. -repo_root; if -repo_root is not given, this is the directory containing the
  392. WORKSPACE file.
  393. FLAGS:
  394. `)
  395. fs.PrintDefaults()
  396. }
  397. func fixRepoFiles(c *config.Config, loads []rule.LoadInfo) error {
  398. uc := getUpdateConfig(c)
  399. if !c.ShouldFix {
  400. return nil
  401. }
  402. shouldFix := false
  403. for _, d := range uc.dirs {
  404. if d == c.RepoRoot {
  405. shouldFix = true
  406. }
  407. }
  408. if !shouldFix {
  409. return nil
  410. }
  411. for _, f := range uc.workspaceFiles {
  412. merger.FixLoads(f, loads)
  413. if f.Path == filepath.Join(c.RepoRoot, "WORKSPACE") {
  414. removeLegacyGoRepository(f)
  415. if err := merger.CheckGazelleLoaded(f); err != nil {
  416. return err
  417. }
  418. }
  419. if err := uc.emit(c, f); err != nil {
  420. return err
  421. }
  422. }
  423. return nil
  424. }
  425. // removeLegacyGoRepository removes loads of go_repository from
  426. // @io_bazel_rules_go. FixLoads should be called after this; it will load from
  427. // @bazel_gazelle.
  428. func removeLegacyGoRepository(f *rule.File) {
  429. for _, l := range f.Loads {
  430. if l.Name() == "@io_bazel_rules_go//go:def.bzl" {
  431. l.Remove("go_repository")
  432. if l.IsEmpty() {
  433. l.Delete()
  434. }
  435. }
  436. }
  437. }
  438. func findWorkspaceName(f *rule.File) string {
  439. for _, r := range f.Rules {
  440. if r.Kind() == "workspace" {
  441. return r.Name()
  442. }
  443. }
  444. return ""
  445. }
  446. func isDescendingDir(dir, root string) bool {
  447. rel, err := filepath.Rel(root, dir)
  448. if err != nil {
  449. return false
  450. }
  451. if rel == "." {
  452. return true
  453. }
  454. return !strings.HasPrefix(rel, "..")
  455. }
  456. func findOutputPath(c *config.Config, f *rule.File) string {
  457. if c.ReadBuildFilesDir == "" && c.WriteBuildFilesDir == "" {
  458. return f.Path
  459. }
  460. baseDir := c.WriteBuildFilesDir
  461. if c.WriteBuildFilesDir == "" {
  462. baseDir = c.RepoRoot
  463. }
  464. outputDir := filepath.Join(baseDir, filepath.FromSlash(f.Pkg))
  465. defaultOutputPath := filepath.Join(outputDir, c.DefaultBuildFileName())
  466. files, err := ioutil.ReadDir(outputDir)
  467. if err != nil {
  468. // Ignore error. Directory probably doesn't exist.
  469. return defaultOutputPath
  470. }
  471. outputPath := rule.MatchBuildFileName(outputDir, c.ValidBuildFileNames, files)
  472. if outputPath == "" {
  473. return defaultOutputPath
  474. }
  475. return outputPath
  476. }
  477. func unionKindInfoMaps(a, b map[string]rule.KindInfo) map[string]rule.KindInfo {
  478. if len(a) == 0 {
  479. return b
  480. }
  481. if len(b) == 0 {
  482. return a
  483. }
  484. result := make(map[string]rule.KindInfo, len(a)+len(b))
  485. for _, m := range []map[string]rule.KindInfo{a, b} {
  486. for k, v := range m {
  487. result[k] = v
  488. }
  489. }
  490. return result
  491. }
  492. // applyKindMappings returns a copy of LoadInfo that includes c.KindMap.
  493. func applyKindMappings(mappedKinds []config.MappedKind, loads []rule.LoadInfo) []rule.LoadInfo {
  494. if len(mappedKinds) == 0 {
  495. return loads
  496. }
  497. // Add new RuleInfos or replace existing ones with merged ones.
  498. mappedLoads := make([]rule.LoadInfo, len(loads))
  499. copy(mappedLoads, loads)
  500. for _, mappedKind := range mappedKinds {
  501. mappedLoads = appendOrMergeKindMapping(mappedLoads, mappedKind)
  502. }
  503. return mappedLoads
  504. }
  505. // appendOrMergeKindMapping adds LoadInfo for the given replacement.
  506. func appendOrMergeKindMapping(mappedLoads []rule.LoadInfo, mappedKind config.MappedKind) []rule.LoadInfo {
  507. // If mappedKind.KindLoad already exists in the list, create a merged copy.
  508. for i, load := range mappedLoads {
  509. if load.Name == mappedKind.KindLoad {
  510. mappedLoads[i].Symbols = append(load.Symbols, mappedKind.KindName)
  511. return mappedLoads
  512. }
  513. }
  514. // Add a new LoadInfo.
  515. return append(mappedLoads, rule.LoadInfo{
  516. Name: mappedKind.KindLoad,
  517. Symbols: []string{mappedKind.KindName},
  518. })
  519. }