diff --git a/docs/common_settings_doc.md b/docs/common_settings_doc.md index 7de7965..b224f52 100755 --- a/docs/common_settings_doc.md +++ b/docs/common_settings_doc.md @@ -50,7 +50,7 @@ A bool-typed build setting that cannot be set on the command line ## int_flag
-int_flag(name)
+int_flag(name, make_variable)
 
An int-typed build setting that can be set on the command line @@ -61,6 +61,7 @@ An int-typed build setting that can be set on the command line | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | +| make_variable | If set, the build setting's value will be available as a Make variable with this name in the attributes of rules that list this build setting in their 'toolchains' attribute. | String | optional | "" | @@ -68,7 +69,7 @@ An int-typed build setting that can be set on the command line ## int_setting
-int_setting(name)
+int_setting(name, make_variable)
 
An int-typed build setting that cannot be set on the command line @@ -79,6 +80,7 @@ An int-typed build setting that cannot be set on the command line | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | +| make_variable | If set, the build setting's value will be available as a Make variable with this name in the attributes of rules that list this build setting in their 'toolchains' attribute. | String | optional | "" | @@ -86,7 +88,7 @@ An int-typed build setting that cannot be set on the command line ## string_flag
-string_flag(name, values)
+string_flag(name, make_variable, values)
 
A string-typed build setting that can be set on the command line @@ -97,6 +99,7 @@ A string-typed build setting that can be set on the command line | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | +| make_variable | If set, the build setting's value will be available as a Make variable with this name in the attributes of rules that list this build setting in their 'toolchains' attribute. | String | optional | "" | | values | The list of allowed values for this setting. An error is raised if any other value is given. | List of strings | optional | [] | @@ -141,7 +144,7 @@ A string list-typed build setting that cannot be set on the command line ## string_setting
-string_setting(name, values)
+string_setting(name, make_variable, values)
 
A string-typed build setting that cannot be set on the command line @@ -152,6 +155,7 @@ A string-typed build setting that cannot be set on the command line | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this target. | Name | required | | +| make_variable | If set, the build setting's value will be available as a Make variable with this name in the attributes of rules that list this build setting in their 'toolchains' attribute. | String | optional | "" | | values | The list of allowed values for this setting. An error is raised if any other value is given. | List of strings | optional | [] | diff --git a/rules/common_settings.bzl b/rules/common_settings.bzl index eb057cc..58a15ae 100644 --- a/rules/common_settings.bzl +++ b/rules/common_settings.bzl @@ -30,18 +30,52 @@ BuildSettingInfo = provider( }, ) +_MAKE_VARIABLE_ATTR = attr.string( + doc = "If set, the build setting's value will be available as a Make variable with this " + + "name in the attributes of rules that list this build setting in their 'toolchains' " + + "attribute.", +) + +def _is_valid_make_variable_char(c): + # Restrict make variable names for consistency with predefined ones. There are no enforced + # restrictions on make variable names, but when they contain e.g. spaces or braces, they + # aren't expanded by e.g. cc_binary. + return c == "_" or c.isdigit() or (c.isalpha() and c.isupper()) + +def _get_template_variable_info(ctx): + make_variable = getattr(ctx.attr, "make_variable", None) + if not make_variable: + return [] + + if not all([_is_valid_make_variable_char(c) for c in make_variable.elems()]): + fail("Error setting " + _no_at_str(ctx.label) + ": invalid make variable name '" + make_variable + "'. Make variable names may only contain uppercase letters, digits, and underscores.") + + return [ + platform_common.TemplateVariableInfo({ + make_variable: str(ctx.build_setting_value), + }), + ] + def _impl(ctx): - return BuildSettingInfo(value = ctx.build_setting_value) + return [ + BuildSettingInfo(value = ctx.build_setting_value), + ] + _get_template_variable_info(ctx) int_flag = rule( implementation = _impl, build_setting = config.int(flag = True), + attrs = { + "make_variable": _MAKE_VARIABLE_ATTR, + }, doc = "An int-typed build setting that can be set on the command line", ) int_setting = rule( implementation = _impl, build_setting = config.int(), + attrs = { + "make_variable": _MAKE_VARIABLE_ATTR, + }, doc = "An int-typed build setting that cannot be set on the command line", ) @@ -82,7 +116,7 @@ def _string_impl(ctx): allowed_values = ctx.attr.values value = ctx.build_setting_value if len(allowed_values) == 0 or value in ctx.attr.values: - return BuildSettingInfo(value = value) + return [BuildSettingInfo(value = value)] + _get_template_variable_info(ctx) else: fail("Error setting " + _no_at_str(ctx.label) + ": invalid value '" + value + "'. Allowed values are " + str(allowed_values)) @@ -93,6 +127,7 @@ string_flag = rule( "values": attr.string_list( doc = "The list of allowed values for this setting. An error is raised if any other value is given.", ), + "make_variable": _MAKE_VARIABLE_ATTR, }, doc = "A string-typed build setting that can be set on the command line", ) @@ -104,6 +139,7 @@ string_setting = rule( "values": attr.string_list( doc = "The list of allowed values for this setting. An error is raised if any other value is given.", ), + "make_variable": _MAKE_VARIABLE_ATTR, }, doc = "A string-typed build setting that cannot be set on the command line", ) diff --git a/tests/BUILD b/tests/BUILD index bbab077..7f056d2 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -1,6 +1,7 @@ load("//:bzl_library.bzl", "bzl_library") load(":build_test_tests.bzl", "build_test_test_suite") load(":collections_tests.bzl", "collections_test_suite") +load(":common_settings_tests.bzl", "common_settings_test_suite") load(":dicts_tests.bzl", "dicts_test_suite") load(":new_sets_tests.bzl", "new_sets_test_suite") load(":partial_tests.bzl", "partial_test_suite") @@ -24,6 +25,8 @@ build_test_test_suite() collections_test_suite() +common_settings_test_suite() + dicts_test_suite() new_sets_test_suite() diff --git a/tests/common_settings/BUILD b/tests/common_settings/BUILD new file mode 100644 index 0000000..bbf32d5 --- /dev/null +++ b/tests/common_settings/BUILD @@ -0,0 +1,25 @@ +load("//rules:common_settings.bzl", "int_flag", "string_flag") + +int_flag( + name = "my_int_flag", + build_setting_default = 42, + make_variable = "MY_INT_FLAG", +) + +string_flag( + name = "my_string_flag", + build_setting_default = "foo", + make_variable = "MY_STRING_FLAG", +) + +sh_test( + name = "make_variable_test", + srcs = ["make_variable_test.sh"], + env = { + "MESSAGE": "Hello, $(MY_STRING_FLAG)! My name is $(MY_INT_FLAG).", + }, + toolchains = [ + ":my_int_flag", + ":my_string_flag", + ], +) diff --git a/tests/common_settings/make_variable_test.sh b/tests/common_settings/make_variable_test.sh new file mode 100755 index 0000000..546c53d --- /dev/null +++ b/tests/common_settings/make_variable_test.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +# Copyright 2023 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. + +[[ "$MESSAGE" == "Hello, foo! My name is 42." ]] diff --git a/tests/common_settings_tests.bzl b/tests/common_settings_tests.bzl new file mode 100644 index 0000000..efbe0e0 --- /dev/null +++ b/tests/common_settings_tests.bzl @@ -0,0 +1,167 @@ +# Copyright 2023 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. + +"""Analysis tests for common_settings.bzl.""" + +load("//lib:unittest.bzl", "analysistest", "asserts") +load("//rules:common_settings.bzl", "int_flag", "int_setting", "string_flag", "string_setting") + +def _template_variable_info_contents_test_impl(ctx): + env = analysistest.begin(ctx) + + target_under_test = analysistest.target_under_test(env) + if ctx.attr.expected: + asserts.equals( + env, + expected = ctx.attr.expected, + actual = target_under_test[platform_common.TemplateVariableInfo].variables, + ) + else: + asserts.false(env, platform_common.TemplateVariableInfo in target_under_test) + + return analysistest.end(env) + +_template_variable_info_contents_test = analysistest.make( + _template_variable_info_contents_test_impl, + attrs = { + "expected": attr.string_dict(), + }, +) + +def _test_template_variable_info_contents(): + int_flag( + name = "my_int_flag", + build_setting_default = 42, + make_variable = "MY_INT_1", + ) + + _template_variable_info_contents_test( + name = "my_int_flag_test", + target_under_test = ":my_int_flag", + expected = { + "MY_INT_1": "42", + }, + ) + + int_setting( + name = "my_int_setting", + build_setting_default = 21, + make_variable = "MY_INT_2", + ) + + _template_variable_info_contents_test( + name = "my_int_setting_test", + target_under_test = ":my_int_setting", + expected = { + "MY_INT_2": "21", + }, + ) + + string_flag( + name = "my_string_flag", + build_setting_default = "foo", + make_variable = "MY_STRING_1", + ) + + _template_variable_info_contents_test( + name = "my_string_flag_test", + target_under_test = ":my_string_flag", + expected = { + "MY_STRING_1": "foo", + }, + ) + + string_setting( + name = "my_string_setting", + build_setting_default = "bar", + make_variable = "MY_STRING_2", + ) + + _template_variable_info_contents_test( + name = "my_string_setting_test", + target_under_test = ":my_string_setting", + expected = { + "MY_STRING_2": "bar", + }, + ) + + string_flag( + name = "my_string_flag_without_make_variable", + build_setting_default = "foo", + ) + + _template_variable_info_contents_test( + name = "my_string_flag_without_make_variable_test", + target_under_test = ":my_string_flag_without_make_variable", + expected = {}, + ) + +def _failure_test_impl(ctx): + env = analysistest.begin(ctx) + + asserts.expect_failure(env, ctx.attr.expected_failure) + + return analysistest.end(env) + +_failure_test = analysistest.make( + _failure_test_impl, + attrs = { + "expected_failure": attr.string(), + }, + expect_failure = True, +) + +def _test_make_variable_name_failures(): + int_flag( + name = "my_failing_int_flag", + build_setting_default = 42, + make_variable = "my_int_1", + tags = ["manual"], + ) + + _failure_test( + name = "my_failing_int_flag_test", + target_under_test = ":my_failing_int_flag", + expected_failure = "Error setting //tests:my_failing_int_flag: invalid make variable name 'my_int_1'. Make variable names may only contain uppercase letters, digits, and underscores.", + ) + + string_flag( + name = "my_failing_string_flag", + build_setting_default = "foo", + make_variable = "MY STRING", + tags = ["manual"], + ) + + _failure_test( + name = "my_failing_string_flag_test", + target_under_test = ":my_failing_string_flag", + expected_failure = "Error setting //tests:my_failing_string_flag: invalid make variable name 'MY STRING'. Make variable names may only contain uppercase letters, digits, and underscores.", + ) + +def common_settings_test_suite(name = "common_settings_test_suite"): + _test_template_variable_info_contents() + _test_make_variable_name_failures() + + native.test_suite( + name = "common_settings_test_suite", + tests = [ + "my_int_flag_test", + "my_int_setting_test", + "my_string_flag_test", + "my_string_setting_test", + "my_string_flag_without_make_variable_test", + "my_failing_int_flag_test", + "my_failing_string_flag_test", + ], + ) diff --git a/tests/subpackages_tests.bzl b/tests/subpackages_tests.bzl index 07a52e9..3c494d6 100644 --- a/tests/subpackages_tests.bzl +++ b/tests/subpackages_tests.bzl @@ -21,6 +21,7 @@ def _all_test(env): """Unit tests for subpackages.all.""" all_pkgs = [ + "common_settings", "copy_directory", "copy_file", "diff_test", @@ -38,6 +39,7 @@ def _all_test(env): # These exist in all cases filtered_pkgs = [ + "common_settings", "copy_directory", "copy_file", "expand_template",