#!/usr/bin/env python import os def get_libcxx_paths(): utils_path = os.path.dirname(os.path.abspath(__file__)) script_name = os.path.basename(__file__) assert os.path.exists(utils_path) src_root = os.path.dirname(utils_path) test_path = os.path.join(src_root, 'test', 'libcxx', 'inclusions') assert os.path.exists(test_path) assert os.path.exists(os.path.join(test_path, 'algorithm.inclusions.compile.pass.cpp')) return script_name, src_root, test_path script_name, source_root, test_path = get_libcxx_paths() # This table was produced manually, by grepping the TeX source of the Standard's # library clauses for the string "#include". Each header's synopsis contains # explicit "#include" directives for its mandatory inclusions. # For example, [algorithm.syn] contains "#include ". # mandatory_inclusions = { "algorithm": ["initializer_list"], "array": ["compare", "initializer_list"], "bitset": ["iosfwd", "string"], "chrono": ["compare"], "cinttypes": ["cstdint"], "complex.h": ["complex"], # TODO "coroutine": ["compare"], "deque": ["compare", "initializer_list"], "filesystem": ["compare"], "forward_list": ["compare", "initializer_list"], "ios": ["iosfwd"], "iostream": ["ios", "istream", "ostream", "streambuf"], "iterator": ["compare", "concepts"], "list": ["compare", "initializer_list"], "map": ["compare", "initializer_list"], "memory": ["compare"], "optional": ["compare"], "queue": ["compare", "initializer_list"], "random": ["initializer_list"], "ranges": ["compare", "initializer_list", "iterator"], "regex": ["compare", "initializer_list"], "set": ["compare", "initializer_list"], "stack": ["compare", "initializer_list"], "string_view": ["compare"], "string": ["compare", "initializer_list"], # TODO "syncstream": ["ostream"], "system_error": ["compare"], "tgmath.h": ["cmath", "complex"], "thread": ["compare"], "tuple": ["compare"], "typeindex": ["compare"], "unordered_map": ["compare", "initializer_list"], "unordered_set": ["compare", "initializer_list"], "utility": ["compare", "initializer_list"], "valarray": ["initializer_list"], "variant": ["compare"], "vector": ["compare", "initializer_list"], } new_in_version = { "chrono": "11", "compare": "20", "concepts": "20", "coroutine": "20", "filesystem": "17", "initializer_list": "11", "optional": "17", "ranges": "20", "string_view": "17", "syncstream": "20", "system_error": "11", "thread": "11", "tuple": "11", "unordered_map": "11", "unordered_set": "11", "variant": "17", } assert all(v == sorted(v) for k, v in mandatory_inclusions.items()) # Map from each header to the Lit annotations that should be used for # tests that include that header. # # For example, when threads are not supported, any test # that includes should be marked as UNSUPPORTED, because including # is a hard error in that case. lit_markup = { "atomic": ["UNSUPPORTED: libcpp-has-no-threads"], "barrier": ["UNSUPPORTED: libcpp-has-no-threads"], "filesystem": ["UNSUPPORTED: libcpp-has-no-filesystem-library"], "format": ["UNSUPPORTED: libcpp-has-no-incomplete-format"], "iomanip": ["UNSUPPORTED: libcpp-has-no-localization"], "ios": ["UNSUPPORTED: libcpp-has-no-localization"], "iostream": ["UNSUPPORTED: libcpp-has-no-localization"], "istream": ["UNSUPPORTED: libcpp-has-no-localization"], "latch": ["UNSUPPORTED: libcpp-has-no-threads"], "locale": ["UNSUPPORTED: libcpp-has-no-localization"], "ostream": ["UNSUPPORTED: libcpp-has-no-localization"], "ranges": ["UNSUPPORTED: libcpp-has-no-incomplete-ranges"], "regex": ["UNSUPPORTED: libcpp-has-no-localization"], "semaphore": ["UNSUPPORTED: libcpp-has-no-threads"], "shared_mutex": ["UNSUPPORTED: libcpp-has-no-threads"], "thread": ["UNSUPPORTED: libcpp-has-no-threads"], } def get_std_ver_test(includee): v = new_in_version.get(includee, "03") if v == "03": return '' versions = ["03", "11", "14", "17", "20"] return 'TEST_STD_VER > {} && '.format(max(i for i in versions if i < v)) def get_unsupported_line(includee): v = new_in_version.get(includee, "03") return { "03": [], "11": ['UNSUPPORTED: c++03'], "14": ['UNSUPPORTED: c++03, c++11'], "17": ['UNSUPPORTED: c++03, c++11, c++14'], "20": ['UNSUPPORTED: c++03, c++11, c++14, c++17'], "2b": ['UNSUPPORTED: c++03, c++11, c++14, c++17, c++20'], }[v] def get_libcpp_header_symbol(header_name): return '_LIBCPP_' + header_name.upper().replace('.', '_') def get_includer_symbol_test(includer): symbol = get_libcpp_header_symbol(includer) return """ #if !defined({symbol}) # error "{message}" #endif """.strip().format( symbol=symbol, message="<{}> was expected to define {}".format(includer, symbol), ) def get_ifdef(includer, includee): version = max(new_in_version.get(h, "03") for h in [includer, includee]) symbol = get_libcpp_header_symbol(includee) return """ #if {includee_test}!defined({symbol}) # error "{message}" #endif """.strip().format( includee_test=get_std_ver_test(includee), symbol=symbol, message="<{}> should include <{}> in C++{} and later".format(includer, includee, version) ) test_body_template = """ //===----------------------------------------------------------------------===// // // 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. // // clang-format off {markup} // <{header}> // Test that <{header}> includes all the other headers it's supposed to. #include <{header}> #include "test_macros.h" {test_includers_symbol} {test_per_includee} """.strip() def produce_tests(): for includer, includees in mandatory_inclusions.items(): markup_tags = get_unsupported_line(includer) + lit_markup.get(includer, []) test_body = test_body_template.format( script_name=script_name, header=includer, markup=('\n' + '\n'.join('// ' + m for m in markup_tags) + '\n') if markup_tags else '', test_includers_symbol=get_includer_symbol_test(includer), test_per_includee='\n'.join(get_ifdef(includer, includee) for includee in includees), ) test_name = "{header}.inclusions.compile.pass.cpp".format(header=includer) out_path = os.path.join(test_path, test_name) with open(out_path, 'w', newline='\n') as f: f.write(test_body + '\n') if __name__ == '__main__': produce_tests()