In recent years there has been disagreement between myself and other contributors about how to handle the in-source version of the libc++ headers headers (or “in-tree headers”).
The disagreement boils down to this:
Should the in-tree headers be usable for building, testing, or otherwise developing the library?
Or said another way
should we enforce the out-of-tree headers as the only valid way to consume the library?
The disagreement has prevented changes that support using in-tree headers in any capacity.
Personally, this has made it harder for to work on the library, since my IDE and other tooling are unable to cope or even understand the in-tree headers I need to edit.
If you’re familiar with the details of the build process, skip ahead to “The Debate” section. Otherwise, background is provided below.
Background
=========
Summary
Currently, when we build, we copy all of the headers into a separate directory inside the build directory used to store build artifacts (the “generated include directory”). That copy of the headers is then used to build and test the library.
The headers are copied verbatim from the source tree to the build directory, except for two headers:
__config_site
, which is generated by the build fromlibcxx/include/__config_site.in
. The generated header contains macro definitions that are used to customize the behavior of libc++ depending on how the build was configured.__assertion_handler
: which is copied verbatim fromlibcxx/vendor/llvm/default_assertion_handler.in
.
Libc++ currently supports two layouts of headers in the build directory. One where all of the headers, including the generated __config_site
are in the same folder, and another where the __config_site
and other target specific headers are placed in a separate directory “target include directory”. Apple does not support this configuration.
Additionally, the <cxxabi.h>
file is copied into either the generated include directory or
the target include directory.
In this latter configuration, the files present in the “generated include directory” are the exact same as the headers present in the “in-tree headers”, With the exception of the cxxabi.h
file, which comes from the ABI library, and the __assertion_handler
file, which we copy from elsewhere in the source tree.[1][2]
The default Linux runtimes build uses the “target include directory”.
The default Apple build does not.
The defacto build and test setup uses the copies of the headers inside the build directory. With minimal modification, roughly ~5 lines of changes, the in-tree headers can be used in the same manner.
Notes
[1] With the notable addition of <cxxabi.h>
from whichever ABI library was chosen at configuration time. Though, that header likely belongs in the “target include directory” when present.
[2] I’ve created a patch to simplify how the __assertion_handler
default implementation is handled. The patch moves the default implementation into the source tree Simplify the __assertion_handler build logic. Be friendly to IDEs. by EricWF · Pull Request #93333 · llvm/llvm-project · GitHub
A separate 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.
The Debate
==========
To be super cheesy about this, let’s get all 8th grade debate class:
Be it resolved that: libc++ should maintain support for building, testing, and developing the library using the in-tree C++ headers, in addition to supporting builds using the copied headers in the generated build directory.
Arguments For
=============
-
Supporting in-tree headers simplifies development by allowing IDE’s and other developer tools to properly parse, highlight, and analyze the libc++ source code. Many tools stop working when encountering missing headers, which is currently an issue with the in-tree layout.
-
The in-tree headers are the actual files that developers read, edit, and work with on a daily basis. Ensuring they are valid and usable fosters a better development workflow and catches potential issues earlier.
-
Diagnostics from building the source or running tests point to the generated header copies in the build directory, making it easy for developers to accidentally edit the wrong files which then get overwritten on the next build. Referencing the in-tree headers avoids this confusion.
-
Only a single header is actually “generated” (
__config_site
), while the rest are copied verbatim from the in-tree headers. On Linux the generated__config_site
is output in a separate directory. With minimal modifications, roughly 5 lines of changes to CMake, the in-tree headers can be made to work for building and testing. -
Libc++ has a history of supporting in-tree builds, and this setup worked well for years. Continuing to allow this configuration, in addition to the generated build directory layout, provides flexibility without harming the library.
-
Neither the in-tree layout nor the generated build directory layout exactly match the final installed header layout that users will consume. If validating the installed layout is the priority, libc++ should develop a dedicated testing configuration for that purpose, rather than relying on the build directory layout as an imperfect proxy.
Arguments Against
================
-
The validity and correctness of libc++'s headers are currently dependent on the build system, due to generated files like
__config_site
and__assertion_handler
. Presenting the in-tree headers as a valid, standalone layout risks causing confusion due to subtle differences from the generated build. -
Enforcing a clear separation between the in-tree headers and the generated install directory helps avoid issues stemming from any divergences or invalid assumptions. Developers should be steered towards treating the generated headers as the proper source of truth.
-
Allowing too much flexibility in the supported header layouts risks adding unnecessary variance to the development and testing process. Libc++ should aim to standardize on a single “blessed” layout that is validated by CI and the test suite.
-
Making the in-tree headers independently valid, in addition to supporting the generated headers, imposes an added maintenance burden on the project. The more configurations supported, the harder it is to make changes and enforce correctness.
-
Even if the in-tree and generated headers differ from the final installed layout, keeping them as similar as possible helps catch potential bugs or issues that could arise from the installation process. The generated headers are a useful intermediate step.
Suggested Resolution
==================
After considering the arguments on both sides, my recommendation would be for libc++ to continue supporting both the in-tree and generated build directory header layouts in the near-term, while working towards a longer-term solution.
In the short-term, officially sanctioning and documenting the in-tree layout as a valid configuration would ease development friction and assist developers relying on IDE tooling. This would be a temporary measure while the project focuses on developing a more robust testing setup for the installed headers. The historical precedent and relative ease of making the in-tree layout work suggests this would not be an undue maintenance burden.
However, I believe the libc++ project should prioritize developing a dedicated testing configuration that validates the final installed headers, as consumed by end-users. Ensuring the correctness of the shipped product is the most important goal. Once this testing setup is in place, libc++ could consider deprecating the build directory layout in favor of a simpler model of in-tree headers for development and installed headers for testing/validation.
In the meantime, clearly documenting the differences between the in-tree, generated, and installed layouts would help minimize confusion and set appropriate expectations. Automated CI checks could also be added to flag any unintentional divergences between the layouts.
As a step towards making the in-tree headers easier to support, I have opened a pull request (#93333) to simplify the default implementation of __assertion_handler
.
This approach would meet the needs of developers while keeping a focus on the long-term goal of shipping high-quality, well-validated headers to users. It aims to avoid an over-emphasis on intermediate build artifacts at the expense of either developer experience or end-user assurance.