-
-
Notifications
You must be signed in to change notification settings - Fork 571
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
feat: uv lock rule instead of genrule #2657
base: main
Are you sure you want to change the base?
feat: uv lock rule instead of genrule #2657
Conversation
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.
OK, after doing self review I think I want to have:
- A script for launching
uv
frombash
that would be compatible with UNIX. - A script for launching
uv
frompowershell
that would be compatible with Windows (generative AI may help with initial translation here). - Rewrite the internals a little to drop the Python usage (or at least most of it).
fcc17c3
to
7ddfd28
Compare
928f1e2
to
2c29ae2
Compare
The core of the implementation looks good to me (rule that looks up toolchains to run an build action; lock_run uses info lock provides via a provider). I have a variety of smaller comments about some particulars, but gtg now, so I'll have to wait until, ah, probably the weekend sometime. The |
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.
got about half way through
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.
Also, to clarify, this is the sort of interface I imagined:
bazel build //:requirements
generates a requirements file using a build actionbazel run //:requirements.update
generates a requirements file using a build action and copies it into the local client to update the requirements filebazel run //:requirements.run -- <args>
more of a direct invocation; runs uv directly; this is for debugging or experimenting with settings without having to modify the BUILD file.
The reason for (2) to use the output of (1) is because that's where the magic of bazel happens. By this i mean: build actions have isolated/deterministic/hermetic capabilities, can run remotely, are more amenable to having transitions applied, and are better about not having different output per user machine.
OK, addressed the comments, PTAL. I see that Windows is failing, because something is not compatible with the CI Windows version. I wonder if this means we should just tell users that Windows may be unsupported? |
daab523
to
c67cdcd
Compare
Open questions:
|
Sorry, some short notice deadlines came upon me. I'll be able to have another look Weds evening or after |
Since uv is coming from a toolchain, and we need python to match uv, I think both should come from a toolchain. A behavior unique to toolchains is that a group of toolchains can get resolved with the same config state. Getting the same behavior using labels, or a mix of a label and toolchain, might be tricky (it might be possible using exec groups?). |
return [ | ||
DefaultInfo( | ||
executable = executable, | ||
runfiles = ctx.runfiles(transitive_files = info.srcs), |
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.
I think there's a subtle config mismatch here: info.srcs contains uv in exec config, but here it's going to run in target config.
I'm OK with ignoring this for now, though, to keep progress moving along.
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.
I have added a transition, but I am still new as to how to ensure that this will be in exec
configuration.
I can follow this up with a separate PR.
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 transition LGTM.
When a transition is applied to the rule itself, it decides what the "target" configuration is for the current target. It doesn't affect the exec config directly.
When toolchain resolution occurs, Bazel finds a toolchain that is compatible with the current target configuration. e.g. if python_version=3.12.1 is in the target configuration, then Bazel looks for a matching exec_tools toolchain with target_compatible_with=3.12. The e.g. exec_interpreter attribute will be in the exec config, but that's fine; the toolchain is claiming all its pieces are intended to produce output valid for 3.12.
HTH
python/uv/private/lock.bzl
Outdated
args.run_shell.add("--no-progress") | ||
args.run_shell.add("--quiet") | ||
|
||
ctx.actions.run_shell( |
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.
I'm having trouble understanding why this step needs the copy step as part of its execution.
Isn't this the same behavior?
srcs = list(ctx.attr.srcs)
if existing_file:
srcs.append(existing_file)
output = declare_file(name + ".out")
ctx.actions.run([uv, "--output={output}"] + srcs, inputs=srcs, output=output)
uv is going to overwrite whatever --output specifies, right?
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.
uv
expects the previous output to be in the location that is defined by --output={output}
. If you pass it as a source then you will have extra log lines via requirements-existing.txt
, which is not what you want here.
# FIXME @aignas 2025-03-17: should we have one more target that transitions | ||
# the python_version to ensure that if somebody calls `bazel build | ||
# :requirements` that it is locked with the right `python_version`? |
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 separate target, no. An attribute with rule-level cfg transition, yes.
This also enables tricks like this:
lock(python_version="3.10", srcs=select(":py310": "requirements_310.txt", ...))
Similarly, because an attr.output
is not used, the outputs can be varied to e.g. include the python version (or platform, etc) into the output file name.
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.
Thanks for the nudge. I have added the transition to the lock rule and I think the overall design is now simpler.
I have also created an internal expand_template
rule that makes the wiring of files easier. I have added the python version to the file name, so it might be both, a good and a bad decision and we will see. :)
RBE is failing with an error:
Not sure exactly why this is happening because I am passing I think the documentation mentioning to use EDIT: this guess was wrong. I am fiddling with the EDIT2: the |
This change implements the uv pip compile as a rule. In order to also make things easier to debug we provide a runnable rule that has the same arguments and updates the source tree output file automatically. The main design is to have a regular lock rule and then it returns a custom provider that has all of the recipe ingredients to construct an executable rule. The execution depends on having bash or powershell, however the powershell script is not yet complete and requires some help from the community. Work towards bazel-contrib#1975. Address all of the comments
f6a052f
to
7a37d5d
Compare
It seems that the
I chose the 3.i. method because I only need one extra symlink and it does not Maybe the 3.ii solution could be also beneficial for creating #2156 and setting up EDIT: it seems that there has to be an extra option that I can chose as the 3.i EDIT2: I wonder if I am hitting bazelbuild/bazel#23620 EDIT3: OK, so in the end the solution was to forward the |
Would this feature fix the issue #2640? |
It would not, because the linked issue is about building wheels rather than locking them. |
Yeah, I'm pretty sure that's what you're seeing. I haven't looked at the PR code yet, but what I recall was you can't simply Using declare_symlink, or a wrapper script, should work, though. I'll have a look at the PR now. |
Beh. Looking at current_interpreter_executable.bzl, I think changing L93 to use declare_symlink() should work? I tried setting up a local RBE to test it, but couldn't get bazel and the RBE talking. I'll have to try again when I have more time |
If you use |
], | ||
# It seems that the CI remote executors for the RBE do not have network | ||
# connectivity. Is it only our setup or is it a property of RBE? | ||
tags = ["no-remote-exec"], |
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 this is where the RBE is disabled for our tests
I got an RBE setup locally. Yeah, current_interpreter_executable (the thing that backs ExecTools.exec_interpreter) does indeed look entirely broken with RBE. Argh >.< So...
The only way I can think of to make both work is, essentially, to make the output executable a wrapper. e.g. shell code that figures out how to locate the file it wants to run and execs it. Or, maybe if py_runtime() is directly executable? i.e. sets executable=True, and returns The reason I'm so keen on having an exectuable=True attribute (i.e. a thing with FilesToRun provider) that is fed to ctx.actions.run() is that is supposed to be the proper abstraction -- "run the interpreter, let its executable rule figure out the details". This is in comparison to e.g. having to directly use PyRuntime, then manually pass PyRuntime.files etc to ctx.actions.run. Well, its late now, so gotta log off. |
Hmmm... since I have a question on RB slack about the I kind of am getting where you are coming from - just running python should not be that hard and should not require |
I poked this some more and have some ideas, but want to explore them a bit more. I don't want to block this PR on them, though. How about for now, revert the changes to the exec toolchain stuff. The uv rule can still get at the PyRuntime object via exec_interpreter: |
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.
Just remove the exec tools toolchain changes as I mentioned in the other comment, otherwise LGTM
return [SentinelInfo()] | ||
return [ | ||
SentinelInfo(), | ||
# Also output ToolchainInfo |
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.
# Also output ToolchainInfo | |
# Also output ToolchainInfo to allow it to be used for no-op toolchains |
progress_message = "Creating a requirements.txt with uv: //{}:{}".format( | ||
ctx.label.package, | ||
ctx.label.name, | ||
), |
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.
nit: Use %{label} instead
progress_message = "Creating a requirements.txt with uv: //{}:{}".format( | |
ctx.label.package, | |
ctx.label.name, | |
), | |
progress_message = "Creating a requirements.txt with uv: %{label}", |
doc = """\ | ||
""", |
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.
nit: add doc or just omit the doc attribute
return [ | ||
DefaultInfo( | ||
executable = executable, | ||
runfiles = ctx.runfiles(transitive_files = info.srcs), |
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 transition LGTM.
When a transition is applied to the rule itself, it decides what the "target" configuration is for the current target. It doesn't affect the exec config directly.
When toolchain resolution occurs, Bazel finds a toolchain that is compatible with the current target configuration. e.g. if python_version=3.12.1 is in the target configuration, then Bazel looks for a matching exec_tools toolchain with target_compatible_with=3.12. The e.g. exec_interpreter attribute will be in the exec config, but that's fine; the toolchain is claiming all its pieces are intended to produce output valid for 3.12.
HTH
# It seems that the CI remote executors for the RBE do not have network | ||
# connectivity. Is it only our setup or is it a property of RBE? |
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's not intrinsic to RBE, so must be something wit our RBE.
This change implements the uv pip compile as a rule.
In order to also make things easier to debug we provide
a runnable rule that has the same arguments and updates
the source tree output file automatically.
The main design is to have a regular lock rule and then
it returns a custom provider that has all of the recipe
ingredients to construct an executable rule. The execution
depends on having
bash
orbat
files on Windows when runningthe debugging rule target.
There are integration tests that exercise the locker. However,
things that are untested:
running the
uv
binary. Need help from some Windows users.RBE
seems to not workbut locking when using
RBE
still works - there is anative_test
exercising this.
keyring
integration to pull packages from privateindex servers. https://docs.astral.sh/uv/configuration/authentication/
should be supported.
Work towards #1325
Work towards #1975
Related #2663