diff --git a/README.md b/README.md index ce70baba..12ef7dce 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ s = shell.quote(p) ## List of modules (in lib/) * [collections](docs/collections_doc.md) +* [compatibility](docs/compatibility_doc.md) * [dicts](docs/dicts_doc.md) * [partial](docs/partial_doc.md) * [paths](docs/paths_doc.md) diff --git a/docs/BUILD b/docs/BUILD index 4d31cd10..ddff1b27 100644 --- a/docs/BUILD +++ b/docs/BUILD @@ -26,6 +26,11 @@ stardoc_with_diff_test( out_label = "//docs:common_settings_doc.md", ) +stardoc_with_diff_test( + bzl_library_target = "//lib:compatibility", + out_label = "//docs:compatibility_doc.md", +) + stardoc_with_diff_test( name = "copy_directory", bzl_library_target = "//rules:copy_directory", diff --git a/docs/compatibility_doc.md b/docs/compatibility_doc.md new file mode 100644 index 00000000..1e939029 --- /dev/null +++ b/docs/compatibility_doc.md @@ -0,0 +1,145 @@ + + +Skylib module of convenience functions for `target_compatible_with`. + +Load the macros as follows in your `BUILD` files: +```python +load("@bazel_skylib//lib:compatibility.bzl", "compatibility") +``` + +See the [Platform docs](https://bazel.build/docs/platforms#skipping-incompatible-targets) for +more information. + + + + +## compatibility.all_of + +
+compatibility.all_of(settings) ++ +Create a `select()` for `target_compatible_with` which matches all of the given settings. + +All of the settings must be true to get an empty list. Failure to match will result +in an incompatible `constraint_value` for the purpose of target skipping. + +In other words, use this function to make a target incompatible unless all of the settings are +true. + +Example: + +```python +config_setting( + name = "dbg", + values = {"compilation_mode": "dbg"}, +) + +cc_binary( + name = "bin", + srcs = ["bin.cc"], + # This target can only be built for Linux in debug mode. + target_compatible_with = compatibility.all_of( + ":dbg", + "@platforms//os:linux", + ), +) +``` + +See also: `selects.config_setting_group(match_all)` + + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| settings | The
config_setting
or constraint_value
targets. | none |
+
+**RETURNS**
+
+A native series of `select()`s. The result is "incompatible" unless all settings are true.
+
+
+
+
+## compatibility.any_of
+
++compatibility.any_of(settings) ++ +Create a `select()` for `target_compatible_with` which matches any of the given settings. + +Any of the settings will resolve to an empty list, while the default condition will map to +an incompatible `constraint_value` for the purpose of target skipping. + +In other words, use this function to make target incompatible unless one or more of the +settings are true. + +```python +cc_binary( + name = "bin", + srcs = ["bin.cc"], + # This target can only be built for Linux or Mac. + target_compatible_with = compatibility.any_of( + "@platforms//os:linux", + "@platforms//os:macos", + ), +) +``` + + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| settings | The
config_settings
or constraint_value
targets. | none |
+
+**RETURNS**
+
+A native `select()` which maps any of the settings an empty list.
+
+
+
+
+## compatibility.none_of
+
++compatibility.none_of(settings) ++ +Create a `select()` for `target_compatible_with` which matches none of the given settings. + +Any of the settings will resolve to an incompatible `constraint_value` for the +purpose of target skipping. + +In other words, use this function to make target incompatible if any of the settings are true. + +```python +cc_binary( + name = "bin", + srcs = ["bin.cc"], + # This target cannot be built for Linux or Mac, but can be built for + # everything else. + target_compatible_with = compatibility.none_of( + "@platforms//os:linux", + "@platforms//os:macos", + ), +) +``` + + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| settings | The
config_setting
or constraint_value
targets. | none |
+
+**RETURNS**
+
+A native `select()` which maps any of the settings to the incompatible target.
+
+
diff --git a/lib/BUILD b/lib/BUILD
index 23280816..c44ee12f 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -15,6 +15,14 @@ bzl_library(
srcs = ["collections.bzl"],
)
+bzl_library(
+ name = "compatibility",
+ srcs = ["compatibility.bzl"],
+ deps = [
+ ":selects",
+ ],
+)
+
bzl_library(
name = "dicts",
srcs = ["dicts.bzl"],
diff --git a/lib/compatibility.bzl b/lib/compatibility.bzl
new file mode 100644
index 00000000..b75baab4
--- /dev/null
+++ b/lib/compatibility.bzl
@@ -0,0 +1,126 @@
+"""Skylib module of convenience functions for `target_compatible_with`.
+
+Load the macros as follows in your `BUILD` files:
+```python
+load("@bazel_skylib//lib:compatibility.bzl", "compatibility")
+```
+
+See the [Platform docs](https://bazel.build/docs/platforms#skipping-incompatible-targets) for
+more information.
+"""
+
+load(":selects.bzl", "selects")
+
+def _none_of(*settings):
+ """Create a `select()` for `target_compatible_with` which matches none of the given settings.
+
+ Any of the settings will resolve to an incompatible `constraint_value` for the
+ purpose of target skipping.
+
+ In other words, use this function to make target incompatible if any of the settings are true.
+
+ ```python
+ cc_binary(
+ name = "bin",
+ srcs = ["bin.cc"],
+ # This target cannot be built for Linux or Mac, but can be built for
+ # everything else.
+ target_compatible_with = compatibility.none_of(
+ "@platforms//os:linux",
+ "@platforms//os:macos",
+ ),
+ )
+ ```
+
+ Args:
+ *settings: The `config_setting` or `constraint_value` targets.
+
+ Returns:
+ A native `select()` which maps any of the settings to the incompatible target.
+ """
+ return selects.with_or({
+ tuple(settings): ["@platforms//:incompatible"],
+ "//conditions:default": [],
+ })
+
+def _any_of(*settings):
+ """Create a `select()` for `target_compatible_with` which matches any of the given settings.
+
+ Any of the settings will resolve to an empty list, while the default condition will map to
+ an incompatible `constraint_value` for the purpose of target skipping.
+
+ In other words, use this function to make target incompatible unless one or more of the
+ settings are true.
+
+ ```python
+ cc_binary(
+ name = "bin",
+ srcs = ["bin.cc"],
+ # This target can only be built for Linux or Mac.
+ target_compatible_with = compatibility.any_of(
+ "@platforms//os:linux",
+ "@platforms//os:macos",
+ ),
+ )
+ ```
+
+ Args:
+ *settings: The `config_settings` or `constraint_value` targets.
+
+ Returns:
+ A native `select()` which maps any of the settings an empty list.
+ """
+ return selects.with_or({
+ tuple(settings): [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ })
+
+def _all_of(*settings):
+ """Create a `select()` for `target_compatible_with` which matches all of the given settings.
+
+ All of the settings must be true to get an empty list. Failure to match will result
+ in an incompatible `constraint_value` for the purpose of target skipping.
+
+ In other words, use this function to make a target incompatible unless all of the settings are
+ true.
+
+ Example:
+
+ ```python
+ config_setting(
+ name = "dbg",
+ values = {"compilation_mode": "dbg"},
+ )
+
+ cc_binary(
+ name = "bin",
+ srcs = ["bin.cc"],
+ # This target can only be built for Linux in debug mode.
+ target_compatible_with = compatibility.all_of(
+ ":dbg",
+ "@platforms//os:linux",
+ ),
+ )
+ ```
+
+ See also: `selects.config_setting_group(match_all)`
+
+ Args:
+ *settings: The `config_setting` or `constraint_value` targets.
+
+ Returns:
+ A native series of `select()`s. The result is "incompatible" unless all settings are true.
+ """
+ result = []
+ for setting in settings:
+ result += select({
+ setting: [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ })
+ return result
+
+compatibility = struct(
+ all_of = _all_of,
+ any_of = _any_of,
+ none_of = _none_of,
+)
diff --git a/tests/BUILD b/tests/BUILD
index bbab077a..babb4b95 100644
--- a/tests/BUILD
+++ b/tests/BUILD
@@ -92,6 +92,19 @@ sh_test(
tags = ["local"],
)
+sh_test(
+ name = "compatibility_test",
+ srcs = ["compatibility_test.sh"],
+ data = [
+ ":unittest.bash",
+ "//lib:compatibility",
+ ],
+ tags = ["local"],
+ deps = [
+ "@bazel_tools//tools/bash/runfiles",
+ ],
+)
+
shell_args_test_gen(
name = "shell_spawn_e2e_test_src",
)
diff --git a/tests/compatibility_test.sh b/tests/compatibility_test.sh
new file mode 100755
index 00000000..80007111
--- /dev/null
+++ b/tests/compatibility_test.sh
@@ -0,0 +1,320 @@
+#!/bin/bash
+#
+# 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.
+#
+# Test building targets that are declared as compatible only with certain
+# platforms (see the "target_compatible_with" common build rule attribute).
+
+# --- begin runfiles.bash initialization v2 ---
+# Copy-pasted from the Bazel Bash runfiles library v2.
+set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$0.runfiles/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v2 ---
+
+source "$(rlocation bazel_skylib/tests/unittest.bash)" \
+ || { echo "Could not source bazel_skylib/tests/unittest.bash" >&2; exit 1; }
+
+# `uname` returns the current platform, e.g "MSYS_NT-10.0" or "Linux".
+# `tr` converts all upper case letters to lower case.
+# `case` matches the result if the `uname | tr` expression to string prefixes
+# that use the same wildcards as names do in Bash, i.e. "msys*" matches strings
+# starting with "msys", and "*" matches everything (it's the default case).
+case "$(uname -s | tr [:upper:] [:lower:])" in
+msys*)
+ # As of 2019-01-15, Bazel on Windows only supports MSYS Bash.
+ declare -r is_windows=true
+ ;;
+*)
+ declare -r is_windows=false
+ ;;
+esac
+
+if "$is_windows"; then
+ export MSYS_NO_PATHCONV=1
+ export MSYS2_ARG_CONV_EXCL="*"
+fi
+
+function set_up() {
+ mkdir -p target_skipping || fail "couldn't create directory"
+
+ cat > target_skipping/pass.sh <