1616"""
1717
1818load ("@bazel_skylib//rules:expand_template.bzl" , "expand_template" )
19- load ("@bazel_skylib//rules:native_binary.bzl" , "native_binary" )
2019load ("@bazel_skylib//rules:write_file.bzl" , "write_file" )
2120load ("//python:py_binary.bzl" , "py_binary" )
2221load ("//python/private:bzlmod_enabled.bzl" , "BZLMOD_ENABLED" ) # buildifier: disable=bzl-visibility
@@ -25,17 +24,6 @@ visibility(["//..."])
2524
2625_REQUIREMENTS_TARGET_COMPATIBLE_WITH = [] if BZLMOD_ENABLED else ["@platforms//:incompatible" ]
2726
28- _RunLockInfo = provider (
29- doc = "Information for running the underlying Sphinx command directly" ,
30- fields = {
31- "cmd" : """
32- :type: Target
33-
34- The locker binary to run.
35- """ ,
36- },
37- )
38-
3927def _impl (ctx ):
4028 args = ctx .actions .args ()
4129
@@ -47,15 +35,12 @@ def _impl(ctx):
4735 ctx .files .src_outs [0 ].path ,
4836 ])
4937 args .add ("--output-file" , ctx .outputs .out )
50-
51- # TODO @aignas 2025-03-02: add the following deps to _RunLockInfo
52- srcs = ctx .files .srcs + ctx .files .src_outs
5338 args .add_all (ctx .files .srcs )
5439
5540 ctx .actions .run (
5641 executable = ctx .executable .cmd ,
5742 mnemonic = "RulesPythonLock" ,
58- inputs = srcs ,
43+ inputs = ctx . files . srcs + ctx . files . src_outs ,
5944 outputs = [
6045 ctx .outputs .out ,
6146 ],
@@ -67,10 +52,7 @@ def _impl(ctx):
6752 env = ctx .attr .env ,
6853 )
6954
70- return [
71- DefaultInfo (files = depset ([ctx .outputs .out ])),
72- _RunLockInfo (cmd = ctx .executable .cmd ),
73- ]
55+ return [DefaultInfo (files = depset ([ctx .outputs .out ]))]
7456
7557_lock = rule (
7658 implementation = _impl ,
@@ -79,7 +61,7 @@ _lock = rule(
7961 attrs = {
8062 "args" : attr .string_list (),
8163 "cmd" : attr .label (
82- default = "//python/uv/private:pip_compile" ,
64+ mandatory = True ,
8365 executable = True ,
8466 cfg = "target" ,
8567 ),
@@ -90,6 +72,25 @@ _lock = rule(
9072 },
9173)
9274
75+ def _glob (path ):
76+ """A small function to return a list of existing outputs.
77+
78+ If the file referenced by the input argument exists, then it will return
79+ it, otherwise it will return an empty list. This is useful to for programs
80+ like pip-compile which behave differently if the output file exists and
81+ update the output file in place.
82+
83+ The API of the function ensures that path is not a glob itself.
84+
85+ Args:
86+ path: {type}`str` the file name.
87+ """
88+ for p in native .glob ([path ], allow_empty = True ):
89+ if path == p :
90+ return [p ]
91+
92+ return []
93+
9394def lock (* , name , srcs , out , args = [], ** kwargs ):
9495 """Pin the requirements based on the src files.
9596
@@ -108,17 +109,11 @@ def lock(*, name, srcs, out, args = [], **kwargs):
108109 **kwargs: Extra kwargs passed to the {obj}`py_binary` rule.
109110 """
110111 pkg = native .package_name ()
111- update_target = name + ".update"
112-
113- user_args = args
114-
115- existing_outputs = []
116- for path in native .glob ([out ], allow_empty = True ):
117- if path == out :
118- existing_outputs = [out ]
119- break
112+ update_target = "{}.update" .format (name )
113+ locker_target = "{}.run" .format (name )
120114
121115 # TODO @aignas 2025-03-02: move the following args to a template expansion action
116+ user_args = args
122117 args = [
123118 # FIXME @aignas 2025-03-02: this acts differently in native_binary and the rule
124119 "--custom-compile-command='bazel run //{}:{}'" .format (pkg , update_target ),
@@ -129,10 +124,30 @@ def lock(*, name, srcs, out, args = [], **kwargs):
129124 "--no-cache" ,
130125 ]
131126 args += user_args
127+ run_args = []
128+
129+ existing_outputs = _glob (out )
130+ if existing_outputs :
131+ # This means that the output file already exists and it should be used
132+ # to create a new file. This will be taken care by the locker tool.
133+ #
134+ # TODO @aignas 2025-03-02: similarly to sphinx rule, expand the output to short_path
135+ run_args += ["--output-file" , "$(rootpath {})" .format (existing_outputs [0 ])]
136+ else :
137+ # TODO @aignas 2025-03-02: pass the output as a string
138+ run_out = "{}/{}" .format (pkg , out )
139+ run_args += ["--output-file" , run_out ]
140+
141+ # args just get passed as is
142+ run_args += [
143+ # TODO @aignas 2025-03-02: get the full source location for these
144+ "$(rootpath {})" .format (s )
145+ for s in srcs
146+ ]
132147
133148 expand_template (
134- name = name + "_locker_src " ,
135- out = name + "_locker .py" ,
149+ name = locker_target + "_gen " ,
150+ out = locker_target + ".py" ,
136151 template = "//python/uv/private:pip_compile.py" ,
137152 substitutions = {
138153 " args = []" : " args = " + repr (args ),
@@ -141,11 +156,12 @@ def lock(*, name, srcs, out, args = [], **kwargs):
141156 )
142157
143158 py_binary (
144- name = name + "_locker" ,
145- srcs = [name + "_locker .py" ],
159+ name = locker_target ,
160+ srcs = [locker_target + ".py" ],
146161 data = [
147162 "//python/uv:current_toolchain" ,
148- ],
163+ ] + srcs + existing_outputs ,
164+ args = run_args ,
149165 tags = ["manual" ],
150166 deps = ["//python/runfiles" ],
151167 )
@@ -165,45 +181,13 @@ def lock(*, name, srcs, out, args = [], **kwargs):
165181 ],
166182 args = args ,
167183 target_compatible_with = _REQUIREMENTS_TARGET_COMPATIBLE_WITH ,
168- cmd = name + "_locker" ,
169- )
170-
171- run_args = []
172- if existing_outputs :
173- # This means that the output file already exists and it should be used
174- # to create a new file. This will be taken care by the locker tool.
175- #
176- # TODO @aignas 2025-03-02: similarly to sphinx rule, expand the output to short_path
177- run_args += ["--output-file" , "$(location {})" .format (existing_outputs [0 ])]
178- else :
179- # TODO @aignas 2025-03-02: pass the output as a string
180- run_out = "{}/{}" .format (pkg , out )
181- run_args += ["--output-file" , run_out ]
182-
183- # args just get passed as is
184- run_args += args + [
185- # TODO @aignas 2025-03-02: get the full source location for these
186- "$(location {})" .format (s )
187- for s in srcs
188- ]
189-
190- native_binary (
191- # this is expand_template using the stuff from the provider from the _lock rule
192- name = name + ".run" ,
193- args = run_args , # the main difference is the expansion of args here
194- data = srcs + existing_outputs , # This only depends on inputs to the _lock
195- src = "//python/uv/private:pip_compile" ,
196- tags = [
197- "local" ,
198- "manual" ,
199- "no-cache" ,
200- ],
184+ cmd = locker_target ,
201185 )
202186
203187 # Write a script that can be used for updating the in-tree version of the
204188 # requirements file
205189 write_file (
206- name = name + ".update_gen " ,
190+ name = update_target + "_gen " ,
207191 out = update_target + ".py" ,
208192 content = [
209193 "from os import environ" ,
0 commit comments