Skip to content

Commit

Permalink
Add use_repo_add and use_repo_remove commands (#1152)
Browse files Browse the repository at this point in the history
* Preserve compactness of `use_repo` and `use_extension` calls

* Add `use_repo_add` and `use_repo_remove` commands

These commands can be used to programmatically update `use_repo` calls
in MODULE.bazel files. In the future, Bazel will emit buildozer fixups
relying on this command when it detects that such calls are out-of-date.

* Adapt label normalization to be closer to upstream

* Fix CI
  • Loading branch information
fmeum authored Apr 12, 2023
1 parent 7ca77e1 commit a3f457d
Show file tree
Hide file tree
Showing 11 changed files with 1,056 additions and 9 deletions.
13 changes: 12 additions & 1 deletion build/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,17 @@ func isBazelDep(x Expr) bool {
return false
}

func isUseRepoOrUseExtension(x Expr) bool {
call, ok := x.(*CallExpr)
if !ok {
return false
}
if ident, ok := call.X.(*Ident); ok && (ident.Name == "use_repo" || ident.Name == "use_extension") {
return true
}
return false
}

func isModuleOverride(x Expr) bool {
call, ok := x.(*CallExpr)
if !ok {
Expand Down Expand Up @@ -751,7 +762,7 @@ func (p *printer) expr(v Expr, outerPrec int) {

case *CallExpr:
forceCompact := v.ForceCompact
if p.fileType == TypeModule && isBazelDep(v) {
if p.fileType == TypeModule && (isBazelDep(v) || isUseRepoOrUseExtension(v)) {
start, end := v.Span()
forceCompact = start.Line == end.Line
}
Expand Down
4 changes: 2 additions & 2 deletions buildifier/integration_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ use_repo(
"bazel_gazelle_go_repository_config",
"bazel_gazelle_go_repository_cache",
)
rules_go_non_module_deps = use_extension("@io_bazel_rules_go//go/private:extensions.bzl","non_module_dependencies")
rules_go_non_module_deps = use_extension("@io_bazel_rules_go//go/private:extensions.bzl","non_module_dependencies",dev_dependency=True)
use_repo(rules_go_non_module_deps,"go_googleapis")
go_deps = use_extension("//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
Expand Down Expand Up @@ -180,7 +180,7 @@ use_repo(
"bazel_gazelle_go_repository_tools",
)
rules_go_non_module_deps = use_extension("@io_bazel_rules_go//go/private:extensions.bzl", "non_module_dependencies")
rules_go_non_module_deps = use_extension("@io_bazel_rules_go//go/private:extensions.bzl", "non_module_dependencies", dev_dependency = True)
use_repo(rules_go_non_module_deps, "go_googleapis")
go_deps = use_extension("//:extensions.bzl", "go_deps")
Expand Down
11 changes: 11 additions & 0 deletions buildozer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ Buildozer supports the following commands(`'command args'`):
* `dict_list_add <attr> <key> <value(s)>`: Adds value(s) to the list in the
dict attribute `attr`.

The following commands only apply to `MODULE.bazel` files (e.g. the target
`//MODULE.bazel:__pkg__`):
* `use_repo_add [dev] <extension .bzl file> <extension name> <repo(s)>`:
Ensures that the given repositories generated by the given extension are
imported via `use_repo`. If the `dev` argument is given, extension usages
with `dev_dependency = True` will be considered instead.
* `use_repo_remove [dev] <extension .bzl file> <extension name> <repo(s)>`:
Ensures that the given repositories generated by the given extension are
*not* imported via `use_repo`. If the `dev` argument is given, extension
usages with `dev_dependency = True` will be considered instead.

Here, `<attr>` represents an attribute (being `add`ed/`rename`d/`delete`d etc.),
e.g.: `srcs`, `<value(s)>` represents values of the attribute and so on.
A '?' indicates that the preceding argument is optional.
Expand Down
170 changes: 170 additions & 0 deletions buildozer/buildozer_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2059,5 +2059,175 @@ EOF
diff -u MODULE.bazel.expected MODULE.bazel || fail "Output didn't match"
}

function test_use_repo_add() {
cat > MODULE.bazel <<EOF
module(
name = "foo",
version = "0.27.0",
)
bazel_dep(name = "gazelle", version = "0.30.0")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "com_example_foo")
go_dev_deps = use_extension("@gazelle//:extensions.bzl", "go_deps", dev_dependency = True)
go_dev_deps.from_file(go_mod = "//:go_dev.mod")
EOF

cat > MODULE.bazel.expected <<EOF
module(
name = "foo",
version = "0.27.0",
)
bazel_dep(name = "gazelle", version = "0.30.0")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "com_example_foo", "org_example_bar")
go_dev_deps = use_extension("@gazelle//:extensions.bzl", "go_deps", dev_dependency = True)
go_dev_deps.from_file(go_mod = "//:go_dev.mod")
EOF

$buildozer 'use_repo_add @gazelle//:extensions.bzl go_deps org_example_bar com_example_foo' //MODULE.bazel:all
diff -u MODULE.bazel.expected MODULE.bazel || fail "Output didn't match"
}

function test_use_repo_add_dev() {
cat > MODULE.bazel <<EOF
module(
name = "foo",
version = "0.27.0",
)
bazel_dep(name = "gazelle", version = "0.30.0")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "com_example_foo")
go_dev_deps = use_extension("@gazelle//:extensions.bzl", "go_deps", dev_dependency = True)
go_dev_deps.from_file(go_mod = "//:go_dev.mod")
EOF

cat > MODULE.bazel.expected <<EOF
module(
name = "foo",
version = "0.27.0",
)
bazel_dep(name = "gazelle", version = "0.30.0")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "com_example_foo")
go_dev_deps = use_extension("@gazelle//:extensions.bzl", "go_deps", dev_dependency = True)
go_dev_deps.from_file(go_mod = "//:go_dev.mod")
use_repo(go_dev_deps, "org_example_bar", "org_example_foo")
EOF

$buildozer 'use_repo_add dev @gazelle//:extensions.bzl go_deps org_example_foo org_example_bar' //MODULE.bazel:all
diff -u MODULE.bazel.expected MODULE.bazel || fail "Output didn't match"
}

function test_use_repo_remove() {
cat > MODULE.bazel <<EOF
module(
name = "foo",
version = "0.27.0",
)
bazel_dep(name = "gazelle", version = "0.30.0")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "com_example_foo")
go_dev_deps = use_extension("@gazelle//:extensions.bzl", "go_deps", dev_dependency = True)
go_dev_deps.from_file(go_mod = "//:go_dev.mod")
EOF

cat > MODULE.bazel.expected <<EOF
module(
name = "foo",
version = "0.27.0",
)
bazel_dep(name = "gazelle", version = "0.30.0")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
go_dev_deps = use_extension("@gazelle//:extensions.bzl", "go_deps", dev_dependency = True)
go_dev_deps.from_file(go_mod = "//:go_dev.mod")
EOF

$buildozer 'use_repo_remove @gazelle//:extensions.bzl go_deps bar_example com_example_foo' //MODULE.bazel:all
diff -u MODULE.bazel.expected MODULE.bazel || fail "Output didn't match"
}

