Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions edit/buildozer.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,85 @@ func cmdSetSelect(opts *Options, env CmdEnvironment) (*build.File, error) {
return env.File, nil
}

// addToListSelect adds value under key in the last select() of a list attribute,
// creating a new bare select if none exists, or concatenating one if the attribute
// is already a plain list.
func addToListSelect(r *build.Rule, attr, pkg, key string, value build.Expr) {
existing := r.Attr(attr)
selects := AllSelects(existing)

if len(selects) > 0 {
last := selects[len(selects)-1]
dict := last.List[0].(*build.DictExpr)
cur := DictionaryGet(dict, key)
if cur != nil {
DictionarySet(dict, key, AddValueToList(cur, pkg, value, false))
Comment thread
kisabaka marked this conversation as resolved.
Outdated
} else {
DictionarySet(dict, key, &build.ListExpr{List: []build.Expr{value}})
if DictionaryGet(dict, "//conditions:default") == nil {
DictionarySet(dict, "//conditions:default", &build.ListExpr{})
}
}
r.SetAttr(attr, existing)
return
}

dict := &build.DictExpr{
List: []*build.KeyValueExpr{
{Key: &build.StringExpr{Value: key}, Value: &build.ListExpr{List: []build.Expr{value}}},
{Key: &build.StringExpr{Value: "//conditions:default"}, Value: &build.ListExpr{}},
},
}
sel := &build.CallExpr{List: []build.Expr{dict}}
sel.X = &build.Ident{Name: "select"}
if existing == nil {
r.SetAttr(attr, sel)
} else {
r.SetAttr(attr, &build.BinaryExpr{Op: "+", X: existing, Y: sel})
}
}

// addToScalarSelect sets value under key in the last select() of a scalar attribute,
// creating a new select if none exists. An existing key is overwritten.
func addToScalarSelect(r *build.Rule, attr, key string, value build.Expr) {
existing := r.Attr(attr)
selects := AllSelects(existing)

if len(selects) > 0 {
last := selects[len(selects)-1]
dict := last.List[0].(*build.DictExpr)
DictionarySet(dict, key, value)
r.SetAttr(attr, existing)
return
}
dict := &build.DictExpr{
List: []*build.KeyValueExpr{
{Key: &build.StringExpr{Value: key}, Value: value},
},
}
sel := &build.CallExpr{List: []build.Expr{dict}}
sel.X = &build.Ident{Name: "select"}
r.SetAttr(attr, sel)
}

func cmdAddSelect(opts *Options, env CmdEnvironment) (*build.File, error) {
attr := env.Args[0]
key := env.Args[1]
values := env.Args[2:]

if IsList(attr) {
for _, val := range values {
addToListSelect(env.Rule, attr, env.Pkg, key, getStringExpr(val, env.Pkg))
}
} else {
if len(values) != 1 {
return nil, fmt.Errorf("add_select: scalar attribute %q requires exactly one value", attr)
}
addToScalarSelect(env.Rule, attr, key, getStringExpr(values[0], env.Pkg))
}
return env.File, nil
}

