diff --git a/libcxx/docs/UsingLibcxx.rst b/libcxx/docs/UsingLibcxx.rst --- a/libcxx/docs/UsingLibcxx.rst +++ b/libcxx/docs/UsingLibcxx.rst @@ -122,6 +122,21 @@ -ex "python register_libcxx_printer_loader()" \ +.. _include-what-you-use: + +include-what-you-use (IWYU) +=========================== + +libc++ provides an IWYU `mapping file `, +which drastically improves the accuracy of the tool when using libc++. To use the mapping file with +IWYU, you should run the tool like so: + +.. code-block:: bash + + $ include-what-you-use -Xiwyu /path/to/libcxx/include/libcxx.imp file.cpp + +If you would prefer to not use that flag, then you can replace ``/path/to/include-what-you-use/share/libcxx.imp``` +file with the libc++-provided ``libcxx.imp`` file. .. _assertions-mode: diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -779,6 +779,7 @@ istream iterator latch + libcxx.imp limits limits.h list diff --git a/libcxx/include/libcxx.imp b/libcxx/include/libcxx.imp new file mode 100644 --- /dev/null +++ b/libcxx/include/libcxx.imp @@ -0,0 +1,45 @@ +[ + { include: [ "<__bits>", "private", "", "public" ] }, + { include: [ "<__hash_table>", "private", "", "public" ] }, + { include: [ "<__hash_table>", "private", "", "public" ] }, + { include: [ "<__locale>", "private", "", "public" ] }, + { include: [ "<__node_handle>", "private", "", "public" ] }, + { include: [ "<__node_handle>", "private", "", "public" ] }, + { include: [ "<__node_handle>", "private", "", "public" ] }, + { include: [ "<__node_handle>", "private", "", "public" ] }, + { include: [ "<__split_buffer>", "private", "", "public" ] }, + { include: [ "<__split_buffer>", "private", "", "public" ] }, + { include: [ "<__std_stream>", "private", "", "public" ] }, + { include: [ "<__threading_support>", "private", "", "public" ] }, + { include: [ "<__threading_support>", "private", "", "public" ] }, + { include: [ "<__threading_support>", "private", "", "public" ] }, + { include: [ "<__threading_support>", "private", "", "public" ] }, + { include: [ "<__tree>", "private", "", "public" ] }, + { include: [ "<__tree>", "private", "", "public" ] }, + { include: [ "@<__algorithm/.*>", "private", "", "public" ] }, + { include: [ "@<__bit/.*>", "private", "", "public" ] }, + { include: [ "@<__charconv/.*>", "private", "", "public" ] }, + { include: [ "@<__chrono/.*>", "private", "", "public" ] }, + { include: [ "@<__compare/.*>", "private", "", "public" ] }, + { include: [ "@<__concepts/.*>", "private", "", "public" ] }, + { include: [ "@<__coroutine/.*>", "private", "", "public" ] }, + { include: [ "@<__debug_utils/.*>", "private", "", "public" ] }, + { include: [ "@<__filesystem/.*>", "private", "", "public" ] }, + { include: [ "@<__format/.*>", "private", "", "public" ] }, + { include: [ "@<__functional/.*>", "private", "", "public" ] }, + { include: [ "@<__fwd/.*>", "private", "", "public" ] }, + { include: [ "@<__ios/.*>", "private", "", "public" ] }, + { include: [ "@<__iterator/.*>", "private", "", "public" ] }, + { include: [ "@<__memory/.*>", "private", "", "public" ] }, + { include: [ "@<__memory_resource/.*>", "private", "", "public" ] }, + { include: [ "@<__numeric/.*>", "private", "", "public" ] }, + { include: [ "@<__random/.*>", "private", "", "public" ] }, + { include: [ "@<__ranges/.*>", "private", "", "public" ] }, + { include: [ "@<__string/.*>", "private", "", "public" ] }, + { include: [ "@<__support/.*>", "private", "", "public" ] }, + { include: [ "@<__thread/.*>", "private", "", "public" ] }, + { include: [ "@<__tuple/.*>", "private", "", "public" ] }, + { include: [ "@<__type_traits/.*>", "private", "", "public" ] }, + { include: [ "@<__utility/.*>", "private", "", "public" ] }, + { include: [ "@<__variant/.*>", "private", "", "public" ] }, +] diff --git a/libcxx/utils/CMakeLists.txt b/libcxx/utils/CMakeLists.txt --- a/libcxx/utils/CMakeLists.txt +++ b/libcxx/utils/CMakeLists.txt @@ -32,6 +32,12 @@ "${LIBCXX_SOURCE_DIR}/include/__format/escaped_output_table.h" COMMENT "Generate the escaped output header") +add_custom_target(libcxx-generate-iwyu-mapping + COMMAND + "${Python3_EXECUTABLE}" + "${LIBCXX_SOURCE_DIR}/utils/generate_iwyu_mapping.py" + COMMENT "Generate the mapping file for include-what-you-use") + add_custom_target(libcxx-generate-files DEPENDS libcxx-generate-public-header-transitive-inclusion-tests libcxx-generate-public-header-tests @@ -39,4 +45,5 @@ libcxx-generate-extended-grapheme-cluster-tables libcxx-generate-extended-grapheme-cluster-tests libcxx-generate-escaped-output-table + libcxx-generate-iwyu-mapping COMMENT "Create all the auto-generated files in libc++ and its tests.") diff --git a/libcxx/utils/generate_header_tests.py b/libcxx/utils/generate_header_tests.py --- a/libcxx/utils/generate_header_tests.py +++ b/libcxx/utils/generate_header_tests.py @@ -16,9 +16,7 @@ "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)", @@ -33,14 +31,13 @@ "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/coroutine": + "__cplusplus >= 201103L && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_COROUTINES)", "experimental/deque": "__cplusplus >= 201103L", "experimental/forward_list": "__cplusplus >= 201103L", "experimental/functional": "__cplusplus >= 201103L", @@ -49,7 +46,8 @@ "experimental/map": "__cplusplus >= 201103L", "experimental/memory_resource": "__cplusplus >= 201103L", "experimental/propagate_const": "__cplusplus >= 201103L", - "experimental/regex": "!defined(_LIBCPP_HAS_NO_LOCALIZATION) && __cplusplus >= 201103L", + "experimental/regex": + "!defined(_LIBCPP_HAS_NO_LOCALIZATION) && __cplusplus >= 201103L", "experimental/set": "__cplusplus >= 201103L", "experimental/simd": "__cplusplus >= 201103L", "experimental/span": "__cplusplus >= 201103L", @@ -62,11 +60,12 @@ } private_headers_still_public_in_modules = [ - '__assert', '__bsd_locale_defaults.h', '__bsd_locale_fallbacks.h', '__config', - '__config_site.in', '__debug', '__hash_table', + '__assert', '__bsd_locale_defaults.h', '__bsd_locale_fallbacks.h', + '__config', '__config_site.in', '__debug', '__hash_table', '__threading_support', '__tree', '__undef_macros', '__verbose_abort' ] + 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. @@ -74,11 +73,16 @@ with open(file, 'r') as f: content = f.read() - match = re.search(r'^BEGIN-SCRIPT$(.+)^END-SCRIPT$', content, flags=re.MULTILINE | re.DOTALL) + 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)) + 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. @@ -90,6 +94,7 @@ 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. @@ -98,12 +103,18 @@ old_content = f.read() try: - before, begin_marker, _, end_marker, after = re.split(r'(// GENERATED-MARKER\n)', old_content, flags=re.MULTILINE | re.DOTALL) + 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)) + 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) @@ -111,38 +122,62 @@ 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.in' + return not file.is_dir( + ) and file.name != 'module.modulemap.in' and file.name != 'libcxx.imp' + def main(): - monorepo_root = pathlib.Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + 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('__')) + 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 + '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_verbose_abort.sh.cpp'), variables) + produce( + test.joinpath( + 'libcxx/assertions/headers_declare_verbose_abort.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/no_assert_include.compile.pass.cpp'), + variables) produce(test.joinpath('libcxx/private_headers.verify.cpp'), variables) produce(test.joinpath('libcxx/transitive_includes.sh.cpp'), variables) diff --git a/libcxx/utils/generate_iwyu_mapping.py b/libcxx/utils/generate_iwyu_mapping.py new file mode 100644 --- /dev/null +++ b/libcxx/utils/generate_iwyu_mapping.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python + +import os, pathlib, sys + +def generate(private, public): + return f'{{ include: [ "{private}", "private", "<{public}>", "public" ] }}' + + +def panic(file): + print(f'========== {__file__} error ==========', file=sys.stderr) + print(f'\tFile \'{file}\' is a top-level detail header without a mapping', file=sys.stderr) + sys.exit(1) + + +def generate_map(include): + detail_files = [] + detail_directories = [] + c_headers = [] + + for i in include.iterdir(): + if i.is_dir() and i.name.startswith('__'): + detail_directories.append(f'{i.name}') + continue + + if i.name.startswith('__'): + detail_files.append(i.name) + continue + + if i.name.endswith('.h'): + c_headers.append(i.name) + + result = [] + for i in detail_directories: + result.append(f'{generate(f"@<{i}/.*>", i[2:])},') + + for i in detail_files: + public = [] + match i: + case '__assert': continue + case '__availability': continue + case '__bit_reference': continue + case '__bits': public = ['bits'] + case '__bsd_locale_defaults.h': continue + case '__bsd_locale_fallbacks.h': continue + case '__config_site.in': continue + case '__config': continue + case '__debug': continue + case '__errc': continue + case '__hash_table': public = ['unordered_map', 'unordered_set'] + case '__locale': public = ['locale'] + case '__mbstate_t.h': continue + case '__mutex_base': continue + case '__node_handle': public = ['map', 'set', 'unordered_map', 'unordered_set'] + case '__split_buffer': public = ['deque', 'vector'] + case '__std_stream': public = ['iostream'] + case '__threading_support': public = ['atomic', 'mutex', 'semaphore', 'thread'] + case '__tree': public = ['map', 'set'] + case '__undef_macros': continue + case '__verbose_abort': continue + case _: panic() + + for p in public: + result.append(f'{generate(f"<{i}>", p)},') + + result.sort() + return result + +def main(): + monorepo_root = pathlib.Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + assert(monorepo_root.exists()) + include = pathlib.Path(os.path.join(monorepo_root, 'libcxx', 'include')) + + mapping = generate_map(include) + data = '[\n ' + '\n '.join(mapping) + '\n]\n' + with open(f'{include}/libcxx.imp', 'w') as f: + f.write(data) + + +if __name__ == '__main__': + main()