diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/BUILD | 5 | ||||
-rw-r--r-- | lib/dicts.bzl | 27 | ||||
-rw-r--r-- | lib/subpackages.bzl | 96 | ||||
-rw-r--r-- | lib/unittest.bzl | 40 | ||||
-rw-r--r-- | lib/versions.bzl | 5 |
5 files changed, 168 insertions, 5 deletions
@@ -62,6 +62,11 @@ bzl_library( ) bzl_library( + name = "subpackages", + srcs = ["subpackages.bzl"], +) + +bzl_library( name = "types", srcs = ["types.bzl"], ) diff --git a/lib/dicts.bzl b/lib/dicts.bzl index 3f8e661..5a92aa1 100644 --- a/lib/dicts.bzl +++ b/lib/dicts.bzl @@ -38,6 +38,33 @@ def _add(*dictionaries, **kwargs): result.update(kwargs) return result +def _omit(dictionary, keys): + """Returns a new `dict` that has all the entries of `dictionary` with keys not in `keys`. + + Args: + dictionary: A `dict`. + keys: A sequence. + + Returns: + A new `dict` that has all the entries of `dictionary` with keys not in `keys`. + """ + keys_set = {k: None for k in keys} + return {k: dictionary[k] for k in dictionary if k not in keys_set} + +def _pick(dictionary, keys): + """Returns a new `dict` that has all the entries of `dictionary` with keys in `keys`. + + Args: + dictionary: A `dict`. + keys: A sequence. + + Returns: + A new `dict` that has all the entries of `dictionary` with keys in `keys`. + """ + return {k: dictionary[k] for k in keys if k in dictionary} + dicts = struct( add = _add, + omit = _omit, + pick = _pick, ) diff --git a/lib/subpackages.bzl b/lib/subpackages.bzl new file mode 100644 index 0000000..5b674fd --- /dev/null +++ b/lib/subpackages.bzl @@ -0,0 +1,96 @@ +# Copyright 2022 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. + +"""Skylib module containing common functions for working with native.subpackages() +""" +_SUBPACKAGES_SUPPORTED = hasattr(native, "subpackages") + +def _supported(): + return _SUBPACKAGES_SUPPORTED + +def _check_supported(): + if not _SUBPACKAGES_SUPPORTED: + fail("native.subpackages not supported in this version of Bazel.") + +def _all(exclude = [], allow_empty = False, fully_qualified = True): + """List all direct subpackages of the current package regardless of directory depth. + + The returned list contains all subpackages, but not subpackages of subpackages. + + Example: + Assuming the following BUILD files exist: + + BUILD + foo/BUILD + foo/sub/BUILD + bar/BUILD + baz/deep/dir/BUILD + + If the current package is '//' all() will return ['//foo', '//bar', + '//baz/deep/dir']. //foo/sub is not included because it is a direct + subpackage of '//foo' not '//' + + NOTE: fail()s if native.subpackages() is not supported. + + Args: + exclude: see native.subpackages(exclude) + allow_empty: see native.subpackages(allow_empty) + fully_qualified: It true return fully qualified Labels for subpackages, + otherwise returns subpackage path relative to current package. + + Returns: + A mutable sorted list containing all sub-packages of the current Bazel + package. + """ + _check_supported() + + subs = native.subpackages(include = ["**"], exclude = exclude, allow_empty = allow_empty) + if fully_qualified: + return [_fully_qualified(s) for s in subs] + + return subs + +def _fully_qualified(relative_path): + return "//%s/%s" % (native.package_name(), relative_path) + +def _exists(relative_path): + """Checks to see if relative_path is a direct subpackage of the current package. + + Example: + + BUILD + foo/BUILD + foo/sub/BUILD + + If the current package is '//' (the top-level BUILD file): + subpackages.exists("foo") == True + subpackages.exists("foo/sub") == False + subpackages.exists("bar") == False + + NOTE: fail()s if native.subpackages() is not supported in the current Bazel version. + + Args: + relative_path: a path to a subpackage to test, must not be an absolute Label. + + Returns: + True if 'relative_path' is a subpackage of the current package. + """ + _check_supported() + return relative_path in native.subpackages(include = [relative_path], allow_empty = True) + +subpackages = struct( + all = _all, + exists = _exists, + supported = _supported, +) diff --git a/lib/unittest.bzl b/lib/unittest.bzl index 3757b08..02e374c 100644 --- a/lib/unittest.bzl +++ b/lib/unittest.bzl @@ -14,9 +14,18 @@ """Unit testing support. -Unlike most Skylib files, this exports two modules: `unittest` which contains -functions to declare and define unit tests, and `asserts` which contains the -assertions used to within tests. +Unlike most Skylib files, this exports four modules: +* `unittest` contains functions to declare and define unit tests for ordinary + Starlark functions; +* `analysistest` contains functions to declare and define tests for analysis + phase behavior of a rule, such as a given target's providers or registered + actions; +* `loadingtest` contains functions to declare and define tests for loading + phase behavior, such as macros and `native.*`; +* `asserts` contains the assertions used within tests. + +See https://bazel.build/extending/concepts for background about macros, rules, +and the different phases of a build. """ load(":new_sets.bzl", new_sets = "sets") @@ -139,7 +148,7 @@ def _impl_function_name(impl): impl_name = impl_name.partition("<function ")[-1] return impl_name.rpartition(">")[0] -def _make(impl, attrs = {}): +def _make(impl, attrs = {}, doc = ""): """Creates a unit test rule from its implementation function. Each unit test is defined in an implementation function that must then be @@ -169,6 +178,7 @@ def _make(impl, attrs = {}): impl: The implementation function of the unit test. attrs: An optional dictionary to supplement the attrs passed to the unit test's `rule()` constructor. + doc: A description of the rule that can be extracted by documentation generating tools. Returns: A rule definition that should be stored in a global whose name ends in @@ -179,6 +189,7 @@ def _make(impl, attrs = {}): return rule( impl, + doc = doc, attrs = attrs, _skylark_testable = True, test = True, @@ -364,6 +375,25 @@ def _begin(ctx): """ return struct(ctx = ctx, failures = []) +def _begin_analysis_test(ctx): + """Begins an analysis test. + + This should be the first function called in an analysis test implementation + function. It initializes a "test environment" that is used to collect + assertion failures so that they can be reported and logged at the end of the + test. + + Args: + ctx: The Starlark context. Pass the implementation function's `ctx` argument + in verbatim. + + Returns: + A test environment struct that must be passed to assertions and finally to + `analysistest.end`. Do not rely on internal details about the fields in this + struct as it may change. + """ + return struct(ctx = ctx, failures = []) + def _end_analysis_test(env): """Ends an analysis test and logs the results. @@ -647,7 +677,7 @@ unittest = struct( analysistest = struct( make = _make_analysis_test, - begin = _begin, + begin = _begin_analysis_test, end = _end_analysis_test, fail = _fail, target_actions = _target_actions, diff --git a/lib/versions.bzl b/lib/versions.bzl index 0209a6f..87ca73c 100644 --- a/lib/versions.bzl +++ b/lib/versions.bzl @@ -44,6 +44,9 @@ def _parse_bazel_version(bazel_version): int tuples can be compared directly using binary operators (<, >). + For a development build of Bazel, this returns an unspecified version tuple + that compares higher than any released version. + Args: bazel_version: the Bazel version string @@ -52,6 +55,8 @@ def _parse_bazel_version(bazel_version): """ version = _extract_version_number(bazel_version) + if not version: + return (999999, 999999, 999999) return tuple([int(n) for n in version.split(".")]) def _is_at_most(threshold, version): |