Invoking a rule should return a label.
Description of the feature request:
I'd like for invoking a rule to return a label. This should be relatively trivial, but may be complicated by the fact that this is exactly what you want in a .bzl file, but probably not what you want in a BUILD file most of the time.
This may need to be accompanied by some kind of lint to ensure you don't use the return value in BUILD files, only in .bzl files.
Which category does this issue belong to?
Rules API
What underlying problem are you trying to solve with this feature?
When writing a macro, typically you write it like so:
def my_macro(name):
rule_foo(
name = "_%s_foo" % name,
...
)
rule_bar(
name = name,
foo = "_%s_foo" % name
)
This can, of course, be written instead as:
def my_macro(name):
foo_name = "_%s_foo" % name
rule_foo(
name = foo_name,
...
)
rule_bar(
name = name,
foo = foo_name
)
However, the former is prone to bugs, since you have to rewrite a string, and the latter is more verbose and less clear, as bazel users expect to be able to just look at the name field directly.
I think that the simplest and nicest API would simply be the following:
def my_macro(name):
foo_label = rule_foo(
name = "_%s_foo" % name,
...
)
rule_bar(
name = name,
foo = foo_label
)
Which operating system are you running Bazel on?
Linux
What is the output of bazel info release?
7.2.0
If bazel info release returns development version or (@non-git), tell us how you built Bazel.
No response
What's the output of git remote get-url origin; git rev-parse HEAD ?
No response
Have you found anything relevant by searching the web?
No response
Any other information, logs, or outputs that you want to share?
No response
One caveat: This would turn converting a rule into a macro a breaking change unless this behavior is taken into account.
cc @comius @brandjon
@fmeum I love that you brought up migration concerns. We do want rules to be substitutable by macros generally, and are concerned about designs that make this more difficult.
But in this case, wouldn't the answer be that when writing a new macro to replace a rule, you have the macro return the label of the main target?
I think the incompatibility to consider is existing macros that have None returns. Also there's the arbitrariness of getting the label of the main target out of the macro, but not other targets it might produce -- that makes the main target more central to the idea of a macro than it was previously.
But in this case, wouldn't the answer be that when writing a new macro to replace a rule, you have the macro return the label of the main target?
Yes, it's easy to preserve the behavior, but macro authors need to be aware of it to preserve it. I'm in favor of this feature nonetheless, it removes a lot of boilerplate from macros.
I guess instead of viewing this as privileging the main target over other macro targets, it's just a reflection of the fact that the UI the user sees is "I pass foo to name, so I expect //pkg:foo back".
@comius recently proposed that symbolic macros do something to allow omitting the macro's name (as a prefix) from the strings passed to name and other attrs. I viewed this as too magical. But it would be less magical if we just used the return value of a target/macro call. What's more, it seems straightforwardly backwards compatible from the caller's pov if the macro behaves correctly.
For symbolic macros it would be easy to make them behave the same as rules and just return a label, with no intervention (or ability to mess it up) by the macro author. So I think the only real concerns here are:
- Do we want to invent a new way to write build files / macros
- How do we prevent legacy macros from messing this up for the user (either by being too clever and intentionally doing something fancy besides returning the label of the main target, or by simply not upgrading to the new convention)?
Or to put it another way, do we really want to make the user responsible for understanding whether each thing they're calling behaves this way or not?
- Do we want to invent a new way to write build files / macros
IMO, we do want to invent a new way to write macros, but I think that a new way to write BUILD files would concern me. My biggest concern with this is that the following BUILD file now becomes valid (maybe we just trust our users not to do this? I proposed a lint for it to avoid it, so that users could explicitly override it since in rare occasions this kind of thing may be useful.).
cc_binary(
name = "main",
srcs = ["main.cc"],
deps = [
cc_library(
name = "lib",
srcs = ["lib.cc"],
)
]
)
- How do we prevent legacy macros from messing this up for the user (either by being too clever and intentionally doing something fancy besides returning the label of the main target, or by simply not upgrading to the new convention)?
Or to put it another way, do we really want to make the user responsible for understanding whether each thing they're calling behaves this way or not?
While what you're saying is a concern, I'd like to add that there is a very specific set of conditions required for this to be a concern.
- If I call a macro from a BUILD file, I don't need to care about this, so this only matters for macro authors, not for regular users.
- If I'm only creating a single target, it's not a concern at all for me. Just write
return other_macro(...)and then we get the changed convention when they change the convention. - If I have control over the macro that I call (eg. the macro is in
rules_fooand I'm writing code inrules_foo), it's not too much of a concern
FWIW, I really like the changes this would allow to symbolic macros. I don't personally take too much of an issue with the "if the macro behaves correctly" part, but if it doesn't, I added a proposal to the doc. I also would much prefer if we didn't do the whole "main" thing, and instead macros would have to return the label to their primary rule.
@comius recently proposed that symbolic macros do something to allow omitting the macro's name (as a prefix) from the strings passed to name and other attrs. I viewed this as too magical.
I can't fight the "too magical" argument. What I proposed are macro relative labels. I believe this has a better usability and solves @matts1 problem without returning labels. At the same time is still declarative as in each target can be evaluated on its own.
The example simply translates into:
@Macro
def my_macro(name):
rule_foo(
name = "_foo",
...
)
rule_bar(
name = name,
foo = "_foo"
)
It also creates a symmetry with BUILD files, that already can use relative labels. Is that also too magical?
The moment you start returning labels, you've lost declarative properties.
The moment you start returning labels, you've lost declarative properties.
I agree with this statement, which is why I think a distinction needs to be made between build and bzl files. IMO build files should be declarative, while bzl files are not. Don't know if that's a controversial take.
I personally feel that build files that used the return values would be mostly bad, while bzl files would be much cleaner that way