fix-update.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  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. "strings"
  22. "github.com/bazelbuild/bazel-gazelle/internal/config"
  23. gzflag "github.com/bazelbuild/bazel-gazelle/internal/flag"
  24. "github.com/bazelbuild/bazel-gazelle/internal/label"
  25. "github.com/bazelbuild/bazel-gazelle/internal/merger"
  26. "github.com/bazelbuild/bazel-gazelle/internal/repos"
  27. "github.com/bazelbuild/bazel-gazelle/internal/resolve"
  28. "github.com/bazelbuild/bazel-gazelle/internal/rule"
  29. "github.com/bazelbuild/bazel-gazelle/internal/walk"
  30. )
  31. // updateConfig holds configuration information needed to run the fix and
  32. // update commands. This includes everything in config.Config, but it also
  33. // includes some additional fields that aren't relevant to other packages.
  34. type updateConfig struct {
  35. dirs []string
  36. emit emitFunc
  37. repos []repos.Repo
  38. useIndex bool
  39. walkMode walk.Mode
  40. patchPath string
  41. patchBuffer bytes.Buffer
  42. }
  43. type emitFunc func(c *config.Config, f *rule.File) error
  44. var modeFromName = map[string]emitFunc{
  45. "print": printFile,
  46. "fix": fixFile,
  47. "diff": diffFile,
  48. }
  49. const updateName = "_update"
  50. func getUpdateConfig(c *config.Config) *updateConfig {
  51. return c.Exts[updateName].(*updateConfig)
  52. }
  53. type updateConfigurer struct {
  54. mode string
  55. recursive bool
  56. }
  57. func (ucr *updateConfigurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {
  58. uc := &updateConfig{}
  59. c.Exts[updateName] = uc
  60. c.ShouldFix = cmd == "fix"
  61. 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")
  62. fs.BoolVar(&uc.useIndex, "index", true, "when true, gazelle will build an index of libraries in the workspace for dependency resolution")
  63. fs.BoolVar(&ucr.recursive, "r", true, "when true, gazelle will update subdirectories recursively")
  64. fs.StringVar(&uc.patchPath, "patch", "", "when set with -mode=diff, gazelle will write to a file instead of stdout")
  65. }
  66. func (ucr *updateConfigurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
  67. uc := getUpdateConfig(c)
  68. var ok bool
  69. uc.emit, ok = modeFromName[ucr.mode]
  70. if !ok {
  71. return fmt.Errorf("unrecognized emit mode: %q", ucr.mode)
  72. }
  73. if uc.patchPath != "" && ucr.mode != "diff" {
  74. return fmt.Errorf("-patch set but -mode is %s, not diff", ucr.mode)
  75. }
  76. dirs := fs.Args()
  77. if len(dirs) == 0 {
  78. dirs = []string{"."}
  79. }
  80. uc.dirs = make([]string, len(dirs))
  81. for i := range dirs {
  82. dir, err := filepath.Abs(dirs[i])
  83. if err != nil {
  84. return fmt.Errorf("%s: failed to find absolute path: %v", dirs[i], err)
  85. }
  86. dir, err = filepath.EvalSymlinks(dir)
  87. if err != nil {
  88. return fmt.Errorf("%s: failed to resolve symlinks: %v", dirs[i], err)
  89. }
  90. if !isDescendingDir(dir, c.RepoRoot) {
  91. return fmt.Errorf("dir %q is not a subdirectory of repo root %q", dir, c.RepoRoot)
  92. }
  93. uc.dirs[i] = dir
  94. }
  95. if ucr.recursive {
  96. uc.walkMode = walk.VisitAllUpdateSubdirsMode
  97. } else if uc.useIndex {
  98. uc.walkMode = walk.VisitAllUpdateDirsMode
  99. } else {
  100. uc.walkMode = walk.UpdateDirsMode
  101. }
  102. return nil
  103. }
  104. func (ucr *updateConfigurer) KnownDirectives() []string { return nil }
  105. func (ucr *updateConfigurer) Configure(c *config.Config, rel string, f *rule.File) {}
  106. // visitRecord stores information about about a directory visited with
  107. // packages.Walk.
  108. type visitRecord struct {
  109. // pkgRel is the slash-separated path to the visited directory, relative to
  110. // the repository root. "" for the repository root itself.
  111. pkgRel string
  112. // rules is a list of generated Go rules.
  113. rules []*rule.Rule
  114. // empty is a list of empty Go rules that may be deleted.
  115. empty []*rule.Rule
  116. // file is the build file being processed.
  117. file *rule.File
  118. }
  119. type byPkgRel []visitRecord
  120. func (vs byPkgRel) Len() int { return len(vs) }
  121. func (vs byPkgRel) Less(i, j int) bool { return vs[i].pkgRel < vs[j].pkgRel }
  122. func (vs byPkgRel) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
  123. var genericLoads = []rule.LoadInfo{
  124. {
  125. Name: "@bazel_gazelle//:def.bzl",
  126. Symbols: []string{"gazelle"},
  127. },
  128. }
  129. func runFixUpdate(cmd command, args []string) error {
  130. cexts := make([]config.Configurer, 0, len(languages)+3)
  131. cexts = append(cexts,
  132. &config.CommonConfigurer{},
  133. &updateConfigurer{},
  134. &walk.Configurer{},
  135. &resolve.Configurer{})
  136. kindToResolver := make(map[string]resolve.Resolver)
  137. kinds := make(map[string]rule.KindInfo)
  138. loads := genericLoads
  139. for _, lang := range languages {
  140. cexts = append(cexts, lang)
  141. for kind, info := range lang.Kinds() {
  142. kindToResolver[kind] = lang
  143. kinds[kind] = info
  144. }
  145. loads = append(loads, lang.Loads()...)
  146. }
  147. ruleIndex := resolve.NewRuleIndex(kindToResolver)
  148. c, err := newFixUpdateConfiguration(cmd, args, cexts, loads)
  149. if err != nil {
  150. return err
  151. }
  152. if cmd == fixCmd {
  153. // Only check the version when "fix" is run. Generated build files
  154. // frequently work with older version of rules_go, and we don't want to
  155. // nag too much since there's no way to disable this warning.
  156. checkRulesGoVersion(c.RepoRoot)
  157. }
  158. // Visit all directories in the repository.
  159. var visits []visitRecord
  160. uc := getUpdateConfig(c)
  161. walk.Walk(c, cexts, uc.dirs, uc.walkMode, func(dir, rel string, c *config.Config, update bool, f *rule.File, subdirs, regularFiles, genFiles []string) {
  162. // If this file is ignored or if Gazelle was not asked to update this
  163. // directory, just index the build file and move on.
  164. if !update {
  165. if uc.useIndex && f != nil {
  166. for _, r := range f.Rules {
  167. ruleIndex.AddRule(c, r, f)
  168. }
  169. }
  170. return
  171. }
  172. // Fix any problems in the file.
  173. if f != nil {
  174. for _, l := range languages {
  175. l.Fix(c, f)
  176. }
  177. }
  178. // Generate rules.
  179. var empty, gen []*rule.Rule
  180. for _, l := range languages {
  181. lempty, lgen := l.GenerateRules(c, dir, rel, f, subdirs, regularFiles, genFiles, empty, gen)
  182. empty = append(empty, lempty...)
  183. gen = append(gen, lgen...)
  184. }
  185. if f == nil && len(gen) == 0 {
  186. return
  187. }
  188. // Insert or merge rules into the build file.
  189. if f == nil {
  190. f = rule.EmptyFile(filepath.Join(dir, c.DefaultBuildFileName()), rel)
  191. for _, r := range gen {
  192. r.Insert(f)
  193. }
  194. } else {
  195. merger.MergeFile(f, empty, gen, merger.PreResolve, kinds)
  196. }
  197. visits = append(visits, visitRecord{
  198. pkgRel: rel,
  199. rules: gen,
  200. empty: empty,
  201. file: f,
  202. })
  203. // Add library rules to the dependency resolution table.
  204. for _, r := range f.Rules {
  205. ruleIndex.AddRule(c, r, f)
  206. }
  207. })
  208. // Finish building the index for dependency resolution.
  209. ruleIndex.Finish()
  210. // Resolve dependencies.
  211. rc := repos.NewRemoteCache(uc.repos)
  212. for _, v := range visits {
  213. for _, r := range v.rules {
  214. from := label.New(c.RepoName, v.pkgRel, r.Name())
  215. kindToResolver[r.Kind()].Resolve(c, ruleIndex, rc, r, from)
  216. }
  217. merger.MergeFile(v.file, v.empty, v.rules, merger.PostResolve, kinds)
  218. }
  219. // Emit merged files.
  220. for _, v := range visits {
  221. merger.FixLoads(v.file, loads)
  222. if err := uc.emit(c, v.file); err != nil {
  223. log.Print(err)
  224. }
  225. }
  226. if uc.patchPath != "" {
  227. if err := ioutil.WriteFile(uc.patchPath, uc.patchBuffer.Bytes(), 0666); err != nil {
  228. return err
  229. }
  230. }
  231. return nil
  232. }
  233. func newFixUpdateConfiguration(cmd command, args []string, cexts []config.Configurer, loads []rule.LoadInfo) (*config.Config, error) {
  234. c := config.New()
  235. fs := flag.NewFlagSet("gazelle", flag.ContinueOnError)
  236. // Flag will call this on any parse error. Don't print usage unless
  237. // -h or -help were passed explicitly.
  238. fs.Usage = func() {}
  239. var knownImports []string
  240. fs.Var(&gzflag.MultiFlag{Values: &knownImports}, "known_import", "import path for which external resolution is skipped (can specify multiple times)")
  241. for _, cext := range cexts {
  242. cext.RegisterFlags(fs, cmd.String(), c)
  243. }
  244. if err := fs.Parse(args); err != nil {
  245. if err == flag.ErrHelp {
  246. fixUpdateUsage(fs)
  247. return nil, err
  248. }
  249. // flag already prints the error; don't print it again.
  250. log.Fatal("Try -help for more information.")
  251. }
  252. for _, cext := range cexts {
  253. if err := cext.CheckFlags(fs, c); err != nil {
  254. return nil, err
  255. }
  256. }
  257. uc := getUpdateConfig(c)
  258. workspacePath := filepath.Join(c.RepoRoot, "WORKSPACE")
  259. if workspace, err := rule.LoadFile(workspacePath, ""); err != nil {
  260. if !os.IsNotExist(err) {
  261. return nil, err
  262. }
  263. } else {
  264. if err := fixWorkspace(c, workspace, loads); err != nil {
  265. return nil, err
  266. }
  267. c.RepoName = findWorkspaceName(workspace)
  268. uc.repos = repos.ListRepositories(workspace)
  269. }
  270. repoPrefixes := make(map[string]bool)
  271. for _, r := range uc.repos {
  272. repoPrefixes[r.GoPrefix] = true
  273. }
  274. for _, imp := range knownImports {
  275. if repoPrefixes[imp] {
  276. continue
  277. }
  278. repo := repos.Repo{
  279. Name: label.ImportPathToBazelRepoName(imp),
  280. GoPrefix: imp,
  281. }
  282. uc.repos = append(uc.repos, repo)
  283. }
  284. return c, nil
  285. }
  286. func fixUpdateUsage(fs *flag.FlagSet) {
  287. fmt.Fprint(os.Stderr, `usage: gazelle [fix|update] [flags...] [package-dirs...]
  288. The update command creates new build files and update existing BUILD files
  289. when needed.
  290. The fix command also creates and updates build files, and in addition, it may
  291. make potentially breaking updates to usage of rules. For example, it may
  292. delete obsolete rules or rename existing rules.
  293. There are several output modes which can be selected with the -mode flag. The
  294. output mode determines what Gazelle does with updated BUILD files.
  295. fix (default) - write updated BUILD files back to disk.
  296. print - print updated BUILD files to stdout.
  297. diff - diff updated BUILD files against existing files in unified format.
  298. Gazelle accepts a list of paths to Go package directories to process (defaults
  299. to the working directory if none are given). It recursively traverses
  300. subdirectories. All directories must be under the directory specified by
  301. -repo_root; if -repo_root is not given, this is the directory containing the
  302. WORKSPACE file.
  303. FLAGS:
  304. `)
  305. fs.PrintDefaults()
  306. }
  307. func fixWorkspace(c *config.Config, workspace *rule.File, loads []rule.LoadInfo) error {
  308. uc := getUpdateConfig(c)
  309. if !c.ShouldFix {
  310. return nil
  311. }
  312. shouldFix := false
  313. for _, d := range uc.dirs {
  314. if d == c.RepoRoot {
  315. shouldFix = true
  316. }
  317. }
  318. if !shouldFix {
  319. return nil
  320. }
  321. merger.FixWorkspace(workspace)
  322. merger.FixLoads(workspace, loads)
  323. if err := merger.CheckGazelleLoaded(workspace); err != nil {
  324. return err
  325. }
  326. return uc.emit(c, workspace)
  327. }
  328. func findWorkspaceName(f *rule.File) string {
  329. for _, r := range f.Rules {
  330. if r.Kind() == "workspace" {
  331. return r.Name()
  332. }
  333. }
  334. return ""
  335. }
  336. func isDescendingDir(dir, root string) bool {
  337. rel, err := filepath.Rel(root, dir)
  338. if err != nil {
  339. return false
  340. }
  341. if rel == "." {
  342. return true
  343. }
  344. return !strings.HasPrefix(rel, "..")
  345. }
  346. func findOutputPath(c *config.Config, f *rule.File) string {
  347. if c.ReadBuildFilesDir == "" && c.WriteBuildFilesDir == "" {
  348. return f.Path
  349. }
  350. baseDir := c.WriteBuildFilesDir
  351. if c.WriteBuildFilesDir == "" {
  352. baseDir = c.RepoRoot
  353. }
  354. outputDir := filepath.Join(baseDir, filepath.FromSlash(f.Pkg))
  355. defaultOutputPath := filepath.Join(outputDir, c.DefaultBuildFileName())
  356. files, err := ioutil.ReadDir(outputDir)
  357. if err != nil {
  358. // Ignore error. Directory probably doesn't exist.
  359. return defaultOutputPath
  360. }
  361. outputPath := rule.MatchBuildFileName(outputDir, c.ValidBuildFileNames, files)
  362. if outputPath == "" {
  363. return defaultOutputPath
  364. }
  365. return outputPath
  366. }