1#!/usr/bin/env python 2 3import contextlib 4import glob 5import io 6import os 7import pathlib 8import re 9 10header_restrictions = { 11 "barrier": "!defined(_LIBCPP_HAS_NO_THREADS)", 12 "future": "!defined(_LIBCPP_HAS_NO_THREADS)", 13 "latch": "!defined(_LIBCPP_HAS_NO_THREADS)", 14 "mutex": "!defined(_LIBCPP_HAS_NO_THREADS)", 15 "semaphore": "!defined(_LIBCPP_HAS_NO_THREADS)", 16 "shared_mutex": "!defined(_LIBCPP_HAS_NO_THREADS)", 17 "stdatomic.h": "__cplusplus > 202002L && !defined(_LIBCPP_HAS_NO_THREADS)", 18 "thread": "!defined(_LIBCPP_HAS_NO_THREADS)", 19 20 "filesystem": "!defined(_LIBCPP_HAS_NO_FILESYSTEM_LIBRARY)", 21 22 # TODO LLVM17: simplify this to __cplusplus >= 202002L 23 "coroutine": "(defined(__cpp_impl_coroutine) && __cpp_impl_coroutine >= 201902L) || (defined(__cpp_coroutines) && __cpp_coroutines >= 201703L)", 24 25 "clocale": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 26 "codecvt": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 27 "fstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION) && !defined(_LIBCPP_HAS_NO_FSTREAM)", 28 "iomanip": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 29 "ios": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 30 "iostream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 31 "istream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 32 "locale.h": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 33 "locale": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 34 "ostream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 35 "regex": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 36 "sstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 37 "streambuf": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 38 "strstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)", 39 40 "wctype.h": "!defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)", 41 "cwctype": "!defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)", 42 "cwchar": "!defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)", 43 "wchar.h": "!defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)", 44 45 "experimental/algorithm": "__cplusplus >= 201103L", 46 "experimental/coroutine": "__cplusplus >= 202002L && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_COROUTINES)", 47 "experimental/deque": "__cplusplus >= 201103L", 48 "experimental/forward_list": "__cplusplus >= 201103L", 49 "experimental/functional": "__cplusplus >= 201103L", 50 "experimental/iterator": "__cplusplus >= 201103L", 51 "experimental/list": "__cplusplus >= 201103L", 52 "experimental/map": "__cplusplus >= 201103L", 53 "experimental/memory_resource": "__cplusplus >= 201103L", 54 "experimental/propagate_const": "__cplusplus >= 201103L", 55 "experimental/regex": "!defined(_LIBCPP_HAS_NO_LOCALIZATION) && __cplusplus >= 201103L", 56 "experimental/set": "__cplusplus >= 201103L", 57 "experimental/simd": "__cplusplus >= 201103L", 58 "experimental/span": "__cplusplus >= 201103L", 59 "experimental/string": "__cplusplus >= 201103L", 60 "experimental/type_traits": "__cplusplus >= 201103L", 61 "experimental/unordered_map": "__cplusplus >= 201103L", 62 "experimental/unordered_set": "__cplusplus >= 201103L", 63 "experimental/utility": "__cplusplus >= 201103L", 64 "experimental/vector": "__cplusplus >= 201103L", 65} 66 67private_headers_still_public_in_modules = [ 68 '__assert', '__bsd_locale_defaults.h', '__bsd_locale_fallbacks.h', '__config', 69 '__config_site.in', '__debug', '__hash_table', 70 '__threading_support', '__tree', '__undef_macros', '__verbose_abort' 71] 72 73def find_script(file): 74 """Finds the script used to generate a file inside the file itself. The script is delimited by 75 BEGIN-SCRIPT and END-SCRIPT markers. 76 """ 77 with open(file, 'r') as f: 78 content = f.read() 79 80 match = re.search(r'^BEGIN-SCRIPT$(.+)^END-SCRIPT$', content, flags=re.MULTILINE | re.DOTALL) 81 if not match: 82 raise RuntimeError("Was unable to find a script delimited with BEGIN-SCRIPT/END-SCRIPT markers in {}".format(test_file)) 83 return match.group(1) 84 85def execute_script(script, variables): 86 """Executes the provided Mako template with the given variables available during the 87 evaluation of the script, and returns the result. 88 """ 89 code = compile(script, 'fake-filename', 'exec') 90 output = io.StringIO() 91 with contextlib.redirect_stdout(output): 92 exec(code, variables) 93 output = output.getvalue() 94 return output 95 96def generate_new_file(file, new_content): 97 """Generates the new content of the file by inserting the new content in-between 98 two '// GENERATED-MARKER' markers located in the file. 99 """ 100 with open(file, 'r') as f: 101 old_content = f.read() 102 103 try: 104 before, begin_marker, _, end_marker, after = re.split(r'(// GENERATED-MARKER\n)', old_content, flags=re.MULTILINE | re.DOTALL) 105 except ValueError: 106 raise RuntimeError("Failed to split {} based on markers, please make sure the file has exactly two '// GENERATED-MARKER' occurrences".format(file)) 107 108 return before + begin_marker + new_content + end_marker + after 109 110def produce(test_file, variables): 111 script = find_script(test_file) 112 result = execute_script(script, variables) 113 new_content = generate_new_file(test_file, result) 114 with open(test_file, 'w', newline='\n') as f: 115 f.write(new_content) 116 117def is_header(file): 118 """Returns whether the given file is a header (i.e. not a directory or the modulemap file).""" 119 return not file.is_dir() and not file.name == 'module.modulemap.in' and file.name != 'libcxx.imp' 120 121 122def main(): 123 monorepo_root = pathlib.Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) 124 include = pathlib.Path(os.path.join(monorepo_root, 'libcxx', 'include')) 125 test = pathlib.Path(os.path.join(monorepo_root, 'libcxx', 'test')) 126 assert(monorepo_root.exists()) 127 128 toplevel_headers = sorted(str(p.relative_to(include)) for p in include.glob('[a-z]*') if is_header(p)) 129 experimental_headers = sorted(str(p.relative_to(include)) for p in include.glob('experimental/[a-z]*') if is_header(p)) 130 extended_headers = sorted(str(p.relative_to(include)) for p in include.glob('ext/[a-z]*') if is_header(p)) 131 public_headers = toplevel_headers + experimental_headers + extended_headers 132 private_headers = sorted(str(p.relative_to(include)) for p in include.rglob('*') if is_header(p) and str(p.relative_to(include)).startswith('__')) 133 variables = { 134 'toplevel_headers': toplevel_headers, 135 'experimental_headers': experimental_headers, 136 'extended_headers': extended_headers, 137 'public_headers': public_headers, 138 'private_headers': private_headers, 139 'header_restrictions': header_restrictions, 140 'private_headers_still_public_in_modules': private_headers_still_public_in_modules 141 } 142 143 produce(test.joinpath('libcxx/assertions/headers_declare_verbose_abort.sh.cpp'), variables) 144 produce(test.joinpath('libcxx/clang_tidy.sh.cpp'), variables) 145 produce(test.joinpath('libcxx/double_include.sh.cpp'), variables) 146 produce(test.joinpath('libcxx/min_max_macros.compile.pass.cpp'), variables) 147 produce(test.joinpath('libcxx/modules_include.sh.cpp'), variables) 148 produce(test.joinpath('libcxx/nasty_macros.compile.pass.cpp'), variables) 149 produce(test.joinpath('libcxx/no_assert_include.compile.pass.cpp'), variables) 150 produce(test.joinpath('libcxx/private_headers.verify.cpp'), variables) 151 produce(test.joinpath('libcxx/transitive_includes.sh.cpp'), variables) 152 153 154if __name__ == '__main__': 155 main() 156