blob: 976ec595085d49796bdede94e5f5654ad48ab53e [file] [log] [blame]
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)
}
}
}