Windows平台基于MacOSX-SDK交叉编译boost小记

Windows平台基于MacOSX-SDK交叉编译boost小记

本文记录如何在 Windows 环境下,借助 MacOSX12.3.sdk 和 LLVM/Clang 交叉编译 Boost 的 macOS 静态库。目标架构包括:

  • macOS Intel:x86_64
  • macOS Apple Silicon:arm64

最终会分别得到两套 .a 静态库:

stage/macos/x86_64/lib
stage/macos/arm64/lib

如果后续需要,也可以再用 lipo / llvm-lipo 合并成 macOS universal .a

1. 环境说明

本文示例环境:

Windows
Boost
MacOSX12.3.sdk
Visual Studio 2019 Enterprise LLVM/Clang

示例 SDK 路径:

D:/Installed_Files/macosx_sdk/MacOSX12.3.sdk

示例 LLVM 路径:

C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Tools/Llvm

其中主要会用到:

clang++.exe
llvm-ar.exe
llvm-ranlib.exe
ld64.lld.exe

这里使用 Visual Studio 自带的 LLVM/Clang,并不是因为只能使用 Visual Studio 的 LLVM,而是因为这套工具链安装方便,并且组件相对完整。除了 clang++.exellvm-ar.exellvm-ranlib.exe 之外,关键还需要支持 Darwin/Mach-O 的 linker,也就是 ld64.lld.exe。很多人口头上会把它叫作 lld64,实际文件名通常是 ld64.lld.exe

如果你的其他 LLVM 工具链组件也完整,也可以替换成其他 LLVM。例如自定义 LLVM、llvm-mingw、osxcross 相关工具链都可以尝试,前提是至少满足:

clang++.exe       能识别 --target=x86_64-apple-darwin / --target=arm64-apple-darwin
llvm-ar.exe       能生成 Mach-O 静态库 archive
llvm-ranlib.exe   能为 Mach-O 静态库生成索引
ld64.lld.exe      能在需要链接 Mach-O 目标时作为 Darwin linker 使用
llvm-lipo.exe     可选,用于合并 x86_64 + arm64 universal .a

注意:本文主要编译 macOS 静态库 .a。纯静态库归档阶段通常主要依赖 clang++llvm-arllvm-ranlib;但如果构建过程中有链接测试、生成可执行文件、生成 .dylib,或者后续要在 Windows 上继续链接 macOS 目标文件,就需要可用的 ld64.lld.exe。如果工具链缺少 Mach-O linker,即使 .cpp 能编译成 .o,也可能在链接阶段失败。

2. 准备 MacOSX12.3.sdk

准备好 MacOSX12.3.sdk,本文假设路径为:

D:/Installed_Files/macosx_sdk/MacOSX12.3.sdk

确认 SDK 目录中至少能看到类似结构:

MacOSX12.3.sdk/usr/include/lib/System/Library/

后续会通过 clang 参数指定:

--sysroot=D:/Installed_Files/macosx_sdk/MacOSX12.3.sdk

3. 准备 Boost 1.74

下载并解压 Boost 1.74,例如解压到:

D:/workspace/boost_1_74_0

从开始菜单打开 VS20XX 的开发人员命令提示符(x86 或 x64 版本都可以):
VS2022命令行编译环境

切换到 Boost 源码根目录:

cd D:\workspace\boost_1_74_0

选一个对应的msvc版本(这里以vc142为例),生成 b2 构建工具:

.\bootstrap.bat vc142

执行成功后,根目录下会出现 b2.exe


后续所有编译命令都在 Boost 根目录下执行。

4. 编辑 user-config.jam

在 Boost 根目录新建或编辑:

user-config.jam

写入如下配置:

macosSdk = D:/Installed_Files/macosx_sdk/MacOSX12.3.sdk ; 
llvmMingwRoot = "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Tools/Llvm" ;
llvmMingwBin = $(llvmMingwRoot)/bin ;# macOS Intel x86_64 static libraries.
using clang : macos_x86_64 :$(llvmMingwBin)/clang++.exe:<compileflags>--target=x86_64-apple-darwin<compileflags>--sysroot=$(macosSdk)<compileflags>-mmacosx-version-min=10.13<compileflags>-stdlib=libc++<cxxflags>-std=c++14<cxxflags>-O2<cxxflags>-g<cxxflags>-DNDEBUG<cxxflags>-Wno-enum-constexpr-conversion<linkflags>--target=x86_64-apple-darwin<linkflags>--sysroot=$(macosSdk)<linkflags>-mmacosx-version-min=10.13<linkflags>-stdlib=libc++<archiver>$(llvmMingwBin)/llvm-ar.exe<ranlib>$(llvmMingwBin)/llvm-ranlib.exe;# macOS Apple Silicon arm64 static libraries.
using clang : macos_arm64 :$(llvmMingwBin)/clang++.exe:<compileflags>--target=arm64-apple-darwin<compileflags>--sysroot=$(macosSdk)<compileflags>-mmacosx-version-min=11.0<compileflags>-stdlib=libc++<cxxflags>-std=c++14<cxxflags>-O2<cxxflags>-g<cxxflags>-DNDEBUG<cxxflags>-Wno-enum-constexpr-conversion<linkflags>--target=arm64-apple-darwin<linkflags>--sysroot=$(macosSdk)<linkflags>-mmacosx-version-min=11.0<linkflags>-stdlib=libc++<archiver>$(llvmMingwBin)/llvm-ar.exe<ranlib>$(llvmMingwBin)/llvm-ranlib.exe;

