179 lines
5.9 KiB
Go
179 lines
5.9 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/bazelbuild/buildtools/build"
|
|
)
|
|
|
|
var (
|
|
// Generator tags are specified using the format "// +k8s:name=value"
|
|
genTagRe = regexp.MustCompile(`//\s*\+k8s:([^\s=]+)(?:=(\S+))\s*\n`)
|
|
)
|
|
|
|
// {tagName: {value: {pkgs}}} or {tagName: {pkg: {values}}}
|
|
type generatorTagsMap map[string]map[string]map[string]bool
|
|
|
|
// extractTags finds k8s codegen tags found in b listed in requestedTags.
|
|
// It returns a map of {tag name: slice of values for that tag}.
|
|
func extractTags(b []byte, requestedTags map[string]bool) map[string][]string {
|
|
tags := make(map[string][]string)
|
|
matches := genTagRe.FindAllSubmatch(b, -1)
|
|
for _, m := range matches {
|
|
if len(m) >= 3 {
|
|
tag, values := string(m[1]), string(m[2])
|
|
if _, requested := requestedTags[tag]; !requested {
|
|
continue
|
|
}
|
|
tags[tag] = append(tags[tag], strings.Split(values, ",")...)
|
|
}
|
|
}
|
|
return tags
|
|
}
|
|
|
|
// findGeneratorTags searches for all packages under root that include a kubernetes generator
|
|
// tag comment. It does not follow symlinks, and any path in the configured skippedPaths
|
|
// or codegen skipped paths is skipped.
|
|
func (v *Vendorer) findGeneratorTags(root string, requestedTags map[string]bool) (tagsValuesPkgs, tagsPkgsValues generatorTagsMap, err error) {
|
|
tagsValuesPkgs = make(generatorTagsMap)
|
|
tagsPkgsValues = make(generatorTagsMap)
|
|
|
|
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pkg := filepath.Dir(path)
|
|
|
|
for _, r := range v.skippedK8sCodegenPaths {
|
|
if r.MatchString(pkg) {
|
|
return filepath.SkipDir
|
|
}
|
|
}
|
|
|
|
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
|
|
b, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for tag, values := range extractTags(b, requestedTags) {
|
|
if _, present := tagsValuesPkgs[tag]; !present {
|
|
tagsValuesPkgs[tag] = make(map[string]map[string]bool)
|
|
}
|
|
if _, present := tagsPkgsValues[tag]; !present {
|
|
tagsPkgsValues[tag] = make(map[string]map[string]bool)
|
|
}
|
|
if _, present := tagsPkgsValues[tag][pkg]; !present {
|
|
tagsPkgsValues[tag][pkg] = make(map[string]bool)
|
|
}
|
|
for _, v := range values {
|
|
if _, present := tagsValuesPkgs[tag][v]; !present {
|
|
tagsValuesPkgs[tag][v] = make(map[string]bool)
|
|
}
|
|
// Since multiple files in the same package may list a given tag/value, use a set to deduplicate.
|
|
tagsValuesPkgs[tag][v][pkg] = true
|
|
tagsPkgsValues[tag][pkg][v] = true
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// flattened returns a copy of the map with the final stringSet flattened into a sorted slice.
|
|
func flattened(m generatorTagsMap) map[string]map[string][]string {
|
|
flattened := make(map[string]map[string][]string)
|
|
for tag, subMap := range m {
|
|
flattened[tag] = make(map[string][]string)
|
|
for k, subSet := range subMap {
|
|
for v := range subSet {
|
|
flattened[tag][k] = append(flattened[tag][k], v)
|
|
}
|
|
sort.Strings(flattened[tag][k])
|
|
}
|
|
}
|
|
return flattened
|
|
}
|
|
|
|
// walkGenerated generates a k8s codegen bzl file that can be parsed by Starlark
|
|
// rules and macros to find packages needed k8s code generation.
|
|
// This involves reading all non-test go sources in the tree and looking for
|
|
// "+k8s:name=value" tags. Only those tags listed in K8sCodegenTags will be
|
|
// included.
|
|
// If a K8sCodegenBoilerplateFile was configured, the contents of this file
|
|
// will be included as the header of the generated bzl file.
|
|
// Returns true if there are diffs against the existing generated bzl file.
|
|
func (v *Vendorer) walkGenerated() (bool, error) {
|
|
if v.cfg.K8sCodegenBzlFile == "" {
|
|
return false, nil
|
|
}
|
|
// only include the specified tags
|
|
requestedTags := make(map[string]bool)
|
|
for _, tag := range v.cfg.K8sCodegenTags {
|
|
requestedTags[tag] = true
|
|
}
|
|
tagsValuesPkgs, tagsPkgsValues, err := v.findGeneratorTags(".", requestedTags)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
f := &build.File{
|
|
Path: v.cfg.K8sCodegenBzlFile,
|
|
}
|
|
addCommentBefore(f, "#################################################")
|
|
addCommentBefore(f, "# # # # # # # # # # # # # # # # # # # # # # # # #")
|
|
addCommentBefore(f, "This file is autogenerated by kazel. DO NOT EDIT.")
|
|
addCommentBefore(f, "# # # # # # # # # # # # # # # # # # # # # # # # #")
|
|
addCommentBefore(f, "#################################################")
|
|
addCommentBefore(f, "")
|
|
|
|
f.Stmt = append(f.Stmt, varExpr("go_prefix", "The go prefix passed to kazel", v.cfg.GoPrefix))
|
|
f.Stmt = append(f.Stmt, varExpr("kazel_configured_tags", "The list of codegen tags kazel is configured to find", v.cfg.K8sCodegenTags))
|
|
f.Stmt = append(f.Stmt, varExpr("tags_values_pkgs", "tags_values_pkgs is a dictionary mapping {k8s build tag: {tag value: [pkgs including that tag:value]}}", flattened(tagsValuesPkgs)))
|
|
f.Stmt = append(f.Stmt, varExpr("tags_pkgs_values", "tags_pkgs_values is a dictionary mapping {k8s build tag: {pkg: [tag values in pkg]}}", flattened(tagsPkgsValues)))
|
|
|
|
var boilerplate []byte
|
|
if v.cfg.K8sCodegenBoilerplateFile != "" {
|
|
boilerplate, err = ioutil.ReadFile(v.cfg.K8sCodegenBoilerplateFile)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
// Open existing file to use in diff mode.
|
|
_, err = os.Stat(f.Path)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return false, err
|
|
}
|
|
return writeFile(f.Path, f, boilerplate, !os.IsNotExist(err), v.dryRun)
|
|
}
|