Skip to content

Commit c304268

Browse files
committed
wip: rule builder
1 parent f78bec7 commit c304268

File tree

5 files changed

+186
-99
lines changed

5 files changed

+186
-99
lines changed

python/private/builders.bzl

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,131 @@ def _is_file(value):
184184
def _is_runfiles(value):
185185
return type(value) == "runfiles"
186186

187+
def _Optional(*initial):
188+
if len(initial) > 1:
189+
fail("only one positional arg allowed")
190+
191+
# buildifier: disable=uninitialized
192+
self = struct(
193+
_value = list(initial),
194+
present = lambda *a, **k: _Optional_present(self, *a, **k),
195+
set = lambda *a, **k: _Optional_set(self, *a, **k),
196+
get = lambda *a, **k: _Optional_get(self, *a, **k),
197+
)
198+
return self
199+
200+
def _Optional_set(self, v):
201+
if len(self._value) == 0:
202+
self._value.append(v)
203+
else:
204+
self._value[0] = v
205+
206+
def _Optional_get(self):
207+
if not len(self._value):
208+
fail("Value not present")
209+
return self._value[0]
210+
211+
def _Optional_present(self):
212+
return len(self._value) > 0
213+
214+
def _TransitionBuilder(implementation = None, inputs = None, outputs = None, **kwargs):
215+
# buildifier: disable=uninitialized
216+
self = struct(
217+
implementation = _Optional(implementation),
218+
inputs = _SetBuilder(inputs),
219+
outputs = _SetBuilder(outputs),
220+
kwargs = kwargs,
221+
build = lambda *a, **k: _TransitionBuilder_build(self, *a, **k),
222+
)
223+
return self
224+
225+
def _TransitionBuilder_build(self):
226+
return transition(
227+
implementation = self.implementation.get(),
228+
inputs = self.inputs.build(),
229+
outputs = self.outputs.build(),
230+
**self.kwargs
231+
)
232+
233+
def _SetBuilder(initial = None):
234+
initial = {} if not initial else {v: None for v in initial}
235+
236+
# buildifier: disable=uninitialized
237+
self = struct(
238+
_values = initial,
239+
extend = lambda *a, **k: _SetBuilder_extend(self, *a, **k),
240+
build = lambda *a, **k: _SetBuilder_build(self, *a, **k),
241+
)
242+
return self
243+
244+
def _SetBuilder_build(self):
245+
return self._values.keys()
246+
247+
def _SetBuilder_extend(self, values):
248+
for v in values:
249+
if v not in self._values:
250+
self._values[v] = None
251+
252+
def _RuleBuilder(implementation = None, **kwargs):
253+
# buildifier: disable=uninitialized
254+
self = struct(
255+
attrs = dict(kwargs.pop("attrs", None) or {}),
256+
cfg = kwargs.pop("cfg", None) or _TransitionBuilder(),
257+
exec_groups = dict(kwargs.pop("exec_groups", None) or {}),
258+
executable = _Optional(),
259+
fragments = list(kwargs.pop("fragments", None) or []),
260+
implementation = _Optional(implementation),
261+
extra_kwargs = kwargs,
262+
provides = list(kwargs.pop("provides", None) or []),
263+
test = _Optional(),
264+
toolchains = list(kwargs.pop("toolchains", None) or []),
265+
build = lambda *a, **k: _RuleBuilder_build(self, *a, **k),
266+
to_kwargs = lambda *a, **k: _RuleBuilder_to_kwargs(self, *a, **k),
267+
)
268+
if "test" in kwargs:
269+
self.test.set(kwargs.pop("test"))
270+
if "executable" in kwargs:
271+
self.executable.set(kwargs.pop("executable"))
272+
return self
273+
274+
def _RuleBuilder_build(self, debug = ""):
275+
kwargs = self.to_kwargs()
276+
if debug:
277+
lines = ["=" * 80, "rule kwargs: {}:".format(debug)]
278+
for k, v in sorted(kwargs.items()):
279+
lines.append(" {}={}".format(k, v))
280+
281+
# buildifier: disable=print
282+
print("\n".join(lines))
283+
return rule(**kwargs)
284+
285+
def _RuleBuilder_to_kwargs(self):
286+
kwargs = {}
287+
if self.executable.present():
288+
kwargs["executable"] = self.executable.get()
289+
if self.test.present():
290+
kwargs["test"] = self.test.get()
291+
292+
kwargs.update(
293+
implementation = self.implementation.get(),
294+
cfg = self.cfg.build(),
295+
attrs = {
296+
k: (v.build() if hasattr(v, "build") else v)
297+
for k, v in self.attrs.items()
298+
},
299+
exec_groups = self.exec_groups,
300+
fragments = self.fragments,
301+
provides = self.provides,
302+
toolchains = self.toolchains,
303+
)
304+
kwargs.update(self.extra_kwargs)
305+
return kwargs
306+
187307
builders = struct(
188308
DepsetBuilder = _DepsetBuilder,
189309
RunfilesBuilder = _RunfilesBuilder,
310+
RuleBuilder = _RuleBuilder,
311+
TransitionBuilder = _TransitionBuilder,
312+
SetBuilder = _SetBuilder,
313+
Optional = _Optional,
190314
)

