Library / SDK
leetal/ios-cmake avatar
leetal/ios-cmake

ios-cmake: one CMake toolchain file for every Apple platform you have to ship

A CMake toolchain file for iOS/iPadOS, visionOS, macOS, watchOS & tvOS C/C++/Obj-C++ development

2,185 stars465 forksCMakeBSD-3-Clause

At a glance

What is it?
A single toolchain file that teaches CMake how to cross-compile C, C++, Objective-C and Swift for iOS, tvOS, watchOS, visionOS, macOS and Mac Catalyst.
Who is it for?
ios-cmake solves a narrow problem that becomes painful fast: the moment a CMake project has to produce a binary for more than one Apple destination, the build system stops being portable and starts being a pile of per-platform exceptions. This toolchain file collapses that into a `PLATFORM` value and a deployment target, and the two command lines in its quick start do produce a working arm64 device build.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly CMake, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two commands, then the rest of the README is reference material

The quick start is deliberately short. You download `ios.toolchain.cmake` from the latest release, or add the repository as a submodule, and then run two commands:

bash
cmake -B build -G Xcode -DCMAKE_TOOLCHAIN_FILE=path/to/ios.toolchain.cmake -DPLATFORM=OS64
cmake --build build --config Release

That produces a Release build for iOS devices on arm64. Everything else in the README is a variation on those two lines. The Xcode generator is recommended, though Ninja and Unix Makefiles both work, which matters if you want the same build to run on a Linux CI box as well as on a Mac.

The repository itself is almost empty in the way you want a toolchain file to be: a README, a license, the single `ios.toolchain.cmake` file, an `example/` directory, and CI workflows. There is no wrapper library to link, no plugin to install, no bootstrap step. You are copying one file into your build, which means you can read it end to end and there is no version of it you are accidentally depending on.

One documentation detail worth flagging, because it is unusual and it is a deliberate choice by the maintainer: the README opens with an HTML comment addressed to AI coding agents, telling them which commands to run and warning them not to produce fat libraries containing both device and simulator arm64 slices. That guidance is the same advice the prose gives, written for a different reader. If you are working on this build from a scripted assistant, the file is deliberately written to be found.

One PLATFORM value per destination, and the fat library problem

You pass exactly one `PLATFORM` value, and the tables list every legal one. For iOS that means `OS64` for devices, `SIMULATOR64` for the Intel Mac simulator, `SIMULATORARM64` for Apple Silicon, and the two `COMBINED` variants that produce a fat library containing arm64 and x86_64 slices. The tvOS table mirrors that shape with `TVOS`, `SIMULATOR_TVOS`, `SIMULATORARM64_TVOS` and `TVOSCOMBINED`.

watchOS is the odd one out, because the device target is genuinely multi-architecture: `WATCHOS` covers arm64, armv7k and arm64_32, and `WATCHOSCOMBINED` adds the x86_64 simulator slice on top. Release 4.6.0 changed this default to include the arm64 device slice when building with Xcode 15 or later, because Apple requires arm64 support in watchOS submissions from April 2026. The release notes call it a potentially breaking change and give you the override, `-DARCHS="armv7k;arm64_32"`, which is exactly the kind of migration detail that is easier to read than to rediscover.

Here is the part that matters most and is easy to skim past. A fat library cannot contain both a device arm64 slice and a simulator arm64 slice, because both are arm64 and the linker cannot tell them apart. That makes every `COMBINED` value unreliable on Apple Silicon. The platform tables carry the caveat inline, pointing at a note, and the distribution section explains the alternative.

Building device and simulator separately and combining with xcodebuild

The supported answer to the fat library problem is an xcframework, and the README gives the whole sequence: one build directory per platform, then `xcodebuild -create-xcframework` to merge the two static libraries into a single distributable bundle that resolves the right slice at load time.

bash
cmake -S . -B build-device -G Xcode -DCMAKE_TOOLCHAIN_FILE=ios.toolchain.cmake -DPLATFORM=OS64
cmake --build build-device --config Release

cmake -S . -B build-simulator -G Xcode -DCMAKE_TOOLCHAIN_FILE=ios.toolchain.cmake -DPLATFORM=SIMULATOR64COMBINED
cmake --build build-simulator --config Release

xcodebuild -create-xcframework \
  -library build-device/Release-iphoneos/libexample.a \
  -library build-simulator/Release-iphonesimulator/libexample.a \
  -output example.xcframework

Read the output paths in that fence carefully, because they encode the single most common first-attempt error. The device build lands in a directory named after `iphoneos` and the simulator build in one named `iphonesimulator`, and `xcodebuild` will not find either library if you guess the path instead of copying it. The simulator side uses `SIMULATOR64COMBINED` even in an xcframework workflow, because on an Intel host that value gives you the x86_64 slice, and if you are on Apple Silicon you would point at `SIMULATORARM64` instead. The README notes that CMake 3.28 and later can consume the resulting framework directly.

This is also where the newer platform values earn their place. `MAC_CATALYST_UNIVERSAL` and `MAC_CATALYST_ARM64` cover Mac Catalyst on both host architectures, and release 4.6.0 changed Catalyst builds under the Xcode generator so that they actually produce the `macabi` variant instead of plain macOS, removing a workaround people had been passing to `xcodebuild` by hand.

Options that change what the compiler does, not just where it points

Beyond `PLATFORM`, the options table is short and each entry has a real consequence. `DEPLOYMENT_TARGET` sets the minimum OS version and is the one you will end up passing explicitly. `ARCHS` overrides the per-platform architecture list with a semicolon separated value such as `-DARCHS="arm64;x86_64"`.