几个关键点:

  • --target=x86_64-apple-darwin:生成 macOS Intel 目标文件。
  • --target=arm64-apple-darwin:生成 macOS Apple Silicon 目标文件。
  • --sysroot=$(macosSdk):指定使用 MacOSX12.3.sdk
  • -stdlib=libc++:macOS C++ 标准库使用 libc++。
  • -mmacosx-version-min=10.13:x86_64 的最低系统版本。
  • -mmacosx-version-min=11.0:arm64 的最低系统版本,Apple Silicon 通常要求 macOS 11.0 起。
  • -std=c++14:Boost 老版本和较新 clang/libc++ 组合下,C++14 通常更稳。
  • -Wno-enum-constexpr-conversion:规避部分 Boost 版本在新 clang 下的枚举 constexpr 转换警告/错误。

注意 toolset 名中的版本号使用下划线:

macos_x86_64
macos_arm64

后续 b2 中对应:

toolset=clang-macos_x86_64
toolset=clang-macos_arm64

不要写成 macos-x86_64 这类带额外横线的名字,避免被 Boost.Build 解析成 toolset 子特性。

5. 编译 macOS x86_64 静态库

在 Boost 根目录执行:

.\b2 -q -j8 -a -d+2 --debug-configuration `--user-config=.\user-config.jam `toolset=clang-macos_x86_64 `target-os=darwin binary-format=mach-o `architecture=x86 address-model=64 `threading=multi link=static variant=release cxxstd=14 `--without-python `--stagedir=stage/macos/x86_64 stage `2>&1 | Tee-Object -FilePath .\build-macos-x86_64.log

编译完成后,x86_64 静态库位于:

stage/macos/x86_64/lib

6. 编译 macOS arm64 静态库

在 Boost 根目录执行:

.\b2 -q -j8 -a -d+2 --debug-configuration `--user-config=.\user-config.jam `toolset=clang-macos_arm64 `target-os=darwin binary-format=mach-o `architecture=arm address-model=64 `threading=multi link=static variant=release cxxstd=14 `--without-python `--stagedir=stage/macos/arm64 stage `2>&1 | Tee-Object -FilePath .\build-macos-arm64.log

编译完成后,arm64 静态库位于:

stage/macos/arm64/lib

7. 参数说明

7.1 静态库相关参数

link=static

表示生成 Boost 静态库,也就是 .a 文件。

threading=multi

表示生成多线程版本 Boost 库。

variant=release

表示生成 release 版本。需要 debug 版本时,可以改成:

variant=debug

7.2 macOS 目标相关参数

target-os=darwin binary-format=mach-o

表示目标平台是 Darwin/macOS,二进制格式是 Mach-O。

x86_64 使用:

architecture=x86 address-model=64

arm64 使用:

architecture=arm address-model=64

7.3 调试日志参数

-d+2 --debug-configuration

这两个参数是可选的,用于输出更详细的编译命令和 Boost.Build 配置加载信息。调试 user-config.jam 是否生效、实际调用了哪个 clang++.exe、传入了哪些参数时非常有用。

确认配置稳定后,可以去掉它们以减少日志量。

7.4 保存日志

命令末尾使用:

2>&1 | Tee-Object -FilePath .\build-macos-x86_64.log

这样可以把标准输出和错误输出同时保存到日志文件,避免 PowerShell 控制台滚动太快导致看不到真正错误。

如果日志太多,可以临时把:

-j8

改成:

-j1

单线程构建更容易定位第一个错误。

8. 为什么加 --without-python

本文命令中加了:

--without-python

原因是 Boost 的 bootstrap.bat 可能会自动探测 Windows 主机上的 Python。如果交叉编译 macOS 时误用了 Windows Python 的头文件和库,容易导致主机平台和目标平台不匹配的问题。

除非确实需要 Boost.Python,并且已经准备好 macOS x86_64/arm64 对应的 Python 头文件和库,否则建议直接跳过:

