| """General utility functions""" | |
| load("@bazel_tools//tools/build_defs/repo:http.bzl", _http_archive = "http_archive") | |
| load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") | |
| def _propagate_well_known_tags(tags = []): | |
| """Returns a list of tags filtered from the input set that only contains the ones that are considered "well known" | |
| These are listed in Bazel's documentation: | |
| https://docs.bazel.build/versions/main/test-encyclopedia.html#tag-conventions | |
| https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes | |
| Args: | |
| tags: List of tags to filter | |
| Returns: | |
| List of tags that only contains the well known set | |
| """ | |
| WELL_KNOWN_TAGS = [ | |
| "no-sandbox", | |
| "no-cache", | |
| "no-remote-cache", | |
| "no-remote-exec", | |
| "no-remote", | |
| "local", | |
| "requires-network", | |
| "block-network", | |
| "requires-fakeroot", | |
| "exclusive", | |
| "manual", | |
| "external", | |
| ] | |
| # cpu:n tags allow setting the requested number of CPUs for a test target. | |
| # More info at https://docs.bazel.build/versions/main/test-encyclopedia.html#other-resources | |
| CPU_PREFIX = "cpu:" | |
| return [ | |
| tag | |
| for tag in tags | |
| if tag in WELL_KNOWN_TAGS or tag.startswith(CPU_PREFIX) | |
| ] | |
| def _to_label(param): | |
| """Converts a string to a Label. If Label is supplied, the same label is returned. | |
| Args: | |
| param: a string representing a label or a Label | |
| Returns: | |
| a Label | |
| """ | |
| root_repo = "@@" if _is_bazel_6_or_greater() else "@" | |
| param_type = type(param) | |
| if param_type == "string": | |
| if param.startswith("@"): | |
| return Label(param) | |
| if param.startswith("//"): | |
| return Label("{}{}".format(root_repo, param)) | |
| if param.startswith(":"): | |
| param = param[1:] | |
| return Label("{}//{}:{}".format(root_repo, native.package_name(), param)) | |
| elif param_type == "Label": | |
| return param | |
| else: | |
| msg = "Expected 'string' or 'Label' but got '{}'".format(param_type) | |
| fail(msg) | |
| def _consistent_label_str(ctx, label): | |
| """Generate a consistent label string for all Bazel versions. | |
| Starting in Bazel 6, the workspace name is empty for the local workspace and there's no other | |
| way to determine it. This behavior differs from Bazel 5 where the local workspace name was fully | |
| qualified in str(label). | |
| This utility function is meant for use in rules and requires the rule context to determine the | |
| user's workspace name (`ctx.workspace_name`). | |
| Args: | |
| ctx: The rule context. | |
| label: A Label. | |
| Returns: | |
| String representation of the label including the repository name if the label is from an | |
| external repository. For labels in the user's repository the label will start with `@//`. | |
| """ | |
| return "@{}//{}:{}".format( | |
| "" if label.workspace_name == ctx.workspace_name else label.workspace_name, | |
| label.package, | |
| label.name, | |
| ) | |
| def _is_external_label(param): | |
| """Returns True if the given Label (or stringy version of a label) represents a target outside of the workspace | |
| Args: | |
| param: a string or label | |
| Returns: | |
| a bool | |
| """ | |
| if not _is_bazel_6_or_greater() and str(param).startswith("@@//"): | |
| # Work-around for https://github.com/bazelbuild/bazel/issues/16528 | |
| return False | |
| return len(_to_label(param).workspace_root) > 0 | |
| # Path to the root of the workspace | |
| def _path_to_workspace_root(): | |
| """ Returns the path to the workspace root under bazel | |
| Returns: | |
| Path to the workspace root | |
| """ | |
| return "/".join([".."] * len(native.package_name().split("/"))) | |
| # Like glob() but returns directories only | |
| def _glob_directories(include, **kwargs): | |
| all = native.glob(include, exclude_directories = 0, **kwargs) | |
| files = native.glob(include, **kwargs) | |
| directories = [p for p in all if p not in files] | |
| return directories | |
| def _file_exists(path): | |
| """Check whether a file exists. | |
| Useful in macros to set defaults for a configuration file if it is present. | |
| This can only be called during the loading phase, not from a rule implementation. | |
| Args: | |
| path: a label, or a string which is a path relative to this package | |
| """ | |
| label = _to_label(path) | |
| file_abs = "%s/%s" % (label.package, label.name) | |
| file_rel = file_abs[len(native.package_name()) + 1:] | |
| file_glob = native.glob([file_rel], exclude_directories = 1, allow_empty = True) | |
| return len(file_glob) > 0 | |
| def _default_timeout(size, timeout): | |
| """Provide a sane default for *_test timeout attribute. | |
| The [test-encyclopedia](https://bazel.build/reference/test-encyclopedia) says: | |
| > Tests may return arbitrarily fast regardless of timeout. | |
| > A test is not penalized for an overgenerous timeout, although a warning may be issued: | |
| > you should generally set your timeout as tight as you can without incurring any flakiness. | |
| However Bazel's default for timeout is medium, which is dumb given this guidance. | |
| It also says: | |
| > Tests which do not explicitly specify a timeout have one implied based on the test's size as follows | |
| Therefore if size is specified, we should allow timeout to take its implied default. | |
| If neither is set, then we can fix Bazel's wrong default here to avoid warnings under | |
| `--test_verbose_timeout_warnings`. | |
| This function can be used in a macro which wraps a testing rule. | |
| Args: | |
| size: the size attribute of a test target | |
| timeout: the timeout attribute of a test target | |
| Returns: | |
| "short" if neither is set, otherwise timeout | |
| """ | |
| if size == None and timeout == None: | |
| return "short" | |
| return timeout | |
| def _is_bazel_6_or_greater(): | |
| """Detects if the Bazel version being used is greater than or equal to 6 (including Bazel 6 pre-releases and RCs). | |
| Detecting Bazel 6 or greater is particularly useful in rules as slightly different code paths may be needed to | |
| support bzlmod which was added in Bazel 6. | |
| Unlike the undocumented `native.bazel_version`, which only works in WORKSPACE and repository rules, this function can | |
| be used in rules and BUILD files. | |
| An alternate approach to make the Bazel version available in BUILD files and rules would be to | |
| use the [host_repo](https://github.com/aspect-build/bazel-lib/blob/main/docs/host_repo.md) repository rule | |
| which contains the bazel_version in the exported `host` struct: | |
| WORKSPACE: | |
| ``` | |
| load("@aspect_bazel_lib//lib:host_repo.bzl", "host_repo") | |
| host_repo(name = "aspect_bazel_lib_host") | |
| ``` | |
| BUILD.bazel: | |
| ``` | |
| load("@aspect_bazel_lib_host//:defs.bzl", "host") | |
| print(host.bazel_version) | |
| ``` | |
| That approach, however, incurs a cost in the user's WORKSPACE. | |
| Returns: | |
| True if the Bazel version being used is greater than or equal to 6 (including pre-releases and RCs) | |
| """ | |
| # Hacky way to check if the we're using at least Bazel 6. Would be nice if there was a ctx.bazel_version instead. | |
| # native.bazel_version only works in repository rules. | |
| return "apple_binary" not in dir(native) | |
| def _is_bazel_7_or_greater(): | |
| """Detects if the Bazel version being used is greater than or equal to 7 (including Bazel 7 pre-releases and RCs). | |
| Unlike the undocumented `native.bazel_version`, which only works in WORKSPACE and repository rules, this function can | |
| be used in rules and BUILD files. | |
| An alternate approach to make the Bazel version available in BUILD files and rules would be to | |
| use the [host_repo](https://github.com/aspect-build/bazel-lib/blob/main/docs/host_repo.md) repository rule | |
| which contains the bazel_version in the exported `host` struct: | |
| WORKSPACE: | |
| ``` | |
| load("@aspect_bazel_lib//lib:host_repo.bzl", "host_repo") | |
| host_repo(name = "aspect_bazel_lib_host") | |
| ``` | |
| BUILD.bazel: | |
| ``` | |
| load("@aspect_bazel_lib_host//:defs.bzl", "host") | |
| print(host.bazel_version) | |
| ``` | |
| That approach, however, incurs a cost in the user's WORKSPACE. | |
| Returns: | |
| True if the Bazel version being used is greater than or equal to 7 (including pre-releases and RCs) | |
| """ | |
| # Hacky way to check if the we're using at least Bazel 7. Would be nice if there was a ctx.bazel_version instead. | |
| # native.bazel_version only works in repository rules. | |
| return "apple_binary" not in dir(native) and "cc_host_toolchain_alias" not in dir(native) | |
| def is_bzlmod_enabled(): | |
| """Detect the value of the --enable_bzlmod flag""" | |
| return str(Label("@//:BUILD.bazel")).startswith("@@") | |
| def _maybe_http_archive(**kwargs): | |
| """Adapts a maybe(http_archive, ...) to look like an http_archive. | |
| This makes WORKSPACE dependencies easier to read and update. | |
| Typical usage looks like, | |
| ``` | |
| load("//lib:utils.bzl", http_archive = "maybe_http_archive") | |
| http_archive( | |
| name = "aspect_rules_js", | |
| sha256 = "5bb643d9e119832a383e67f946dc752b6d719d66d1df9b46d840509ceb53e1f1", | |
| strip_prefix = "rules_js-1.6.2", | |
| url = "https://github.com/aspect-build/rules_js/archive/refs/tags/v1.6.2.tar.gz", | |
| ) | |
| ``` | |
| instead of the classic maybe pattern of, | |
| ``` | |
| load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") | |
| load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") | |
| maybe( | |
| http_archive, | |
| name = "aspect_rules_js", | |
| sha256 = "5bb643d9e119832a383e67f946dc752b6d719d66d1df9b46d840509ceb53e1f1", | |
| strip_prefix = "rules_js-1.6.2", | |
| url = "https://github.com/aspect-build/rules_js/archive/refs/tags/v1.6.2.tar.gz", | |
| ) | |
| ``` | |
| Args: | |
| **kwargs: all arguments to pass-forward to http_archive | |
| """ | |
| maybe(_http_archive, **kwargs) | |
| _COMMON_RULE_ATTRIBUTES = [ | |
| "compatible_with", | |
| "deprecation", | |
| "distribs", | |
| "exec_compatible_with", | |
| "exec_properties", | |
| "features", | |
| "restricted_to", | |
| "tags", | |
| "target_compatible_with", | |
| "testonly", | |
| "toolchains", | |
| "visibility", | |
| ] | |
| _COMMON_TEST_RULE_ATTRIBUTES = _COMMON_RULE_ATTRIBUTES + [ | |
| "args", | |
| "env", | |
| "env_inherit", | |
| "size", | |
| "timeout", | |
| "flaky", | |
| "shard_count", | |
| "local", | |
| ] | |
| _COMMON_BINARY_RULE_ATTRIBUTES = _COMMON_RULE_ATTRIBUTES + [ | |
| "args", | |
| "env", | |
| "output_licenses", | |
| ] | |
| def _propagate_common_rule_attributes(attrs): | |
| """Returns a dict of rule parameters filtered from the input dict that only contains the onces that are common to all rules | |
| These are listed in Bazel's documentation: | |
| https://bazel.build/reference/be/common-definitions#common-attributes | |
| Args: | |
| attrs: Dict of parameters to filter | |
| Returns: | |
| The dict of parameters, containing only common attributes | |
| """ | |
| return { | |
| k: attrs[k] | |
| for k in attrs | |
| if k in _COMMON_RULE_ATTRIBUTES | |
| } | |
| def _propagate_common_test_rule_attributes(attrs): | |
| """Returns a dict of rule parameters filtered from the input dict that only contains the onces that are common to all test rules | |
| These are listed in Bazel's documentation: | |
| https://bazel.build/reference/be/common-definitions#common-attributes | |
| https://bazel.build/reference/be/common-definitions#common-attributes-tests | |
| Args: | |
| attrs: Dict of parameters to filter | |
| Returns: | |
| The dict of parameters, containing only common test attributes | |
| """ | |
| return { | |
| k: attrs[k] | |
| for k in attrs | |
| if k in _COMMON_TEST_RULE_ATTRIBUTES | |
| } | |
| def _propagate_common_binary_rule_attributes(attrs): | |
| """Returns a dict of rule parameters filtered from the input dict that only contains the onces that are common to all binary rules | |
| These are listed in Bazel's documentation: | |
| https://bazel.build/reference/be/common-definitions#common-attributes | |
| https://bazel.build/reference/be/common-definitions#common-attributes-binary | |
| Args: | |
| attrs: Dict of parameters to filter | |
| Returns: | |
| The dict of parameters, containing only common binary attributes | |
| """ | |
| return { | |
| k: attrs[k] | |
| for k in attrs | |
| if k in _COMMON_RULE_ATTRIBUTES or k in _COMMON_BINARY_RULE_ATTRIBUTES | |
| } | |
| utils = struct( | |
| default_timeout = _default_timeout, | |
| file_exists = _file_exists, | |
| glob_directories = _glob_directories, | |
| is_bazel_6_or_greater = _is_bazel_6_or_greater, | |
| is_bazel_7_or_greater = _is_bazel_7_or_greater, | |
| is_bzlmod_enabled = is_bzlmod_enabled, | |
| is_external_label = _is_external_label, | |
| maybe_http_archive = _maybe_http_archive, | |
| path_to_workspace_root = _path_to_workspace_root, | |
| propagate_well_known_tags = _propagate_well_known_tags, | |
| propagate_common_rule_attributes = _propagate_common_rule_attributes, | |
| propagate_common_test_rule_attributes = _propagate_common_test_rule_attributes, | |
| propagate_common_binary_rule_attributes = _propagate_common_binary_rule_attributes, | |
| to_label = _to_label, | |
| consistent_label_str = _consistent_label_str, | |
| ) |