1#!/usr/bin/env python 2 3import os 4 5 6def get_libcxx_paths(): 7 utils_path = os.path.dirname(os.path.abspath(__file__)) 8 script_name = os.path.basename(__file__) 9 assert os.path.exists(utils_path) 10 src_root = os.path.dirname(utils_path) 11 test_path = os.path.join(src_root, 'test', 'libcxx', 'inclusions') 12 assert os.path.exists(test_path) 13 assert os.path.exists(os.path.join(test_path, 'algorithm.inclusions.compile.pass.cpp')) 14 return script_name, src_root, test_path 15 16 17script_name, source_root, test_path = get_libcxx_paths() 18 19 20# This table was produced manually, by grepping the TeX source of the Standard's 21# library clauses for the string "#include". Each header's synopsis contains 22# explicit "#include" directives for its mandatory inclusions. 23# For example, [algorithm.syn] contains "#include <initializer_list>". 24# 25mandatory_inclusions = { 26 "algorithm": ["initializer_list"], 27 "array": ["compare", "initializer_list"], 28 "bitset": ["iosfwd", "string"], 29 "chrono": ["compare"], 30 "cinttypes": ["cstdint"], 31 "complex.h": ["complex"], 32 # TODO "coroutine": ["compare"], 33 "deque": ["compare", "initializer_list"], 34 "filesystem": ["compare"], 35 "forward_list": ["compare", "initializer_list"], 36 "ios": ["iosfwd"], 37 "iostream": ["ios", "istream", "ostream", "streambuf"], 38 "iterator": ["compare", "concepts"], 39 "list": ["compare", "initializer_list"], 40 "map": ["compare", "initializer_list"], 41 "memory": ["compare"], 42 "optional": ["compare"], 43 "queue": ["compare", "initializer_list"], 44 "random": ["initializer_list"], 45 "ranges": ["compare", "initializer_list", "iterator"], 46 "regex": ["compare", "initializer_list"], 47 "set": ["compare", "initializer_list"], 48 "stack": ["compare", "initializer_list"], 49 "string": ["compare", "initializer_list"], 50 "string_view": ["compare"], 51 # TODO "syncstream": ["ostream"], 52 "system_error": ["compare"], 53 "tgmath.h": ["cmath", "complex"], 54 "thread": ["compare"], 55 "tuple": ["compare"], 56 "typeindex": ["compare"], 57 "unordered_map": ["compare", "initializer_list"], 58 "unordered_set": ["compare", "initializer_list"], 59 "utility": ["compare", "initializer_list"], 60 "valarray": ["initializer_list"], 61 "variant": ["compare"], 62 "vector": ["compare", "initializer_list"], 63} 64 65new_in_version = { 66 "chrono": "11", 67 "compare": "20", 68 "concepts": "20", 69 "coroutine": "20", 70 "filesystem": "17", 71 "initializer_list": "11", 72 "optional": "17", 73 "system_error": "11", 74 "thread": "11", 75 "tuple": "11", 76 "unordered_map": "11", 77 "unordered_set": "11", 78 "string_view": "17", 79 "ranges": "20", 80 "syncstream": "20", 81 "variant": "17", 82} 83 84assert all(v == sorted(v) for k, v in mandatory_inclusions.items()) 85 86# Map from each header to the Lit annotations that should be used for 87# tests that include that header. 88# 89# For example, when threads are not supported, any test 90# that includes <thread> should be marked as UNSUPPORTED, because including 91# <thread> is a hard error in that case. 92lit_markup = { 93 "atomic": ["UNSUPPORTED: libcpp-has-no-threads"], 94 "barrier": ["UNSUPPORTED: libcpp-has-no-threads"], 95 "filesystem": ["UNSUPPORTED: libcpp-has-no-filesystem-library"], 96 "iomanip": ["UNSUPPORTED: libcpp-has-no-localization"], 97 "istream": ["UNSUPPORTED: libcpp-has-no-localization"], 98 "ios": ["UNSUPPORTED: libcpp-has-no-localization"], 99 "iostream": ["UNSUPPORTED: libcpp-has-no-localization"], 100 "latch": ["UNSUPPORTED: libcpp-has-no-threads"], 101 "locale": ["UNSUPPORTED: libcpp-has-no-localization"], 102 "ostream": ["UNSUPPORTED: libcpp-has-no-localization"], 103 "regex": ["UNSUPPORTED: libcpp-has-no-localization"], 104 "semaphore": ["UNSUPPORTED: libcpp-has-no-threads"], 105 "shared_mutex": ["UNSUPPORTED: libcpp-has-no-threads"], 106 "thread": ["UNSUPPORTED: libcpp-has-no-threads"], 107} 108 109 110def get_std_ver_test(includee): 111 v = new_in_version.get(includee, "03") 112 if v == "03": 113 return '' 114 versions = ["03", "11", "14", "17", "20"] 115 return 'TEST_STD_VER > {} && '.format(max(i for i in versions if i < v)) 116 117 118def get_unsupported_line(includee): 119 v = new_in_version.get(includee, "03") 120 return { 121 "03": [], 122 "11": ['UNSUPPORTED: c++03'], 123 "14": ['UNSUPPORTED: c++03, c++11'], 124 "17": ['UNSUPPORTED: c++03, c++11, c++14'], 125 "20": ['UNSUPPORTED: c++03, c++11, c++14, c++17'], 126 "2b": ['UNSUPPORTED: c++03, c++11, c++14, c++17, c++20'], 127 }[v] 128 129 130def get_libcpp_header_symbol(header_name): 131 return '_LIBCPP_' + header_name.upper().replace('.', '_') 132 133 134def get_includer_symbol_test(includer): 135 symbol = get_libcpp_header_symbol(includer) 136 return """ 137#if !defined({symbol}) 138 # error "{message}" 139#endif 140 """.strip().format( 141 symbol=symbol, 142 message="<{}> was expected to define {}".format(includer, symbol), 143 ) 144 145 146def get_ifdef(includer, includee): 147 version = max(new_in_version.get(h, "03") for h in [includer, includee]) 148 symbol = get_libcpp_header_symbol(includee) 149 return """ 150#if {includee_test}!defined({symbol}) 151 # error "{message}" 152#endif 153 """.strip().format( 154 includee_test=get_std_ver_test(includee), 155 symbol=symbol, 156 message="<{}> should include <{}> in C++{} and later".format(includer, includee, version) 157 ) 158 159 160test_body_template = """ 161//===----------------------------------------------------------------------===// 162// 163// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 164// See https://llvm.org/LICENSE.txt for license information. 165// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 166// 167//===----------------------------------------------------------------------===// 168// 169// WARNING: This test was generated by {script_name} 170// and should not be edited manually. 171// 172// clang-format off 173{markup} 174// <{header}> 175 176// Test that <{header}> includes all the other headers it's supposed to. 177 178#include <{header}> 179#include "test_macros.h" 180 181{test_includers_symbol} 182{test_per_includee} 183""".strip() 184 185 186def produce_tests(): 187 for includer, includees in mandatory_inclusions.items(): 188 markup_tags = get_unsupported_line(includer) + lit_markup.get(includer, []) 189 test_body = test_body_template.format( 190 script_name=script_name, 191 header=includer, 192 markup=('\n' + '\n'.join('// ' + m for m in markup_tags) + '\n') if markup_tags else '', 193 test_includers_symbol=get_includer_symbol_test(includer), 194 test_per_includee='\n'.join(get_ifdef(includer, includee) for includee in includees), 195 ) 196 test_name = "{header}.inclusions.compile.pass.cpp".format(header=includer) 197 out_path = os.path.join(test_path, test_name) 198 with open(out_path, 'w', newline='\n') as f: 199 f.write(test_body + '\n') 200 201 202if __name__ == '__main__': 203 produce_tests() 204