4. CMake与Makefile在iOS编译中的应用
说实话,很多iOS开发者对Xcode项目文件太依赖了。
一做SDK就拖拽文件、改Build Settings。遇到跨平台需求,立刻傻眼。
我早年接手过一个密码SDK,Android那边用CMake用得飞起,iOS这边还在手动维护xcconfig。每次新增一个算法文件,两边同步一遍,累到崩溃。后来花了几天把iOS构建也统一到CMake+Makefile体系下,舒服多了。
4.1 为什么iOS需要CMake和Makefile
先给个直观的对比。你想想看:
| 工具 | 适用场景 | 我个人的偏好 |
|---|---|---|
| Xcode项目 | 纯iOS App,团队全是Apple生态 | 没啥说的,标准做法 |
| Makefile | 跨平台C/C++库,需要精细控制编译参数 | 我常用于快速原型验证 |
| CMake | 多平台SDK、依赖管理、自动化构建 | 现在主力方案,推荐你也用 |
做密码SDK时,你要对付的不是.swift文件,而是大量C和汇编。
这些源码跟平台绑得松,但跟编译器和架构绑得紧。
一旦涉及ARM64、ARMv7、x86_64模拟器架构的交叉编译… Xcode的Build Settings界面根本不够看。
4.2 Makefile:最朴素但最透明的构建方式
先聊聊Makefile。虽然老派,但搞清它的逻辑,你才能真正理解CMake在做什么。
4.2.1 一个iOS静态库的Makefile长什么样
# 目标架构
ARCHS = arm64 armv7 x86_64
SIMULATOR_ARCHS = x86_64 arm64
# SDK路径
IOS_SDK = $(shell xcrun --sdk iphoneos --show-sdk-path)
SIM_SDK = $(shell xcrun --sdk iphonesimulator --show-sdk-path)
# 输出目录
BUILD_DIR = build
OUTPUT_DIR = output
# 源文件
SRC = $(wildcard src/*.c)
# 每个架构先编译对象文件
define build_arch
$(BUILD_DIR)/$(1)/%.o: src/%.c
@mkdir -p $(@D)
$(CC) -c $< -o $@ \
-arch $(1) \
-isysroot $(2) \
-Os \
-fembed-bitcode
endef
# 为每个架构生成规则
$(foreach arch,$(ARCHS),$(eval $(call build_arch,$(arch),$(IOS_SDK))))
$(foreach arch,$(SIMULATOR_ARCHS),$(eval $(call build_arch,$(arch),$(SIM_SDK))))
# 编译所有架构对象
.PHONY: all
all: $(foreach arch,$(ARCHS) $(SIMULATOR_ARCHS),$(patsubst src/%.c,$(BUILD_DIR)/$(arch)/%.o,$(SRC)))
# 打包成静态库(每个架构一个)
$(OUTPUT_DIR)/libcrypto_ios_%.a: $(patsubst src/%.c,$(BUILD_DIR)/%/%.o,$(SRC))
@mkdir -p $(OUTPUT_DIR)
libtool -static -o $@ $^
# 合并成胖二进制
$(OUTPUT_DIR)/libcrypto.a: $(foreach arch,$(ARCHS),$(OUTPUT_DIR)/libcrypto_ios_$(arch).a) \
$(foreach arch,$(SIMULATOR_ARCHS),$(OUTPUT_DIR)/libcrypto_ios_$(arch).a)
lipo -create $^ -output $@
.PHONY: clean
clean:
rm -rf $(BUILD_DIR) $(OUTPUT_DIR)
我的小经验:
Makefile最容易被忽略的是
结果真机跑得好好的,CI上一跑Xcode测试直接链接失败。
我也在那上面吃过亏,后来每次看见
Makefile最容易被忽略的是
lipo那一步。很多人只在真机架构里混,忘了加模拟器。结果真机跑得好好的,CI上一跑Xcode测试直接链接失败。
我也在那上面吃过亏,后来每次看见
lipo -create都多检查一遍输入。
4.2.2 Makefile在密码SDK里的痛点
- 架构扩展麻烦:每加一个架构,就得手动写
-arch和路径。 - 依赖管理弱:如果引用OpenSSL或者BoringSSL,Makefile里要手写头文件搜索路径。
- 调试配置困难:Debug和Release的切换靠变量,不够直观。
嗯,这时候就该CMake上场了。
4.3 CMake for iOS:写一次,生成到处跑
CMake不是构建器,它是构建系统的生成器。
你写一份CMakeLists.txt,它能产出Xcode项目、Makefile、Ninja文件… 随你选。
4.3.1 一份iOS密码SDK的CMakeLists.txt样板
cmake_minimum_required(VERSION 3.16)
project(CryptoSDK VERSION 1.0.0 LANGUAGES C ASM)
# 设置iOS平台属性
set(CMAKE_SYSTEM_NAME iOS)
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
# 支持模拟器 + 真机架构
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "Architectures")
# 第三方依赖路径(以OpenSSL为例)
set(OPENSSL_ROOT_DIR "${CMAKE_SOURCE_DIR}/thirdparty/openssl")
include_directories(${OPENSSL_ROOT_DIR}/include)
# 收集源文件
file(GLOB_RECURSE CRYPTO_SRC "src/*.c" "src/*.S")
# 创建静态库目标
add_library(crypto STATIC ${CRYPTO_SRC})
# 编译选项
target_compile_options(crypto PRIVATE
$<$<CONFIG:Debug>:-g -O0>
$<$<CONFIG:Release>:-Os -fembed-bitcode>
)
# 链接选项
target_link_options(crypto PRIVATE
-L${OPENSSL_ROOT_DIR}/lib
)
# 安装规则
install(TARGETS crypto
ARCHIVE DESTINATION lib
PUBLIC_HEADER DESTINATION include
)
关键点:
不加这句,CMake会认为你在为macOS编译。
我第一次用CMake for iOS就忘了写,折腾了一下午。
CMAKE_SYSTEM_NAME iOS 这句不是摆设。不加这句,CMake会认为你在为macOS编译。
我第一次用CMake for iOS就忘了写,折腾了一下午。
4.3.2 用CMake生成Xcode项目
# 真机架构
cmake -B build/ios \
-DCMAKE_TOOLCHAIN_FILE=ios.toolchain.cmake \
-DPLATFORM=OS64 \
-G "Xcode"
# 模拟器架构
cmake -B build/simulator \
-DCMAKE_TOOLCHAIN_FILE=ios.toolchain.cmake \
-DPLATFORM=SIMULATOR64 \
-G "Xcode"
为什么我喜欢CMake?
- 一套
CMakeLists.txt,生成Xcode项目、Makefile、甚至VS项目。 - 架构和平台通过参数切换,不用修改构建脚本。
- 配合
FetchContent或ExternalProject,依赖管理很方便。
4.3.3 iOS密码SDK的典型CMake工作流
4.4 避坑指南(全是我踩过的)
我曾经…
漏了
当时CI跑了一夜,第二天一看红了一片。
从此,每一份CMakeLists.txt里的Release编译选项,我一定写全
漏了
-fembed-bitcode,App Store上架直接报错。当时CI跑了一夜,第二天一看红了一片。
从此,每一份CMakeLists.txt里的Release编译选项,我一定写全
fembed-bitcode。
- 一直用
ios.toolchain.cmake工具链:别自己裸配-DCMAKE_C_COMPILER=clang,很容易走偏。 - 架构列表别写错:ARM64不带下划线,x86_64带下划线,大小写敏感。
- 模拟器和真机分开编译:最后才lipo合并。不要试图一次性编译胖二进制,CMake和Xcode都不推荐。
- 头文件路径用
target_include_directories:不要用全局的include_directories,SDK大了之后容易冲突。
4.5 总结一张表,清晰对比
| 对比项 | Makefile | CMake |
|---|---|---|
| 学习曲线 | 中等(语法诡异) | 中等(概念略多) |
| 跨平台能力 | 依赖手写适配 | 内置支持 |
| Xcode生成 | 不支持 | 原生支持 |
| IDE集成 | 一般(CLion有限支持) | Clion、VS、VSCode都支持 |
| 密码SDK推荐度 | 简单项目可以用 | 强烈推荐 |
我个人习惯:小工具或一次性测试用Makefile;面向交付的SDK,一律CMake。
不管是BoringSSL、OpenSSL还是Apple CryptoKit的底层封装,CMake这套体系已经经过大量生产环境验证。学它,值得。