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    "wctype.h": ["ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS"],
54    "cwctype": ["ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS"],
55    "cwchar": ["ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS"],
56    "wchar.h": ["ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS"],
57
58    "experimental/coroutine": ["if defined(__cpp_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 = "{preambule}#{indentation}include <{include}>{post_include}{postambule}"
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        preambule = '#{indentation}{directive}\n'.format(
125            directive=markup[0],
126            indentation=base_indentation,
127        )
128        postambule = '\n#{indentation}endif'.format(
129            indentation=base_indentation,
130        )
131        indentation = next_indentation
132    else:
133        preambule = ''
134        postambule = ''
135        indentation = base_indentation
136
137    return template.format(
138        include=relpath,
139        post_include=post_include,
140        preambule=preambule,
141        postambule=postambule,
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    preambule = begin_pattern + '\n// clang-format off\n\n' + warning_note
178    postambule = '\n// clang-format on\n\n' + end_pattern
179    content = generated_part_pattern.sub(
180        preambule + test_str + postambule, 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',
207                 post_include='TEST_MACROS();')
208    produce_test('no_assert_include.compile.pass.cpp',
209                 exclusions=['cassert'])
210
211
212if __name__ == '__main__':
213    main()
214