| # Copyright 2025 The Bazel Authors. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """Builders for creating rules, aspects et al. | |
| When defining rules, Bazel only allows creating *immutable* objects that can't | |
| be introspected. This makes it difficult to perform arbitrary customizations of | |
| how a rule is defined, which makes extending a rule implementation prone to | |
| copy/paste issues and version skew. | |
| These builders are, essentially, mutable and inspectable wrappers for those | |
| Bazel objects. This allows defining a rule where the values are mutable and | |
| callers can customize them to derive their own variant of the rule while still | |
| inheriting everything else about the rule. | |
| To that end, the builders are not strict in how they handle values. They | |
| generally assume that the values provided are valid and provide ways to | |
| override their logic and force particular values to be used when they are | |
| eventually converted to the args for calling e.g. `rule()`. | |
| :::{important} | |
| When using builders, most lists, dicts, et al passed into them **must** be | |
| locally created values, otherwise they won't be mutable. This is due to Bazel's | |
| implicit immutability rules: after evaluating a `.bzl` file, its global | |
| variables are frozen. | |
| ::: | |
| :::{tip} | |
| To aid defining reusable pieces, many APIs accept no-arg callable functions | |
| that create a builder. For example, common attributes can be stored | |
| in a `dict[str, lambda]`, e.g. `ATTRS = {"srcs": lambda: LabelList(...)}`. | |
| ::: | |
| Example usage: | |
| ``` | |
| load(":rule_builders.bzl", "ruleb") | |
| load(":attr_builders.bzl", "attrb") | |
| # File: foo_binary.bzl | |
| _COMMON_ATTRS = { | |
| "srcs": lambda: attrb.LabelList(...), | |
| } | |
| def create_foo_binary_builder(): | |
| foo = ruleb.Rule( | |
| executable = True, | |
| ) | |
| foo.implementation.set(_foo_binary_impl) | |
| foo.attrs.update(COMMON_ATTRS) | |
| return foo | |
| def create_foo_test_builder(): | |
| foo = create_foo_binary_build() | |
| binary_impl = foo.implementation.get() | |
| def foo_test_impl(ctx): | |
| binary_impl(ctx) | |
| ... | |
| foo.implementation.set(foo_test_impl) | |
| foo.executable.set(False) | |
| foo.test.test(True) | |
| foo.attrs.update( | |
| _coverage = attrb.Label(default="//:coverage") | |
| ) | |
| return foo | |
| foo_binary = create_foo_binary_builder().build() | |
| foo_test = create_foo_test_builder().build() | |
| # File: custom_foo_binary.bzl | |
| load(":foo_binary.bzl", "create_foo_binary_builder") | |
| def create_custom_foo_binary(): | |
| r = create_foo_binary_builder() | |
| r.attrs["srcs"].default.append("whatever.txt") | |
| return r.build() | |
| custom_foo_binary = create_custom_foo_binary() | |
| ``` | |
| :::{versionadded} 1.3.0 | |
| ::: | |
| """ | |
| load("@bazel_skylib//lib:types.bzl", "types") | |
| load( | |
| ":builders_util.bzl", | |
| "kwargs_getter", | |
| "kwargs_getter_doc", | |
| "kwargs_set_default_dict", | |
| "kwargs_set_default_doc", | |
| "kwargs_set_default_ignore_none", | |
| "kwargs_set_default_list", | |
| "kwargs_setter", | |
| "kwargs_setter_doc", | |
| "list_add_unique", | |
| "normalize_transition_in_out_value", | |
| "normalize_transition_in_out_values", | |
| ) | |
| # Various string constants for kwarg key names used across two or more | |
| # functions, or in contexts with optional lookups (e.g. dict.dict, key in dict). | |
| # Constants are used to reduce the chance of typos. | |
| # NOTE: These keys are often part of function signature via `**kwargs`; they | |
| # are not simply internal names. | |
| _ATTRS = "attrs" | |
| _CFG = "cfg" | |
| _EXEC_COMPATIBLE_WITH = "exec_compatible_with" | |
| _EXEC_GROUPS = "exec_groups" | |
| _IMPLEMENTATION = "implementation" | |
| _INPUTS = "inputs" | |
| _OUTPUTS = "outputs" | |
| _TOOLCHAINS = "toolchains" | |
| def _is_builder(obj): | |
| return hasattr(obj, "build") | |
| def _ExecGroup_typedef(): | |
| """Builder for {external:bzl:obj}`exec_group` | |
| :::{function} toolchains() -> list[ToolchainType] | |
| ::: | |
| :::{function} exec_compatible_with() -> list[str | Label] | |
| ::: | |
| :::{include} /_includes/field_kwargs_doc.md | |
| ::: | |
| """ | |
| def _ExecGroup_new(**kwargs): | |
| """Creates a builder for {external:bzl:obj}`exec_group`. | |
| Args: | |
| **kwargs: Same as {external:bzl:obj}`exec_group` | |
| Returns: | |
| {type}`ExecGroup` | |
| """ | |
| kwargs_set_default_list(kwargs, _TOOLCHAINS) | |
| kwargs_set_default_list(kwargs, _EXEC_COMPATIBLE_WITH) | |
| for i, value in enumerate(kwargs[_TOOLCHAINS]): | |
| kwargs[_TOOLCHAINS][i] = _ToolchainType_maybe_from(value) | |
| # buildifier: disable=uninitialized | |
| self = struct( | |
| toolchains = kwargs_getter(kwargs, _TOOLCHAINS), | |
| exec_compatible_with = kwargs_getter(kwargs, _EXEC_COMPATIBLE_WITH), | |
| kwargs = kwargs, | |
| build = lambda: _ExecGroup_build(self), | |
| ) | |
| return self | |
| def _ExecGroup_maybe_from(obj): | |
| if types.is_function(obj): | |
| return obj() | |
| else: | |
| return obj | |
| def _ExecGroup_build(self): | |
| kwargs = dict(self.kwargs) | |
| if kwargs.get(_TOOLCHAINS): | |
| kwargs[_TOOLCHAINS] = [ | |
| v.build() if _is_builder(v) else v | |
| for v in kwargs[_TOOLCHAINS] | |
| ] | |
| if kwargs.get(_EXEC_COMPATIBLE_WITH): | |
| kwargs[_EXEC_COMPATIBLE_WITH] = [ | |
| v.build() if _is_builder(v) else v | |
| for v in kwargs[_EXEC_COMPATIBLE_WITH] | |
| ] | |
| return exec_group(**kwargs) | |
| # buildifier: disable=name-conventions | |
| ExecGroup = struct( | |
| TYPEDEF = _ExecGroup_typedef, | |
| new = _ExecGroup_new, | |
| build = _ExecGroup_build, | |
| ) | |
| def _ToolchainType_typedef(): | |
| """Builder for {obj}`config_common.toolchain_type` | |
| :::{include} /_includes/field_kwargs_doc.md | |
| ::: | |
| :::{function} mandatory() -> bool | |
| ::: | |
| :::{function} name() -> str | Label | None | |
| ::: | |
| :::{function} set_name(v: str) | |
| ::: | |
| :::{function} set_mandatory(v: bool) | |
| ::: | |
| """ | |
| def _ToolchainType_new(name = None, **kwargs): | |
| """Creates a builder for `config_common.toolchain_type`. | |
| Args: | |
| name: {type}`str | Label | None` the toolchain type target. | |
| **kwargs: Same as {obj}`config_common.toolchain_type` | |
| Returns: | |
| {type}`ToolchainType` | |
| """ | |
| kwargs["name"] = name | |
| kwargs_set_default_ignore_none(kwargs, "mandatory", True) | |
| # buildifier: disable=uninitialized | |
| self = struct( | |
| # keep sorted | |
| build = lambda: _ToolchainType_build(self), | |
| kwargs = kwargs, | |
| mandatory = kwargs_getter(kwargs, "mandatory"), | |
| name = kwargs_getter(kwargs, "name"), | |
| set_mandatory = kwargs_setter(kwargs, "mandatory"), | |
| set_name = kwargs_setter(kwargs, "name"), | |
| ) | |
| return self | |
| def _ToolchainType_maybe_from(obj): | |
| if types.is_string(obj) or type(obj) == "Label": | |
| return ToolchainType.new(name = obj) | |
| elif types.is_function(obj): | |
| # A lambda to create a builder | |
| return obj() | |
| else: | |
| # For lack of another option, return it as-is. | |
| # Presumably it's already a builder or other valid object. | |
| return obj | |
| def _ToolchainType_build(self): | |
| """Builds a `config_common.toolchain_type` | |
| Args: | |
| self: implicitly added | |
| Returns: | |
| {type}`toolchain_type` | |
| """ | |
| kwargs = dict(self.kwargs) | |
| name = kwargs.pop("name") # Name must be positional | |
| return config_common.toolchain_type(name, **kwargs) | |
| # buildifier: disable=name-conventions | |
| ToolchainType = struct( | |
| TYPEDEF = _ToolchainType_typedef, | |
| new = _ToolchainType_new, | |
| build = _ToolchainType_build, | |
| ) | |
| def _RuleCfg_typedef(): | |
| """Wrapper for `rule.cfg` arg. | |
| :::{function} implementation() -> str | callable | None | config.target | config.none | |
| ::: | |
| ::::{function} inputs() -> list[Label] | |
| :::{seealso} | |
| The {obj}`add_inputs()` and {obj}`update_inputs` methods for adding unique | |
| values. | |
| ::: | |
| :::: | |
| :::{function} outputs() -> list[Label] | |
| :::{seealso} | |
| The {obj}`add_outputs()` and {obj}`update_outputs` methods for adding unique | |
| values. | |
| ::: | |
| ::: | |
| :::{function} set_implementation(v: str | callable | None | config.target | config.none) | |
| The string values "target" and "none" are supported. | |
| ::: | |
| """ | |
| def _RuleCfg_new(rule_cfg_arg): | |
| """Creates a builder for the `rule.cfg` arg. | |
| Args: | |
| rule_cfg_arg: {type}`str | dict | None` The `cfg` arg passed to Rule(). | |
| Returns: | |
| {type}`RuleCfg` | |
| """ | |
| state = {} | |
| if types.is_dict(rule_cfg_arg): | |
| state.update(rule_cfg_arg) | |
| else: | |
| # Assume its a string, config.target, config.none, or other | |
| # valid object. | |
| state[_IMPLEMENTATION] = rule_cfg_arg | |
| kwargs_set_default_list(state, _INPUTS) | |
| kwargs_set_default_list(state, _OUTPUTS) | |
| normalize_transition_in_out_values("input", state[_INPUTS]) | |
| normalize_transition_in_out_values("output", state[_OUTPUTS]) | |
| # buildifier: disable=uninitialized | |
| self = struct( | |
| add_inputs = lambda *a, **k: _RuleCfg_add_inputs(self, *a, **k), | |
| add_outputs = lambda *a, **k: _RuleCfg_add_outputs(self, *a, **k), | |
| _state = state, | |
| build = lambda: _RuleCfg_build(self), | |
| implementation = kwargs_getter(state, _IMPLEMENTATION), | |
| inputs = kwargs_getter(state, _INPUTS), | |
| outputs = kwargs_getter(state, _OUTPUTS), | |
| set_implementation = kwargs_setter(state, _IMPLEMENTATION), | |
| update_inputs = lambda *a, **k: _RuleCfg_update_inputs(self, *a, **k), | |
| update_outputs = lambda *a, **k: _RuleCfg_update_outputs(self, *a, **k), | |
| ) | |
| return self | |
| def _RuleCfg_add_inputs(self, *inputs): | |
| """Adds an input to the list of inputs, if not present already. | |
| :::{seealso} | |
| The {obj}`update_inputs()` method for adding a collection of | |
| values. | |
| ::: | |
| Args: | |
| self: implicitly arg. | |
| *inputs: {type}`Label` the inputs to add. Note that a `Label`, | |
| not `str`, should be passed to ensure different apparent labels | |
| can be properly de-duplicated. | |
| """ | |
| self.update_inputs(inputs) | |
| def _RuleCfg_add_outputs(self, *outputs): | |
| """Adds an output to the list of outputs, if not present already. | |
| :::{seealso} | |
| The {obj}`update_outputs()` method for adding a collection of | |
| values. | |
| ::: | |
| Args: | |
| self: implicitly arg. | |
| *outputs: {type}`Label` the outputs to add. Note that a `Label`, | |
| not `str`, should be passed to ensure different apparent labels | |
| can be properly de-duplicated. | |
| """ | |
| self.update_outputs(outputs) | |
| def _RuleCfg_build(self): | |
| """Builds the rule cfg into the value rule.cfg arg value. | |
| Returns: | |
| {type}`transition` the transition object to apply to the rule. | |
| """ | |
| impl = self._state[_IMPLEMENTATION] | |
| if impl == "target" or impl == None: | |
| # config.target is Bazel 8+ | |
| if hasattr(config, "target"): | |
| return config.target() | |
| else: | |
| return None | |
| elif impl == "none": | |
| return config.none() | |
| elif types.is_function(impl): | |
| return transition( | |
| implementation = impl, | |
| # Transitions only accept unique lists of strings. | |
| inputs = {str(v): None for v in self._state[_INPUTS]}.keys(), | |
| outputs = {str(v): None for v in self._state[_OUTPUTS]}.keys(), | |
| ) | |
| else: | |
| # Assume its valid. Probably an `config.XXX` object or manually | |
| # set transition object. | |
| return impl | |
| def _RuleCfg_update_inputs(self, *others): | |
| """Add a collection of values to inputs. | |
| Args: | |
| self: implicitly added | |
| *others: {type}`list[Label]` collection of labels to add to | |
| inputs. Only values not already present are added. Note that a | |
| `Label`, not `str`, should be passed to ensure different apparent | |
| labels can be properly de-duplicated. | |
| """ | |
| list_add_unique( | |
| self._state[_INPUTS], | |
| others, | |
| convert = lambda v: normalize_transition_in_out_value("input", v), | |
| ) | |
| def _RuleCfg_update_outputs(self, *others): | |
| """Add a collection of values to outputs. | |
| Args: | |
| self: implicitly added | |
| *others: {type}`list[Label]` collection of labels to add to | |
| outputs. Only values not already present are added. Note that a | |
| `Label`, not `str`, should be passed to ensure different apparent | |
| labels can be properly de-duplicated. | |
| """ | |
| list_add_unique( | |
| self._state[_OUTPUTS], | |
| others, | |
| convert = lambda v: normalize_transition_in_out_value("output", v), | |
| ) | |
| # buildifier: disable=name-conventions | |
| RuleCfg = struct( | |
| TYPEDEF = _RuleCfg_typedef, | |
| new = _RuleCfg_new, | |
| # keep sorted | |
| add_inputs = _RuleCfg_add_inputs, | |
| add_outputs = _RuleCfg_add_outputs, | |
| build = _RuleCfg_build, | |
| update_inputs = _RuleCfg_update_inputs, | |
| update_outputs = _RuleCfg_update_outputs, | |
| ) | |
| def _Rule_typedef(): | |
| """A builder to accumulate state for constructing a `rule` object. | |
| :::{field} attrs | |
| :type: AttrsDict | |
| ::: | |
| :::{field} cfg | |
| :type: RuleCfg | |
| ::: | |
| :::{function} doc() -> str | |
| ::: | |
| :::{function} exec_groups() -> dict[str, ExecGroup] | |
| ::: | |
| :::{function} executable() -> bool | |
| ::: | |
| :::{include} /_includes/field_kwargs_doc.md | |
| ::: | |
| :::{function} fragments() -> list[str] | |
| ::: | |
| :::{function} implementation() -> callable | None | |
| ::: | |
| :::{function} provides() -> list[provider | list[provider]] | |
| ::: | |
| :::{function} set_doc(v: str) | |
| ::: | |
| :::{function} set_executable(v: bool) | |
| ::: | |
| :::{function} set_implementation(v: callable) | |
| ::: | |
| :::{function} set_test(v: bool) | |
| ::: | |
| :::{function} test() -> bool | |
| ::: | |
| :::{function} toolchains() -> list[ToolchainType] | |
| ::: | |
| """ | |
| def _Rule_new(**kwargs): | |
| """Builder for creating rules. | |
| Args: | |
| **kwargs: The same as the `rule()` function, but using builders or | |
| dicts to specify sub-objects instead of the immutable Bazel | |
| objects. | |
| """ | |
| kwargs.setdefault(_IMPLEMENTATION, None) | |
| kwargs_set_default_doc(kwargs) | |
| kwargs_set_default_dict(kwargs, _EXEC_GROUPS) | |
| kwargs_set_default_ignore_none(kwargs, "executable", False) | |
| kwargs_set_default_list(kwargs, "fragments") | |
| kwargs_set_default_list(kwargs, "provides") | |
| kwargs_set_default_ignore_none(kwargs, "test", False) | |
| kwargs_set_default_list(kwargs, _TOOLCHAINS) | |
| for name, value in kwargs[_EXEC_GROUPS].items(): | |
| kwargs[_EXEC_GROUPS][name] = _ExecGroup_maybe_from(value) | |
| for i, value in enumerate(kwargs[_TOOLCHAINS]): | |
| kwargs[_TOOLCHAINS][i] = _ToolchainType_maybe_from(value) | |
| # buildifier: disable=uninitialized | |
| self = struct( | |
| attrs = _AttrsDict_new(kwargs.pop(_ATTRS, None)), | |
| build = lambda *a, **k: _Rule_build(self, *a, **k), | |
| cfg = _RuleCfg_new(kwargs.pop(_CFG, None)), | |
| doc = kwargs_getter_doc(kwargs), | |
| exec_groups = kwargs_getter(kwargs, _EXEC_GROUPS), | |
| executable = kwargs_getter(kwargs, "executable"), | |
| fragments = kwargs_getter(kwargs, "fragments"), | |
| implementation = kwargs_getter(kwargs, _IMPLEMENTATION), | |
| kwargs = kwargs, | |
| provides = kwargs_getter(kwargs, "provides"), | |
| set_doc = kwargs_setter_doc(kwargs), | |
| set_executable = kwargs_setter(kwargs, "executable"), | |
| set_implementation = kwargs_setter(kwargs, _IMPLEMENTATION), | |
| set_test = kwargs_setter(kwargs, "test"), | |
| test = kwargs_getter(kwargs, "test"), | |
| to_kwargs = lambda: _Rule_to_kwargs(self), | |
| toolchains = kwargs_getter(kwargs, _TOOLCHAINS), | |
| ) | |
| return self | |
| def _Rule_build(self, debug = ""): | |
| """Builds a `rule` object | |
| Args: | |
| self: implicitly added | |
| debug: {type}`str` If set, prints the args used to create the rule. | |
| Returns: | |
| {type}`rule` | |
| """ | |
| kwargs = self.to_kwargs() | |
| if debug: | |
| lines = ["=" * 80, "rule kwargs: {}:".format(debug)] | |
| for k, v in sorted(kwargs.items()): | |
| if types.is_dict(v): | |
| lines.append(" %s={" % k) | |
| for k2, v2 in sorted(v.items()): | |
| lines.append(" {}: {}".format(k2, v2)) | |
| lines.append(" }") | |
| elif types.is_list(v): | |
| lines.append(" {}=[".format(k)) | |
| for i, v2 in enumerate(v): | |
| lines.append(" [{}] {}".format(i, v2)) | |
| lines.append(" ]") | |
| else: | |
| lines.append(" {}={}".format(k, v)) | |
| print("\n".join(lines)) # buildifier: disable=print | |
| return rule(**kwargs) | |
| def _Rule_to_kwargs(self): | |
| """Builds the arguments for calling `rule()`. | |
| This is added as an escape hatch to construct the final values `rule()` | |
| kwarg values in case callers want to manually change them. | |
| Args: | |
| self: implicitly added. | |
| Returns: | |
| {type}`dict` | |
| """ | |
| kwargs = dict(self.kwargs) | |
| if _EXEC_GROUPS in kwargs: | |
| kwargs[_EXEC_GROUPS] = { | |
| k: v.build() if _is_builder(v) else v | |
| for k, v in kwargs[_EXEC_GROUPS].items() | |
| } | |
| if _TOOLCHAINS in kwargs: | |
| kwargs[_TOOLCHAINS] = [ | |
| v.build() if _is_builder(v) else v | |
| for v in kwargs[_TOOLCHAINS] | |
| ] | |
| if _ATTRS not in kwargs: | |
| kwargs[_ATTRS] = self.attrs.build() | |
| if _CFG not in kwargs: | |
| kwargs[_CFG] = self.cfg.build() | |
| return kwargs | |
| # buildifier: disable=name-conventions | |
| Rule = struct( | |
| TYPEDEF = _Rule_typedef, | |
| new = _Rule_new, | |
| build = _Rule_build, | |
| to_kwargs = _Rule_to_kwargs, | |
| ) | |
| def _AttrsDict_typedef(): | |
| """Builder for the dictionary of rule attributes. | |
| :::{field} map | |
| :type: dict[str, AttributeBuilder] | |
| The underlying dict of attributes. Directly accessible so that regular | |
| dict operations (e.g. `x in y`) can be performed, if necessary. | |
| ::: | |
| :::{function} get(key, default=None) | |
| Get an entry from the dict. Convenience wrapper for `.map.get(...)` | |
| ::: | |
| :::{function} items() -> list[tuple[str, object]] | |
| Returns a list of key-value tuples. Convenience wrapper for `.map.items()` | |
| ::: | |
| :::{function} pop(key, default) -> object | |
| Removes a key from the attr dict | |
| ::: | |
| """ | |
| def _AttrsDict_new(initial): | |
| """Creates a builder for the `rule.attrs` dict. | |
| Args: | |
| initial: {type}`dict[str, callable | AttributeBuilder] | None` dict of | |
| initial values to populate the attributes dict with. | |
| Returns: | |
| {type}`AttrsDict` | |
| """ | |
| # buildifier: disable=uninitialized | |
| self = struct( | |
| # keep sorted | |
| build = lambda: _AttrsDict_build(self), | |
| get = lambda *a, **k: self.map.get(*a, **k), | |
| items = lambda: self.map.items(), | |
| map = {}, | |
| put = lambda key, value: _AttrsDict_put(self, key, value), | |
| update = lambda *a, **k: _AttrsDict_update(self, *a, **k), | |
| pop = lambda *a, **k: self.map.pop(*a, **k), | |
| ) | |
| if initial: | |
| _AttrsDict_update(self, initial) | |
| return self | |
| def _AttrsDict_put(self, name, value): | |
| """Sets a value in the attrs dict. | |
| Args: | |
| self: implicitly added | |
| name: {type}`str` the attribute name to set in the dict | |
| value: {type}`AttributeBuilder | callable` the value for the | |
| attribute. If a callable, then it is treated as an | |
| attribute builder factory (no-arg callable that returns an | |
| attribute builder) and is called immediately. | |
| """ | |
| if types.is_function(value): | |
| # Convert factory function to builder | |
| value = value() | |
| self.map[name] = value | |
| def _AttrsDict_update(self, other): | |
| """Merge `other` into this object. | |
| Args: | |
| self: implicitly added | |
| other: {type}`dict[str, callable | AttributeBuilder]` the values to | |
| merge into this object. If the value a function, it is called | |
| with no args and expected to return an attribute builder. This | |
| allows defining dicts of common attributes (where the values are | |
| functions that create a builder) and merge them into the rule. | |
| """ | |
| for k, v in other.items(): | |
| # Handle factory functions that create builders | |
| if types.is_function(v): | |
| self.map[k] = v() | |
| else: | |
| self.map[k] = v | |
| def _AttrsDict_build(self): | |
| """Build an attribute dict for passing to `rule()`. | |
| Returns: | |
| {type}`dict[str, Attribute]` where the values are `attr.XXX` objects | |
| """ | |
| attrs = {} | |
| for k, v in self.map.items(): | |
| attrs[k] = v.build() if _is_builder(v) else v | |
| return attrs | |
| def _AttributeBuilder_typedef(): | |
| """An abstract base typedef for builder for a Bazel {obj}`Attribute` | |
| Instances of this are a builder for a particular `Attribute` type, | |
| e.g. `attr.label`, `attr.string`, etc. | |
| """ | |
| # buildifier: disable=name-conventions | |
| AttributeBuilder = struct( | |
| TYPEDEF = _AttributeBuilder_typedef, | |
| ) | |
| # buildifier: disable=name-conventions | |
| AttrsDict = struct( | |
| TYPEDEF = _AttrsDict_typedef, | |
| new = _AttrsDict_new, | |
| update = _AttrsDict_update, | |
| build = _AttrsDict_build, | |
| ) | |
| ruleb = struct( | |
| Rule = _Rule_new, | |
| ToolchainType = _ToolchainType_new, | |
| ExecGroup = _ExecGroup_new, | |
| ) |