Skip to content

Adding an example for setting multiple fields based on one flag #200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
11 changes: 11 additions & 0 deletions rules/starlark_configurations/multi_field_string_flag/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
load (":defs.bzl", "ice_cream", "flavor", "flavors", "flavor_flag")

flavor_flag(name = "flavor_flag", build_setting_default = "vanilla")

flavors(name = "flavors", flavors = [":grape", ":cherry", ":vanilla"])

flavor(name = "vanilla", color = "white")
flavor(name = "grape", color = "purple", type = "sugar-free")
flavor(name = "cherry", color = "red")

ice_cream(name = "ice_cream", flavors = ":flavors")
10 changes: 10 additions & 0 deletions rules/starlark_configurations/multi_field_string_flag/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
This is an example of encoding multiple fields into a single string flag. It makes use of starlark aspects to lookup several
fields based on the value of the flag.

This is useful if you have several settings that are interdependent and you don't want the user to need to define them all on command line.

To test it out, cd to this directory and run the following:
```
$ bazel build :ice_cream # => "vanilla is white"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate the output into distinct lines to look more realistic?

$ bazel build :ice_cream --//starlark_configurations/multi_field_string_flag:flavor_flag=grape # => "sugar-free grape is purple"
```
81 changes: 81 additions & 0 deletions rules/starlark_configurations/multi_field_string_flag/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# example/buildsettings/build_settings.bzl
FlavorsProvider = provider(fields = ['flavors'])

def _flavors_impl(ctx):
return FlavorsProvider(flavors = ctx.attr.flavors)

flavors = rule(
implementation = _flavors_impl,
attrs = {
"flavors": attr.label_list(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For completeness, I think attr can restrict allowed deps to only those that provide FlavorProvider, to be more explicit that this is only intended to be consumed by flavor instances.

}
)

FlavorProvider = provider(fields = ['flavor', 'color', 'type'])

def _flavor_impl(ctx):
return FlavorProvider(flavor = ctx.attr.name, color = ctx.attr.color, type = ctx.attr.type)

flavor = rule(
implementation = _flavor_impl,
attrs = {
"color": attr.string(),
"type": attr.string(default = "undefined"),
}
)

FlavorFlagProvider = provider(fields = ['flavor'])

def _flavor_flag_impl(ctx):
# use `ctx.build_setting_value` to access the raw value
# of this build setting. This value is either derived from
# the default value set on the target or from the setting
# being set somewhere on the command line/in a transition, etc.
raw_flavor = ctx.build_setting_value

# Returns a provider like a normal rule
return FlavorFlagProvider(flavor = raw_flavor)

flavor_flag = rule(
implementation = _flavor_flag_impl,
# This line separates a build setting from a regular target, by using
# the `build_setting` atttribute, you mark this rule as a build setting
# including what raw type it is and if it can be used on the command
# line or not (if yes, you must set `flag = True`)
build_setting = config.string(flag = True)
)

FlavorAspectProvider = provider(fields = ['flavor', 'color', 'type'])

def _aspect_impl(target, ctx):
raw_color = "undefined"
raw_type = "undefined"
raw_flavor = "undefined"
if hasattr(ctx.rule.attr, 'flavors'):
for flavor in ctx.rule.attr.flavors:
if flavor[FlavorProvider].flavor == ctx.attr._flavor[FlavorFlagProvider].flavor:
raw_flavor = flavor[FlavorProvider].flavor
raw_color = flavor[FlavorProvider].color
raw_type = flavor[FlavorProvider].type
return [FlavorAspectProvider(flavor = raw_flavor, color = raw_color, type = raw_type)]

flavor_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = ["flavors"],
# Passing the build setting flag here makes the string available to the aspect
attrs = {
"_flavor": attr.label(default = ":flavor_flag")
}
)

def _impl(ctx):
print (ctx.attr.flavors[FlavorAspectProvider].type if ctx.attr.flavors[FlavorAspectProvider].type != "undefined" else "",
ctx.attr.flavors[FlavorAspectProvider].flavor + " is " + ctx.attr.flavors[FlavorAspectProvider].color)
return []

ice_cream = rule(
implementation = _impl,
attrs = {
"flavors": attr.label( aspects = [flavor_aspect]),
},
)