I’d love to make use of C++ 20 concepts in our codebase. Since Apple clang only has very limited support for concepts, I wanted to try out a homebred-installed vanilla clang 13.
After fiddling around for approx a day long, I finally managed to build our in-house unit test project which is a JUCE command line app. But there are some problems I encountered on the way and I’d love to get some input about what’s the right approach to them and for the JUCE-related ones, if the JUCE team could do anything about them.
A few words regarding my test configuration: I’m running macOS 12 on an Intel mac, have Xcode 13 and the Xcode command line tools installed and do all my cmake stuff via CLion. The project I built uses a slightly older JUCE 6 version that should be updated to the latest release in the near future (so sorry if any issue I’m addressing here should have been fixed in the meantime). I’m compiling with C++ 20.
Part 1: Pass the CMake configure step
My first naive approach was to just pass -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++
to the cmake
command.
Pass the CMake compiler check
Turns out that this fails early with
The C compiler
"/usr/local/opt/llvm/bin/clang"
is not able to compile a simple test program.
It fails with the following output:
Change Dir: /Users/me/Projects/testProject/build/CLion/Debug/CMakeFiles/CMakeTmp
Run Build Command(s):/usr/local/bin/ninja cmTC_d2d4b && [1/2] Building C object CMakeFiles/cmTC_d2d4b.dir/testCCompiler.c.o
[2/2] Linking C executable cmTC_d2d4b
FAILED: cmTC_d2d4b
: && /usr/local/opt/llvm/bin/clang -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk -Wl,-search_paths_first -Wl,-headerpad_max_install_names CMakeFiles/cmTC_d2d4b.dir/testCCompiler.c.o -o cmTC_d2d4b && :
ld: library not found for -lSystem
clang-13: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
Okay, so the home-brew clang won’t find the system libraries. After trying out a few different options, I ended up supplying the library search paths via the LDFLAGS
environment variable like -L/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk/usr/lib -L/usr/local/opt/llvm/lib
. The first search path seems to be the one needed for all the Apple system libraries, (the C compiler check passes with this one only) while the second one supplies C++ libraries. This makes the CMake compiler check pass. Note that I explicitly passed the MacOSX11.0.sdk
since CMake obviously automatically choses that SDK as sysroot when attempting to compile the test files. Not sure if that’s a good choice but more on that later.
Build juceaide
After I got the compiler check passing, configuration failed during the compilation of juceaide
. While compilation succeeded, I got linker errors regarding multiple ObjectiveC symbols like undefined symbols _objc_opt_class
. Since that one looks like it does not link to the ObjectiveC library, I added -lobjc
to LDFLAGS
and voila: juceaide
builds successfully!
Part 2: Actually build the project
The first error I encountered here was here:
The compiler cannot find std::result_of
which has been removed in C++ 20. Looking at the code it seems that this tried to work around an Apple clang specific issue. Removing the || (JUCE_CLANG && ! JUCE_WINDOWS)
condition fixes this one. Sidenote: We use std::invoke_result
here and there in our codebase and see no issues using Apple clang 12. Not sure which Apple clang version caused that problem, but it might be needed to rework the condition in a way that it’s only taken for the problematic version of Apple clang. Not sure if there is any way to determine wether Apple clang is used from a define?
The rest of the code builds without issues. The next error is a linker error again, complaining that the Accelerate
framework cannot be found. So it seems that the compiler also misses the framework search path. Adding -F/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk/System/Library/Frameworks
to the LDFLAGS
also solves this one and finally the build succeeds and I’m able to run my unit test executable!
Open Questions
Although the build now succeeded, I’m still not sure if the approaches chosen here are the optimal ones.
How to correctly pass the search paths?
Is supplying -L/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk/usr/lib -L/usr/local/opt/llvm/lib -F/Library/Developer/CommandLineTools/SDKs/MacOSX11.0.sdk/System/Library/Frameworks -lobjc
via LDFLAGS
really the way to go? Usually using a quite puristic CMake workflow to specify search paths etc. this seems a bit wrong. On the other hand, specifying the search paths in my CMakeLists does not get me past the CMake configuration phase. Since Apple clang does not need those argument, it feels like the Apple toolchain has some default settings somewhere. Is there any system-wide way to specify those basic settings for my homebrew-based toolchain and if so, would it be a good way to go? Or might e.g. a CMake toolchain file be a better solution?
How to link against libraries matching my deployment target?
The default deployment target for our projects is 10.9 until now, this is set via CMAKE_OSX_DEPLOYMENT_TARGET
. To be honest, I don’t know in detail what this flag causes. But specifying a 11.0 SDK as sysroot and as library search path seems a bit wrong to me? However, since those libraries all seem to be dynamic libraries, I’m not sure if this choice will even affect where the final executable will look for its libraries? At least I get no linker warnings here. Indeed, looking into Library/Developer/CommandLineTools/SDKs
, I only see MacOSX10.14.sdk, MacOSX10.15.sdk and MacOSX11.0.sdk. Looking into the Xcode bundle I furthermore find MacOSX12.1.sdk.
However, I get a linker warning here:
ld: warning: dylib (/usr/local/opt/llvm/lib/libunwind.dylib) was built for newer macOS version (12.0) than being linked (10.9)
Seems like the homebrew supplied libraries are built for the target operating system of the build machine. Again, this is a dynamic library, so I expect this one to reference a matching library found on the target machine the executable will run on – right? Can I safely ignore that warning? Or does the homebrew clang reference any libraries or symbols that might not even be present on the target system?
Conclusion
I’m pretty happy that I made it until here And I’m quite confident that the remaining problems and warnings can be resolved in some way. For now I need some clarification on the open questions, but maybe someone round here knows a thing or two. Does anyone else here successfully use a non-Apple clang to build for macOS?