This document provides a detailed exploration of how C++ standard library headers are used when building, testing, and using the LLVM libc++ library. It covers the different types of headers involved, the main header layouts used, and the importance of include paths and how they are constructed. It also dives into the specific include paths used in key scenarios on Linux and Apple platforms. The goal is to explain the key concepts and complexities around libc++ headers to support informed decision making about the library's header layout.
There are several types of headers involved in the libc++ library:
-
The primary headers: These include all of the non-generated headers that are part of the standard library, such as
<__config>
,<vector>
, andmodule.modulemap
. -
<__config_site>
: This is a configuration header generated by the build system, used to configure libc++ for the target platform. It contains key macro definitions. -
<__assertion_handler>
: This header is copied verbatim fromlibcxx/vendor/llvm/default_assertion_handler.in
unless overridden by the user viaLIBCXX_ASSERTION_HANDLER_FILE
during CMake configuration. -
<cxxabi.h>
(and<__cxxabi_config.h>
for libc++abi): These headers are copied from the ABI library chosen at configuration time, such as libc++abi, libsupc++/libstdc++, or libcxxrt.
This means that in the two-directory layout, the only differences in contents between the in-tree and build-tree primary include directorys is cxxabi.h
, and until #93333 lands, __assertion_handler
. Otherwise, they're functionally identical.
Libc++ supports two main layouts for the headers as copied into the build directory:
- Single-directory layout: All headers, including generated ones, are placed in a single directory,
<include-root>/c++/v1
. - Two-directory layout: Headers are split between
<include-root>/<triple>/c++/v1
for target-specific headers and<include-root>/c++/v1
for the rest.
The two-directory layout is used by default on Linux, while the single-directory layout is used on Apple platforms.
The include paths are critical for two main reasons:
- Libc++'s use of
#include_next
- Ensuring we "test as we ship to users"
Libc++ uses #include_next
to wrap C standard library headers to make adjustments for C++. For example, the libc++ <stddef.h>
uses #include_next
to include the C version and then adds the ::nullptr_t
type.
#include_next
tells the preprocessor to search for the specified header in the include paths, starting from the path after the one in which the current file was found. If the headers are not laid out correctly, this can lead to the wrong headers being included.
Here's an example of how #include_next
works:
// libc++ <inttypes.h>
#include_next <inttypes.h>
// Clang <inttypes.h>
#include_next <inttypes.h>
// System <inttypes.h>
...
If the include paths are not ordered correctly, with libc++ first, Clang second, and system headers last, the #include_next
chain will break.
When building and testing libc++, the include paths are constructed to mirror the installed header layout. The exact paths depend on the Clang driver used.
On Linux, the driver searches for c++/v1
in the following prefixes:
<clang-binary-path>/../include/
/usr/local/include
/usr/include
If found, it adds -internal-isystem <libcxx-include-prefix>/c++/v1
to the compile command. If a <triple>/c++/v1
directory exists, it is added before the main path.
On Apple, the driver looks in:
<clang-binary-path>/../include/c++/v1
<sysroot>/usr/include/c++/v1
It does not search for a target-specific directory.
When building libc++ on Linux, the command line contains:
-nostdinc++
-I <src-root>/libcxx/src
-I <src-root>/build/include/x86_64-unknown-linux-gnu/c++/v1
-I <src-root>/build/include/c++/v1
-I <src-root>/libcxxabi/include
the include paths are:
#include "..." search starts here:
#include <...> search starts here:
/home/eric/llvm-project/libcxx/src
/home/eric/llvm-project/build/include/x86_64-unknown-linux-gnu/c++/v1
/home/eric/llvm-project/build/include/c++/v1
/home/eric/llvm-project/libcxxabi/include
/usr/local/lib/clang/19/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
When running tests on Linux, the command line contains:
-nostdinc++
-I <src-root>/build/include/c++/v1
-I <src-root>/build/include/x86_64-unknown-linux-gnu/c++/v1
-I <src-root>/libcxx/test/support
the include paths are:
#include "..." search starts here:
#include <...> search starts here:
/home/eric/llvm-project/build/include/c++/v1
/home/eric/llvm-project/build/include/x86_64-unknown-linux-gnu/c++/v1
/home/eric/llvm-project/libcxx/test/support
/usr/local/lib/clang/19/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
For an installed libc++ on Linux, the include paths with -stdlib=libc++
are:
#include "..." search starts here:
#include <...> search starts here:
/usr/local/bin/../include/x86_64-unknown-linux-gnu/c++/v1
/usr/local/bin/../include/c++/v1
/usr/local/lib/clang/19/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
When building on Apple, the command line contains:
-nostdinc++
-I <src-root>/libcxx/src
-I <src-root>/build/include/c++/v1
-I <src-root>/libcxxabi/include
and the include paths are:
#include "..." search starts here:
#include <...> search starts here:
/Users/eric/llvm-project/libcxx/src
/Users/eric/llvm-project/build/include/c++/v1
/Users/eric/llvm-project/libcxxabi/include
/Library/Developer/CommandLineTools/usr/lib/clang/15.0.0/include
/Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/usr/include
/Library/Developer/CommandLineTools/usr/include
/Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/System/Library/Frameworks (framework directory)
When running tests on Apple, the command line contains:
-nostdinc++
-I <src-root>/build/include/c++/v1
-I <src-root>/build/include/c++/v1
-I <src-root>/libcxx/test/support
and the final include paths are:
#include "..." search starts here:
#include <...> search starts here:
/Users/eric/llvm-project/build/include/c++/v1
/Users/eric/llvm-project/libcxx/test/support
/Library/Developer/CommandLineTools/usr/lib/clang/15.0.0/include
/Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/usr/include
/Library/Developer/CommandLineTools/usr/include
/Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/System/Library/Frameworks (framework directory)
For an installed libc++ on Apple, the include paths with -stdlib=libc++
are:
#include "..." search starts here:
#include <...> search starts here:
/usr/local/include
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1
/Library/Developer/CommandLineTools/usr/lib/clang/15.0.0/include
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include
/Library/Developer/CommandLineTools/usr/include
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks (framework directory)
- Libc++ supports two main header layouts in the build directory: single-directory and two-directory.
- The include paths are critical due to libc++'s use of
#include_next
and the need to "test as you fly". - The exact include paths depend on the platform and Clang driver, but in general, libc++ headers must come first, followed by Clang, then system headers.
- On both Linux and Apple, the include paths used when building and testing libc++ are different than for an installed libc++.
- The single-directory layout is simpler and avoids some bugs that can occur with the two-directory layout.
- However, neither the single-directory nor two-directory build layouts exactly match the include paths of an installed libc++.
Libc++ should consider how to balance supporting development and testing with ensuring the installed header layout is properly validated. Improvements to the current system are likely possible to enhance simplicity and correctness.