aboutsummaryrefslogtreecommitdiff
path: root/libcxx/utils/generate_header_tests.py
blob: fa9ab3cd7dcaae53854e0cc0d0ff5382ee941010 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/env python

import contextlib
import glob
import io
import os
import pathlib
import re

header_restrictions = {
    "barrier": "!defined(_LIBCPP_HAS_NO_THREADS)",
    "future": "!defined(_LIBCPP_HAS_NO_THREADS)",
    "latch": "!defined(_LIBCPP_HAS_NO_THREADS)",
    "mutex": "!defined(_LIBCPP_HAS_NO_THREADS)",
    "semaphore": "!defined(_LIBCPP_HAS_NO_THREADS)",
    "shared_mutex": "!defined(_LIBCPP_HAS_NO_THREADS)",
    "stdatomic.h": "__cplusplus > 202002L && !defined(_LIBCPP_HAS_NO_THREADS)",
    "thread": "!defined(_LIBCPP_HAS_NO_THREADS)",

    "filesystem": "!defined(_LIBCPP_HAS_NO_FILESYSTEM_LIBRARY)",

    "clocale": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "codecvt": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "fstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "iomanip": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "ios": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "iostream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "istream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "locale.h": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "locale": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "ostream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "regex": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "sstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "streambuf": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
    "strstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",

    "wctype.h": "!defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)",
    "cwctype": "!defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)",
    "cwchar": "!defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)",
    "wchar.h": "!defined(_LIBCPP_HAS_NO_WIDE_CHARACTERS)",

    "experimental/algorithm": "__cplusplus >= 201103L",
    "experimental/coroutine": "__cplusplus >= 201103L && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_COROUTINES)",
    "experimental/deque": "__cplusplus >= 201103L",
    "experimental/forward_list": "__cplusplus >= 201103L",
    "experimental/functional": "__cplusplus >= 201103L",
    "experimental/iterator": "__cplusplus >= 201103L",
    "experimental/list": "__cplusplus >= 201103L",
    "experimental/map": "__cplusplus >= 201103L",
    "experimental/memory_resource": "__cplusplus >= 201103L",
    "experimental/propagate_const": "__cplusplus >= 201103L",
    "experimental/regex": "!defined(_LIBCPP_HAS_NO_LOCALIZATION) && __cplusplus >= 201103L",
    "experimental/set": "__cplusplus >= 201103L",
    "experimental/simd": "__cplusplus >= 201103L",
    "experimental/span": "__cplusplus >= 201103L",
    "experimental/string": "__cplusplus >= 201103L",
    "experimental/type_traits": "__cplusplus >= 201103L",
    "experimental/unordered_map": "__cplusplus >= 201103L",
    "experimental/unordered_set": "__cplusplus >= 201103L",
    "experimental/utility": "__cplusplus >= 201103L",
    "experimental/vector": "__cplusplus >= 201103L",
}

private_headers_still_public_in_modules = [
    '__assert', '__bsd_locale_defaults.h', '__bsd_locale_fallbacks.h', '__config',
    '__config_site.in', '__debug', '__hash_table',
    '__threading_support', '__tree', '__undef_macros'
]

def find_script(file):
    """Finds the script used to generate a file inside the file itself. The script is delimited by
       BEGIN-SCRIPT and END-SCRIPT markers.
    """
    with open(file, 'r') as f:
        content = f.read()

    match = re.search(r'^BEGIN-SCRIPT$(.+)^END-SCRIPT$', content, flags=re.MULTILINE | re.DOTALL)
    if not match:
        raise RuntimeError("Was unable to find a script delimited with BEGIN-SCRIPT/END-SCRIPT markers in {}".format(test_file))
    return match.group(1)

def execute_script(script, variables):
    """Executes the provided Mako template with the given variables available during the
       evaluation of the script, and returns the result.
    """
    code = compile(script, 'fake-filename', 'exec')
    output = io.StringIO()
    with contextlib.redirect_stdout(output):
        exec(code, variables)
        output = output.getvalue()
    return output

def generate_new_file(file, new_content):
    """Generates the new content of the file by inserting the new content in-between
       two '// GENERATED-MARKER' markers located in the file.
    """
    with open(file, 'r') as f:
        old_content = f.read()

    try:
        before, begin_marker, _, end_marker, after = re.split(r'(// GENERATED-MARKER\n)', old_content, flags=re.MULTILINE | re.DOTALL)
    except ValueError:
        raise RuntimeError("Failed to split {} based on markers, please make sure the file has exactly two '// GENERATED-MARKER' occurrences".format(file))

    return before + begin_marker + new_content + end_marker + after

def produce(test_file, variables):
    script = find_script(test_file)
    result = execute_script(script, variables)
    new_content = generate_new_file(test_file, result)
    with open(test_file, 'w', newline='\n') as f:
        f.write(new_content)

def is_header(file):
    """Returns whether the given file is a header (i.e. not a directory or the modulemap file)."""
    return not file.is_dir() and not file.name == 'module.modulemap'

def main():
    monorepo_root = pathlib.Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
    include = pathlib.Path(os.path.join(monorepo_root, 'libcxx', 'include'))
    test = pathlib.Path(os.path.join(monorepo_root, 'libcxx', 'test'))
    assert(monorepo_root.exists())

    toplevel_headers     = sorted(str(p.relative_to(include)) for p in include.glob('[a-z]*') if is_header(p))
    experimental_headers = sorted(str(p.relative_to(include)) for p in include.glob('experimental/[a-z]*') if is_header(p))
    extended_headers     = sorted(str(p.relative_to(include)) for p in include.glob('ext/[a-z]*') if is_header(p))
    public_headers       = toplevel_headers + experimental_headers + extended_headers
    private_headers      = sorted(str(p.relative_to(include)) for p in include.rglob('*') if is_header(p) and str(p.relative_to(include)).startswith('__'))
    variables = {
        'toplevel_headers': toplevel_headers,
        'experimental_headers': experimental_headers,
        'extended_headers': extended_headers,
        'public_headers': public_headers,
        'private_headers': private_headers,
        'header_restrictions': header_restrictions,
        'private_headers_still_public_in_modules': private_headers_still_public_in_modules
    }

    produce(test.joinpath('libcxx/assertions/headers_declare_assertion_handler.sh.cpp'), variables)
    produce(test.joinpath('libcxx/clang_tidy.sh.cpp'), variables)
    produce(test.joinpath('libcxx/double_include.sh.cpp'), variables)
    produce(test.joinpath('libcxx/min_max_macros.compile.pass.cpp'), variables)
    produce(test.joinpath('libcxx/modules_include.sh.cpp'), variables)
    produce(test.joinpath('libcxx/nasty_macros.compile.pass.cpp'), variables)
    produce(test.joinpath('libcxx/no_assert_include.compile.pass.cpp'), variables)
    produce(test.joinpath('libcxx/private_headers.verify.cpp'), variables)


if __name__ == '__main__':
    main()