How to build clang and libc++ for macOS and iOS devices?

I finally got something working. The key for me was to separate it into multiple builds and stop trying to get it all into one giant cmake invocation. First I built llvm and clang, without libcxx. I set LLVM_TARGETS_TO_BUILD to X86;ARM;AArch64.

Next, I built libcxx using the new compiler, for x86. Then a second build of libcxx for arm64. So three different build directories total. Building libcxx only takes about a minute (compared to about 1 hour for the first step) so tweaking and rerunning them is not so painful.

Then I was able to compile and run a “hello world” on my Mac and iPhone using this compiler and libc++. Never touched Xcode.

Do you possibly have any samples to share of all the steps? I’ve been working on something similar. Also, were you using Apple’s fork of LLVM for this?

If it helps, here is the CMake invocation that I use to build LLVM from source on macOS:

cmake --fresh -G 'Ninja' -S llvm -B build -DLLVM_ENABLE_PROJECTS='clang;lldb;clang-tools-extra' -DCMAKE_INSTALL_PREFIX=`pwd`/install -DLLVM_TARGETS_TO_BUILD='AArch64' -DCMAKE_BUILD_TYPE=Debug -DLLDB_ENABLE_PYTHON=Yes -DLLDB_ENABLE_LIBEDIT=Yes -DLLVM_ENABLE_RUNTIMES='libcxx;libcxxabi' -DLIBCXX_INCLUDE_TESTS=On -DLIBCXX_INCLUDE_TESTS=On -DLIBCXX_INCLUDE_TESTS=On -DLLVM_ENABLE_ASSERTIONS=Off -DLIBCXX_HARDENING_MODE=debug

Note: The -DLIBCXX_HARDENING_MODE=debug is not a standard way to build libc++ (it is added for certain testing that I am doing to contribute to the standard library).

I am on the beta channel for macOS and just used this command in the middle of last week, so I know that it works. I hope that it helps you!

Will

1 Like

I have not been working on this lately, because I’ve been in Windows-land working with Visual Studio, but let me dig up some files and paste them below.

One disclaimer: it is easier to simply use Xcode, and Apple’s C++; but, in my case, I really wanted to play with the latest C++ features. So I was trying to fight with this.

To your question, I was not using Apple’s fork of LLVM. I was using the main llvm-project.

These files below were working for me, for building a macOS executable, which I was running on my own MacBook. I did not deploy to the app store, so there is potentially some issue lurking there that could tank all of this, if you’re trying to get a binary accepted by Apple. I hope not. I also created some “hello world” iOS stuff, and it ran on my iPhone or simulator, but I was not working on that every day. So there are probably some things still wrong with these files.

I start with stock macOS and XCode, so I have Apple’s clang. There are paths in the comments that you’ll want to adjust. Eg, ~/Dev/llvm-project is my clone from github. And the ~/Workspace/MonoRepo/clang_scripts folder is the location of these cmake files. The paths to the SDKs may be out of date now too.

File #1, phase1.cmake:

# Goal here:
# - build LLVM, Clang, and tools, with backends needed (x86, arm)
#
# Shell Commands:
#
#     cd ~/Dev/llvm-project
#
# Run cmake with this file setting cache variables:
#
#     rm -rf build_phase1
#     cmake -G Ninja -C ~/Workspace/MonoRepo/clang_scripts/phase1.cmake -S llvm -B build_phase1
#
# Then build. This takes a long time - about an hour on my MacBook Pro.
#
#     ninja -C build_phase1; date
#

# TODO: enable lldb too?

set(LLVM_TARGETS_TO_BUILD "X86;ARM;AArch64" CACHE STRING "")

set(LLVM_ENABLE_PROJECTS "llvm;clang;clang-tools-extra;lld" CACHE STRING "")

# I had this installed via brew and it causes problems because it's not a fat library.
set(LLVM_ENABLE_ZSTD OFF CACHE BOOL "")

set(CMAKE_BUILD_TYPE Release CACHE STRING "")

File #2, phase2.x86.macos.cmake:

# Goal here: build libc++ for x86 macOS (my host system)
#
# https://libcxx.llvm.org/BuildingLibcxx.html
# That says to use `-S runtimes`
#
# To run this, first build phase1 (phase1.cmake). Then run this file to define a new build in a new directory.
#
#     rm -rf build_phase2_x86_macos
#     cmake -G Ninja -C ~/Workspace/MonoRepo/clang_scripts/phase2.x86.macos.cmake -S runtimes -B build_phase2_x86_macos
#
# And build it:
#
#     ninja -C build_phase2_x86_macos

include(/Users/rob/Workspace/MonoRepo/clang_scripts/phase2.common.cmake)

