diff --git a/README.md b/README.md index 3c4ec2b..52733eb 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ s = shell.quote(p) * [new_sets](docs/new_sets_doc.md) * [shell](docs/shell_doc.md) * [structs](docs/structs_doc.md) +* [subpackages](docs/subpackages_doc.md) * [types](docs/types_doc.md) * [unittest](docs/unittest_doc.md) * [versions](docs/versions_doc.md) diff --git a/docs/BUILD b/docs/BUILD index 284db3c..988b53a 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -82,6 +82,11 @@ stardoc_with_diff_test( out_label = "//docs:structs_doc.md", ) +stardoc_with_diff_test( + bzl_library_target = "//lib:subpackages", + out_label = "//docs:subpackages_doc.md", +) + stardoc_with_diff_test( bzl_library_target = "//lib:types", out_label = "//docs:types_doc.md", diff --git a/docs/subpackages_doc.md b/docs/subpackages_doc.md new file mode 100755 index 0000000..b2363cd --- /dev/null +++ b/docs/subpackages_doc.md @@ -0,0 +1,96 @@ + + +Skylib module containing common functions for working with native.subpackages() + + + + +## subpackages.all + +
+subpackages.all(exclude, allow_empty, fully_qualified)
+
+ +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. + + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| exclude | see native.subpackages(exclude) | [] | +| allow_empty | see native.subpackages(allow_empty) | False | +| fully_qualified | It true return fully qualified Labels for subpackages, otherwise returns subpackage path relative to current package. | True | + +**RETURNS** + +A mutable sorted list containing all sub-packages of the current Bazel +package. + + + + +## subpackages.exists + +
+subpackages.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. + + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| relative_path | a path to a subpackage to test, must not be an absolute Label. | none | + +**RETURNS** + +True if 'relative_path' is a subpackage of the current package. + + + + +## subpackages.supported + +
+subpackages.supported()
+
+ + + + + diff --git a/lib/BUILD b/lib/BUILD index 7e7a9a4..2328081 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -61,6 +61,11 @@ bzl_library( srcs = ["structs.bzl"], ) +bzl_library( + name = "subpackages", + srcs = ["subpackages.bzl"], +) + bzl_library( name = "types", srcs = ["types.bzl"], 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/tests/BUILD b/tests/BUILD index c2a4223..bbab077 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -8,6 +8,7 @@ load(":paths_tests.bzl", "paths_test_suite") load(":selects_tests.bzl", "selects_test_suite") load(":shell_tests.bzl", "shell_args_test_gen", "shell_test_suite") load(":structs_tests.bzl", "structs_test_suite") +load(":subpackages_tests.bzl", "subpackages_test_suite") load(":types_tests.bzl", "types_test_suite") load(":unittest_tests.bzl", "unittest_passing_tests_suite") load(":versions_tests.bzl", "versions_test_suite") @@ -37,6 +38,8 @@ shell_test_suite() structs_test_suite() +subpackages_test_suite() + types_test_suite() unittest_passing_tests_suite() diff --git a/tests/subpackages_tests.bzl b/tests/subpackages_tests.bzl new file mode 100644 index 0000000..623a446 --- /dev/null +++ b/tests/subpackages_tests.bzl @@ -0,0 +1,84 @@ +# 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. + +"""Unit tests for subpackages.bzl.""" + +load("//lib:subpackages.bzl", "subpackages") +load("//lib:unittest.bzl", "loadingtest") + +def _all_test(env): + """Unit tests for subpackages.all.""" + + all_pkgs = [ + "copy_file", + "diff_test", + "expand_template", + "select_file", + "write_file", + ] + + # Not all pkgs exist in all test environments. + if subpackages.exists("run_binary"): + all_pkgs.append("run_binary") + + if subpackages.exists("native_binary"): + all_pkgs.append("native_binary") + + # These exist in all cases + filtered_pkgs = [ + "copy_file", + "expand_template", + "select_file", + "write_file", + ] + + # subpackages is always in sorted order: + all_pkgs = sorted(all_pkgs) + + # test defaults + loadingtest.equals( + env, + "all", + ["//tests/" + pkg for pkg in all_pkgs], + subpackages.all(), + ) + + # test non-fully-qualified output + loadingtest.equals( + env, + "all_not_fully_qualified", + all_pkgs, + subpackages.all(fully_qualified = False), + ) + + # test exclusion + loadingtest.equals( + env, + "all_w_exclude", + filtered_pkgs, + subpackages.all(exclude = ["diff_test", "run_binary", "native_binary"], fully_qualified = False), + ) + +def _exists_test(env): + """Unit tests for subpackages.exists.""" + loadingtest.equals(env, "exists_yes", True, subpackages.exists("copy_file")) + loadingtest.equals(env, "exists_no", False, subpackages.exists("never_existed")) + +def subpackages_test_suite(): + """Creates the test targets and test suite for subpackages.bzl tests.""" + + if subpackages.supported(): + env = loadingtest.make("subpackages") + _all_test(env) + _exists_test(env)