kazel.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package main
  14. import (
  15. "bytes"
  16. "flag"
  17. "fmt"
  18. "io/ioutil"
  19. "os"
  20. "path/filepath"
  21. "reflect"
  22. "regexp"
  23. "sort"
  24. "github.com/bazelbuild/buildtools/build"
  25. "k8s.io/klog"
  26. )
  27. const (
  28. automanagedTag = "automanaged"
  29. )
  30. var (
  31. root = flag.String("root", ".", "root of go source")
  32. dryRun = flag.Bool("dry-run", false, "run in dry mode")
  33. printDiff = flag.Bool("print-diff", false, "print diff to stdout")
  34. validate = flag.Bool("validate", false, "run in dry mode and exit nonzero if any BUILD files need to be updated")
  35. cfgPath = flag.String("cfg-path", ".kazelcfg.json", "path to kazel config (relative paths interpreted relative to -repo.")
  36. )
  37. func main() {
  38. flag.Parse()
  39. flag.Set("alsologtostderr", "true")
  40. if *root == "" {
  41. klog.Fatalf("-root argument is required")
  42. }
  43. if *validate {
  44. *dryRun = true
  45. }
  46. v, err := newVendorer(*root, *cfgPath, *dryRun)
  47. if err != nil {
  48. klog.Fatalf("unable to build vendorer: %v", err)
  49. }
  50. if err = os.Chdir(v.root); err != nil {
  51. klog.Fatalf("cannot chdir into root %q: %v", v.root, err)
  52. }
  53. if v.cfg.ManageGoRules {
  54. klog.Fatalf("kazel no longer supports managing Go rules")
  55. }
  56. wroteGenerated := false
  57. if wroteGenerated, err = v.walkGenerated(); err != nil {
  58. klog.Fatalf("err walking generated: %v", err)
  59. }
  60. if _, err = v.walkSource("."); err != nil {
  61. klog.Fatalf("err walking source: %v", err)
  62. }
  63. written := 0
  64. if written, err = v.reconcileAllRules(); err != nil {
  65. klog.Fatalf("err reconciling rules: %v", err)
  66. }
  67. if wroteGenerated {
  68. written++
  69. }
  70. if *validate && written > 0 {
  71. fmt.Fprintf(os.Stderr, "\n%d BUILD files not up-to-date.\n", written)
  72. os.Exit(1)
  73. }
  74. }
  75. // Vendorer collects context, configuration, and cache while walking the tree.
  76. type Vendorer struct {
  77. skippedPaths []*regexp.Regexp
  78. skippedK8sCodegenPaths []*regexp.Regexp
  79. dryRun bool
  80. root string
  81. cfg *Cfg
  82. newRules map[string][]*build.Rule // package path -> list of rules to add or update
  83. managedAttrs []string // which rule attributes kazel will overwrite
  84. }
  85. func newVendorer(root, cfgPath string, dryRun bool) (*Vendorer, error) {
  86. absRoot, err := filepath.Abs(root)
  87. if err != nil {
  88. return nil, fmt.Errorf("could not get absolute path: %v", err)
  89. }
  90. if !filepath.IsAbs(cfgPath) {
  91. cfgPath = filepath.Join(absRoot, cfgPath)
  92. }
  93. cfg, err := ReadCfg(cfgPath)
  94. if err != nil {
  95. return nil, err
  96. }
  97. v := Vendorer{
  98. dryRun: dryRun,
  99. root: absRoot,
  100. cfg: cfg,
  101. newRules: make(map[string][]*build.Rule),
  102. managedAttrs: []string{"srcs"},
  103. }
  104. builtIn, err := compileSkippedPaths([]string{"^\\.git", "^bazel-*"})
  105. if err != nil {
  106. return nil, err
  107. }
  108. sp, err := compileSkippedPaths(cfg.SkippedPaths)
  109. if err != nil {
  110. return nil, err
  111. }
  112. sp = append(builtIn, sp...)
  113. v.skippedPaths = sp
  114. sop, err := compileSkippedPaths(cfg.SkippedK8sCodegenPaths)
  115. if err != nil {
  116. return nil, err
  117. }
  118. v.skippedK8sCodegenPaths = append(sop, sp...)
  119. return &v, nil
  120. }
  121. func writeRules(file *build.File, rules []*build.Rule) {
  122. for _, rule := range rules {
  123. file.Stmt = append(file.Stmt, rule.Call)
  124. }
  125. }
  126. func (v *Vendorer) addRules(pkgPath string, rules []*build.Rule) {
  127. cleanPath := filepath.Clean(pkgPath)
  128. v.newRules[cleanPath] = append(v.newRules[cleanPath], rules...)
  129. }
  130. func (v *Vendorer) reconcileAllRules() (int, error) {
  131. var paths []string
  132. for path := range v.newRules {
  133. paths = append(paths, path)
  134. }
  135. sort.Strings(paths)
  136. written := 0
  137. for _, path := range paths {
  138. w, err := ReconcileRules(path, v.newRules[path], v.managedAttrs, v.dryRun)
  139. if w {
  140. written++
  141. }
  142. if err != nil {
  143. return written, err
  144. }
  145. }
  146. return written, nil
  147. }
  148. // addCommentBefore adds a whole-line comment before the provided Expr.
  149. func addCommentBefore(e build.Expr, comment string) {
  150. c := e.Comment()
  151. c.Before = append(c.Before, build.Comment{Token: fmt.Sprintf("# %s", comment)})
  152. }
  153. // varExpr creates a variable expression of the form "name = expr".
  154. // v will be converted into an appropriate Expr using asExpr.
  155. // The optional description will be included as a comment before the expression.
  156. func varExpr(name, desc string, v interface{}) build.Expr {
  157. e := &build.BinaryExpr{
  158. X: &build.LiteralExpr{Token: name},
  159. Op: "=",
  160. Y: asExpr(v),
  161. }
  162. if desc != "" {
  163. addCommentBefore(e, desc)
  164. }
  165. return e
  166. }
  167. // rvSliceLessFunc returns a function that can be used with sort.Slice() or sort.SliceStable()
  168. // to sort a slice of reflect.Values.
  169. // It sorts ints and floats as their native kinds, and everything else as a string.
  170. func rvSliceLessFunc(k reflect.Kind, vs []reflect.Value) func(int, int) bool {
  171. switch k {
  172. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
  173. reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  174. return func(i, j int) bool { return vs[i].Int() < vs[j].Int() }
  175. case reflect.Float32, reflect.Float64:
  176. return func(i, j int) bool { return vs[i].Float() < vs[j].Float() }
  177. default:
  178. return func(i, j int) bool {
  179. return fmt.Sprintf("%v", vs[i]) < fmt.Sprintf("%v", vs[j])
  180. }
  181. }
  182. }
  183. // asExpr converts a native Go type into the equivalent Starlark expression using reflection.
  184. // The keys of maps will be sorted for reproducibility.
  185. func asExpr(e interface{}) build.Expr {
  186. rv := reflect.ValueOf(e)
  187. switch rv.Kind() {
  188. case reflect.Bool:
  189. return &build.LiteralExpr{Token: fmt.Sprintf("%t", e)}
  190. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
  191. reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  192. return &build.LiteralExpr{Token: fmt.Sprintf("%d", e)}
  193. case reflect.Float32, reflect.Float64:
  194. return &build.LiteralExpr{Token: fmt.Sprintf("%g", e)}
  195. case reflect.String:
  196. return &build.StringExpr{Value: e.(string)}
  197. case reflect.Slice, reflect.Array:
  198. var list []build.Expr
  199. for i := 0; i < rv.Len(); i++ {
  200. list = append(list, asExpr(rv.Index(i).Interface()))
  201. }
  202. return &build.ListExpr{List: list}
  203. case reflect.Map:
  204. var list []build.Expr
  205. keys := rv.MapKeys()
  206. sort.SliceStable(keys, rvSliceLessFunc(rv.Type().Key().Kind(), keys))
  207. for _, key := range keys {
  208. list = append(list, &build.KeyValueExpr{
  209. Key: asExpr(key.Interface()),
  210. Value: asExpr(rv.MapIndex(key).Interface()),
  211. })
  212. }
  213. return &build.DictExpr{List: list}
  214. default:
  215. klog.Fatalf("unhandled kind: %q for value: %q", rv.Kind(), rv)
  216. return nil
  217. }
  218. }
  219. func newRule(rt, name string, attrs map[string]build.Expr) *build.Rule {
  220. rule := &build.Rule{
  221. Call: &build.CallExpr{
  222. X: &build.LiteralExpr{Token: rt},
  223. },
  224. }
  225. rule.SetAttr("name", asExpr(name))
  226. for k, v := range attrs {
  227. rule.SetAttr(k, v)
  228. }
  229. rule.SetAttr("tags", asExpr([]string{automanagedTag}))
  230. return rule
  231. }
  232. // findBuildFile determines the name of a preexisting BUILD file, returning
  233. // a default if no such file exists.
  234. func findBuildFile(pkgPath string) (bool, string) {
  235. options := []string{"BUILD.bazel", "BUILD"}
  236. for _, b := range options {
  237. path := filepath.Join(pkgPath, b)
  238. info, err := os.Stat(path)
  239. if err == nil && !info.IsDir() {
  240. return true, path
  241. }
  242. }
  243. return false, filepath.Join(pkgPath, "BUILD.bazel")
  244. }
  245. // ReconcileRules reconciles, simplifies, and writes the rules for the specified package, adding
  246. // additional dependency rules as needed.
  247. func ReconcileRules(pkgPath string, rules []*build.Rule, managedAttrs []string, dryRun bool) (bool, error) {
  248. _, path := findBuildFile(pkgPath)
  249. info, err := os.Stat(path)
  250. if err != nil && os.IsNotExist(err) {
  251. f := &build.File{}
  252. writeRules(f, rules)
  253. return writeFile(path, f, nil, false, dryRun)
  254. } else if err != nil {
  255. return false, err
  256. }
  257. if info.IsDir() {
  258. return false, fmt.Errorf("%q cannot be a directory", path)
  259. }
  260. b, err := ioutil.ReadFile(path)
  261. if err != nil {
  262. return false, err
  263. }
  264. f, err := build.Parse(path, b)
  265. if err != nil {
  266. return false, err
  267. }
  268. oldRules := make(map[string]*build.Rule)
  269. for _, r := range f.Rules("") {
  270. oldRules[r.Name()] = r
  271. }
  272. for _, r := range rules {
  273. o, ok := oldRules[r.Name()]
  274. if !ok {
  275. f.Stmt = append(f.Stmt, r.Call)
  276. continue
  277. }
  278. if !RuleIsManaged(o) {
  279. continue
  280. }
  281. reconcileAttr := func(o, n *build.Rule, name string) {
  282. if e := n.Attr(name); e != nil {
  283. o.SetAttr(name, e)
  284. } else {
  285. o.DelAttr(name)
  286. }
  287. }
  288. for _, attr := range managedAttrs {
  289. reconcileAttr(o, r, attr)
  290. }
  291. delete(oldRules, r.Name())
  292. }
  293. for _, r := range oldRules {
  294. if !RuleIsManaged(r) {
  295. continue
  296. }
  297. f.DelRules(r.Kind(), r.Name())
  298. }
  299. return writeFile(path, f, nil, true, dryRun)
  300. }
  301. // RuleIsManaged returns whether the provided rule is managed by this tool,
  302. // based on the tags set on the rule.
  303. func RuleIsManaged(r *build.Rule) bool {
  304. for _, tag := range r.AttrStrings("tags") {
  305. if tag == automanagedTag {
  306. return true
  307. }
  308. }
  309. return false
  310. }
  311. // writeFile writes out f to path, prepending boilerplate to the output.
  312. // If exists is true, compares against the existing file specified by path,
  313. // returning false if there are no changes.
  314. // Otherwise, returns true.
  315. // If dryRun is false, no files are actually changed; otherwise, the file will be written.
  316. func writeFile(path string, f *build.File, boilerplate []byte, exists, dryRun bool) (bool, error) {
  317. var info build.RewriteInfo
  318. build.Rewrite(f, &info)
  319. var out []byte
  320. out = append(out, boilerplate...)
  321. out = append(out, build.Format(f)...)
  322. if exists {
  323. orig, err := ioutil.ReadFile(path)
  324. if err != nil {
  325. return false, err
  326. }
  327. if bytes.Compare(orig, out) == 0 {
  328. return false, nil
  329. }
  330. if *printDiff {
  331. Diff(orig, out)
  332. }
  333. }
  334. if dryRun {
  335. fmt.Fprintf(os.Stderr, "DRY-RUN: wrote %q\n", path)
  336. return true, nil
  337. }
  338. werr := ioutil.WriteFile(path, out, 0644)
  339. if werr == nil {
  340. fmt.Fprintf(os.Stderr, "wrote %q\n", path)
  341. }
  342. return werr == nil, werr
  343. }
  344. func compileSkippedPaths(skippedPaths []string) ([]*regexp.Regexp, error) {
  345. regexPaths := []*regexp.Regexp{}
  346. for _, sp := range skippedPaths {
  347. r, err := regexp.Compile(sp)
  348. if err != nil {
  349. return nil, err
  350. }
  351. regexPaths = append(regexPaths, r)
  352. }
  353. return regexPaths, nil
  354. }