bazel icon indicating copy to clipboard operation
bazel copied to clipboard

Invoking a rule should return a label.

Open matts1 opened this issue 1 year ago • 8 comments

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

matts1 avatar Jul 31 '24 00:07 matts1

One caveat: This would turn converting a rule into a macro a breaking change unless this behavior is taken into account.

fmeum avatar Jul 31 '24 09:07 fmeum

cc @comius @brandjon

meisterT avatar Jul 31 '24 11:07 meisterT

@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.

brandjon avatar Jul 31 '24 15:07 brandjon

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.

fmeum avatar Jul 31 '24 15:07 fmeum

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:

  1. Do we want to invent a new way to write build files / macros
  2. 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?

brandjon avatar Jul 31 '24 17:07 brandjon

  1. 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"],
        )
    ]
)
  1. 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_foo and I'm writing code in rules_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.

matts1 avatar Aug 01 '24 01:08 matts1

@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.

comius avatar Aug 26 '24 09:08 comius

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

matts1 avatar Aug 26 '24 10:08 matts1