`ENABLE_ARC` is on by default and controls Objective-C automatic reference counting, which is the correct default today and worth knowing about if you are porting older code that predates ARC. `ENABLE_VISIBILITY` defaults to off, and turning it on passes `-fvisibility=hidden` so internal symbols are not exported. `ENABLE_STRICT_TRY_COMPILE` is the subtle one: with it off, `try_compile()` does not really link, so any check that depends on linking, such as testing for `HAVE_LIBATOMIC`, can give you the wrong answer. Turning it on costs configure time and buys you correct answers.

Two entries are there mostly so you can turn them off. `ENABLE_BITCODE` defaults to off and the description says bitcode has been dead since Xcode 14. `NAMED_LANGUAGE_SUPPORT`, on by default, is what calls `enable_language(OBJC)` and `enable_language(OBJCXX)` so that Objective-C sources are compiled with the right language settings rather than picked up incidentally.

The deployment target defaults are the part that changed most recently, and they now depend on your Xcode. With Xcode 27 or later the defaults are 15.0 for iOS and tvOS, 9.0 for watchOS, 1.0 for visionOS, 12.0 for macOS and 15.0 for Mac Catalyst. With an older Xcode the defaults stay at 13.0, 6.0, 1.0, 11.0 and 13.1. Release 4.6.1 exists specifically because Xcode 27 fails the build outright when the deployment target is below its supported range, which broke every configuration that did not pass the flag. Passing `-DDEPLOYMENT_TARGET` yourself still behaves exactly as before, so setting it explicitly remains the safe habit.

visionOS, macOS, and the platforms that arrived over time

visionOS is the newest addition and the smallest table. `VISIONOS` builds for Apple Vision Pro on arm64, `SIMULATOR_VISIONOS` does the simulator, and `VISIONOSCOMBINED` produces a fat library, all requiring an Apple Silicon host. `VISIONOSCOMBINED` was added in release 4.5.0.

The macOS table is the one you will use even if you never ship to a phone. `MAC` is Intel, `MAC_ARM64` is Apple Silicon, `MAC_UNIVERSAL` is both, `MAC_CATALYST` is Intel Catalyst, `MAC_CATALYST_ARM64` is Catalyst on Apple Silicon, and `MAC_CATALYST_UNIVERSAL` covers both. `MAC_CATALYST_UNIVERSAL` also arrived in 4.5.0, and both universal values hit a configure error on Xcode 16.3 and later that 4.6.0 fixed.

The example directory is worth a look even if you only skim it. It contains four separate readme and project directories covering an app, a library, an example curl library and their documentation, which is a better signal than a single toy project would be. Release 4.5.0 also removed an optimisation that ran the toolchain only once during setup, because it broke nested libraries with multiple targets; the opt-out flag `OS_SINGLE_BUILD` is preserved for anyone who depended on the old behaviour.

Editorial conclusion

ios-cmake solves a narrow problem that becomes painful fast: the moment a CMake project has to produce a binary for more than one Apple destination, the build system stops being portable and starts being a pile of per-platform exceptions. This toolchain file collapses that into a `PLATFORM` value and a deployment target, and the two command lines in its quick start do produce a working arm64 device build. The pieces that trip people up are all documented, just not all in the same place. Fat libraries cannot hold both a device arm64 slice and a simulator arm64 slice, so the `COMBINED` platform values are on borrowed time on Apple Silicon and the README pushes you toward xcframeworks and separate build directories. Deployment targets now depend on your Xcode version, because Xcode 27 rejects anything below its supported range, which is what release 4.6.1 exists to fix. With only two open issues against more than two thousand stars, and a push on 2026-09-24 that matches the newest release the same day, this is one of the more quietly reliable build files in the ecosystem. Start with the quick start, then read the platform tables before your first multi-platform build.

Frequently asked questions

Is CMake for C++ or C?

Both, and this toolchain file also handles Objective-C, Objective-C++ and Swift. The repository description lists C, C++, Objective-C/C++ and Swift as the languages it can cross-compile for every Apple platform.

How do I build a CMake library for an iOS device with this toolchain?

Pass -DPLATFORM=OS64 with -DCMAKE_TOOLCHAIN_FILE pointing at ios.toolchain.cmake and the Xcode generator, then build with --config Release. That produces an arm64 device build; other destinations come from the platform table.

Can I ship one library for both device and simulator?

Not as a fat library, because a device arm64 slice and a simulator arm64 slice cannot coexist in one. The README directs you to build each platform in its own build directory and combine them with xcodebuild -create-xcframework.

What does the SIMULATOR64COMBINED platform value produce?

A fat iOS simulator library containing both arm64 and x86_64 slices. It is one of the COMBINED values the README marks as unreliable on Apple Silicon for device and simulator distribution, though it still works for a simulator-only fat build on an Intel host.

Do I need to set a deployment target myself?

No, and you probably should not. Since release 4.6.1 the defaults depend on your Xcode version: 15.0 for iOS and tvOS, 9.0 for watchOS, 12.0 for macOS and 15.0 for Catalyst on Xcode 27 or later, with older defaults for earlier Xcodes.

What is ENABLE_STRICT_TRY_COMPILE for?

It is off by default. Turning it on makes try_compile() link for real, so checks that depend on linking, such as testing for HAVE_LIBATOMIC, return correct answers at the cost of some configure time.

Official sources

  1. Issues
  2. leetal/ios-cmake on GitHub
  3. License: BSD-3-Clause
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/leetal-ios-cmake.svg)](https://hysenlabs.com/projects/leetal-ios-cmake)