xref: /openbsd-src/gnu/llvm/libcxx/utils/generate_header_tests.py (revision 4bdff4bed0e3d54e55670334c7d0077db4170f86)
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