function test_use_repo_remove_dev() {
cat > MODULE.bazel <<EOF
module(
name = "foo",
version = "0.27.0",
)
bazel_dep(name = "gazelle", version = "0.30.0")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "com_example_foo")
go_dev_deps = use_extension("@gazelle//:extensions.bzl", "go_deps", dev_dependency = True)
go_dev_deps.from_file(go_mod = "//:go_dev.mod")
use_repo(go_dev_deps, "invalid_foo")
use_repo(
go_dev_deps,
# group these
"org_example_quz",
"org_example_foo",
"org_example_bar",
# and these
"com_example_quz",
my_com_example_baz = "com_example_baz",
"com_example_bar",
)
EOF

cat > MODULE.bazel.expected <<EOF
module(
name = "foo",
version = "0.27.0",
)
bazel_dep(name = "gazelle", version = "0.30.0")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "com_example_foo")
go_dev_deps = use_extension("@gazelle//:extensions.bzl", "go_deps", dev_dependency = True)
go_dev_deps.from_file(go_mod = "//:go_dev.mod")
use_repo(
go_dev_deps,
# group these
"org_example_foo",
"org_example_quz",
# and these
"com_example_bar",
"com_example_quz",
)
EOF

$buildozer 'use_repo_remove dev @gazelle//:extensions.bzl go_deps com_example_baz org_example_bar invalid_foo' //MODULE.bazel:all
diff -u MODULE.bazel.expected MODULE.bazel || fail "Output didn't match"
}

run_suite "buildozer tests"
2 changes: 1 addition & 1 deletion buildozer/npm/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ pkg_npm(
deps = [
"LICENSE",
"buildozer.js",
"index.js",
"index.d.ts",
"index.js",
] + _PARENT_PACKAGE_FILES,
)