// cmdDictSet adds a key to a dict, overwriting any previous values.
func cmdDictSet(opts *Options, env CmdEnvironment) (*build.File, error) {
attr := env.Args[0]
Expand Down Expand Up @@ -926,6 +1005,7 @@ type CommandInfo struct {
// of arguments.
var AllCommands = map[string]CommandInfo{
"add": {cmdAdd, true, 2, -1, "<attr> <value(s)>"},
"add_select": {cmdAddSelect, true, 3, -1, "<attr> <key> <value(s)>"},
"new_load": {cmdNewLoad, false, 1, -1, "<path> <[to=]from(s)>"},
"replace_load": {cmdReplaceLoad, false, 1, -1, "<path> <[to=]symbol(s)>"},
"substitute_load": {cmdSubstituteLoad, false, 2, 2, "<old_regexp> <new_template>"},
Expand Down
274 changes: 274 additions & 0 deletions edit/buildozer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,280 @@ func TestCmdSetSelect(t *testing.T) {
}
}

func TestCmdAddSelect_listValues(t *testing.T) {
for _, tc := range []struct {
name string
args []string
buildFile string
expected string
}{
{
name: "list_attr_absent",
args: []string{"deps", ":added_select_key", "//some_package:lib"},
buildFile: `foo(
name = "foo",
)`,
expected: `foo(
name = "foo",
deps = select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": [],
}),
)`,
},
{
name: "list_attr_plain_list",
args: []string{"deps", ":added_select_key", "//some_package:lib"},
buildFile: `foo(
name = "foo",
deps = ["//base:lib"],
)`,
expected: `foo(
name = "foo",
deps = ["//base:lib"] + select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": [],
}),
)`,
},
{
name: "list_attr_pure_select_new_key",
args: []string{"deps", ":second_select_key", "//my_path:lib"},
buildFile: `foo(
name = "foo",
deps = select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": [],
}),
)`,
expected: `foo(
name = "foo",
deps = select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": [],
":second_select_key": ["//my_path:lib"],
}),
)`,
},
{
name: "list_attr_pure_select_existing_key",
args: []string{"deps", ":added_select_key", "//some_package:lib2"},
buildFile: `foo(
name = "foo",
deps = select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": [],
}),
)`,
expected: `foo(
name = "foo",
deps = select({
":added_select_key": [
"//some_package:lib",
"//some_package:lib2",
],
"//conditions:default": [],
}),
)`,
},
{
name: "list_attr_list_plus_select_new_key",
args: []string{"deps", ":second_select_key", "//my_path:lib"},
buildFile: `foo(
name = "foo",
deps = ["//base:lib"] + select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": [],
}),
)`,
expected: `foo(
name = "foo",
deps = ["//base:lib"] + select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": [],
":second_select_key": ["//my_path:lib"],
}),
)`,
},
{
name: "list_attr_list_plus_select_existing_key",
args: []string{"deps", ":added_select_key", "//some_package:lib2"},
buildFile: `foo(
name = "foo",
deps = ["//base:lib"] + select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": [],
}),
)`,
expected: `foo(
name = "foo",
deps = ["//base:lib"] + select({
":added_select_key": [
"//some_package:lib",
"//some_package:lib2",
],
"//conditions:default": [],
}),
)`,
},
{
name: "list_attr_duplicate_value_is_noop",
args: []string{"deps", ":added_select_key", "//some_package:lib"},
buildFile: `foo(
name = "foo",
deps = ["//base:lib"] + select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": [],
}),
)`,
expected: `foo(
name = "foo",
deps = ["//base:lib"] + select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": [],
}),
)`,
},
{
name: "list_attr_multiple_values",
args: []string{"deps", ":added_select_key", "//some_package:lib1", "//some_package:lib2"},
buildFile: `foo(
name = "foo",
deps = ["//base:lib"],
)`,
expected: `foo(
name = "foo",
deps = ["//base:lib"] + select({
":added_select_key": [
"//some_package:lib1",
"//some_package:lib2",
],
"//conditions:default": [],
}),
)`,
},
{
name: "list_attr_new_key_no_default_added_when_default_exists",
args: []string{"deps", ":second_select_key", "//my_path:lib"},
buildFile: `foo(
name = "foo",
deps = select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": ["//fallback:lib"],
}),
)`,
expected: `foo(
name = "foo",
deps = select({
":added_select_key": ["//some_package:lib"],
"//conditions:default": ["//fallback:lib"],
":second_select_key": ["//my_path:lib"],
}),
)`,
},
} {
t.Run(tc.name, func(t *testing.T) {
bld, err := build.Parse("BUILD", []byte(tc.buildFile))
if err != nil {
t.Error(err)
}
rl := bld.Rules("foo")[0]
env := CmdEnvironment{
File: bld,
Rule: rl,
Args: tc.args,
}
bld, err = cmdAddSelect(NewOpts(), env)
if err != nil {
t.Fatalf("cmdAddSelect returned err: %s", err)
}
got := strings.TrimSpace(string(build.Format(bld)))
if got != tc.expected {
t.Errorf("cmdAddSelect %v:\ngot:\n%s\nexpected:\n%s", tc.args, got, tc.expected)
}
})
}
}

func TestCmdAddSelect_scalarValues(t *testing.T) {
for _, tc := range []struct {
name string
args []string
buildFile string
expected string
}{
{
name: "scalar_attr_absent",
args: []string{"deprecation", ":added_select_key", "deprecated on some_package"},
buildFile: `foo(
name = "foo",
)`,
expected: `foo(
name = "foo",
deprecation = select({":added_select_key": "deprecated on some_package"}),
)`,
},
{
name: "scalar_attr_pure_select_new_key",
args: []string{"deprecation", ":second_select_key", "deprecated on my_path"},
buildFile: `foo(
name = "foo",
deprecation = select({
":added_select_key": "deprecated on some_package",
"//conditions:default": "not deprecated",
}),
)`,
expected: `foo(
name = "foo",
deprecation = select({
":added_select_key": "deprecated on some_package",
"//conditions:default": "not deprecated",
":second_select_key": "deprecated on my_path",
}),
)`,
},
{
name: "scalar_attr_pure_select_existing_key_overwrites",
args: []string{"deprecation", ":added_select_key", "updated message"},
buildFile: `foo(
name = "foo",
deprecation = select({
":added_select_key": "old message",
"//conditions:default": "not deprecated",
}),
)`,
expected: `foo(
name = "foo",
deprecation = select({
":added_select_key": "updated message",
"//conditions:default": "not deprecated",
}),
)`,
},
} {
t.Run(tc.name, func(t *testing.T) {
bld, err := build.Parse("BUILD", []byte(tc.buildFile))
if err != nil {
t.Error(err)
}
rl := bld.Rules("foo")[0]
env := CmdEnvironment{
File: bld,
Rule: rl,
Args: tc.args,
}
bld, err = cmdAddSelect(NewOpts(), env)
if err != nil {
t.Fatalf("cmdAddSelect returned err: %s", err)
}
got := strings.TrimSpace(string(build.Format(bld)))
if got != tc.expected {
t.Errorf("cmdAddSelect %v:\ngot:\n%s\nexpected:\n%s", tc.args, got, tc.expected)
}
})
}
}

func TestExecuteCommandsOnInlineFile(t *testing.T) {
tests := []struct {
name string
Expand Down