python/private/py_binary_rule.bzl

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
# limitations under the License.
1414
"""Rule implementation of py_binary for Bazel."""
1515

16-
load("@bazel_skylib//lib:dicts.bzl", "dicts")
1716
load(":attributes.bzl", "AGNOSTIC_BINARY_ATTRS")
1817
load(
1918
":py_executable.bzl",
20-
"create_executable_rule",
19+
"create_executable_rule_builder",
2120
"py_executable_impl",
2221
)
2322

@@ -45,16 +44,13 @@ def _py_binary_impl(ctx):
4544
inherited_environment = [],
4645
)
4746

48-
def create_binary_rule(*, attrs = None, **kwargs):
49-
kwargs.setdefault("implementation", _py_binary_impl)
50-
kwargs.setdefault("executable", True)
51-
return create_executable_rule(
52-
attrs = dicts.add(
53-
AGNOSTIC_BINARY_ATTRS,
54-
_COVERAGE_ATTRS,
55-
attrs or {},
56-
),
57-
**kwargs
47+
def create_binary_rule_builder():
48+
builder = create_executable_rule_builder(
49+
implementation = _py_binary_impl,
50+
executable = True,
5851
)
52+
builder.attrs.update(AGNOSTIC_BINARY_ATTRS)
53+
builder.attrs.update(_COVERAGE_ATTRS)
54+
return builder
5955

60-
py_binary = create_binary_rule()
56+
py_binary = create_binary_rule_builder().build()

python/private/py_executable.bzl

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,66 +1747,44 @@ def _transition_executable_impl(input_settings, attr):
17471747
settings[_PYTHON_VERSION_FLAG] = attr.python_version
17481748
return settings
17491749

1750-
def create_transition(extend_implementation = None, inputs = None, outputs = None, **kwargs):
1751-
if extend_implementation:
1752-
implementation = lambda *args: extend_implementation(base_impl = _transition_executable_impl, *args)
1753-
else:
1754-
implementation = _transition_executable_impl
1755-
1756-
# todo: dedupe inputs/outputs
1757-
return transition(
1758-
implementation = implementation,
1759-
inputs = [_PYTHON_VERSION_FLAG] + (inputs or []),
1760-
outputs = [_PYTHON_VERSION_FLAG] + (outputs or []),
1761-
**kwargs
1762-
)
1763-
1764-
_transition_executable = transition(
1765-
implementation = _transition_executable_impl,
1766-
inputs = [
1767-
_PYTHON_VERSION_FLAG,
1768-
],
1769-
outputs = [
1770-
_PYTHON_VERSION_FLAG,
1771-
],
1772-
)
1773-
1774-
transition_executable_impl = _transition_executable_impl
1775-
17761750
def create_executable_rule(*, attrs, **kwargs):
17771751
return create_base_executable_rule(
17781752
attrs = attrs,
17791753
fragments = ["py", "bazel_py"],
17801754
**kwargs
17811755
)
17821756

1783-
def create_base_executable_rule(*, attrs, fragments = [], **kwargs):
1757+
def create_base_executable_rule():
17841758
"""Create a function for defining for Python binary/test targets.
17851759
1786-
Args:
1787-
attrs: Rule attributes
1788-
fragments: List of str; extra config fragments that are required.
1789-
**kwargs: Additional args to pass onto `rule()`
1790-
17911760
Returns:
17921761
A rule function
17931762
"""
1794-
if "py" not in fragments:
1795-
# The list might be frozen, so use concatentation
1796-
fragments = fragments + ["py"]
1797-
kwargs.setdefault("provides", []).append(PyExecutableInfo)
1798-
kwargs["exec_groups"] = REQUIRED_EXEC_GROUPS | (kwargs.get("exec_groups") or {})
1799-
kwargs.setdefault("cfg", _transition_executable)
1800-
return rule(
1801-
# TODO: add ability to remove attrs, i.e. for imports attr
1802-
attrs = dicts.add(EXECUTABLE_ATTRS, attrs),
1763+
return create_executable_rule_builder().build()
1764+
1765+
def create_executable_rule_builder(implementation, **kwargs):
1766+
builder = builders.RuleBuilder(
1767+
implementation = implementation,
1768+
attrs = EXECUTABLE_ATTRS,
1769+
exec_groups = REQUIRED_EXEC_GROUPS,
1770+
fragments = ["py", "bazel_py"],
1771+
provides = [PyExecutableInfo],
18031772
toolchains = [
18041773
TOOLCHAIN_TYPE,
18051774
config_common.toolchain_type(EXEC_TOOLS_TOOLCHAIN_TYPE, mandatory = False),
18061775
] + _CC_TOOLCHAINS,
1807-
fragments = fragments,
1776+
cfg = builders.TransitionBuilder(
1777+
implementation = _transition_executable_impl,
1778+
inputs = [
1779+
_PYTHON_VERSION_FLAG,
1780+
],
1781+
outputs = [
1782+
_PYTHON_VERSION_FLAG,
1783+
],
1784+
),
18081785
**kwargs
18091786
)
1787+
return builder
18101788

18111789
def cc_configure_features(
18121790
ctx,

python/private/py_test_rule.bzl

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@
1313
# limitations under the License.
1414
"""Implementation of py_test rule."""
1515

16-
load("@bazel_skylib//lib:dicts.bzl", "dicts")
1716
load(":attributes.bzl", "AGNOSTIC_TEST_ATTRS")
1817
load(":common.bzl", "maybe_add_test_execution_info")
1918
load(
2019
":py_executable.bzl",
21-
"create_executable_rule",
20+
"create_executable_rule_builder",
2221
"py_executable_impl",
2322
)
2423

@@ -48,16 +47,13 @@ def _py_test_impl(ctx):
4847
maybe_add_test_execution_info(providers, ctx)
4948
return providers
5049

51-
def create_test_rule(*, attrs = None, **kwargs):
52-
kwargs.setdefault("implementation", _py_test_impl)
53-
kwargs.setdefault("test", True)
54-
return create_executable_rule(
55-
attrs = dicts.add(
56-
AGNOSTIC_TEST_ATTRS,
57-
_BAZEL_PY_TEST_ATTRS,
58-
attrs or {},
59-
),
60-
**kwargs
50+
def create_test_rule_builder():
51+
builder = create_executable_rule_builder(
52+
implementation = _py_test_impl,
53+
test = True,
6154
)
55+
builder.attrs.update(AGNOSTIC_TEST_ATTRS)
56+
builder.attrs.update(_BAZEL_PY_TEST_ATTRS)
57+
return builder
6258

63-
py_test = create_test_rule()
59+
py_test = create_test_rule_builder().build()

tests/support/sh_py_run_test.bzl

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,10 @@ without the overhead of a bazel-in-bazel integration test.
1818
"""
1919

2020
load("@rules_shell//shell:sh_test.bzl", "sh_test")
21-
load("//python:py_binary.bzl", "py_binary")
22-
load("//python:py_test.bzl", "py_test")
23-
load("//python/private:py_binary_macro.bzl", "py_binary_macro")
24-
load("//python/private:py_binary_rule.bzl", "create_binary_rule")
25-
load("//python/private:py_executable.bzl", create_executable_transition = "create_transition")
26-
load("//python/private:py_test_macro.bzl", "py_test_macro")
27-
load("//python/private:py_test_rule.bzl", "create_test_rule")
21+
load("//python/private:py_binary_macro.bzl", "py_binary_macro") # buildifier: disable=bzl-visibility
22+
load("//python/private:py_binary_rule.bzl", "create_binary_rule_builder") # buildifier: disable=bzl-visibility
23+
load("//python/private:py_test_macro.bzl", "py_test_macro") # buildifier: disable=bzl-visibility
24+
load("//python/private:py_test_rule.bzl", "create_test_rule_builder") # buildifier: disable=bzl-visibility
2825
load("//python/private:toolchain_types.bzl", "TARGET_TOOLCHAIN_TYPE") # buildifier: disable=bzl-visibility
2926
load("//tests/support:support.bzl", "VISIBLE_FOR_TESTING")
3027

@@ -40,21 +37,15 @@ def _perform_transition_impl(input_settings, attr, base_impl):
4037
settings["//python/config_settings:venvs_use_declare_symlink"] = attr.venvs_use_declare_symlink
4138
return settings
4239

43-
_perform_transition = create_executable_transition(
44-
extend_implementation = _perform_transition_impl,
45-
inputs = [
46-
"//python/config_settings:bootstrap_impl",
47-
"//command_line_option:extra_toolchains",
48-
"//python/config_settings:venvs_use_declare_symlink",
49-
],
50-
outputs = [
51-
"//command_line_option:build_python_zip",
52-
"//command_line_option:extra_toolchains",
53-
"//python/config_settings:bootstrap_impl",
54-
"//python/config_settings:venvs_use_declare_symlink",
55-
VISIBLE_FOR_TESTING,
56-
],
57-
)
40+
_RECONFIG_INPUTS = [
41+
"//python/config_settings:bootstrap_impl",
42+
"//command_line_option:extra_toolchains",
43+
"//python/config_settings:venvs_use_declare_symlink",
44+
]
45+
_RECONFIG_OUTPUTS = _RECONFIG_INPUTS + [
46+
"//command_line_option:build_python_zip",
47+
VISIBLE_FOR_TESTING,
48+
]
5849

5950
_RECONFIG_ATTRS = {
6051
"bootstrap_impl": attr.string(),
@@ -71,21 +62,23 @@ toolchain.
7162
"venvs_use_declare_symlink": attr.string(),
7263
}
7364

74-
_py_reconfig_binary = create_binary_rule(
75-
attrs = _RECONFIG_ATTRS,
76-
cfg = _perform_transition,
77-
)
65+
def _create_reconfig_rule(builder):
66+
builder.attrs.update(_RECONFIG_ATTRS)
7867

79-
_py_reconfig_test = create_test_rule(
80-
attrs = _RECONFIG_ATTRS,
81-
cfg = _perform_transition,
82-
)
68+
base_cfg_impl = builder.cfg.implementation.get()
69+
builder.cfg.implementation.set(lambda *args: _perform_transition_impl(base_impl = base_cfg_impl, *args))
70+
builder.cfg.inputs.extend(_RECONFIG_INPUTS)
71+
builder.cfg.outputs.extend(_RECONFIG_OUTPUTS)
72+
return builder.build()
73+
74+
_py_reconfig_binary = _create_reconfig_rule(create_binary_rule_builder())
75+
76+
_py_reconfig_test = _create_reconfig_rule(create_test_rule_builder())
8377

8478
def py_reconfig_test(**kwargs):
8579
"""Create a py_test with customized build settings for testing.
8680
8781
Args:
88-
name: str, name of test target.
8982
**kwargs: kwargs to pass along to _py_reconfig_test.
9083
"""
9184
py_test_macro(_py_reconfig_test, **kwargs)

0 commit comments

Comments
 (0)