set(LLVM_RUNTIME_TARGETS "x86_64-apple-darwin" CACHE STRING "")
set(LLVM_DEFAULT_TARGET_TRIPLE "x86_64-apple-darwin" CACHE STRING "")
# set(CMAKE_OSX_ARCHITECTURES "x86" CACHE STRING "")
set(CMAKE_OSX_SYSROOT "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk" CACHE STRING "")

That includes a file, phase2.common.cmake. Here it is below. Note the __mylastname variable. In my real file, it is actually my last name. The purpose is to isolate this new C++ standard library from the standard library used by Apple, which will also be loaded at runtime, as it is used internally and some of their frameworks. Is that going to blow up in my face later? I don’t know. It seemed to be working. :slight_smile:

phase2.common.cmake:


set(PHASE1_BUILD_DIR /Users/rob/Dev/llvm-project/build_phase1)
set(CMAKE_C_COMPILER ${PHASE1_BUILD_DIR}/bin/clang CACHE STRING "")
set(CMAKE_CXX_COMPILER ${PHASE1_BUILD_DIR}/bin/clang++ CACHE STRING "")

message(${CMAKE_CXX_COMPILER})

# set(LLVM_ENABLE_RUNTIMES "libcxx;libcxxabi;libunwind;compiler-rt" CACHE STRING "")
set(LLVM_ENABLE_RUNTIMES "libcxx;libcxxabi;libunwind;" CACHE STRING "")

# I had this installed via brew and it causes problems because it's not a fat library.
set(LLVM_ENABLE_ZSTD OFF CACHE BOOL "")

set(CMAKE_BUILD_TYPE Release CACHE STRING "")

set(LIBCXX_ABI_NAMESPACE "__mylastname" CACHE STRING "")

Here’s an example of a different “phase 2” file. As you can see it uses the same “common” file, but builds the library for use with apps built for the iOS simulator.

phase2.x86.ios.sim.cmake:

# Goal here:
# - build libc++ for x86 (my host system) iOS simulator
#
# https://libcxx.llvm.org/BuildingLibcxx.html
# That says to -S runtimes
#
# To run this, first build phase1 (phase1.cmake). Then run this file to define a new build in a new directory.
#
#     cmake -G Ninja -C ~/Workspace/MonoRepo/clang_scripts/phase2.x86.ios.sim.cmake -S runtimes -B build_phase2_x86_ios_sim
#
# And build it:
#
#     ninja -C build_phase2_x86_ios_sim

include(/Users/rob/Workspace/MonoRepo/clang_scripts/phase2.common.cmake)

set(LLVM_RUNTIME_TARGETS "x86_64-apple-darwin" CACHE STRING "")
set(LLVM_DEFAULT_TARGET_TRIPLE "x86_64-apple-darwin" CACHE STRING "")
set(CMAKE_OSX_SYSROOT "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator16.4.sdk" CACHE STRING "")

I also had a phase3, for a while, because I was trying to build the LLVM library for use in my app, which had a JIT compiler inside it. However, I’ve abandoned that for now.

Next, when you try to use this new clang and libc++ to build a program, you have to point your build tool to the compiler and the libraries.

So the C++ compiler will be in the build_phase1/bin directory. And I needed the following compiler flags:

-nostdinc++ -nostdlib++ -isystem ${new_lib_dir}/include/c++/v1    
-isysroot ${sdk}    

The new_lib_dir points to one of the build_phase2 directories above. Without those flags it started trying to include stuff from Apple’s system that conflicted with the new standard lib. Maybe there’s a better way to handle that.

And for the linker, I add these arguments to statically link the new C++ libs to my executable:

${new_lib_dir}/lib/libc++.a ${new_lib_dir}/lib/libc++abi.a         
  ${new_lib_dir}/lib/libc++experimental.a

Maybe that will help. If you add or fix some things, we could start a github repo with these cmake files and some documentation.

Rob

Hi Rob,

I really appreciate the details here. My use-case is a little different but I think your work will still be helpful. I’d like to use the Clang libraries, such as AST parsing, for an iOS project. I realize Xcode offers some command line utilities for working with the AST but I was hoping to link to these Clang tooling libraries directly. There is a problem with that though.

First, Apple’s shipped version of Clang does not include the Clang tools so you have to build or download those yourself. The challenge there is that the compilation database generated by Xcode’s clang is not compatible with the public clang. That led me down the road of trying to build Clang the way Apple so the Clang I build the compilation database with matches the tools.

I took a different approach in my experiments than yours. I’m using the Apple fork of LLVM. I came across a README in that repo that describes how you can use a pre-built set of CMAKE files to build like Apple does with a 2 or 3 stage build process. With that resulting build toolchain, I was able to use it with Xcode with some success. However, I could only build to the simulator, not a device (which may be ok for my AST tools case).

Anyways, I’m going to look at your helpful notes here and try to make sense of the 2 different approaches. I’ll be sure to share if I come up with something useful.

1 Like