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