Expand Down
1 change: 1 addition & 0 deletions edit/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ go_library(
"//api_proto:go_default_library",
"//build:go_default_library",
"//build_proto:go_default_library",
"//edit/bzlmod:go_default_library",
"//file:go_default_library",
"//labels:go_default_library",
"//lang:go_default_library",
Expand Down
60 changes: 55 additions & 5 deletions edit/buildozer.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (

apipb "github.com/bazelbuild/buildtools/api_proto"
"github.com/bazelbuild/buildtools/build"
"github.com/bazelbuild/buildtools/edit/bzlmod"
"github.com/bazelbuild/buildtools/file"
"github.com/bazelbuild/buildtools/labels"
"github.com/bazelbuild/buildtools/wspace"
Expand Down Expand Up @@ -723,6 +724,50 @@ func copyAttributeBetweenRules(env CmdEnvironment, attrName string, from string)
return env.File, nil
}

func cmdUseRepoAdd(opts *Options, env CmdEnvironment) (*build.File, error) {
return cmdImplUseRepo(env, "use_repo_add")
}

func cmdUseRepoRemove(opts *Options, env CmdEnvironment) (*build.File, error) {
return cmdImplUseRepo(env, "use_repo_remove")
}

func cmdImplUseRepo(env CmdEnvironment, mode string) (*build.File, error) {
if env.File.Type != build.TypeModule {
return nil, fmt.Errorf("%s: only applies to MODULE.bazel files", mode)
}

dev := false
args := env.Args
if env.Args[0] == "dev" {
dev = true
args = env.Args[1:]
}

extBzlFile := args[0]
extName := args[1]

proxies := bzlmod.Proxies(env.File, extBzlFile, extName, dev)
if len(proxies) == 0 {
return nil, fmt.Errorf("%s: no use_extension assignment found for extension %q defined in %q", mode, extName, extBzlFile)
}

useRepos := bzlmod.UseRepos(env.File, proxies)
if len(useRepos) == 0 {
var newUseRepo *build.CallExpr
env.File, newUseRepo = bzlmod.NewUseRepo(env.File, proxies)
useRepos = []*build.CallExpr{newUseRepo}
}

if mode == "use_repo_add" {
bzlmod.AddRepoUsages(useRepos, args[2:]...)
} else {
bzlmod.RemoveRepoUsages(useRepos, args[2:]...)
}

return env.File, nil
}

func cmdFix(opts *Options, env CmdEnvironment) (*build.File, error) {
// Fix the whole file
if env.Rule.Kind() == "package" {
Expand Down Expand Up @@ -769,6 +814,8 @@ var AllCommands = map[string]CommandInfo{
"dict_set": {cmdDictSet, true, 2, -1, "<attr> <(key:value)(s)>"},
"dict_remove": {cmdDictRemove, true, 2, -1, "<attr> <key(s)>"},
"dict_list_add": {cmdDictListAdd, true, 3, -1, "<attr> <key> <value(s)>"},
"use_repo_add": {cmdUseRepoAdd, false, 2, -1, "[dev] <extension .bzl file> <extension name> <repo(s)>"},
"use_repo_remove": {cmdUseRepoRemove, false, 2, -1, "[dev] <extension .bzl file> <extension name> <repo(s)>"},
}

var readonlyCommands = map[string]bool{
Expand Down Expand Up @@ -857,10 +904,10 @@ func SplitOnSpaces(input string) []string {
// parseCommands parses commands and targets they should be applied on from
// a list of arguments.
// Each argument can be either:
// - a command (as defined by AllCommands) and its parameters, separated by
// whitespace
// - a target all commands that are parsed during one call to parseCommands
// should be applied on
// - a command (as defined by AllCommands) and its parameters, separated by
// whitespace
// - a target all commands that are parsed during one call to parseCommands
// should be applied on
func parseCommands(opts *Options, args []string) (commands []command, targets []string, err error) {
for _, arg := range args {
commandTokens := SplitOnSpaces(arg)
Expand Down Expand Up @@ -908,7 +955,9 @@ type rewriteResult struct {

// getGlobalVariables returns the global variable assignments in the provided list of expressions.
// That is, for each variable assignment of the form
// a = v
//
// a = v
//
// vars["a"] will contain the AssignExpr whose RHS value is the assignment "a = v".
func getGlobalVariables(exprs []build.Expr) (vars map[string]*build.AssignExpr) {
vars = make(map[string]*build.AssignExpr)
Expand Down Expand Up @@ -1059,6 +1108,7 @@ func rewrite(opts *Options, commandsForFile commandsForFile) *rewriteResult {
return &rewriteResult{file: name, errs: errs, records: records}
}
f = RemoveEmptyPackage(f)
f = RemoveEmptyUseRepoCalls(f)
ndata, err := buildifier.Buildify(opts, f)
if err != nil {
return &rewriteResult{file: name, errs: []error{fmt.Errorf("running buildifier: %v", err)}, records: records}
Expand Down
22 changes: 22 additions & 0 deletions edit/bzlmod/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
srcs = ["bzlmod.go"],
importpath = "github.com/bazelbuild/buildtools/edit/bzlmod",
visibility = ["//visibility:public"],
deps = [
"//build:go_default_library",
"//labels:go_default_library",
],
)

go_test(
name = "go_default_test",
srcs = ["bzlmod_test.go"],
deps = [
":go_default_library",
"//build:go_default_library",
"//labels:go_default_library",
],
)
Loading

0 comments on commit a3f457d

Please sign in to comment.