1#!/usr/bin/env python
2
3import glob
4import os
5import posixpath
6import re
7
8
9def get_libcxx_paths():
10    utils_path = os.path.dirname(os.path.abspath(__file__))
11    script_name = os.path.basename(__file__)
12    assert os.path.exists(utils_path)
13    src_root = os.path.dirname(utils_path)
14    include_path = os.path.join(src_root, 'include')
15    assert os.path.exists(include_path)
16    libcxx_test_path = os.path.join(src_root, 'test', 'libcxx')
17    assert os.path.exists(libcxx_test_path)
18    return script_name, src_root, include_path, libcxx_test_path
19
20
21script_name, source_root, include_path, libcxx_test_path = get_libcxx_paths()
22
23header_markup = {
24    "barrier": ["ifndef _LIBCPP_HAS_NO_THREADS"],
25    "future": ["ifndef _LIBCPP_HAS_NO_THREADS"],
26    "latch": ["ifndef _LIBCPP_HAS_NO_THREADS"],
27    "mutex": ["ifndef _LIBCPP_HAS_NO_THREADS"],
28    "semaphore": ["ifndef _LIBCPP_HAS_NO_THREADS"],
29    "shared_mutex": ["ifndef _LIBCPP_HAS_NO_THREADS"],
30    "thread": ["ifndef _LIBCPP_HAS_NO_THREADS"],
31
32    "experimental/filesystem": ["ifndef _LIBCPP_HAS_NO_FILESYSTEM_LIBRARY"],
33    "filesystem": ["ifndef _LIBCPP_HAS_NO_FILESYSTEM_LIBRARY"],
34    "format": ["ifndef _LIBCPP_HAS_NO_INCOMPLETE_FORMAT"],
35
36    "clocale": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
37    "codecvt": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
38    "fstream": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
39    "iomanip": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
40    "ios": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
41    "iostream": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
42    "istream": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
43    "locale.h": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
44    "locale": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
45    "ostream": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
46    "ranges": ["ifndef _LIBCPP_HAS_NO_INCOMPLETE_RANGES"],
47    "regex": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
48    "sstream": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
49    "streambuf": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
50    "strstream": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
51
52    "wctype.h": ["ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS"],
53    "cwctype": ["ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS"],
54    "cwchar": ["ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS"],
55    "wchar.h": ["ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS"],
56
57    "experimental/coroutine": ["ifndef _LIBCPP_HAS_NO_EXPERIMENTAL_COROUTINES"],
58    "coroutine": ["ifndef _LIBCPP_HAS_NO_CXX20_COROUTINES"],
59    "experimental/regex": ["ifndef _LIBCPP_HAS_NO_LOCALIZATION"],
60}
61
62allowed_extensions = ['', '.h']
63indent_width = 4
64
65
66begin_pattern = """\
67////////////////////////////////////////////////////////////////////////////////
68// BEGIN-GENERATED-HEADERS
69////////////////////////////////////////////////////////////////////////////////
70"""
71
72warning_note = """\
73// WARNING: This test was generated by {script_name}
74// and should not be edited manually.
75
76""".format(script_name=script_name)
77
78end_pattern = """\
79////////////////////////////////////////////////////////////////////////////////
80// END-GENERATED-HEADERS
81////////////////////////////////////////////////////////////////////////////////
82"""
83
84generated_part_pattern = re.compile(re.escape(begin_pattern) + ".*" + re.escape(end_pattern),
85                                    re.MULTILINE | re.DOTALL)
86
87headers_template = """\
88// Top level headers
89{top_level_headers}
90
91// experimental headers
92#if __cplusplus >= 201103L
93{experimental_headers}
94#endif // __cplusplus >= 201103L
95
96// extended headers
97{extended_headers}
98"""
99
100
101def should_keep_header(p, exclusions=None):
102    if os.path.isdir(p):
103        return False
104
105    if exclusions:
106        relpath = os.path.relpath(p, include_path)
107        relpath = posixpath.join(*os.path.split(relpath))
108        if relpath in exclusions:
109            return False
110
111    return os.path.splitext(p)[1] in allowed_extensions
112
113
114def produce_include(relpath, indent_level, post_include=None):
115    relpath = posixpath.join(*os.path.split(relpath))
116    template = "{preamble}#{indentation}include <{include}>{post_include}{postamble}"
117
118    base_indentation = ' '*(indent_width * indent_level)
119    next_indentation = base_indentation + ' '*(indent_width)
120    post_include = "\n{}".format(post_include) if post_include else ''
121
122    markup = header_markup.get(relpath, None)
123    if markup:
124        preamble = '#{indentation}{directive}\n'.format(
125            directive=markup[0],
126            indentation=base_indentation,
127        )
128        postamble = '\n#{indentation}endif'.format(
129            indentation=base_indentation,
130        )
131        indentation = next_indentation
132    else:
133        preamble = ''
134        postamble = ''
135        indentation = base_indentation
136
137    return template.format(
138        include=relpath,
139        post_include=post_include,
140        preamble=preamble,
141        postamble=postamble,
142        indentation=indentation,
143    )
144
145
146def produce_headers(path_parts, indent_level, post_include=None, exclusions=None):
147    pattern = os.path.join(*path_parts, '[a-z]*')
148
149    files = sorted(glob.glob(pattern, recursive=False))
150
151    include_headers = [
152        produce_include(os.path.relpath(p, include_path),
153                        indent_level, post_include=post_include)
154        for p in files
155        if should_keep_header(p, exclusions)
156    ]
157
158    return '\n'.join(include_headers)
159
160
161def produce_top_level_headers(post_include=None, exclusions=None):
162    return produce_headers([include_path], 0, post_include=post_include, exclusions=exclusions)
163
164
165def produce_experimental_headers(post_include=None, exclusions=None):
166    return produce_headers([include_path, 'experimental'], 1, post_include=post_include, exclusions=exclusions)
167
168
169def produce_extended_headers(post_include=None, exclusions=None):
170    return produce_headers([include_path, 'ext'], 0, post_include=post_include, exclusions=exclusions)
171
172
173def replace_generated_headers(test_path, test_str):
174    with open(test_path, 'r') as f:
175        content = f.read()
176
177    preamble = begin_pattern + '\n// clang-format off\n\n' + warning_note
178    postamble = '\n// clang-format on\n\n' + end_pattern
179    content = generated_part_pattern.sub(
180        preamble + test_str + postamble, content)
181
182    with open(test_path, 'w', newline='\n') as f:
183        f.write(content)
184
185
186def produce_test(test_filename, exclusions=None, post_include=None):
187    test_str = headers_template.format(
188        top_level_headers=produce_top_level_headers(
189            post_include=post_include,
190            exclusions=exclusions,
191        ),
192        experimental_headers=produce_experimental_headers(
193            post_include=post_include,
194        ),
195        extended_headers=produce_extended_headers(
196            post_include=post_include,
197        ),
198    )
199
200    replace_generated_headers(os.path.join(
201        libcxx_test_path, test_filename), test_str)
202
203
204def main():
205    produce_test('double_include.sh.cpp')
206    produce_test('min_max_macros.compile.pass.cpp', post_include='TEST_MACROS();')
207    produce_test('nasty_macros.compile.pass.cpp')
208    produce_test('no_assert_include.compile.pass.cpp', exclusions=['cassert'])
209
210
211if __name__ == '__main__':
212    main()
213