From 63a4fe67fb33acb66793c495722ab680de6220af Mon Sep 17 00:00:00 2001 From: Mark de Wever Date: Mon, 8 Apr 2024 21:46:06 +0200 Subject: [PATCH 1/4] [RFC][libc++] Testing feature test macro script. This is a proof-of-concept how we can test the script. Instead of storing the data in the script it's stored in a JSON file so a different file can be used for testing. This is related to the RFC https://github.com/llvm/llvm-project/pull/89499 --- .../test/libcxx/feature_test_macro_csv.sh.py | 35 +++++ .../data/feature_test_macros/test_data.json | 136 ++++++++++++++++++ .../generate_feature_test_macro_components.py | 33 +++++ 3 files changed, 204 insertions(+) create mode 100644 libcxx/test/libcxx/feature_test_macro_csv.sh.py create mode 100644 libcxx/utils/data/feature_test_macros/test_data.json diff --git a/libcxx/test/libcxx/feature_test_macro_csv.sh.py b/libcxx/test/libcxx/feature_test_macro_csv.sh.py new file mode 100644 index 0000000000000..2d80bfad63c85 --- /dev/null +++ b/libcxx/test/libcxx/feature_test_macro_csv.sh.py @@ -0,0 +1,35 @@ +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json + +import sys +import json + +sys.path.append(sys.argv[1]) +from generate_feature_test_macro_components import get_table + + +data = json.load(open(f"{sys.argv[2]}")) +table = get_table(data) + +expected = { + "__cpp_lib_any": { + "c++17": "201606L", + "c++20": "201606L", + "c++23": "201606L", + "c++26": "201606L", + }, + "__cpp_lib_barrier": {"c++20": "201907L", "c++23": "201907L", "c++26": "201907L"}, + "__cpp_lib_format": { + "c++20": "", + "c++23": "", + "c++26": "", + }, + "__cpp_lib_variant": { + "c++17": "202102L", + "c++20": "202102L", + "c++23": "202102L", + "c++26": "202102L", + }, +} + + +assert table == expected, f"expected\n{expected}\n\nresult\n{table}" diff --git a/libcxx/utils/data/feature_test_macros/test_data.json b/libcxx/utils/data/feature_test_macros/test_data.json new file mode 100644 index 0000000000000..5a98fba6403c0 --- /dev/null +++ b/libcxx/utils/data/feature_test_macros/test_data.json @@ -0,0 +1,136 @@ +[ + { + "name": "__cpp_lib_any", + "values": { + "c++17": { + "201606": [ + { + "implemented": true + } + ] + } + }, + "headers": [ + "any" + ] + }, + { + "name": "__cpp_lib_barrier", + "values": { + "c++20": { + "201907": [ + { + "implemented": true + } + ] + } + }, + "headers": [ + "barrier" + ], + "test_suite_guard": + "!defined(_LIBCPP_HAS_NO_THREADS) && (!defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_SYNC)", + "libcxx_guard": "!defined(_LIBCPP_HAS_NO_THREADS) && _LIBCPP_AVAILABILITY_HAS_SYNC" + }, + { + "name": "__cpp_lib_format", + "values": { + "c++20": { + "201907": [ + { + "number": "P0645R10", + "title": "Text Formatting", + "implemented": true + }, + { + "number": "P1361R2", + "title": "Integration of chrono with text formatting", + "implemented": false + } + ], + "202106": [ + { + "number": "P2216R3", + "title": "std::format improvements", + "implemented": true + } + ], + "202110": [ + { + "number": "P2372R3", + "title": "Fixing locale handling in chrono formatters", + "implemented": false + }, + { + "number": "P2418R2", + "title": "FAdd support for std::generator-like types to std::format", + "implemented": true + } + ] + }, + "c++23": { + "202207": [ + { + "number": "P2419R2", + "title": "Clarify handling of encodings in localized formatting of chrono types", + "implemented": false + } + ] + }, + "c++26": { + "202306": [ + { + "number": "P2637R3", + "title": "Member Visit", + "implemented": true + } + ], + "202311": [ + { + "number": "P2918R2", + "title": "Runtime format strings II", + "implemented": true + } + ] + } + }, + "headers": [ + "format" + ] + }, + { + "name": "__cpp_lib_variant", + "values": { + "c++17": { + "202102": [ + { + "number": "", + "title": "``std::visit`` for classes derived from ``std::variant``", + "implemented": true + } + ] + }, + "c++20": { + "202106": [ + { + "number": "", + "title": "Fully constexpr ``std::variant``", + "implemented": false + } + ] + }, + "c++26": { + "202306": [ + { + "number": "", + "title": "Member visit", + "implemented": true + } + ] + } + }, + "headers": [ + "variant" + ] + } +] diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index fe5bab05195a3..7be055cdaa188 100755 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -3,6 +3,7 @@ import os from builtins import range from functools import reduce +import json def get_libcxx_paths(): @@ -1867,6 +1868,38 @@ def produce_docs(): f.write(doc_str) +def get_table(data): + result = dict() + for feature in data: + last = None + entry = dict() + implemented = True + for std in get_std_dialects(): + if std not in feature["values"].keys(): + if last == None: + continue + else: + entry[std] = last + else: + if last == None: + last = "" + if implemented: + for value in feature["values"][std]: + for paper in list(feature["values"][std][value]): + if not paper["implemented"]: + implemented = False + break + if implemented: + last = f"{value}L" + else: + break + + entry[std] = last + result[feature["name"]] = entry + + return result + + def main(): produce_version_header() produce_tests() From 49c0d92a456f76f3aa086fbdb376d83c115df40d Mon Sep 17 00:00:00 2001 From: Mark de Wever Date: Mon, 8 Apr 2024 21:46:06 +0200 Subject: [PATCH 2/4] Addresses review comments. --- .../get_dialect_versions.sh.py | 53 +++++ .../get_std_dialect_versions.sh.py | 53 +++++ .../feature_test_macro/get_std_dialects.sh.py | 29 +++ .../libcxx/feature_test_macro/invalid.sh.py | 107 ++++++++++ .../test/libcxx/feature_test_macro_csv.sh.py | 35 ---- .../data/feature_test_macros/test_data.json | 17 +- .../generate_feature_test_macro_components.py | 191 +++++++++++++++++- 7 files changed, 442 insertions(+), 43 deletions(-) create mode 100644 libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py create mode 100644 libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py create mode 100644 libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py create mode 100644 libcxx/test/libcxx/feature_test_macro/invalid.sh.py delete mode 100644 libcxx/test/libcxx/feature_test_macro_csv.sh.py diff --git a/libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py b/libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py new file mode 100644 index 0000000000000..fffe0ddc6e6a7 --- /dev/null +++ b/libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py @@ -0,0 +1,53 @@ +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json +# ===----------------------------------------------------------------------===## +# +# 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 +# +# ===----------------------------------------------------------------------===## + +import sys + +sys.path.append(sys.argv[1]) +from generate_feature_test_macro_components import feature_test_macros + + +def test(output, expected): + assert output == expected, f"expected\n{expected}\n\noutput\n{output}" + + +fmt = feature_test_macros(sys.argv[2]) +test( + fmt.get_dialect_versions(), + { + "__cpp_lib_any": { + "c++17": "201606L", + "c++20": "201606L", + "c++23": "201606L", + "c++26": "201606L", + }, + "__cpp_lib_barrier": { + "c++20": "201907L", + "c++23": "201907L", + "c++26": "201907L", + }, + "__cpp_lib_format": { + "c++20": None, + "c++23": None, + "c++26": None, + }, + "__cpp_lib_parallel_algorithm": { + "c++17": "201603L", + "c++20": "201603L", + "c++23": "201603L", + "c++26": "201603L", + }, + "__cpp_lib_variant": { + "c++17": "202102L", + "c++20": "202102L", + "c++23": "202102L", + "c++26": "202102L", + }, + }, +) diff --git a/libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py b/libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py new file mode 100644 index 0000000000000..5c438da601921 --- /dev/null +++ b/libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py @@ -0,0 +1,53 @@ +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json +# ===----------------------------------------------------------------------===## +# +# 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 +# +# ===----------------------------------------------------------------------===## + +import sys + +sys.path.append(sys.argv[1]) +from generate_feature_test_macro_components import feature_test_macros + + +def test(output, expected): + assert output == expected, f"expected\n{expected}\n\noutput\n{output}" + + +fmt = feature_test_macros(sys.argv[2]) +test( + fmt.get_std_dialect_versions(), + { + "__cpp_lib_any": { + "c++17": "201606L", + "c++20": "201606L", + "c++23": "201606L", + "c++26": "201606L", + }, + "__cpp_lib_barrier": { + "c++20": "201907L", + "c++23": "201907L", + "c++26": "201907L", + }, + "__cpp_lib_format": { + "c++20": "202110L", + "c++23": "202207L", + "c++26": "202311L", + }, + "__cpp_lib_parallel_algorithm": { + "c++17": "201603L", + "c++20": "201603L", + "c++23": "201603L", + "c++26": "201603L", + }, + "__cpp_lib_variant": { + "c++17": "202102L", + "c++20": "202106L", + "c++23": "202106L", + "c++26": "202306L", + }, + }, +) diff --git a/libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py b/libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py new file mode 100644 index 0000000000000..af93f94cbfbe7 --- /dev/null +++ b/libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py @@ -0,0 +1,29 @@ +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json +# ===----------------------------------------------------------------------===## +# +# 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 +# +# ===----------------------------------------------------------------------===## + +import sys + +sys.path.append(sys.argv[1]) +from generate_feature_test_macro_components import feature_test_macros + + +def test(output, expected): + assert output == expected, f"expected\n{expected}\n\noutput\n{output}" + + +fmt = feature_test_macros(sys.argv[2]) +test( + fmt.get_std_dialects(), + [ + "c++17", + "c++20", + "c++23", + "c++26", + ], +) diff --git a/libcxx/test/libcxx/feature_test_macro/invalid.sh.py b/libcxx/test/libcxx/feature_test_macro/invalid.sh.py new file mode 100644 index 0000000000000..ed65242ccc750 --- /dev/null +++ b/libcxx/test/libcxx/feature_test_macro/invalid.sh.py @@ -0,0 +1,107 @@ +# RUN: %{python} %s %{libcxx-dir}/utils %t +# ===----------------------------------------------------------------------===## +# +# 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 +# +# ===----------------------------------------------------------------------===## + +import sys +import json + +sys.path.append(sys.argv[1]) +from generate_feature_test_macro_components import feature_test_macros + + +def test(output, expected): + assert output == expected, f"expected\n{expected}\n\noutput\n{output}" + + +def test_error(data, type, message): + tmp = sys.argv[2] + with open(tmp, "w") as file: + file.write(json.dumps(data)) + fmt = feature_test_macros(tmp) + try: + fmt.get_dialect_versions() + except type as error: + test(str(error), message) + else: + assert False, "no exception was thrown" + + +test_error( + [ + { + "values": { + "c++17": { + "197001": [ + { + "implemented": False, + }, + ], + }, + }, + "headers": [], + }, + ], + KeyError, + "'name'", +) + +test_error( + [ + { + "name": "a", + "headers": [], + }, + ], + KeyError, + "'values'", +) + +test_error( + [ + { + "name": "a", + "values": {}, + "headers": [], + }, + ], + AssertionError, + "'values' is empty", +) + + +test_error( + [ + { + "name": "a", + "values": { + "c++17": {}, + }, + "headers": [], + }, + ], + AssertionError, + "a[c++17] has no entries", +) + +test_error( + [ + { + "name": "a", + "values": { + "c++17": { + "197001": [ + {}, + ], + }, + }, + "headers": [], + }, + ], + KeyError, + "'implemented'", +) diff --git a/libcxx/test/libcxx/feature_test_macro_csv.sh.py b/libcxx/test/libcxx/feature_test_macro_csv.sh.py deleted file mode 100644 index 2d80bfad63c85..0000000000000 --- a/libcxx/test/libcxx/feature_test_macro_csv.sh.py +++ /dev/null @@ -1,35 +0,0 @@ -# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json - -import sys -import json - -sys.path.append(sys.argv[1]) -from generate_feature_test_macro_components import get_table - - -data = json.load(open(f"{sys.argv[2]}")) -table = get_table(data) - -expected = { - "__cpp_lib_any": { - "c++17": "201606L", - "c++20": "201606L", - "c++23": "201606L", - "c++26": "201606L", - }, - "__cpp_lib_barrier": {"c++20": "201907L", "c++23": "201907L", "c++26": "201907L"}, - "__cpp_lib_format": { - "c++20": "", - "c++23": "", - "c++26": "", - }, - "__cpp_lib_variant": { - "c++17": "202102L", - "c++20": "202102L", - "c++23": "202102L", - "c++26": "202102L", - }, -} - - -assert table == expected, f"expected\n{expected}\n\nresult\n{table}" diff --git a/libcxx/utils/data/feature_test_macros/test_data.json b/libcxx/utils/data/feature_test_macros/test_data.json index 5a98fba6403c0..1f8bbe5d769b5 100644 --- a/libcxx/utils/data/feature_test_macros/test_data.json +++ b/libcxx/utils/data/feature_test_macros/test_data.json @@ -98,13 +98,28 @@ "format" ] }, + { + "name": "__cpp_lib_parallel_algorithm", + "values": { + "c++17": { + "201603": [ + { + "implemented": true + } + ] + } + }, + "headers": [ + "algorithm", + "numeric" + ] + }, { "name": "__cpp_lib_variant", "values": { "c++17": { "202102": [ { - "number": "", "title": "``std::visit`` for classes derived from ``std::variant``", "implemented": true } diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index 7be055cdaa188..916ea70e87bcb 100755 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -1868,25 +1868,41 @@ def produce_docs(): f.write(doc_str) -def get_table(data): +def get_std_dialects(data): + """Impementation for feature_test_macros.get_std_dialects().""" + dialects = set() + for feature in data: + keys = feature["values"].keys() + assert len(keys) > 0, "'values' is empty" + dialects |= keys + + return sorted(dialects) + + +def get_dialect_versions(data, std_dialects, use_implemented_status): + """Impementation for feature_test_macros.get_(std_|)dialect_versions().""" result = dict() for feature in data: last = None entry = dict() implemented = True - for std in get_std_dialects(): + for std in std_dialects: if std not in feature["values"].keys(): if last == None: continue else: entry[std] = last else: - if last == None: - last = "" if implemented: - for value in feature["values"][std]: - for paper in list(feature["values"][std][value]): - if not paper["implemented"]: + 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 use_implemented_status and not paper["implemented"]: implemented = False break if implemented: @@ -1900,6 +1916,167 @@ def get_table(data): return result +class feature_test_macros: + """Provides all feature-test macro (FMT) output components. + + The class has several generators to use the feature-test macros in libc++: + - FTM status page + - The version header and its tests + + This class is not intended to duplicate + https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations#library-feature-test-macros + SD-FeatureTest: Feature-Test Macros and Policies + + Historically libc++ did not list all papers affecting a FTM, the new data + structure is able to do that. However there is no intention to add the + historical data. After papers have been implemented this information can be + removed. For example, __cpp_lib_format's value 201907 requires 3 papers, + once implemented it can be reduced to 1 paper and remove the paper number + and title. This would reduce the size of the data. + + The input data is stored in the following JSON format: + [ # A list with multiple feature-test macro entries. + { + # required + # The name of the feature test macro. These names should be unique and + # sorted in the list. + "name": "__cpp_lib_any", + + # required + # A map with the value of the FTM based on the language standard. Only + # the versions in which the value of the FTM changes are listed. For + # example, this macro's value does not change in C++20 so it does not + # list C++20. If it changes in C++26, it will have entries for C++17 and + # C++26. + "values": { + + # required + # The language standard, also named dialect in this class. + "c++17": { + + # required + # The value of the feature test macro. This contains an array with + # one or more papers that need to be implemented before this value + # is considered implemented. + "201606": [ + { + # optional + # Contains the paper number that is part of the FTM version. + "number": "P0220R1", + + # optional + # Contains the title of the paper that is part of the FTM + # version. + "title": "Adopt Library Fundamentals V1 TS Components for C++17" + + # required + # The implementation status of the paper. + "implemented": true + } + ] + } + }, + + # required + # A sorted list of headers that should provide the FTM. The header + # is automatically added to this list. This list could be + # empty. For example, __cpp_lib_modules is only present in version. + # Requiring the field makes it easier to detect accidental omission. + "headers": [ + "any" + ], + + # optional, required when libcxx_guard is present + # This field is used only to generate the unit tests for the + # feature-test macros. It can't depend on macros defined in <__config> + # because the `test/std/` parts of the test suite are intended to be + # portable to any C++ standard library implementation, not just libc++. + # It may depend on + # * macros defined by the compiler itself, or + # * macros generated by CMake. + # In some cases we add also depend on macros defined in + # <__availability>. + "test_suite_guard": "!defined(_LIBCPP_VERSION) || _LIBCPP_AVAILABILITY_HAS_PMR" + + # optional, required when test_suite_guard is present + # This field is used only to guard the feature-test macro in + # . It may be the same as `test_suite_guard`, or it may + # depend on macros defined in <__config>. + "libcxx_guard": "_LIBCPP_AVAILABILITY_HAS_PMR" + }, + ] + """ + + # The JSON data structor. + __data = None + + # These values are used internally multiple times. They are lazily loaded + # and cached. Values that are expected to be used once are not cached. + __std_dialects = None + __std_dialect_versions = None + __dialect_versions = None + + def __init__(self, filename): + """Initializes the class with the JSON data in the file 'filename'.""" + self.__data = json.load(open(filename)) + + def get_std_dialects(self): + """Returns the C++ dialects avaiable. + + The available dialects are based on the 'c++xy' keys found the 'values' + entries in '__data'. So when WG21 starts to feature-test macros for a + future C++ Standard this dialect will automatically be available. + + The return value is a sorted list with the C++ dialects used. Since FTM + were added in C++14 the list will not contain C++98 or C++11. + """ + if not self.__std_dialects: + self.__std_dialects = get_std_dialects(self.__data) + + return self.__std_dialects + + def get_std_dialect_versions(self): + """Returns the FTM versions per dialect in the Standard. + + This function does not use the 'implemented' flag. The output contains + the versions used in the Standard. When a FTM in libc++ is not + implemented according to the Standard to output may opt to show the + expected value. + + The result is a dict with the following content + - key: Name of the feature test macro. + - value: A dict with the following content: + * key: The version of the C++ dialect. + * value: The value of the feature-test macro. + """ + if not self.__std_dialect_versions: + self.__std_dialect_versions = get_dialect_versions( + self.__data, self.get_std_dialects(), False + ) + + return self.__std_dialect_versions + + def get_dialect_versions(self): + """Returns the FTM versions per dialect implemented in libc++. + + Unlike `get_std_dialect_versions` this function uses the 'implemented' + flag. This returns the actual implementation status in libc++. + + The result is a dict with the following content + - key: Name of the feature test macro. + - value: A dict with the following content: + * key: The version of the C++ dialect. + * value: The value of the feature-test macro. When a feature-test + macro is not implemented its value is None. + """ + if not self.__dialect_versions: + self.__dialect_versions = get_dialect_versions( + self.__data, self.get_std_dialects(), True + ) + + return self.__dialect_versions + + def main(): produce_version_header() produce_tests() From 4eaacab3b225a2cf55e74bfdc62d20d1fb81355c Mon Sep 17 00:00:00 2001 From: Mark de Wever Date: Wed, 12 Jun 2024 19:52:40 +0200 Subject: [PATCH 3/4] Addresses review comments. --- ..._versions.sh.py => implemented_ftms.sh.py} | 9 +-- .../libcxx/feature_test_macro/invalid.sh.py | 9 +-- ...ect_versions.sh.py => standard_ftms.sh.py} | 9 +-- ..._std_dialects.sh.py => std_dialects.sh.py} | 9 +-- .../generate_feature_test_macro_components.py | 55 ++++++------------- 5 files changed, 38 insertions(+), 53 deletions(-) rename libcxx/test/libcxx/feature_test_macro/{get_dialect_versions.sh.py => implemented_ftms.sh.py} (90%) rename libcxx/test/libcxx/feature_test_macro/{get_std_dialect_versions.sh.py => standard_ftms.sh.py} (90%) rename libcxx/test/libcxx/feature_test_macro/{get_std_dialects.sh.py => std_dialects.sh.py} (83%) diff --git a/libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py b/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py similarity index 90% rename from libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py rename to libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py index fffe0ddc6e6a7..e210507f18e2e 100644 --- a/libcxx/test/libcxx/feature_test_macro/get_dialect_versions.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py @@ -1,4 +1,3 @@ -# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json # ===----------------------------------------------------------------------===## # # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. @@ -7,19 +6,21 @@ # # ===----------------------------------------------------------------------===## +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json + import sys sys.path.append(sys.argv[1]) -from generate_feature_test_macro_components import feature_test_macros +from generate_feature_test_macro_components import FeatureTestMacros def test(output, expected): assert output == expected, f"expected\n{expected}\n\noutput\n{output}" -fmt = feature_test_macros(sys.argv[2]) +ftm = FeatureTestMacros(sys.argv[2]) test( - fmt.get_dialect_versions(), + ftm.implemented_ftms, { "__cpp_lib_any": { "c++17": "201606L", diff --git a/libcxx/test/libcxx/feature_test_macro/invalid.sh.py b/libcxx/test/libcxx/feature_test_macro/invalid.sh.py index ed65242ccc750..ae457f6e1a545 100644 --- a/libcxx/test/libcxx/feature_test_macro/invalid.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/invalid.sh.py @@ -1,4 +1,3 @@ -# RUN: %{python} %s %{libcxx-dir}/utils %t # ===----------------------------------------------------------------------===## # # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. @@ -7,11 +6,13 @@ # # ===----------------------------------------------------------------------===## +# RUN: %{python} %s %{libcxx-dir}/utils %t + import sys import json sys.path.append(sys.argv[1]) -from generate_feature_test_macro_components import feature_test_macros +from generate_feature_test_macro_components import FeatureTestMacros def test(output, expected): @@ -22,9 +23,9 @@ def test_error(data, type, message): tmp = sys.argv[2] with open(tmp, "w") as file: file.write(json.dumps(data)) - fmt = feature_test_macros(tmp) + ftm = FeatureTestMacros(tmp) try: - fmt.get_dialect_versions() + ftm.implemented_ftms except type as error: test(str(error), message) else: diff --git a/libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py b/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py similarity index 90% rename from libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py rename to libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py index 5c438da601921..25cb306998721 100644 --- a/libcxx/test/libcxx/feature_test_macro/get_std_dialect_versions.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py @@ -1,4 +1,3 @@ -# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json # ===----------------------------------------------------------------------===## # # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. @@ -7,19 +6,21 @@ # # ===----------------------------------------------------------------------===## +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json + import sys sys.path.append(sys.argv[1]) -from generate_feature_test_macro_components import feature_test_macros +from generate_feature_test_macro_components import FeatureTestMacros def test(output, expected): assert output == expected, f"expected\n{expected}\n\noutput\n{output}" -fmt = feature_test_macros(sys.argv[2]) +ftm = FeatureTestMacros(sys.argv[2]) test( - fmt.get_std_dialect_versions(), + ftm.standard_ftms, { "__cpp_lib_any": { "c++17": "201606L", diff --git a/libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py b/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py similarity index 83% rename from libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py rename to libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py index af93f94cbfbe7..42a6d169f720b 100644 --- a/libcxx/test/libcxx/feature_test_macro/get_std_dialects.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py @@ -1,4 +1,3 @@ -# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json # ===----------------------------------------------------------------------===## # # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. @@ -7,19 +6,21 @@ # # ===----------------------------------------------------------------------===## +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json + import sys sys.path.append(sys.argv[1]) -from generate_feature_test_macro_components import feature_test_macros +from generate_feature_test_macro_components import FeatureTestMacros def test(output, expected): assert output == expected, f"expected\n{expected}\n\noutput\n{output}" -fmt = feature_test_macros(sys.argv[2]) +ftm = FeatureTestMacros(sys.argv[2]) test( - fmt.get_std_dialects(), + ftm.std_dialects, [ "c++17", "c++20", diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index 916ea70e87bcb..b03d445e3b4a0 100755 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -3,6 +3,7 @@ import os from builtins import range from functools import reduce +import functools import json @@ -1868,17 +1869,6 @@ def produce_docs(): f.write(doc_str) -def get_std_dialects(data): - """Impementation for feature_test_macros.get_std_dialects().""" - dialects = set() - for feature in data: - keys = feature["values"].keys() - assert len(keys) > 0, "'values' is empty" - dialects |= keys - - return sorted(dialects) - - def get_dialect_versions(data, std_dialects, use_implemented_status): """Impementation for feature_test_macros.get_(std_|)dialect_versions().""" result = dict() @@ -1916,8 +1906,8 @@ def get_dialect_versions(data, std_dialects, use_implemented_status): return result -class feature_test_macros: - """Provides all feature-test macro (FMT) output components. +class FeatureTestMacros: + """Provides all feature-test macro (FTM) output components. The class has several generators to use the feature-test macros in libc++: - FTM status page @@ -2007,20 +1997,15 @@ class feature_test_macros: ] """ - # The JSON data structor. + # The JSON data structure. __data = None - # These values are used internally multiple times. They are lazily loaded - # and cached. Values that are expected to be used once are not cached. - __std_dialects = None - __std_dialect_versions = None - __dialect_versions = None - def __init__(self, filename): """Initializes the class with the JSON data in the file 'filename'.""" self.__data = json.load(open(filename)) - def get_std_dialects(self): + @functools.cached_property + def std_dialects(self): """Returns the C++ dialects avaiable. The available dialects are based on the 'c++xy' keys found the 'values' @@ -2030,12 +2015,16 @@ def get_std_dialects(self): The return value is a sorted list with the C++ dialects used. Since FTM were added in C++14 the list will not contain C++98 or C++11. """ - if not self.__std_dialects: - self.__std_dialects = get_std_dialects(self.__data) + dialects = set() + for feature in self.__data: + keys = feature["values"].keys() + assert len(keys) > 0, "'values' is empty" + dialects |= keys - return self.__std_dialects + return sorted(list(dialects)) - def get_std_dialect_versions(self): + @functools.cached_property + def standard_ftms(self): """Returns the FTM versions per dialect in the Standard. This function does not use the 'implemented' flag. The output contains @@ -2049,14 +2038,10 @@ def get_std_dialect_versions(self): * key: The version of the C++ dialect. * value: The value of the feature-test macro. """ - if not self.__std_dialect_versions: - self.__std_dialect_versions = get_dialect_versions( - self.__data, self.get_std_dialects(), False - ) + return get_dialect_versions(self.__data, self.std_dialects, False) - return self.__std_dialect_versions - - def get_dialect_versions(self): + @functools.cached_property + def implemented_ftms(self): """Returns the FTM versions per dialect implemented in libc++. Unlike `get_std_dialect_versions` this function uses the 'implemented' @@ -2069,12 +2054,8 @@ def get_dialect_versions(self): * value: The value of the feature-test macro. When a feature-test macro is not implemented its value is None. """ - if not self.__dialect_versions: - self.__dialect_versions = get_dialect_versions( - self.__data, self.get_std_dialects(), True - ) - return self.__dialect_versions + return get_dialect_versions(self.__data, self.std_dialects, True) def main(): From 2d681090a3d0212244c4bc0686300724c4f21ea5 Mon Sep 17 00:00:00 2001 From: Mark de Wever Date: Thu, 4 Jul 2024 14:08:05 +0200 Subject: [PATCH 4/4] Address review comments. --- .../feature_test_macro/implemented_ftms.sh.py | 2 +- .../feature_test_macro/standard_ftms.sh.py | 2 +- .../feature_test_macro/std_dialects.sh.py | 2 +- .../libcxx/feature_test_macro}/test_data.json | 0 .../generate_feature_test_macro_components.py | 21 +++++++++++-------- 5 files changed, 15 insertions(+), 12 deletions(-) rename libcxx/{utils/data/feature_test_macros => test/libcxx/feature_test_macro}/test_data.json (100%) 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 e210507f18e2e..67353fc41e509 100644 --- a/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/implemented_ftms.sh.py @@ -6,7 +6,7 @@ # # ===----------------------------------------------------------------------===## -# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/test/libcxx/feature_test_macro/test_data.json import sys 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 25cb306998721..43c90b131bff1 100644 --- a/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/standard_ftms.sh.py @@ -6,7 +6,7 @@ # # ===----------------------------------------------------------------------===## -# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/test/libcxx/feature_test_macro/test_data.json import sys diff --git a/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py b/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py index 42a6d169f720b..368020c91e1d2 100644 --- a/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py +++ b/libcxx/test/libcxx/feature_test_macro/std_dialects.sh.py @@ -6,7 +6,7 @@ # # ===----------------------------------------------------------------------===## -# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/utils/data/feature_test_macros/test_data.json +# RUN: %{python} %s %{libcxx-dir}/utils %{libcxx-dir}/test/libcxx/feature_test_macro/test_data.json import sys diff --git a/libcxx/utils/data/feature_test_macros/test_data.json b/libcxx/test/libcxx/feature_test_macro/test_data.json similarity index 100% rename from libcxx/utils/data/feature_test_macros/test_data.json rename to libcxx/test/libcxx/feature_test_macro/test_data.json diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index b03d445e3b4a0..3f8ecc26321ee 100755 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -3,6 +3,7 @@ import os from builtins import range from functools import reduce +from typing import Any, Dict, List # Needed for python 3.8 compatibility. import functools import json @@ -1869,8 +1870,10 @@ def produce_docs(): f.write(doc_str) -def get_dialect_versions(data, std_dialects, use_implemented_status): - """Impementation for feature_test_macros.get_(std_|)dialect_versions().""" +def get_ftms( + data, std_dialects: List[str], use_implemented_status: bool +) -> Dict[str, Dict[str, Any]]: + """Impementation for FeatureTestMacros.(standard|implemented)_ftms().""" result = dict() for feature in data: last = None @@ -2000,12 +2003,12 @@ class FeatureTestMacros: # The JSON data structure. __data = None - def __init__(self, filename): + def __init__(self, filename: str): """Initializes the class with the JSON data in the file 'filename'.""" self.__data = json.load(open(filename)) @functools.cached_property - def std_dialects(self): + def std_dialects(self) -> List[str]: """Returns the C++ dialects avaiable. The available dialects are based on the 'c++xy' keys found the 'values' @@ -2013,7 +2016,7 @@ def std_dialects(self): future C++ Standard this dialect will automatically be available. The return value is a sorted list with the C++ dialects used. Since FTM - were added in C++14 the list will not contain C++98 or C++11. + were added in C++14 the list will not contain C++03 or C++11. """ dialects = set() for feature in self.__data: @@ -2024,7 +2027,7 @@ def std_dialects(self): return sorted(list(dialects)) @functools.cached_property - def standard_ftms(self): + def standard_ftms(self) -> Dict[str, Dict[str, Any]]: """Returns the FTM versions per dialect in the Standard. This function does not use the 'implemented' flag. The output contains @@ -2038,10 +2041,10 @@ def standard_ftms(self): * key: The version of the C++ dialect. * value: The value of the feature-test macro. """ - return get_dialect_versions(self.__data, self.std_dialects, False) + return get_ftms(self.__data, self.std_dialects, False) @functools.cached_property - def implemented_ftms(self): + def implemented_ftms(self) -> Dict[str, Dict[str, Any]]: """Returns the FTM versions per dialect implemented in libc++. Unlike `get_std_dialect_versions` this function uses the 'implemented' @@ -2055,7 +2058,7 @@ def implemented_ftms(self): macro is not implemented its value is None. """ - return get_dialect_versions(self.__data, self.std_dialects, True) + return get_ftms(self.__data, self.std_dialects, True) def main():