-
Couldn't load subscription status.
- Fork 139
Added cxx_standard package for defining cxxopts
#412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Copyright 2025 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. | ||
| """Known cc compilers""" | ||
|
|
||
| visibility([ | ||
| "//cc/cxx_standard/...", | ||
| ]) | ||
|
|
||
| COMPILERS = [ | ||
| # keep sorted | ||
| "clang", | ||
UebelAndre marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| "clang-cl", | ||
| "emscripten", | ||
| "gcc", | ||
| "mingw-gcc", | ||
| "msvc-cl", | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # Copyright 2025 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. | ||
|
|
||
| load("@bazel_skylib//rules:common_settings.bzl", "string_flag") | ||
| load("//cc/compiler:compilers.bzl", "COMPILERS") | ||
| load(":cxx_standard.bzl", "VERSIONS") | ||
|
|
||
| package(default_visibility = ["//visibility:public"]) | ||
|
|
||
| licenses(["notice"]) | ||
|
|
||
| string_flag( | ||
| name = "cxx_standard", | ||
| build_setting_default = "default", | ||
| values = [ | ||
| "default", | ||
| "none", | ||
| ] + VERSIONS, | ||
| ) | ||
|
|
||
| [ | ||
| config_setting( | ||
| name = "cxx{}_{}".format(version, compiler), | ||
| flag_values = { | ||
| "//cc/private/toolchain:compiler": compiler, | ||
| ":cxx_standard": version, | ||
| }, | ||
| ) | ||
| for version in VERSIONS | ||
| for compiler in COMPILERS | ||
| ] | ||
|
|
||
| [ | ||
| config_setting( | ||
| name = "cxx_default_{}".format(compiler), | ||
| flag_values = { | ||
| "//cc/private/toolchain:compiler": compiler, | ||
| ":cxx_standard": "default", | ||
| }, | ||
| ) | ||
| for compiler in COMPILERS | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,78 @@ | ||||||
| # Copyright 2025 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. | ||||||
| """Globals for C++ std version flags.""" | ||||||
|
|
||||||
| load("//cc/compiler:compilers.bzl", "COMPILERS") | ||||||
|
|
||||||
| VERSIONS = [ | ||||||
| "98", | ||||||
| "03", | ||||||
| "11", | ||||||
| "14", | ||||||
| "17", | ||||||
| "20", | ||||||
| "23", | ||||||
| "26", | ||||||
| "2c", | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the backwards compatibility story here? Could we ever delete this constant once we have added it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the rules are semantically versioned I figured a value could be dropped from release to release but I also think keeping a large list here is low cost. |
||||||
| ] | ||||||
|
|
||||||
| def _flag(version, compiler): | ||||||
| if compiler.startswith("msvc"): | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be an exact match? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, no that also works. I'm no expert in the MSVC ecosystem, I just wanted to make sure the "windows-y" flags made it to the windows compilers so opted for the prefix. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this what you're thinking?
Suggested change
|
||||||
| return "/std:c++{}".format(version) | ||||||
|
|
||||||
| return "-std=c++{}".format(version) | ||||||
|
|
||||||
| def cxxopts(default = None): | ||||||
| """Generate a select statement which contains the correct `stdcxx` flag for `cxxopts` attributes. | ||||||
|
|
||||||
| Example: | ||||||
|
|
||||||
| ```starlark | ||||||
| load("@rules_cc//cc:cc_binary.bzl", "cc_binary") | ||||||
| load("@rules_cc//cc/cxx_standard:cxx_standard.bzl", "cxxopts") | ||||||
|
|
||||||
| cc_binary( | ||||||
| name = "foo", | ||||||
| srcs = ["foo.cc"], | ||||||
| cxxopts = cxxopts(default = "20") + [ | ||||||
| # Any additional cxxopts | ||||||
| ], | ||||||
| ) | ||||||
| ``` | ||||||
|
|
||||||
| Note that the `--@rules_cc//cc/cxx_standard` flag can be used to override specified `default` value. | ||||||
|
|
||||||
| Args: | ||||||
| default (str, optional): The default version of the C++ standard to use. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the use case for not specifying this parameter vs. not using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you could still use the |
||||||
|
|
||||||
| Returns: | ||||||
| select: A mapping of cxx version and compiler to the `cxxopts` flags. | ||||||
| """ | ||||||
| default_branches = {} | ||||||
| if default: | ||||||
| if default not in VERSIONS: | ||||||
| fail("Unexpected stdc++ version: {}".format(default)) | ||||||
|
|
||||||
| default_branches = { | ||||||
| Label("//cc/cxx_standard:cxx_default_{}".format(compiler)): [_flag(default, compiler)] | ||||||
| for compiler in COMPILERS | ||||||
| } | ||||||
|
|
||||||
| return select({ | ||||||
| Label("//cc/cxx_standard:cxx{}_{}".format(version, compiler)): [_flag(version, compiler)] | ||||||
| for version in VERSIONS | ||||||
| for compiler in COMPILERS | ||||||
| } | default_branches | { | ||||||
| "//conditions:default": [], | ||||||
| }) | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Copyright 2025 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. | ||
| load(":cxx_std_test.bzl", "cxx_std_test_suite") | ||
|
|
||
| licenses(["notice"]) | ||
|
|
||
| cxx_std_test_suite(name = "cxx_std_test_suite") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| """Unit tests for cxx_standard flags.""" | ||
|
|
||
| load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") | ||
| load("//cc:cc_binary.bzl", "cc_binary") | ||
| load("//cc/cxx_standard:cxx_standard.bzl", "cxxopts") | ||
|
|
||
| def _get_compile_action(target): | ||
| """Get the C++ compile action from a target.""" | ||
| for action in target.actions: | ||
| if action.mnemonic == "CppCompile": | ||
| return action | ||
| fail("No CppCompile action found") | ||
|
|
||
| def _extract_flags_between_markers(argv): | ||
| """Extract flags between BEGIN and END markers.""" | ||
| begin_marker = "-DRULES_CC_STD_CXX_TEST_BEGIN" | ||
| end_marker = "-DRULES_CC_STD_CXX_TEST_END" | ||
|
|
||
| begin_idx = -1 | ||
| end_idx = -1 | ||
|
|
||
| for i, arg in enumerate(argv): | ||
| if arg == begin_marker: | ||
| begin_idx = i | ||
| elif arg == end_marker: | ||
| end_idx = i | ||
| break | ||
|
|
||
| if begin_idx == -1 or end_idx == -1: | ||
| return [] | ||
|
|
||
| return argv[begin_idx + 1:end_idx] | ||
|
|
||
| def _cxx_flag_test_common_impl(ctx, version, inclusive): | ||
| env = analysistest.begin(ctx) | ||
| tut = analysistest.target_under_test(env) | ||
| action = _get_compile_action(tut) | ||
|
|
||
| flags_between_markers = _extract_flags_between_markers(action.argv) | ||
| use_msvc = ctx.attr.use_msvc | ||
| if use_msvc: | ||
| expected_flag = "/std:c++{}".format(version) | ||
| else: | ||
| expected_flag = "-std=c++{}".format(version) | ||
|
|
||
| if inclusive: | ||
| asserts.true( | ||
| env, | ||
| expected_flag in flags_between_markers, | ||
| "Expected flag '{}' between markers, got: {}".format(expected_flag, flags_between_markers), | ||
| ) | ||
| else: | ||
| asserts.true( | ||
| env, | ||
| expected_flag not in flags_between_markers, | ||
| "Unexpected flag '{}' between markers, got: {}".format(expected_flag, flags_between_markers), | ||
| ) | ||
| return analysistest.end(env) | ||
|
|
||
| def _cxx_default_flag_test_impl(ctx): | ||
| return _cxx_flag_test_common_impl(ctx, ctx.attr.version, True) | ||
|
|
||
| def _cxx_no_flag_test_impl(ctx): | ||
| return _cxx_flag_test_common_impl(ctx, ctx.attr.version, False) | ||
|
|
||
| _COMMON_ATTR = { | ||
| "use_msvc": attr.bool(doc = "Whether to use MSVC-style flags (/std:c++) instead of GCC/Clang style (-std=c++)", mandatory = True), | ||
| } | ||
|
|
||
| cxx_default_flag_test = analysistest.make( | ||
| _cxx_default_flag_test_impl, | ||
| doc = "A test that confirms the default provided to the `cxxopts` macro is used for compilation.", | ||
| attrs = { | ||
| "version": attr.string(doc = "The cxx version", mandatory = True), | ||
| } | _COMMON_ATTR, | ||
| config_settings = { | ||
| str(Label("//cc/cxx_standard:cxx_standard")): "default", | ||
| }, | ||
| ) | ||
|
|
||
| cxx_no_flag_test = analysistest.make( | ||
| _cxx_no_flag_test_impl, | ||
| doc = "A test that force disables flags from the `cxxopts` macro regardless of any default specified.", | ||
| attrs = { | ||
| "version": attr.string(doc = "The cxx version", mandatory = True), | ||
| } | _COMMON_ATTR, | ||
| config_settings = { | ||
| str(Label("//cc/cxx_standard:cxx_standard")): "none", | ||
| }, | ||
| ) | ||
|
|
||
| def _cxx_forced_11_flag_test_impl(ctx): | ||
| return _cxx_flag_test_common_impl(ctx, "11", True) | ||
|
|
||
| cxx_forced_11_flag_test = analysistest.make( | ||
| _cxx_forced_11_flag_test_impl, | ||
| doc = "A test that forces `cxx11` regardless of any default specified to the `cxxopts` macro.", | ||
| attrs = _COMMON_ATTR, | ||
| config_settings = { | ||
| str(Label("//cc/cxx_standard:cxx_standard")): "11", | ||
| }, | ||
| ) | ||
|
|
||
| def _cxx_std_test(): | ||
| """Helper function to create test targets.""" | ||
| tests = [] | ||
|
|
||
| use_msvc = select({ | ||
| "//cc/compiler:msvc-cl": True, | ||
| "//conditions:default": False, | ||
| }) | ||
|
|
||
| for version in ["14", "17"]: | ||
| target_under_test = "test_bin_{}".format(version) | ||
| cc_binary( | ||
| name = target_under_test, | ||
| srcs = ["main.cc"], | ||
| # To identify the specific instance of the macros stdcxx flag, the arguments are wrapped | ||
| # in some identifier flags so we can know when the flag is or isn't acutally produced. | ||
| copts = ["-DRULES_CC_STD_CXX_TEST_BEGIN"] + cxxopts(default = version) + ["-DRULES_CC_STD_CXX_TEST_END"], | ||
| ) | ||
|
|
||
| test_name = "cxx{}_default_flag_test".format(version) | ||
| tests.append(test_name) | ||
| cxx_default_flag_test( | ||
| name = test_name, | ||
| target_under_test = ":" + target_under_test, | ||
| version = version, | ||
| use_msvc = use_msvc, | ||
| ) | ||
|
|
||
| test_name = "cxx{}_no_flag_test".format(version) | ||
| tests.append(test_name) | ||
| cxx_no_flag_test( | ||
| name = test_name, | ||
| target_under_test = ":" + target_under_test, | ||
| version = version, | ||
| use_msvc = use_msvc, | ||
| ) | ||
|
|
||
| test_name = "cxx{}_forced_11_flag_test".format(version) | ||
| tests.append(test_name) | ||
| cxx_forced_11_flag_test( | ||
| name = test_name, | ||
| target_under_test = ":" + target_under_test, | ||
| use_msvc = use_msvc, | ||
| ) | ||
|
|
||
| return tests | ||
|
|
||
| def cxx_std_test_suite(name, **kwargs): | ||
| """Entry-point macro called from the BUILD file. | ||
|
|
||
| Args: | ||
| name: Name of the macro. | ||
| **kwargs: Additional keyword arguments. | ||
| """ | ||
| tests = _cxx_std_test() | ||
|
|
||
| native.test_suite( | ||
| name = name, | ||
| tests = tests, | ||
| **kwargs | ||
| ) |
Uh oh!
There was an error while loading. Please reload this page.