| package main |
| |
| import ( |
| "errors" |
| "flag" |
| "fmt" |
| "io" |
| "log" |
| "os" |
| "sort" |
| "strconv" |
| "strings" |
| |
| "github.com/bazelbuild/bazel-gazelle/repo" |
| "github.com/bazelbuild/bazel-gazelle/rule" |
| "github.com/bazelbuild/buildtools/build" |
| ) |
| |
| const ( |
| _name = "override-generator" |
| _usage = "usage: This script converts `go_repository` rules to Gazelle `go_deps` overrides to assist in the migration to Bzlmod." |
| ) |
| |
| // override kinds. |
| const ( |
| _goDepsf = `go_deps = use_extension("%s//:extensions.bzl", "go_deps")` |
| _gazelleOverride = "go_deps.gazelle_override" |
| _archiveOverride = "go_deps.archive_override" |
| _moduleOverride = "go_deps.module_override" |
| ) |
| |
| // attribute constants that are used multiple times. |
| const ( |
| _buildFileGenerationAttr = "build_file_generation" |
| _buildFileProtoModeAttr = "build_file_proto_mode" |
| _patchArgsAttr = "patch_args" |
| _buildDirectivesAttr = "build_directives" |
| _directivesAttr = "directives" |
| _buildFileProtoModeDefault = "default" |
| _buildFileGenerationModeDefault = "auto" |
| ) |
| |
| var _mapAttrToOverride = map[string]string{ |
| _buildDirectivesAttr: _gazelleOverride, |
| _buildFileGenerationAttr: _gazelleOverride, |
| _patchArgsAttr: _moduleOverride, |
| "patches": _moduleOverride, |
| "build_extra_args": _gazelleOverride, |
| "urls": _archiveOverride, |
| "strip_prefix": _archiveOverride, |
| "sha256": _archiveOverride, |
| } |
| |
| var _attrOverrideKeys = map[string]string{ |
| _buildDirectivesAttr: _directivesAttr, |
| } |
| |
| type overrideSet map[string]*rule.Rule |
| |
| type mainArgs struct { |
| macroPath string |
| workspace string |
| defName string |
| outputFile string |
| gazelleRepoName string |
| |
| defaultBuildFileGeneration string |
| defaultBuildFileProtoMode string |
| } |
| |
| func main() { |
| a, err := parseArgs(os.Stderr, os.Args[1:]) |
| if err != nil && !errors.Is(err, flag.ErrHelp) { |
| log.Fatalf("%+v", err) |
| } |
| if err := run(*a, os.Stderr); err != nil { |
| log.Fatal(err) |
| } |
| } |
| |
| func parseArgs(stderr io.Writer, osArgs []string) (*mainArgs, error) { |
| a := &mainArgs{} |
| flag := flag.NewFlagSet(_name, flag.ContinueOnError) |
| flag.SetOutput(stderr) |
| flag.Usage = func() { |
| fmt.Fprintf(flag.Output(), _usage) |
| flag.PrintDefaults() |
| } |
| flag.StringVar(&a.macroPath, "macro", "", "path to the macro file") |
| flag.StringVar(&a.workspace, "workspace", "", "path to workspace") |
| flag.StringVar(&a.defName, "def_name", "", "name of the macro definition") |
| flag.StringVar(&a.outputFile, "output", "", "path to the output file") |
| flag.StringVar(&a.gazelleRepoName, "gazelle_repo_name", "@bazel_gazelle", "name of the gazelle repo to load go_deps, (default: @bazel_gazelle)") |
| flag.StringVar(&a.defaultBuildFileGeneration, "default_build_file_generation", "auto", "the default value for build_file_generation attribute") |
| flag.StringVar(&a.defaultBuildFileProtoMode, "default_build_file_proto_mode", "default", "the default value for build_file_proto_mode attribute") |
| flag.Parse(osArgs) |
| |
| if a.macroPath != "" && a.workspace != "" { |
| return nil, fmt.Errorf("only one of -macro or -workspace can be specified") |
| } |
| if a.macroPath == "" && a.workspace == "" { |
| return nil, fmt.Errorf("missing required flag: -macro or -workspace") |
| } |
| if a.macroPath != "" && a.defName == "" { |
| return nil, fmt.Errorf("missing required flag: -def_name when -macro is specified") |
| } |
| if a.outputFile == "" { |
| return nil, fmt.Errorf("missing required flag: -output") |
| } |
| return a, nil |
| } |
| |
| func run(a mainArgs, stderr io.Writer) error { |
| var w *rule.File |
| var err error |
| if a.macroPath != "" { |
| w, err = rule.LoadMacroFile(a.macroPath, "", a.defName) |
| if err != nil { |
| return err |
| } |
| } else { |
| w, err = rule.LoadWorkspaceFile(a.workspace, "") |
| if err != nil { |
| return err |
| } |
| } |
| repos, _, err := repo.ListRepositories(w) |
| if err != nil { |
| return err |
| } |
| sort.Slice(repos, func(i, j int) bool { |
| return repos[i].Name() < repos[j].Name() |
| }) |
| |
| var outputOverrides []*rule.Rule |
| |
| // Iterate over all repositories and convert them to override rules |
| // The repos are ordered by "name", and the sets are sorted, so the output |
| // will be deterministic. |
| for _, r := range repos { |
| if r.Kind() == "go_repository" { |
| repoOverrides := goRepositoryToOverrideSet(r, a.defaultBuildFileGeneration, a.defaultBuildFileProtoMode) |
| outputOverrides = append(outputOverrides, setToOverridesSlice(repoOverrides)...) |
| } |
| } |
| |
| if len(outputOverrides) == 0 { |
| fmt.Fprintln(stderr, "no overrides are needed for these repos!") |
| return nil |
| } |
| |
| f, err := rule.LoadData(a.outputFile, "", []byte(fmt.Sprintf(_goDepsf, a.gazelleRepoName))) |
| if err != nil { |
| return err |
| } |
| for _, o := range outputOverrides { |
| o.Insert(f) |
| } |
| |
| if err := f.Save(a.outputFile); err != nil { |
| return fmt.Errorf("error saving file: %w", err) |
| } |
| |
| return nil |
| } |
| |
| func goRepositoryToOverrideSet(r *rule.Rule, defaultBuildFileGeneration, defaultBuildFileProtoMode string) overrideSet { |
| // each repo has its own override set, and can't have multiple |
| // duplicate overrides. This set is created to be populated and read |
| set := make(overrideSet) |
| importPath := r.AttrString("importpath") |
| |
| // Load the attribute keys from the rule. |
| attrs := r.AttrKeys() |
| for _, attr := range attrs { |
| if _, ok := _mapAttrToOverride[attr]; !ok { |
| continue |
| } |
| |
| attrValue := r.Attr(attr) |
| |
| // proto mode and build file generation require special handling. |
| if attrValue == nil || attr == _buildFileProtoModeAttr || attr == _buildFileGenerationAttr { |
| continue |
| } |
| |
| kind := _mapAttrToOverride[attr] |
| override := rule.NewRule(kind, "") |
| if o, ok := set[kind]; ok { |
| override = o |
| } |
| override.SetAttr("path", importPath) |
| val := r.Attr(attr) |
| |
| // Special case for certain renamed attributes like "build_directives" |
| // attribute to convert to "directives" attribute. |
| if k, ok := _attrOverrideKeys[attr]; ok { |
| attr = k |
| } |
| |
| if val != nil { |
| switch v := val.(type) { |
| case *build.StringExpr: |
| override.SetAttr(attr, v) |
| case *build.ListExpr: |
| // Special case for "patch_args" attribute to convert to |
| // "patch_strip" attribute. |
| if attr == _patchArgsAttr { |
| setPatchArgs(r.AttrStrings(_patchArgsAttr), override) |
| } else { |
| override.SetAttr(attr, v) |
| } |
| } |
| } |
| |
| set[kind] = override |
| } |
| |
| // If the user default doesn't match the global default, but there's a gazelle override, we need to still apply |
| // it to the individual overrides. |
| // Also, since "build_file_proto_mode" is added to the "directives", we need |
| // to apply it last to make sure "directives" is set. |
| applyBuildFileGeneration(r, set, defaultBuildFileGeneration) |
| applyBuildFileProtoMode(r, set, defaultBuildFileProtoMode, defaultBuildFileGeneration) |
| return set |
| } |
| |
| func applyBuildFileGeneration(r *rule.Rule, set overrideSet, userDefaultGeneration string) { |
| ruleGeneration := r.AttrString(_buildFileGenerationAttr) |
| o, ok := set[_gazelleOverride] |
| if !ok { |
| if ruleGeneration == "" || ruleGeneration == userDefaultGeneration { |
| return |
| } |
| set[_gazelleOverride] = newGenerationOverride(r.AttrString("importpath"), ruleGeneration) |
| return |
| } |
| |
| if ruleGeneration == "" { |
| ruleGeneration = userDefaultGeneration |
| } |
| |
| o.SetAttr(_buildFileGenerationAttr, ruleGeneration) |
| set[_gazelleOverride] = o |
| return |
| } |
| |
| |
| func newGenerationOverride(path, ruleGeneration string) *rule.Rule { |
| override := rule.NewRule(_gazelleOverride, "") |
| override.SetAttr("path", path) |
| override.SetAttr(_buildFileGenerationAttr, ruleGeneration) |
| return override |
| } |
| |
| func applyBuildFileProtoMode(r *rule.Rule, set overrideSet, userDefaultProtoMode, userDefaultGeneration string) { |
| protoMode := r.AttrString(_buildFileProtoModeAttr) |
| |
| // If the gazelle_override doesn't exist. We only need to apply the proto mode |
| // if it does not match the user default proto mode. |
| gazelleOverride, ok := set[_gazelleOverride] |
| if !ok { |
| if protoMode == "" || protoMode == userDefaultProtoMode { |
| return |
| } |
| |
| set[_gazelleOverride] = newProtoOverride(r.AttrString("importpath"), protoMode) |
| |
| // Since it's a new override, we need to apply build_file_generation again. |
| applyBuildFileGeneration(r, set, userDefaultGeneration) |
| return |
| } |
| |
| // If the gazelle_override exists, we should apply the override anyway since |
| // the tag overwrites the defaults. |
| if protoMode == "" { |
| protoMode = userDefaultProtoMode |
| } |
| |
| safeAppendDirective(gazelleOverride, "gazelle:proto " + protoMode) |
| set[_gazelleOverride] = gazelleOverride |
| return |
| } |
| |
| func newProtoOverride(path, protoMode string) *rule.Rule { |
| override := rule.NewRule(_gazelleOverride, "") |
| override.SetAttr("path", path) |
| directives := []string{"gazelle:proto " + protoMode} |
| override.SetAttr(_directivesAttr, directives) |
| return override |
| } |
| |
| func safeAppendDirective(gazelleOverride *rule.Rule, directive string) { |
| directives := gazelleOverride.AttrStrings(_directivesAttr) |
| directiveMap := make(map[string]struct{}) |
| for _, d := range directives{ |
| directiveMap[d] = struct{}{} |
| } |
| if _, ok := directiveMap[directive]; ok { |
| return |
| } |
| directives = append(directives, directive) |
| gazelleOverride.SetAttr(_directivesAttr, directives) |
| } |
| |
| func setPatchArgs(patchArgs []string, override *rule.Rule) { |
| for _, arg := range patchArgs { |
| if !strings.HasPrefix(arg, "-p") { |
| continue |
| } |
| numStr := strings.TrimPrefix(arg, "-p") |
| if num, err := strconv.Atoi(numStr); err == nil { |
| override.SetAttr("patch_strip", num) |
| return |
| } |
| } |
| } |
| |
| func setToOverridesSlice(set overrideSet) []*rule.Rule { |
| // Check if both archive and module overrides exist |
| if archiveOverride, archiveExists := set[_archiveOverride]; archiveExists { |
| if moduleOverride, moduleExists := set[_moduleOverride]; moduleExists { |
| // Merge attributes from module override into archive override |
| mergeAttributes(moduleOverride, archiveOverride) |
| // Remove the module override as its attributes are now merged |
| delete(set, _moduleOverride) |
| } |
| } |
| |
| // Create a sorted slice of the remaining override keys |
| var keys []string |
| for k := range set { |
| keys = append(keys, k) |
| } |
| sort.Strings(keys) |
| |
| // Create a slice of overrides based on the sorted keys |
| var overrides []*rule.Rule |
| for _, k := range keys { |
| overrides = append(overrides, set[k]) |
| } |
| return overrides |
| } |
| |
| func mergeAttributes(source, destination *rule.Rule) { |
| for _, attr := range source.AttrKeys() { |
| if val := source.Attr(attr); val != nil { |
| destination.SetAttr(attr, val) |
| } |
| } |
| } |