summaryrefslogtreecommitdiff
path: root/libcxx/utils/generate_private_header_tests.py
diff options
context:
space:
mode:
authorChristopher Di Bella <cjdb@google.com>2022-02-25 18:59:32 +0000
committerChristopher Di Bella <cjdb@google.com>2022-02-26 09:00:25 +0000
commit5aaefa510ef055e8f044ca89e352d4313f3aba49 (patch)
tree802d47ed42f359ebc4ff566f7d4859de94bd3651 /libcxx/utils/generate_private_header_tests.py
parent274ec425dcc3e3f637dd006c5e9ae33bd0e2e917 (diff)
downloadllvm-5aaefa510ef055e8f044ca89e352d4313f3aba49.tar.gz
[libcxx][modules] protects users from relying on detail headers
libc++ has started splicing standard library headers into much more fine-grained content for maintainability. It's very likely that outdated and naive tooling (some of which is outside of LLVM's scope) will suggest users include things such as <__ranges/access.h> instead of <ranges>, and Hyrum's law suggests that users will eventually begin to rely on this without the help of tooling. As such, this commit intends to protect users from themselves, by making it a hard error for anyone outside of the standard library to include libc++ detail headers. Differential Revision: https://reviews.llvm.org/D106124
Diffstat (limited to 'libcxx/utils/generate_private_header_tests.py')
-rwxr-xr-xlibcxx/utils/generate_private_header_tests.py71
1 files changed, 66 insertions, 5 deletions
diff --git a/libcxx/utils/generate_private_header_tests.py b/libcxx/utils/generate_private_header_tests.py
index 810657ea4f41..eef03439f74f 100755
--- a/libcxx/utils/generate_private_header_tests.py
+++ b/libcxx/utils/generate_private_header_tests.py
@@ -25,8 +25,33 @@ def get_libcxx_paths():
script_name, include_path, detail_header_test_root = get_libcxx_paths()
-def generate_test(header):
- return f'''
+def generate_header_test(header, include_instead):
+ return f'''// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: modules-build
+// UNSUPPORTED: clang-11, clang-12, clang-13
+// UNSUPPORTED: apple-clang-11, apple-clang-12, apple-clang-13
+// UNSUPPORTED: gcc-11
+// UNSUPPORTED: libcpp-has-no-localization, libcpp-has-no-threads
+// ADDITIONAL_COMPILE_FLAGS: -U_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER
+
+// WARNING: This test was generated by '{script_name}'
+// and should not be edited manually.
+
+#include <{header}>
+// expected-error@-1 {{{{header '<{header}>' is an implementation detail; #include {include_instead} instead}}}}
+'''
+
+
+def generate_module_test(header):
+ return f'''// -*- C++ -*-
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -42,7 +67,7 @@ def generate_test(header):
// expected-error@*:* {{{{use of private header from outside its module: '{header}'}}}}
#include <{header}>
-'''[1:]
+'''
def relative_path(path):
@@ -58,6 +83,35 @@ def is_still_public(path):
]
+def find_header_name(header, directory):
+ """Returns part of the diagnostic for `#pragma clang include_instead`. This
+ usually matches the subdirectory the header lives in (e.g. a header in
+ `__algorithm` will return "'<algorithm>'"), but some headers are
+ special-cased.
+ """
+
+ # Most of the special-cased headers are in the top-level include directory
+ # (and don't have a point of reference for us to hook on to), but any
+ # sub-level header that is exported by multiple top-level headers (e.g.
+ # __compare/compare_three_way.h) is also included in this module map, as the
+ # diagnostic needs to include more than our simple heuristic.
+ header_map = {
+ 'bit_reference': "either '<bitset>' or '<vector>'",
+ 'bits': "one of {'<algorithm>', '<bit>', '<bitset>', '<numeric>', '<random>', '<unordered_map>', '<unordered_set>', '<vector>'}",
+ 'hash_table': "either '<unordered_map>' or '<unordered_set>'",
+ 'locale': "'<locale>'",
+ 'mutex_base': "either '<mutex>' or '<shared_mutex>'",
+ 'node_handle': "one of {'<map>', '<set>', '<unordered_map>', '<unordered_set>'}",
+ 'split_buffer': "either '<vector>' or '<deque>'",
+ 'std_stream': "'<streambuf>'",
+ 'string': "'<string>'",
+ 'threading_support': "one of {'<atomic>', '<mutex>', '<semaphore>', '<thread>'}",
+ 'tree': "either '<map>' or '<set>'",
+ 'tuple': "either '<tuple>' or '<utility>'",
+ }
+ return header_map[header] if header in header_map else f"'<{directory[:-1]}>'"
+
+
def main():
paths = [
relative_path(p) for p in Path(include_path).rglob('*')
@@ -68,11 +122,18 @@ def main():
path_with_subdir = re.search(r'__(\w+)/(\w+)', path)
directory = path_with_subdir.group(1) + '/' if path_with_subdir else ""
file = path_with_subdir.group(2) if path_with_subdir else path[2:]
- path_to_write = f'{detail_header_test_root}/{directory}{file}.module.verify.cpp'
Path(f'{detail_header_test_root}/{directory}').mkdir(exist_ok=True)
assert os.path.exists(f'{detail_header_test_root}/{directory}')
+
+ path_to_write = f'{detail_header_test_root}/{directory}{file}.header.verify.cpp'
+ include_instead = find_header_name(file, directory)
+ if include_instead != "'<>'":
+ with open(path_to_write, 'w') as f:
+ f.write(generate_header_test(path, include_instead))
+
+ path_to_write = f'{detail_header_test_root}/{directory}{file}.module.verify.cpp'
with open(path_to_write, 'w') as f:
- f.write(generate_test(path))
+ f.write(generate_module_test(path))
if __name__ == '__main__':