https://github.com/mordante created https://github.com/llvm/llvm-project/pull/134542
This generator has almost identical output to the existing script. Notable differences are - conditionally include not yet implemented headers - removes the synopsis - uses 2 spaces indent in `# if` There are a few more test macros added that triggered bugs in existing FTM. >From 00cf2e7d9b6945ae738f1487e6f4dd9222e31b79 Mon Sep 17 00:00:00 2001 From: Mark de Wever <ko...@xs4all.nl> Date: Sat, 5 Apr 2025 12:15:10 +0200 Subject: [PATCH] [libc++] Implements the new FTM header test generator. This generator has almost identical output to the existing script. Notable differences are - conditionally include not yet implemented headers - removes the synopsis - uses 2 spaces indent in `# if` There are a few more test macros added that triggered bugs in existing FTM. --- .../feature_test_macro/ftm_metadata.sh.py | 36 +- .../generate_header_test.sh.py | 643 ++++++++++++++++++ .../feature_test_macro/implemented_ftms.sh.py | 9 +- ...implemented_standard_library_headers.sh.py | 32 + .../feature_test_macro/standard_ftms.sh.py | 14 +- .../standard_library_headers.sh.py | 33 + .../libcxx/feature_test_macro/test_data.json | 28 +- .../feature_test_macro/version_header.sh.py | 8 +- .../version_header_implementation.sh.py | 22 +- .../generate_feature_test_macro_components.py | 284 +++++++- 10 files changed, 1092 insertions(+), 17 deletions(-) create mode 100644 libcxx/test/libcxx/feature_test_macro/generate_header_test.sh.py create mode 100644 libcxx/test/libcxx/feature_test_macro/implemented_standard_library_headers.sh.py create mode 100644 libcxx/test/libcxx/feature_test_macro/standard_library_headers.sh.py diff --git a/libcxx/test/libcxx/feature_test_macro/ftm_metadata.sh.py b/libcxx/test/libcxx/feature_test_macro/ftm_metadata.sh.py index 52696d8bc3605..7cf35b2a21d93 100644 --- a/libcxx/test/libcxx/feature_test_macro/ftm_metadata.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/ftm_metadata.sh.py @@ -27,26 +27,52 @@ def setUp(self): def test_implementation(self): expected = { "__cpp_lib_any": Metadata( - headers=["any"], test_suite_guard=None, libcxx_guard=None + headers=["any"], + available_since="c++17", + test_suite_guard=None, + libcxx_guard=None, ), "__cpp_lib_barrier": Metadata( headers=["barrier"], + available_since="c++20", test_suite_guard="!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC)", libcxx_guard="_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC", ), + "__cpp_lib_clamp": Metadata( + headers=["algorithm"], + available_since="c++17", + test_suite_guard=None, + libcxx_guard=None, + ), "__cpp_lib_format": Metadata( - headers=["format"], test_suite_guard=None, libcxx_guard=None + headers=["format"], + available_since="c++20", + test_suite_guard=None, + libcxx_guard=None, ), "__cpp_lib_parallel_algorithm": Metadata( headers=["algorithm", "numeric"], + available_since="c++17", + test_suite_guard=None, + libcxx_guard=None, + ), + "__cpp_lib_to_chars": Metadata( + headers=["charconv"], + available_since="c++17", test_suite_guard=None, libcxx_guard=None, ), "__cpp_lib_variant": Metadata( - headers=["variant"], test_suite_guard=None, libcxx_guard=None + headers=["variant"], + available_since="c++17", + test_suite_guard=None, + libcxx_guard=None, ), - "__cpp_lib_missing_FTM_in_older_standard": Metadata( - headers=[], test_suite_guard=None, libcxx_guard=None + "__cpp_lib_zz_missing_FTM_in_older_standard": Metadata( + headers=[], + available_since="c++17", + test_suite_guard=None, + libcxx_guard=None, ), } self.assertEqual(self.ftm.ftm_metadata, expected) diff --git a/libcxx/test/libcxx/feature_test_macro/generate_header_test.sh.py b/libcxx/test/libcxx/feature_test_macro/generate_header_test.sh.py new file mode 100644 index 0000000000000..cca5bae8e7e70 --- /dev/null +++ b/libcxx/test/libcxx/feature_test_macro/generate_header_test.sh.py @@ -0,0 +1,643 @@ +# ===----------------------------------------------------------------------===## +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===----------------------------------------------------------------------===## + +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/test/libcxx/feature_test_macro/test_data.json %t/tests + +import os +import sys +import unittest + +UTILS = sys.argv[1] +TEST_DATA = sys.argv[2] +OUTPUT_PATH = sys.argv[3] +del sys.argv[1:4] + +sys.path.append(UTILS) +from generate_feature_test_macro_components import FeatureTestMacros + + +class Test(unittest.TestCase): + def setUp(self): + self.ftm = FeatureTestMacros(TEST_DATA) + self.maxDiff = None # This causes the diff to be printed when the test fails + + self.expected = dict( + { + "algorithm": """\ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING: This test was generated by +// generate_feature_test_macro_components.py +// and should not be edited manually. + +// <algorithm> + +// Test the feature test macros defined by <algorithm> + +// clang-format off + +#include <algorithm> +#include "test_macros.h" + +#if TEST_STD_VER < 17 + +# ifdef __cpp_lib_clamp +# error "__cpp_lib_clamp should not be defined before c++17" +# endif + +# ifdef __cpp_lib_parallel_algorithm +# error "__cpp_lib_parallel_algorithm should not be defined before c++17" +# endif + +#elif TEST_STD_VER == 17 + +# ifndef __cpp_lib_clamp +# error "__cpp_lib_clamp should be defined in c++17" +# endif +# if __cpp_lib_clamp != 201603L +# error "__cpp_lib_clamp should have the value 201603L in c++17" +# endif + +# ifndef __cpp_lib_parallel_algorithm +# error "__cpp_lib_parallel_algorithm should be defined in c++17" +# endif +# if __cpp_lib_parallel_algorithm != 201603L +# error "__cpp_lib_parallel_algorithm should have the value 201603L in c++17" +# endif + +#elif TEST_STD_VER == 20 + +# ifndef __cpp_lib_clamp +# error "__cpp_lib_clamp should be defined in c++20" +# endif +# if __cpp_lib_clamp != 201603L +# error "__cpp_lib_clamp should have the value 201603L in c++20" +# endif + +# ifndef __cpp_lib_parallel_algorithm +# error "__cpp_lib_parallel_algorithm should be defined in c++20" +# endif +# if __cpp_lib_parallel_algorithm != 201603L +# error "__cpp_lib_parallel_algorithm should have the value 201603L in c++20" +# endif + +#elif TEST_STD_VER == 23 + +# ifndef __cpp_lib_clamp +# error "__cpp_lib_clamp should be defined in c++23" +# endif +# if __cpp_lib_clamp != 201603L +# error "__cpp_lib_clamp should have the value 201603L in c++23" +# endif + +# ifndef __cpp_lib_parallel_algorithm +# error "__cpp_lib_parallel_algorithm should be defined in c++23" +# endif +# if __cpp_lib_parallel_algorithm != 201603L +# error "__cpp_lib_parallel_algorithm should have the value 201603L in c++23" +# endif + +#elif TEST_STD_VER > 23 + +# ifndef __cpp_lib_clamp +# error "__cpp_lib_clamp should be defined in c++26" +# endif +# if __cpp_lib_clamp != 201603L +# error "__cpp_lib_clamp should have the value 201603L in c++26" +# endif + +# ifndef __cpp_lib_parallel_algorithm +# error "__cpp_lib_parallel_algorithm should be defined in c++26" +# endif +# if __cpp_lib_parallel_algorithm != 201603L +# error "__cpp_lib_parallel_algorithm should have the value 201603L in c++26" +# endif + +#endif // TEST_STD_VER > 23 + +// clang-format on + +""", + "any": """\ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING: This test was generated by +// generate_feature_test_macro_components.py +// and should not be edited manually. + +// <any> + +// Test the feature test macros defined by <any> + +// clang-format off + +#include <any> +#include "test_macros.h" + +#if TEST_STD_VER < 17 + +# ifdef __cpp_lib_any +# error "__cpp_lib_any should not be defined before c++17" +# endif + +#elif TEST_STD_VER == 17 + +# ifndef __cpp_lib_any +# error "__cpp_lib_any should be defined in c++17" +# endif +# if __cpp_lib_any != 201606L +# error "__cpp_lib_any should have the value 201606L in c++17" +# endif + +#elif TEST_STD_VER == 20 + +# ifndef __cpp_lib_any +# error "__cpp_lib_any should be defined in c++20" +# endif +# if __cpp_lib_any != 201606L +# error "__cpp_lib_any should have the value 201606L in c++20" +# endif + +#elif TEST_STD_VER == 23 + +# ifndef __cpp_lib_any +# error "__cpp_lib_any should be defined in c++23" +# endif +# if __cpp_lib_any != 201606L +# error "__cpp_lib_any should have the value 201606L in c++23" +# endif + +#elif TEST_STD_VER > 23 + +# ifndef __cpp_lib_any +# error "__cpp_lib_any should be defined in c++26" +# endif +# if __cpp_lib_any != 201606L +# error "__cpp_lib_any should have the value 201606L in c++26" +# endif + +#endif // TEST_STD_VER > 23 + +// clang-format on + +""", + "barrier": """\ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING: This test was generated by +// generate_feature_test_macro_components.py +// and should not be edited manually. + +// UNSUPPORTED: no-threads + +// <barrier> + +// Test the feature test macros defined by <barrier> + +// clang-format off + +#include <barrier> +#include "test_macros.h" + +#if TEST_STD_VER < 17 + +# ifdef __cpp_lib_barrier +# error "__cpp_lib_barrier should not be defined before c++20" +# endif + +#elif TEST_STD_VER == 17 + +# ifdef __cpp_lib_barrier +# error "__cpp_lib_barrier should not be defined before c++20" +# endif + +#elif TEST_STD_VER == 20 + +# if !defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC) +# ifndef __cpp_lib_barrier +# error "__cpp_lib_barrier should be defined in c++20" +# endif +# if __cpp_lib_barrier != 201907L +# error "__cpp_lib_barrier should have the value 201907L in c++20" +# endif +# else +# ifdef __cpp_lib_barrier +# error "__cpp_lib_barrier should not be defined when the requirement '!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC)' is not met!" +# endif +# endif + +#elif TEST_STD_VER == 23 + +# if !defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC) +# ifndef __cpp_lib_barrier +# error "__cpp_lib_barrier should be defined in c++23" +# endif +# if __cpp_lib_barrier != 201907L +# error "__cpp_lib_barrier should have the value 201907L in c++23" +# endif +# else +# ifdef __cpp_lib_barrier +# error "__cpp_lib_barrier should not be defined when the requirement '!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC)' is not met!" +# endif +# endif + +#elif TEST_STD_VER > 23 + +# if !defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC) +# ifndef __cpp_lib_barrier +# error "__cpp_lib_barrier should be defined in c++26" +# endif +# if __cpp_lib_barrier != 299900L +# error "__cpp_lib_barrier should have the value 299900L in c++26" +# endif +# else +# ifdef __cpp_lib_barrier +# error "__cpp_lib_barrier should not be defined when the requirement '!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC)' is not met!" +# endif +# endif + +#endif // TEST_STD_VER > 23 + +// clang-format on + +""", + "charconv": """\ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING: This test was generated by +// generate_feature_test_macro_components.py +// and should not be edited manually. + +// <charconv> + +// Test the feature test macros defined by <charconv> + +// clang-format off + +#if __has_include(<charconv>) +# include <charconv> +#endif +#include "test_macros.h" + +#if TEST_STD_VER < 17 + +# ifdef __cpp_lib_to_chars +# error "__cpp_lib_to_chars should not be defined before c++17" +# endif + +#elif TEST_STD_VER == 17 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_to_chars +# error "__cpp_lib_to_chars should be defined in c++17" +# endif +# if __cpp_lib_to_chars != 201611L +# error "__cpp_lib_to_chars should have the value 201611L in c++17" +# endif +# else +# ifdef __cpp_lib_to_chars +# error "__cpp_lib_to_chars should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#elif TEST_STD_VER == 20 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_to_chars +# error "__cpp_lib_to_chars should be defined in c++20" +# endif +# if __cpp_lib_to_chars != 201611L +# error "__cpp_lib_to_chars should have the value 201611L in c++20" +# endif +# else +# ifdef __cpp_lib_to_chars +# error "__cpp_lib_to_chars should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#elif TEST_STD_VER == 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_to_chars +# error "__cpp_lib_to_chars should be defined in c++23" +# endif +# if __cpp_lib_to_chars != 201611L +# error "__cpp_lib_to_chars should have the value 201611L in c++23" +# endif +# else +# ifdef __cpp_lib_to_chars +# error "__cpp_lib_to_chars should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#elif TEST_STD_VER > 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_to_chars +# error "__cpp_lib_to_chars should be defined in c++26" +# endif +# if __cpp_lib_to_chars != 201611L +# error "__cpp_lib_to_chars should have the value 201611L in c++26" +# endif +# else +# ifdef __cpp_lib_to_chars +# error "__cpp_lib_to_chars should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#endif // TEST_STD_VER > 23 + +// clang-format on + +""", + "format": """\ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING: This test was generated by +// generate_feature_test_macro_components.py +// and should not be edited manually. + +// <format> + +// Test the feature test macros defined by <format> + +// clang-format off + +#include <format> +#include "test_macros.h" + +#if TEST_STD_VER < 17 + +# ifdef __cpp_lib_format +# error "__cpp_lib_format should not be defined before c++20" +# endif + +#elif TEST_STD_VER == 17 + +# ifdef __cpp_lib_format +# error "__cpp_lib_format should not be defined before c++20" +# endif + +#elif TEST_STD_VER == 20 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_format +# error "__cpp_lib_format should be defined in c++20" +# endif +# if __cpp_lib_format != 202110L +# error "__cpp_lib_format should have the value 202110L in c++20" +# endif +# else +# ifdef __cpp_lib_format +# error "__cpp_lib_format should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#elif TEST_STD_VER == 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_format +# error "__cpp_lib_format should be defined in c++23" +# endif +# if __cpp_lib_format != 202207L +# error "__cpp_lib_format should have the value 202207L in c++23" +# endif +# else +# ifdef __cpp_lib_format +# error "__cpp_lib_format should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#elif TEST_STD_VER > 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_format +# error "__cpp_lib_format should be defined in c++26" +# endif +# if __cpp_lib_format != 202311L +# error "__cpp_lib_format should have the value 202311L in c++26" +# endif +# else +# ifdef __cpp_lib_format +# error "__cpp_lib_format should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#endif // TEST_STD_VER > 23 + +// clang-format on + +""", + "numeric": """\ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING: This test was generated by +// generate_feature_test_macro_components.py +// and should not be edited manually. + +// <numeric> + +// Test the feature test macros defined by <numeric> + +// clang-format off + +#include <numeric> +#include "test_macros.h" + +#if TEST_STD_VER < 17 + +# ifdef __cpp_lib_parallel_algorithm +# error "__cpp_lib_parallel_algorithm should not be defined before c++17" +# endif + +#elif TEST_STD_VER == 17 + +# ifndef __cpp_lib_parallel_algorithm +# error "__cpp_lib_parallel_algorithm should be defined in c++17" +# endif +# if __cpp_lib_parallel_algorithm != 201603L +# error "__cpp_lib_parallel_algorithm should have the value 201603L in c++17" +# endif + +#elif TEST_STD_VER == 20 + +# ifndef __cpp_lib_parallel_algorithm +# error "__cpp_lib_parallel_algorithm should be defined in c++20" +# endif +# if __cpp_lib_parallel_algorithm != 201603L +# error "__cpp_lib_parallel_algorithm should have the value 201603L in c++20" +# endif + +#elif TEST_STD_VER == 23 + +# ifndef __cpp_lib_parallel_algorithm +# error "__cpp_lib_parallel_algorithm should be defined in c++23" +# endif +# if __cpp_lib_parallel_algorithm != 201603L +# error "__cpp_lib_parallel_algorithm should have the value 201603L in c++23" +# endif + +#elif TEST_STD_VER > 23 + +# ifndef __cpp_lib_parallel_algorithm +# error "__cpp_lib_parallel_algorithm should be defined in c++26" +# endif +# if __cpp_lib_parallel_algorithm != 201603L +# error "__cpp_lib_parallel_algorithm should have the value 201603L in c++26" +# endif + +#endif // TEST_STD_VER > 23 + +// clang-format on + +""", + "variant": """\ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING: This test was generated by +// generate_feature_test_macro_components.py +// and should not be edited manually. + +// <variant> + +// Test the feature test macros defined by <variant> + +// clang-format off + +#include <variant> +#include "test_macros.h" + +#if TEST_STD_VER < 17 + +# ifdef __cpp_lib_variant +# error "__cpp_lib_variant should not be defined before c++17" +# endif + +#elif TEST_STD_VER == 17 + +# ifndef __cpp_lib_variant +# error "__cpp_lib_variant should be defined in c++17" +# endif +# if __cpp_lib_variant != 202102L +# error "__cpp_lib_variant should have the value 202102L in c++17" +# endif + +#elif TEST_STD_VER == 20 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_variant +# error "__cpp_lib_variant should be defined in c++20" +# endif +# if __cpp_lib_variant != 202106L +# error "__cpp_lib_variant should have the value 202106L in c++20" +# endif +# else +# ifdef __cpp_lib_variant +# error "__cpp_lib_variant should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#elif TEST_STD_VER == 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_variant +# error "__cpp_lib_variant should be defined in c++23" +# endif +# if __cpp_lib_variant != 202106L +# error "__cpp_lib_variant should have the value 202106L in c++23" +# endif +# else +# ifdef __cpp_lib_variant +# error "__cpp_lib_variant should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#elif TEST_STD_VER > 23 + +# if !defined(_LIBCPP_VERSION) +# ifndef __cpp_lib_variant +# error "__cpp_lib_variant should be defined in c++26" +# endif +# if __cpp_lib_variant != 202306L +# error "__cpp_lib_variant should have the value 202306L in c++26" +# endif +# else +# ifdef __cpp_lib_variant +# error "__cpp_lib_variant should not be defined because it is unimplemented in libc++!" +# endif +# endif + +#endif // TEST_STD_VER > 23 + +// clang-format on + +""", + } + ) + + def test_implementation(self): + # Generate the output + self.ftm.generate_header_test_directory(OUTPUT_PATH) + + for key, value in self.expected.items(): + # Test whether the per header generate function generates the proper output. + self.assertEqual(self.ftm.generate_header_test(key), value) + + # Test whether all header generate function generates the proper output. + with open( + os.path.join(OUTPUT_PATH, f"{key}.version.compile.pass.cpp"), + "r", + newline="\n", + ) as f: + self.assertEqual(f.read(), value) + + +if __name__ == "__main__": + unittest.main() diff --git a/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py b/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py index 4f445d55c883c..1ed7524abf488 100644 --- a/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py @@ -38,6 +38,12 @@ def test_implementation(self): "c++23": "201907L", "c++26": "299900L", }, + "__cpp_lib_clamp": { + "c++17": "201603L", + "c++20": "201603L", + "c++23": "201603L", + "c++26": "201603L", + }, "__cpp_lib_format": {}, "__cpp_lib_parallel_algorithm": { "c++17": "201603L", @@ -45,13 +51,14 @@ def test_implementation(self): "c++23": "201603L", "c++26": "201603L", }, + "__cpp_lib_to_chars": {}, "__cpp_lib_variant": { "c++17": "202102L", "c++20": "202102L", "c++23": "202102L", "c++26": "202102L", }, - "__cpp_lib_missing_FTM_in_older_standard": {}, + "__cpp_lib_zz_missing_FTM_in_older_standard": {}, } self.assertEqual(self.ftm.implemented_ftms, expected) diff --git a/libcxx/test/libcxx/feature_test_macro/implemented_standard_library_headers.sh.py b/libcxx/test/libcxx/feature_test_macro/implemented_standard_library_headers.sh.py new file mode 100644 index 0000000000000..4904ba24ccc05 --- /dev/null +++ b/libcxx/test/libcxx/feature_test_macro/implemented_standard_library_headers.sh.py @@ -0,0 +1,32 @@ +# ===----------------------------------------------------------------------===## +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===----------------------------------------------------------------------===## + +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/test/libcxx/feature_test_macro/test_data.json + +import sys + +sys.path.append(sys.argv[1]) +from generate_feature_test_macro_components import FeatureTestMacros + + +def test(output, expected): + assert output == expected, f"expected\n{expected}\n\noutput\n{output}" + + +ftm = FeatureTestMacros(sys.argv[2]) +test( + sorted(ftm.implemented_standard_library_headers), + [ + "algorithm", + "any", + "barrier", + "format", + "numeric", + "variant", + ], +) diff --git a/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py b/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py index ac3e284261d03..5c63e3e9d0ad9 100644 --- a/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py @@ -37,6 +37,12 @@ def test_implementation(self): "c++23": "201907L", "c++26": "299900L", }, + "__cpp_lib_clamp": { + "c++17": "201603L", + "c++20": "201603L", + "c++23": "201603L", + "c++26": "201603L", + }, "__cpp_lib_format": { "c++20": "202110L", "c++23": "202207L", @@ -48,13 +54,19 @@ def test_implementation(self): "c++23": "201603L", "c++26": "201603L", }, + "__cpp_lib_to_chars": { + "c++17": "201611L", + "c++20": "201611L", + "c++23": "201611L", + "c++26": "201611L", + }, "__cpp_lib_variant": { "c++17": "202102L", "c++20": "202106L", "c++23": "202106L", "c++26": "202306L", }, - "__cpp_lib_missing_FTM_in_older_standard": { + "__cpp_lib_zz_missing_FTM_in_older_standard": { "c++17": "2017L", "c++20": "2020L", "c++23": "2020L", diff --git a/libcxx/test/libcxx/feature_test_macro/standard_library_headers.sh.py b/libcxx/test/libcxx/feature_test_macro/standard_library_headers.sh.py new file mode 100644 index 0000000000000..9cc184bcd9447 --- /dev/null +++ b/libcxx/test/libcxx/feature_test_macro/standard_library_headers.sh.py @@ -0,0 +1,33 @@ +# ===----------------------------------------------------------------------===## +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===----------------------------------------------------------------------===## + +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/test/libcxx/feature_test_macro/test_data.json + +import sys + +sys.path.append(sys.argv[1]) +from generate_feature_test_macro_components import FeatureTestMacros + + +def test(output, expected): + assert output == expected, f"expected\n{expected}\n\noutput\n{output}" + + +ftm = FeatureTestMacros(sys.argv[2]) +test( + sorted(ftm.standard_library_headers), + [ + "algorithm", + "any", + "barrier", + "charconv", + "format", + "numeric", + "variant", + ], +) diff --git a/libcxx/test/libcxx/feature_test_macro/test_data.json b/libcxx/test/libcxx/feature_test_macro/test_data.json index fd698c08b2daa..b0122163f714f 100644 --- a/libcxx/test/libcxx/feature_test_macro/test_data.json +++ b/libcxx/test/libcxx/feature_test_macro/test_data.json @@ -38,6 +38,19 @@ "test_suite_guard": "!defined(_LIBCPP_VERSION) || (_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC)", "libcxx_guard": "_LIBCPP_HAS_THREADS && _LIBCPP_AVAILABILITY_HAS_SYNC" }, + { + "name": "__cpp_lib_clamp", + "values": { + "c++17": { + "201603": [ + { + "implemented": true + } + ] + } + }, + "headers": ["algorithm"] + }, { "name": "__cpp_lib_format", "values": { @@ -120,6 +133,19 @@ "numeric" ] }, + { + "name": "__cpp_lib_to_chars", + "values": { + "c++17": { + "201611": [ + { + "implemented": false + } + ] + } + }, + "headers": ["charconv"] + }, { "name": "__cpp_lib_variant", "values": { @@ -155,7 +181,7 @@ ] }, { - "name": "__cpp_lib_missing_FTM_in_older_standard", + "name": "__cpp_lib_zz_missing_FTM_in_older_standard", "values": { "c++17": { "2017": [ diff --git a/libcxx/test/libcxx/feature_test_macro/version_header.sh.py b/libcxx/test/libcxx/feature_test_macro/version_header.sh.py index e3053c18a5211..7ef2079505aee 100644 --- a/libcxx/test/libcxx/feature_test_macro/version_header.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/version_header.sh.py @@ -45,9 +45,11 @@ def test_implementeation(self): #if _LIBCPP_STD_VER >= 17 # define __cpp_lib_any 201606L +# define __cpp_lib_clamp 201603L # define __cpp_lib_parallel_algorithm 201603L +// define __cpp_lib_to_chars 201611L # define __cpp_lib_variant 202102L -// define __cpp_lib_missing_FTM_in_older_standard 2017L +// define __cpp_lib_zz_missing_FTM_in_older_standard 2017L #endif // _LIBCPP_STD_VER >= 17 #if _LIBCPP_STD_VER >= 20 @@ -56,7 +58,7 @@ def test_implementeation(self): # endif // define __cpp_lib_format 202110L // define __cpp_lib_variant 202106L -// define __cpp_lib_missing_FTM_in_older_standard 2020L +// define __cpp_lib_zz_missing_FTM_in_older_standard 2020L #endif // _LIBCPP_STD_VER >= 20 #if _LIBCPP_STD_VER >= 23 @@ -70,7 +72,7 @@ def test_implementeation(self): # endif // define __cpp_lib_format 202311L // define __cpp_lib_variant 202306L -// define __cpp_lib_missing_FTM_in_older_standard 2026L +// define __cpp_lib_zz_missing_FTM_in_older_standard 2026L #endif // _LIBCPP_STD_VER >= 26 #endif // _LIBCPP_VERSIONH diff --git a/libcxx/test/libcxx/feature_test_macro/version_header_implementation.sh.py b/libcxx/test/libcxx/feature_test_macro/version_header_implementation.sh.py index 2ab6a9be7339b..182aca9862a4f 100644 --- a/libcxx/test/libcxx/feature_test_macro/version_header_implementation.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/version_header_implementation.sh.py @@ -35,6 +35,14 @@ def test_implementation(self): condition=None, ), }, + { + "__cpp_lib_clamp": VersionHeader( + value="201603L", + implemented=True, + need_undef=False, + condition=None, + ) + }, { "__cpp_lib_parallel_algorithm": VersionHeader( value="201603L", @@ -43,6 +51,14 @@ def test_implementation(self): condition=None, ), }, + { + "__cpp_lib_to_chars": VersionHeader( + value="201611L", + implemented=False, + need_undef=False, + condition=None, + ), + }, { "__cpp_lib_variant": VersionHeader( value="202102L", @@ -52,7 +68,7 @@ def test_implementation(self): ), }, { - "__cpp_lib_missing_FTM_in_older_standard": VersionHeader( + "__cpp_lib_zz_missing_FTM_in_older_standard": VersionHeader( value="2017L", implemented=False, need_undef=False, @@ -86,7 +102,7 @@ def test_implementation(self): ), }, { - "__cpp_lib_missing_FTM_in_older_standard": VersionHeader( + "__cpp_lib_zz_missing_FTM_in_older_standard": VersionHeader( value="2020L", implemented=False, need_undef=False, @@ -130,7 +146,7 @@ def test_implementation(self): ), }, { - "__cpp_lib_missing_FTM_in_older_standard": VersionHeader( + "__cpp_lib_zz_missing_FTM_in_older_standard": VersionHeader( value="2026L", implemented=False, need_undef=False, diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index ecf31f5af7fb2..541e32b62437d 100755 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -10,6 +10,7 @@ List, # Needed for python 3.8 compatibility. NewType, Optional, + Set, ) import functools import json @@ -1967,6 +1968,7 @@ def produce_docs(): @dataclass class Metadata: headers: List[str] = None + available_since: Std = None test_suite_guard: str = None libcxx_guard: str = None @@ -1979,6 +1981,12 @@ class VersionHeader: condition: str = None +@dataclass +class FtmHeaderTest: + value: Value = None + implemented: bool = None + condition: str = None + def get_ftms( data, std_dialects: List[Std], use_implemented_status: bool ) -> Dict[Ftm, Dict[Std, Optional[Value]]]: @@ -2069,6 +2077,102 @@ def generate_version_header_implementation( return "\n\n".join(result) +# +# The templates used to create a FTM test file +# + + +ftm_header_test_file_contents = """//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// WARNING: This test was generated by +// {script_name} +// and should not be edited manually. + +{lit_markup}// <{header}> + +// Test the feature test macros defined by <{header}> + +// clang-format off + +{include} +#include "test_macros.h" +{data} + +// clang-format on + +""" + + +ftm_header_test_file_include_unconditional = """\ +#include <{header}>\ +""" + +ftm_header_test_file_include_conditional = """\ +#if __has_include(<{header}>) +# include <{header}> +#endif\ +""" + +ftm_header_test_file_dialect_block = """ +#{pp_if} TEST_STD_VER {operator} {dialect} +{tests}\ +""" + +# +# The templates used for a single FTM block in the tests. +# + +ftm_unavailable_in_dialect = """ +# ifdef {ftm} +# error "{ftm} should not be defined before {dialect}" +# endif +""" + +libcxx_ftm_not_implemented = """ +# if !defined(_LIBCPP_VERSION) +# ifndef {ftm} +# error "{ftm} should be defined in {dialect}" +# endif +# if {ftm} != {value} +# error "{ftm} should have the value {value} in {dialect}" +# endif +# else +# ifdef {ftm} +# error "{ftm} should not be defined because it is unimplemented in libc++!" +# endif +# endif +""" + +libcxx_ftm_conditionally_implemented = """ +# if {condition} +# ifndef {ftm} +# error "{ftm} should be defined in {dialect}" +# endif +# if {ftm} != {value} +# error "{ftm} should have the value {value} in {dialect}" +# endif +# else +# ifdef {ftm} +# error "{ftm} should not be defined when the requirement '{condition}' is not met!" +# endif +# endif +""" + +libcxx_ftm_implemented = """ +# ifndef {ftm} +# error "{ftm} should be defined in {dialect}" +# endif +# if {ftm} != {value} +# error "{ftm} should have the value {value} in {dialect}" +# endif +""" + class FeatureTestMacros: """Provides all feature-test macro (FTM) output components. @@ -2199,6 +2303,17 @@ def standard_ftms(self) -> Dict[Ftm, Dict[Std, Optional[Value]]]: """ return get_ftms(self.__data, self.std_dialects, False) + @functools.cached_property + def standard_library_headers(self) -> Set[str]: + """Returns a list of headers that contain at least one FTM.""" + + result = set() + for value in self.ftm_metadata.values(): + for header in value.headers: + result.add(header) + + return list(result) + @functools.cached_property def implemented_ftms(self) -> Dict[Ftm, Dict[Std, Optional[Value]]]: """Returns the FTM versions per dialect implemented in libc++. @@ -2209,6 +2324,33 @@ def implemented_ftms(self) -> Dict[Ftm, Dict[Std, Optional[Value]]]: return get_ftms(self.__data, self.std_dialects, True) + @functools.cached_property + def implemented_standard_library_headers(self) -> Set[str]: + """Returns a list of headers that contain at least one paper implemented in libc++. + + When a paper is implemented it means the associated header(s) should exist. + """ + + result = set() + for feature in self.__data: + for std in self.std_dialects: + if std in feature["values"].keys(): + values = feature["values"][std] + assert len(values) > 0, f"{feature['name']}[{std}] has no entries" + for value in values: + papers = list(values[value]) + assert ( + len(papers) > 0 + ), f"{feature['name']}[{std}][{value}] has no entries" + for paper in papers: + if paper["implemented"]: + for header in self.ftm_metadata[feature["name"]].headers: + result.add(header) + break + + return result + + def is_implemented(self, ftm: Ftm, std: Std) -> bool: """Has the FTM `ftm` been implemented in the dialect `std`?""" @@ -2222,7 +2364,6 @@ def is_implemented(self, ftm: Ftm, std: Std) -> bool: return self.implemented_ftms[ftm][std] == self.standard_ftms[ftm][std] - @functools.cached_property def ftm_metadata(self) -> Dict[Ftm, Metadata]: """Returns the metadata of the FTMs defined in the Standard. @@ -2233,6 +2374,7 @@ def ftm_metadata(self) -> Dict[Ftm, Metadata]: for feature in self.__data: result[feature["name"]] = Metadata( feature["headers"], + list(feature["values"])[0], feature.get("test_suite_guard", None), feature.get("libcxx_guard", None), ) @@ -2300,14 +2442,148 @@ def version_header(self) -> str: ) ) + def header_ftm(self, header: str) -> Dict[Std, List[Dict[Ftm, FtmHeaderTest]]]: + """Generates the FTM information for a `header`.""" + + result = dict() + for std in self.std_dialects: + result[get_std_number(std)] = list() + + for ftm, values in self.standard_ftms.items(): + if not header in self.ftm_metadata[ftm].headers: + continue + + last_value = None + last_entry = None + + for std in self.std_dialects: + if not std in values.keys(): + result[get_std_number(std)].append(dict({ftm: None})) + continue + + result[get_std_number(std)].append( + dict( + { + ftm: FtmHeaderTest( + values[std], + self.is_implemented(ftm, std), + self.ftm_metadata[ftm].test_suite_guard, + ) + } + ) + ) + + return result + + + def generate_header_test_ftm(self, std: Std, ftm: Ftm, value: FtmHeaderTest) -> str: + """Adds a single `ftm` test for C++ `std` based on the status information in `value`. + + When std == None this test is generating the TEST_STD_VER < MIN. Where + MIN is the minimum version that has a FTM defined. (In the real data + this is 14, since FTM have been introduced in C++14.) + """ + + if std == None or value == None: + return ftm_unavailable_in_dialect.format( + ftm=ftm, dialect=self.ftm_metadata[ftm].available_since + ) + + if not value.implemented: + return libcxx_ftm_not_implemented.format( + ftm=ftm, value=value.value, dialect=std + ) + + if self.ftm_metadata[ftm].test_suite_guard: + return libcxx_ftm_conditionally_implemented.format( + ftm=ftm, + value=value.value, + dialect=std, + condition=self.ftm_metadata[ftm].test_suite_guard, + ) + + return libcxx_ftm_implemented.format(ftm=ftm, value=value.value, dialect=std) + + def generate_header_test_dialect( + self, std: Std, data: List[Dict[Ftm, FtmHeaderTest]] + ) -> str: + """Returns the body a single `std` for the FTM test of a `header`.""" + return "".join( + self.generate_header_test_ftm(std, ftm, value) + for element in data + for ftm, value in element.items() + ) + + def generate_lit_markup(self, header:str) -> str: + if not header in lit_markup.keys(): + return "" + + return "\n".join(f"// {markup}" for markup in lit_markup[header]) + "\n\n" + + def generate_header_test(self, header: str) -> str: + """Returns the body for the FTM test of a `header`.""" + + return ftm_header_test_file_contents.format( + script_name=script_name, + lit_markup=self.generate_lit_markup(header), + header=header, + include=( + ftm_header_test_file_include_unconditional.format(header=header) + if header in self.implemented_standard_library_headers + else ftm_header_test_file_include_conditional.format(header=header) + ), + data= + # FTM block before the first Standard that introduced them. + # This test the macros are not available before this version. + ftm_header_test_file_dialect_block.format( + pp_if="if", + operator="<", + dialect=self.std_dialects[0][3:], + tests=self.generate_header_test_dialect( + None, next(iter(self.header_ftm(header).values())) + ), + ) + + + # FTM for all Standards that have FTM defined. + # Note in libc++ the TEST_STD_VER contains 99 for the Standard + # in development, therefore the last entry uses a different #elif. + "".join( + ftm_header_test_file_dialect_block.format( + pp_if="elif", + operator="==" if std != self.std_dialects[-1][3:] else ">", + dialect=std + if std != self.std_dialects[-1][3:] + else self.std_dialects[-2][3:], + tests=self.generate_header_test_dialect(f"c++{std}", values), + ) + for std, values in self.header_ftm(header).items() + ) + + + # The final #endif for the last #elif block. + f"\n#endif // TEST_STD_VER > {self.std_dialects[-2][3:]}", + ) + + def generate_header_test_directory(self, path: os.path) -> None: + """Generates all FTM tests in the directory `path`.""" + + if not os.path.exists(path): + os.makedirs(path) + + for header in self.standard_library_headers: + with open( + os.path.join(path, f"{header}.version.compile.pass.cpp"), + "w", + newline="\n", + ) as f: + f.write(self.generate_header_test(header)) + def main(): produce_version_header() produce_tests() produce_docs() - # Example how to use the new version header generation function to generate - # the file. + # Example how to use the new generator to generate the output. if False: ftm = FeatureTestMacros( os.path.join( @@ -2318,6 +2594,8 @@ def main(): with open(version_header_path, "w", newline="\n") as f: f.write(ftm.version_header) + ftm.generate_header_test_directory(macro_test_path) + if __name__ == "__main__": main() _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits