Clang and gcc on macOS Catalina - Finding the include paths

macOS 10.15 alias Catalina is out since the end of 2019, but I usually wait a while before I upgrade. Last week I took this step and did it, assuming most issues are either solved or there are solutions out there on the internet.

Getting a macOS version that is no longer in the AppStore

The first barrier was getting macOS Catalina. I did download it at the time but must have deleted it. The issue here is that Apple no longer offers Catalina in the macOS AppStore. Various searches did not come up with a satisfying result. I wanted an official version, not something distributed on the internet. The solution here was macadmin-scripts. It is able to download the files from Apple and creates an installer. First hurdle solved.

/usr/include is no more

The upgrade itself went smooth. Everything looked all right initially. After installing XCode again and the Command Line Tools, I was happy to compile some code. One thing important to mention here, I use gcc as well as the latest Clang version. The latter one to develop C++ Insights. Apple's Clang version only comes to use when I develop apps for the macOS AppStore. My local compilers were the issue. Thanks to a new security measure /usr/include is no more on macOS since /usr is now a read-only partition. Luckily, /usr/local still works, so no trouble for brew. Before Catalina, installing the Command Line Tools also installed /usr/include pointing to a directory inside XCode. The current location of the system header files can be found with:

xcrun --show-sdk-path

Just append /usr/include to the output, and you have the location of the system headers. But /usr/include itself is still gone.

Without this you will end up seeing compiler errors like this:

In file included from ClassOperatorHandler5Test.cpp:1:
In file included from /usr/local/clang-11/include/c++/v1/string:504:
In file included from /usr/local/clang-11/include/c++/v1/string_view:175:
In file included from /usr/local/clang-11/include/c++/v1/__string:57:
In file included from /usr/local/clang-11/include/c++/v1/algorithm:641:
In file included from /usr/local/clang-11/include/c++/v1/cstring:60:
/usr/local/clang-11/include/c++/v1/string.h:60:15: fatal error: 'string.h' file not found
#include_next <string.h>

Teaching the compiler its default include path

My first attempts to solve this were all unsatisfying. I can provide all the search paths via -isystem. That may be all right for projects with CMake, but not for a quick compile directly on the command line. The next tip I found was setting the environment variable CPLUS_INCLUDE_PATH. Both compilers use it, and you can override the default include paths with it. The trouble is that all system include paths, need to be listed as this environment variable replaces them all. It seemed like a doable approach first. But remember that I use Clang and gcc? The compilers C++ includes are different for each of them. But there is only one CPLUS_INCLUDE_PATH.

For Clang it would be:

export CPLUS_INCLUDE_PATH=/usr/local/clang-11/include/c++/v1:/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include

For gcc it would be:

export CPLUS_INCLUDE_PATH=/usr/local/gcc-10/include/c++/10.2.0:/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include

Aside from the fact that the two compilers have a slightly different path scheme, it also depends on the version of the compiler. We can see clang-11 and gcc-10, and even 10.2.0 in the path. This approach does not work even with multiple versions of the same compiler.

My next attempt was to supply only -isysroot with the global CXXFLAGS. Once again, at first, it looked like the right thing, but I learned that only gcc looks at that global environment variable. Clang doesn't.

SDKROOT to rescue

Still unhappy with the status quo, I continued my search and found that Clang supports SDKROOT to supply the base path to an SDK. On my system this is /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk without /usr/include. The last part is stable, and the compiler adds it itself.

After another round of investigation, I found this mailing list entry Pick up SDKROOT as the sysroot fallback from Iain Sandoe. Very good news. Gcc also supports SDKROOT, so all I have to do for all my compilers is to define a single environment variable somewhere in .bashrc:

export SDKROOT="`xcrun --show-sdk-path`"

That's it! No compiler version in it! One environment variable for both compilers.


What initially distracted me was CMake. Because C++ Insights did compile fine, it looks like CMake automatically sets SDKROOT.

Despite that this is not my usual C++ content and very macOS specific, I hope you learned something and that you are able to continue developing C++ code under macOS with custom compilers.