-
-
Notifications
You must be signed in to change notification settings - Fork 588
refactor: API for deriving customized versions of the base rules #2610
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
refactor: API for deriving customized versions of the base rules #2610
Conversation
24616e2
to
f934e94
Compare
The diff here is pretty big, so I'll summarize the changes: In attributes.bzl, the In rule_builders.bzl, all the various builder classes to wrap the inputs to rule, aspect, attr.XXX, etc etc are implemented. It's standard "object oriented" starlark code. Most of the rest of the changes are just whitespace, some incidental moving of code, or notes. I put "necessary" in quotes earlier because it's necessary in the sense of providing a mutable object. It's not necessary in the sense of: I think most things about a rule won't be modified. So it feels a bit wasteful. With that in mind, I did prototype another idea: store both a built object and a callable to create a builder for it. e.g. I also prototyped using regular ol' dicts throughout. This largely eliminated the builder wrappers, but dicts also had to have a special I also briefly explored using a visitor pattern, but no, that was really bad UX. There's really no escaping having a function to create "builders" of some sort. Polymorphic attributes (rule.cfg, attr.cfg take several different data types), and things like providers/aspects (lists of objects that aren't easily identified/introspected) were also complications. Those complications eventually lead me to lean more into the builder pattern, my reasoning being: at least we control the API surface and can make it mostly ergonomic. It leaves me pretty unhappy with the internal details of what attributes.bzl looks like, though. A bunch of In the end, I'm sort of happy with this? Not as happy as I was hoping to be. I'm tempted to shelve it and look for another way to expose a way to create a derived rule. Maybe keep the current |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few thoughts reading this.
…o refactor.attr.builders
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sounds like we both like 1a+2b (struct-api with everything mutable by default), so'll i'll clean things up to do it that way.
Thanks for looking and thinking!
…o refactor.attr.builders
…o refactor.attr.builders
f934e94
to
b9902c8
Compare
…o refactor.attr.builders
I expanded this into the struct-with-mutables style API. Notable changes:
The main thing missing now are tests, I think. Unfortunately, I accidentally amended somewhere in my commits and had to force-push, so the diff is "reset". |
I started adding tests and three things became kind of annoying: polymorphic fields, Optional, and Label's allow_files is particularly annoying because its
It took me 3 tries to get that code above right. The The So I'm changing the API and impl to:
In combination, it means being able to do e.g.
And the functions can transparently handle if the previous state was a a bool, function, etc. It also means None becomes the value which means "missing", which feels more natural. e.g. |
…o refactor.attr.builders
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will look at the biggest files later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comments I have are not blocking. Thanks for designing this. This looks like an easy to maintain solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: I started writing docs with some examples for a followup PR, and it looks pretty good. The main shortcoming I see now is that the implementation function for a rule is very opaque (ctx in, list of providers out). So, that'll be the next thing to figure out, but that's largely separate from "how to define a rule".
…o refactor.attr.builders
This implements a "builder style" API to allow arbitrary modification of rule, attr, etc
objects used when defining a rule. The net effect is users are able to use the base
definition for our rules, but define their own with the modifications they need, without
having to copy/paste portions our implementation, load private files, or patch source.
The basic way it works is a mutable object ("builder") holds the args and state that would
be used to create the immutable Bazel object. When
build()
is called, the immutableBazel object (e.g.
attr.string()
) is created. Builders are implemented for most objectsand their settings (rule, attrs, and supporting objects).
This design is necessary because of three Bazel behaviors:
creating the immutable objects.
of any mutable object must be done at the point of use.
The resulting API resembles the builder APIs common in other languages:
Most objects are thin wrappers for managing a kwargs dict. As such, and because they're
wrapping a foreign API, they aren't strict in enforcing their internal state and the
kwargs dict is publicly exposed as an escape hatch.
As of this PR, no public API for e.g.
create_py_binary_rule_builder()
is exposed. That'll come in a separate PR (to add public access points under python/api).Work towards #1647