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 "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_view": ["compare"], 50 "string": ["compare", "initializer_list"], 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 "cuchar": "11", 71 "expected": "23", 72 "filesystem": "17", 73 "initializer_list": "11", 74 "optional": "17", 75 "ranges": "20", 76 "string_view": "17", 77 "syncstream": "20", 78 "system_error": "11", 79 "thread": "11", 80 "tuple": "11", 81 "uchar.h": "11", 82 "unordered_map": "11", 83 "unordered_set": "11", 84 "variant": "17", 85} 86 87assert all(v == sorted(v) for k, v in mandatory_inclusions.items()) 88 89# Map from each header to the Lit annotations that should be used for 90# tests that include that header. 91# 92# For example, when threads are not supported, any test that includes 93# <thread> should be marked as UNSUPPORTED, because including <thread> 94# is a hard error in that case. 95lit_markup = { 96 "barrier": ["UNSUPPORTED: no-threads"], 97 "filesystem": ["UNSUPPORTED: no-filesystem"], 98 "format": ["UNSUPPORTED: libcpp-has-no-incomplete-format"], 99 "iomanip": ["UNSUPPORTED: no-localization"], 100 "ios": ["UNSUPPORTED: no-localization"], 101 "iostream": ["UNSUPPORTED: no-localization"], 102 "istream": ["UNSUPPORTED: no-localization"], 103 "latch": ["UNSUPPORTED: no-threads"], 104 "locale": ["UNSUPPORTED: no-localization"], 105 "mutex": ["UNSUPPORTED: no-threads"], 106 "ostream": ["UNSUPPORTED: no-localization"], 107 "regex": ["UNSUPPORTED: no-localization"], 108 "semaphore": ["UNSUPPORTED: no-threads"], 109 "shared_mutex": ["UNSUPPORTED: no-threads"], 110 "thread": ["UNSUPPORTED: no-threads"] 111} 112 113 114def get_std_ver_test(includee): 115 v = new_in_version.get(includee, "03") 116 if v == "03": 117 return '' 118 versions = ["03", "11", "14", "17", "20"] 119 return 'TEST_STD_VER > {} && '.format(max(i for i in versions if i < v)) 120 121 122def get_unsupported_line(includee): 123 v = new_in_version.get(includee, "03") 124 return { 125 "03": [], 126 "11": ['UNSUPPORTED: c++03'], 127 "14": ['UNSUPPORTED: c++03, c++11'], 128 "17": ['UNSUPPORTED: c++03, c++11, c++14'], 129 "20": ['UNSUPPORTED: c++03, c++11, c++14, c++17'], 130 "2b": ['UNSUPPORTED: c++03, c++11, c++14, c++17, c++20'], 131 }[v] 132 133 134def get_libcpp_header_symbol(header_name): 135 return '_LIBCPP_' + header_name.upper().replace('.', '_') 136 137 138def get_includer_symbol_test(includer): 139 symbol = get_libcpp_header_symbol(includer) 140 return """ 141#if !defined({symbol}) 142 # error "{message}" 143#endif 144 """.strip().format( 145 symbol=symbol, 146 message="<{}> was expected to define {}".format(includer, symbol), 147 ) 148 149 150def get_ifdef(includer, includee): 151 version = max(new_in_version.get(h, "03") for h in [includer, includee]) 152 symbol = get_libcpp_header_symbol(includee) 153 return """ 154#if {includee_test}!defined({symbol}) 155 # error "{message}" 156#endif 157 """.strip().format( 158 includee_test=get_std_ver_test(includee), 159 symbol=symbol, 160 message="<{}> should include <{}> in C++{} and later".format(includer, includee, version) 161 ) 162 163 164test_body_template = """ 165//===----------------------------------------------------------------------===// 166// 167// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 168// See https://llvm.org/LICENSE.txt for license information. 169// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 170// 171//===----------------------------------------------------------------------===// 172// 173// WARNING: This test was generated by {script_name} 174// and should not be edited manually. 175// 176// clang-format off 177{markup} 178// <{header}> 179 180// Test that <{header}> includes all the other headers it's supposed to. 181 182#include <{header}> 183#include "test_macros.h" 184 185{test_includers_symbol} 186{test_per_includee} 187""".strip() 188 189 190def produce_tests(): 191 for includer, includees in mandatory_inclusions.items(): 192 markup_tags = get_unsupported_line(includer) + lit_markup.get(includer, []) 193 test_body = test_body_template.format( 194 script_name=script_name, 195 header=includer, 196 markup=('\n' + '\n'.join('// ' + m for m in markup_tags) + '\n') if markup_tags else '', 197 test_includers_symbol=get_includer_symbol_test(includer), 198 test_per_includee='\n'.join(get_ifdef(includer, includee) for includee in includees), 199 ) 200 test_name = "{header}.inclusions.compile.pass.cpp".format(header=includer) 201 out_path = os.path.join(test_path, test_name) 202 with open(out_path, 'w', newline='\n') as f: 203 f.write(test_body + '\n') 204 205 206if __name__ == '__main__': 207 produce_tests() 208