--without-python

9. 可选:只编译需要的 Boost 库

如果不想全量编译 Boost,可以使用 --with-xxx 指定需要的库,例如:

--with-atomic --with-chrono --with-date_time --with-filesystem --with-system --with-thread --with-regex

示例:

.\b2 -q -j8 -a `--user-config=.\user-config.jam `toolset=clang-macos_x86_64 `target-os=darwin binary-format=mach-o `architecture=x86 address-model=64 `threading=multi link=static variant=release cxxstd=14 `--without-python `--with-atomic --with-chrono --with-date_time --with-filesystem --with-system --with-thread --with-regex `--stagedir=stage/macos/x86_64 stage `2>&1 | Tee-Object -FilePath .\build-macos-x86_64.log

这样可以减少编译时间,也能避开一些当前项目不需要的 Boost 库带来的额外平台兼容问题。

10. 可选:合并 universal 静态库

macOS 支持 universal/fat binary,可以把 x86_64arm64 两个架构合并到同一个 .a 文件里。

假设已经有:

stage/macos/x86_64/lib/libboost_system-clang-mt-x64-1_88.a
stage/macos/arm64/lib/libboost_system-clang-mt-a64-1_88.a

可以使用 lipollvm-lipo 合并:

$lipo = "D:\Installed_Files\android_ndk\android-ndk-r26d\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-lipo.exe"New-Item -ItemType Directory -Force .\stage\macos\universal\lib | Out-Null& $lipo -create `.\stage\macos\x86_64\lib\libboost_system-clang-mt-x64-1_88.a `.\stage\macos\arm64\lib\libboost_system-clang-mt-a64-1_88.a `-output .\stage\macos\universal\lib\libboost_system-clang-mt-1_88.a& $lipo -info .\stage\macos\universal\lib\libboost_system-clang-mt-1_88.a

正常会看到类似输出:

Architectures in the fat file: ... are: x86_64 arm64

注意:合并后的 universal .a 不要再用普通 llvm-ar.exellvm-ranlib.exe 处理,否则某些 Windows 版 LLVM 工具可能无法识别 fat archive。

11. 验证产物

可以使用 llvm-lipo 查看 universal 库架构:

& $lipo -info .\stage\macos\universal\lib\libboost_system-clang-mt-1_88.a

对于单架构 .a,可以查看 archive 内容:

& "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm\bin\llvm-ar.exe" t .\stage\macos\x86_64\lib\libboost_system-clang-mt-x64-1_88.a

也可以抽出一个 .o 后用 llvm-readobjllvm-objdump 查看是否为 Mach-O 对象文件。

12. 常见问题

12.1 找不到标准库头文件

如果出现类似:

fatal error: 'cstddef' file not found

通常说明 --sysroot=$(macosSdk) 没有生效,或者当前 clang 没有正确找到 SDK 中的 libc++ 头文件。优先检查:

  • macosSdk 路径是否正确。
  • MacOSX12.3.sdk/usr/include/c++/v1 是否存在。
  • user-config.jam 是否被 b2 加载。
  • b2 命令是否包含 --user-config=.\user-config.jam

12.2 user-config.jam 没有生效

可以保留:

-d+2 --debug-configuration

查看 Boost.Build 是否加载了你的 user-config.jam,以及实际调用的编译器路径。

12.3 不要把 macOS arm64 和 Android arm64 混淆

macOS arm64 使用:

--target=arm64-apple-darwin

Android arm64-v8a 使用:

--target=aarch64-linux-android21

两者 ABI、系统库、目标格式都不同,不能混用。

12.4 Windows 下生成的 .a 能否直接给 macOS 用

可以,前提是 .a 内部是 Mach-O 目标文件,而不是 Windows COFF 或 Linux ELF。本文通过:

--target=...-apple-darwin
binary-format=mach-o

生成的就是 macOS Mach-O 静态库。

13. 目录整理建议

编译完成后,可以按如下方式整理:

boost_macos/include/boost/lib/x86_64/libboost_*.aarm64/libboost_*.auniversal/libboost_*.a

如果你的 macOS 工程使用 CMake,可以按架构选择对应目录,或者直接使用 universal .a

14. 小结

整个流程可以概括为:

  1. 准备 MacOSX12.3.sdk
  2. 准备 Windows 下可用的 LLVM/Clang。
  3. 在 Boost 根目录运行 bootstrap.bat 生成 b2.exe
  4. user-config.jam 中配置 x86_64-apple-darwinarm64-apple-darwin
  5. 分别运行 b2 生成 x86_64arm64 的 macOS .a
  6. 如有需要,再用 lipo / llvm-lipo 合并 universal 静态库。