Nixpkgs Darwin (macOS) 平台打包实战指南:stdenv 差异、SDK 与部署目标排查
Nixpkgs Darwin (macOS) 平台打包实战指南stdenv 差异、SDK 与部署目标排查【免费下载链接】nixpkgsNix Packages collection NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs本篇指南聚焦于 Nixpkgs 中 DarwinmacOS平台特有的打包细节面向所有需要在 Darwin 上构建或移植软件包的开发者。文章以 doc/stdenv/platform-notes.chapter.md 为骨架结合仓库中darwinMinVersionHook的实现、平台属性定义与相关 hook 源码完整覆盖 Darwinstdenv与 Linux 的核心差异、SDK 与部署目标deployment target的选择、xcrun/xcodebuild使用、动态库 install name 修复、从旧式apple_sdk迁移到新式apple-sdk以及 Darwin 交叉编译等关键场景。读完本文你将能判断自己的 derivation 是否需要 Darwin 特殊处理并掌握一套可复现的排错与修复方法论。快速判断你的包是否需要 Darwin 特殊处理Darwin 的stdenv与其他平台尤其是 Linux在几个关键假设上不同这些差异反映了该平台上构建软件的默认约定。好消息是绝大多数软件在编写时已经考虑到了这些平台差异因此你通常可以完全忽略它们。推荐的实践路径非常直接先按常规方式编写 derivation不加入任何 Darwin 专属特例在 Darwin 上尝试构建如果构建成功任务完成跳过本文其余内容如果失败再回到这里对照后续章节逐一排查。也就是说先假设一切已经为你配置妥当大多数情况下也确实如此遇到问题再对症下药。Darwin stdenv 与 Linux 的五大核心差异1. 默认编译器是 Clang 而非 GCCDarwin 平台默认使用 Clang。大多数引用$CC或cc的包都能直接工作但有些包会在构建脚本中硬编码gcc或g。针对这类情况通常可以通过设置makeFlags修正makeFlags [ CCcc CXXC ];如果这样仍不生效就需要自行修补构建脚本使其在 Darwin 上使用正确的编译器。2. 默认使用系统 libcDarwin 默认链接系统的 libc以避免混用 LLVM libc 与系统 libc 造成 ODR 违规及潜在兼容性问题。虽然混用两种实现通常也能工作但二者并不保证 ABI 兼容且上游将它们视为两个独立实现。若你需要使用超出默认部署目标支持范围的更新 C 库特性请参考下文libc 版本问题的排查章节。3. 构建需要 SDKDarwin 构建软件离不开 SDK它提供一套默认的 framework 与库其中大部分是 Darwin 特有的。Nixpkgs 中存在多个版本的 SDK 包但stdenv默认携带其中一个通常情况下你不需要更换或挑选拿不准就用默认的。构建所用的 SDK 可以通过DEVELOPER_DIR环境变量获知交叉编译时该变量还存在面向不同角色的变体详见后文交叉编译章节。同时SDKROOT变量被设置为 SDK 库与 framework 的路径且SDKROOT始终是DEVELOPER_DIR的子目录。4. 平台工具xcrunDarwin 有一个平台特定的工具xcrun用于帮助构建过程定位所需二进制。stdenv在 Darwin 上自带一个xcrun。如果你的包通过绝对路径调用xcrun例如/usr/bin/xcrun需要修补构建脚本改为直接使用xcrun。5. 动态库的 install name 机制Darwin 上的库通常以绝对路径链接这由链接期解析的install name决定。部分包没有正确设置 install name会导致依赖它的二进制在运行时找不到库。修复手段包括追加链接器参数或在fixupPhase中使用install_name_tool详见下文专门章节。Darwin 问题排查手册报错某些 API 不可用C 库版本问题有些较新的 API 仅通过头文件即可使用另一些则要求系统 libc 具备相应 API 支持。当后者发生时libc 会把未使用正确的部署目标视为错误导致构建失败。要让新 API 可用需要将部署目标提升到所需版本。理论上也可以改用 LLVM 提供的 libc但不推荐——多个 libc 实现同时链入同一个二进制例如来自不同依赖会引发严重问题。以std::print为例它依赖 macOS 13.3 及以上版本才有的特性。通过darwinMinVersionHook将部署目标设为 13.3 即可stdenv.mkDerivation { name libfoo-1.2.3; # 上游声明最低支持版本为 12.5 buildInputs [ (darwinMinVersionHook 12.5) ]; }darwinMinVersionHook的底层实现该 hook 在仓库中定义为pkgs/os-specific/darwin/darwin-min-version-hook其核心逻辑见 setup-hook.sh。其实现要点如下hook 首先通过getHostRole确定当前构建角色从而选择正确的环境变量名将请求的部署目标与现有部署目标按%02d%02d%02d格式规范化后做版本号比较只有当请求的版本严格大于当前值时才导出新的部署目标变量脚本第 22-24 行if [ $darwinMinVersion -gt $currentDeploymentTarget ]; then export $darwinMinVersionVardeploymentTarget fi注意部署目标变量名的具体取值由平台决定。在 lib/systems/default.nix 中可以看到macOS 平台对应MACOSX_DEPLOYMENT_TARGET而 iOS 平台对应IPHONEOS_DEPLOYMENT_TARGET。这也解释了为何文档强调可以同时存在多个不同实例的darwinMinVersionHook取版本最高者生效——因为 hook 内部做了取最大值语义。报错缺少 API 可用性检查availability 相关这类失败通常是包本身的 bug或部署目标配置错误需要分三种情况处理包在低版本目标上使用了新版本 API例如目标为 macOS 14.0 却使用了 macOS 26.0 的 API应该修补代码为其加上可用性检查__builtin_available。该内建函数同样适用于 C/C对应 Objective-C 的available。包确实要求新平台即不支持在旧版本上以降级功能运行使用darwinMinVersionHook将部署目标提升到所需版本。包通过其他机制处理了兼容性例如 MoltenVK 依赖运行平台的 MSL 版本可以抑制该错误在env.NIX_CFLAGS_COMPILE中加入-Wno-errorunguarded-availability。报错缺少 framework 或符号使用非默认 SDK当包需要默认 SDK 中不存在的 API 时就必须改用非默认 SDK。例如 Metal Performance Shaders 在 macOS 12 才加入若默认 SDK 是 11.3依赖它的包会因缺少 framework 与符号而构建失败。使用非默认 SDK 的方法很轻量把它加进 derivation 的buildInputs即可无需覆盖stdenv中的 SDK也无需改动依赖所用的 SDK。如果你的 derivation 需要在构建期使用非默认 SDK例如为depsBuildBuild的编译器提供 SDK请查阅交叉编译文档确定应放入哪个 input。选择 SDK 的决策顺序先用默认 SDK 尝试构建能成功就结束若包指定了具体版本使用它版本映射见下文表格若包文档说明其在更新的 SDK 上支持可选特性可考虑启用这些特性的 SDK拿不准就回到默认 SDK。需要注意inputs 中可以同时存在多个不同版本的 SDK版本最高者总是生效。stdenv.mkDerivation { name libfoo-1.2.3; # ... buildInputs [ apple-sdk_14 ]; }什么是部署目标最低版本部署目标指运行该应用所需的最低 macOS 版本。多数情况下默认值即可不确定就什么都不用改。只有少数包需要设置非默认部署目标以访问特定 API方法仍是darwinMinVersionHook。确定部署目标的两种主要途径上游文档声明了部署目标或最低版本直接使用构建因某 API 要求特定版本而失败使用报错提示的版本。其余情况通常无需显式指定最低版本。同样的规则适用多个不同实例的darwinMinVersionHook并存时最高版本生效。如何挑选 SDK 版本Xcode ↔ SDK 映射表下面列出 Xcode 版本、Nixpkgs 中的 SDK 版本以及对应的 Nixpkgs 属性名。请查阅包文档平台支持或安装说明确定应使用的 Xcode/SDK 版本。一般而言每个大版本只打包最后一个 SDK 发行版。Xcode 版本SDK 版本Nixpkgs 属性15.0–15.414.4apple-sdk_14/apple-sdk16.015.0apple-sdk_1526.026.0apple-sdk_26等说明仓库中的 Xcode 包集合见 pkgs/os-specific/darwin/xcode/default.nix按xcode_版本形式提供了从 Xcode 8.1 到 26.x 的完整版本序列而 SDK 打包则遵循上述仅大版本最后一个发行版原则。此外旧版 SDK 所对应的旧式darwin.apple_sdk系列已被移除详见下文迁移章节。Darwin 默认 SDK 与部署目标当前默认 SDK 版本与部署目标最低支持版本由 Darwin 专属的平台属性darwinSdkVersion和darwinMinVersion指示。由于最低版本和 SDK 存在一些 Nix 无法感知的变更途径应将这两个属性视为下界lower bounds若需要针对具体版本参数化应编写接收版本参数的函数而不是直接依赖这些属性。当前默认值来自 lib/systems/default.nixmacOSdarwinMinVersion为14.0darwinSdkVersion为14.4平台文件中darwinSdkVersion final.sdkVer or 14.4即默认回退值。xcrun找不到二进制xcrun会搜索PATH与 SDK 工具链来定位要运行的二进制找不到时即失败。解决方法是把提供该二进制的包加入 derivation 的nativeBuildInputs若失败发生在运行测试阶段则加入nativeCheckInputsstdenv.mkDerivation { name libfoo-1.2.3; # ... nativeBuildInputs [ bison ]; buildCommand xcrun bison foo.y # 生成 foo.tab.c # ... ; }包依赖xcodebuild使用 xcbuildxcbuild 包为真正依赖 Xcode 的包提供了xcodebuild命令。该替代实现并非 100% 兼容可能遇到某些问题但能构建大量包。使用方式将xcbuildHook加入nativeBuildInputs它会为 derivation 提供buildPhase用xcbuildFlags向xcodebuild传参例如必需的 scheme。hook 的具体行为见 doc/hooks/xcbuild.section.md它覆盖 build 与 install 阶段以运行 xcbuild 命令适用于只带 Xcode 构建文件的项目也可以通过自定义buildPhase/configurePhase关闭。重要细节如果 scheme 名称包含空格必须设置__structuredAttrs true因为带空格的标志在非结构化属性模式下会被拆分。MoltenVK 是使用 xcbuild 搭建的参考示例。stdenv.mkDerivation { name libfoo-1.2.3; xcbuildFlags [ -configuration Release -project libfoo-project.xcodeproj -scheme libfoo Package (macOS only) ]; __structuredAttrs true; }修复指向xcodebuild/xcrun/PlistBuddy的绝对路径许多构建系统把xcodebuild、xcrun、PlistBuddy硬编码为/usr/bin/xcodebuild、/usr/bin/xcrun、/usr/libexec/PlistBuddy。这些绝对路径需要替换为相对路径如果用到xcodebuild或PListBuddy还需引入 xcbuild 包stdenv.mkDerivation { name libfoo-1.2.3; postPatch substituteInPlace Makefile \ --replace-fail /usr/bin/xcodebuild xcodebuild \ --replace-fail /usr/bin/xcrun xcrun \ --replace-fail /usr/bin/PListBuddy PListBuddy ; }在 Darwin 上使用 libiconvlibiconv 包随 SDK 默认提供同时默认包含的还有 libresolv 与 libsbuf。使用这些包无需任何额外操作。若 derivation 需要iconv二进制才需要把libiconv加入nativeBuildInputs测试阶段则用nativeCheckInputs。动态库 install name 问题Darwin 上的库通常以绝对路径链接由链接期解析的 install name 决定。包未正确设置 install name 时链接它的二进制在运行时找不到库。修复方式有两种通过链接器参数设置 install namestdenv.mkDerivation { name libfoo-1.2.3; # ... makeFlags lib.optional stdenv.hostPlatform.isDarwin LDFLAGS-Wl,-install_name,$(out)/lib/libfoo.dylib; }通过install_name_tool设置 install namestdenv.mkDerivation { name libfoo-1.2.3; # ... postFixup # -id install_name 指定 install name最后一个参数是库路径。 ${stdenv.cc.targetPrefix}install_name_tool -id $out/lib/libfoo.dylib $out/lib/libfoo.dylib ; }测试阶段找不到未安装的库即便库以绝对路径链接且 install name 解析正确checkPhase中的测试有时仍因二进制链接了尚未安装的库而运行失败。通常的解决办法是把测试放到installPhase之后运行或使用DYLD_LIBRARY_PATH详见dyld(1)手册。批量修复fixDarwinDylibNameshook若包中有大量需要修复的 dylib虽然更可取的做法是在包构建中修复源头但也可以通过把fixDarwinDylibNameshook 加入nativeBuildInputs一次性更新全部该 hook 会扫描包的所有 outputs 中的 dylib 并修正其 install name。注意如果 outputs 中有二进制链接了这些 dylib可能仍需用install_name_tool将二进制内的引用替换为正确路径。传播 SDK进阶仅编译器适用SDK 本身是一个包可以被传播带版本参数的darwinMinVersionHook同样可以传播。但绝大多数包不应这样做例外是编译器。原因在于传播 SDK 后它就变成了 derivation 公共 API 的一部分后续更换或移除 SDK 都可能构成破坏性变更——这正是只推荐编译器传播的原因。编写编译器 derivation 时应仅针对你预期用户使用该编译器的方式传播 SDK视使用场景可能需要以下一项或全部当编译器预期被加入nativeBuildInputs时放入depsTargetTargetPropagated这能确保 SDK 实际成为目标 derivationbuildInputs的一部分若编译器通过 hook 使用则放入 hook 的depsTargetTargetPropagated效果同上若包使用 builder 模式更新 builder 将 SDK 加入 derivation 的buildInputs。拿不准要不要传播 SDK 时就不要传播。如果你的包是编译器或语言工具链且仍有疑问可向 NixOS/darwin-maintainers 寻求帮助。从旧式apple_sdk迁移到新式apple-sdk你可能会在代码中看到darwin.apple_sdk.frameworks的引用这是正在被淘汰的旧式 SDK 模式。目前darwin.apple_sdk、darwin.apple_sdk_11_0、darwin.apple_sdk_12_3下的所有包都已被移除。如果 derivation 引用了它们应删除这些引用——默认 SDK 通常足以构建你的包。新旧模式的区分非常直观也是判断是否旧式的唯一标准新式 SDK 命名为apple-sdk与 Nixpkgs 命名约定保持一致旧式 SDK 命名为apple_sdk下划线。有些 derivation 依赖旧包中 framework 的位置。要更新 derivation 以在新 SDK 中找到它们请在preConfigure中使用$SDKROOT。例如若你在postPatch中替换了${darwin.apple_sdk.frameworks.OpenGL}/Library/Frameworks/OpenGL.framework应改为在preConfigure中替换$SDKROOT/System/Library/Frameworks/OpenGL.framework。注意若 derivation 正在修改的是系统路径如/System/Library/Frameworks/OpenGL.framework甚至可以直接删除该路径——面向 Darwin 的编译器和 binutils 会在 SDK sysroot 中查找系统路径部分工具如 Zig、Rust 的bindgen依赖这一行为。旧式 SDK 覆盖机制已被移除旧式 SDK 曾提供两种覆盖默认 SDK 的途径现已随旧式 SDK 一并移除pkgs.darwin.apple_sdk_11_0.callPackage曾用于提供 macOS 11 SDK 的 framework现在与callPackage等价overrideSDK该 stdenv 适配器会尝试替换 derivation 及其传递依赖所用的 framework——它为 12.3 添加apple-sdk_12包对 11.0 则不做任何事若指定了darwinMinVersion会添加带对应最低版本的darwinMinVersionHook且不支持其他 SDK 版本。Darwin 交叉编译Darwin 支持 Darwin 平台之间的交叉编译但目前不支持从 Linux 交叉编译到 Darwin未来可能支持。要对 Darwin 进行交叉编译可以设置crossSystem或使用pkgsCross中的某个 Darwin 系统。darwinMinVersionHook与 SDK 都支持交叉编译。若需要为depsBuildBuild编译器指定不同的 SDK 版本将其加入nativeBuildInputs即可stdenv.mkDerivation { name libfoo-1.2.3; # ... depsBuildBuild [ buildPackages.stdenv.cc ]; nativeBuildInputs [ apple-sdk_12 ]; buildInputs [ apple-sdk_13 ]; depsTargetTargetPropagated [ apple-sdk_14 ]; } # build-build 阶段的 clang 将使用 12.3 SDK包本身构建使用 13.3 SDK。 # 将此包作为 input 的 derivation 将获得 14.4 SDK 的传播。不同角色的 SDK 与部署目标环境变量不同的目标 SDK 与 hook 会根据构建角色role进行名称修饰交叉编译时尤其需要注意DEVELOPER_DIR_FOR_BUILD与MACOSX_DEPLOYMENT_TARGET_FOR_BUILD—— 用于构建平台build platformDEVELOPER_DIR与MACOSX_DEPLOYMENT_TARGET—— 用于宿主平台host platformDEVELOPER_DIR_FOR_TARGET与MACOSX_DEPLOYMENT_TARGET_FOR_TARGET—— 用于目标平台target platform。在静态编译场景中构建平台与宿主平台可能是同一平台却拥有同版本但不同的 SDK一个动态、一个静态。cc-wrapper 与 bintools-wrapper 会负责处理这一区别。实践清单把本文内容浓缩为一份可执行的检查清单先按常规写 derivation 并尝试构建绝大多数情况到此结束编译器报错检查是否硬编码gcc/g用makeFlags [ CCcc CXXC ]修正C API 不可用用darwinMinVersionHook提升部署目标该 hook 内部对版本做取最大值比较见 setup-hook.sh缺 framework/符号把合适的apple-sdk_XX加入buildInputsxcrun找不到二进制把对应包加入nativeBuildInputs测试阶段用nativeCheckInputs依赖 Xcode 构建系统用xcbuildHookxcbuildFlagsscheme 含空格时设__structuredAttrs true硬编码的/usr/bin/xcodebuild等绝对路径用substituteInPlace替换为相对路径运行期找不到 dylib用链接器参数、install_name_tool或fixDarwinDylibNames修复 install name检查是否引用了已移除的apple_sdk下划线统一迁移到apple-sdk并使用$SDKROOT交叉编译按角色区分_FOR_BUILD/普通/_FOR_TARGET环境变量注意 cc-wrapper 对静态 SDK 场景的处理。当前默认值速查darwinMinVersion 14.0、darwinSdkVersion 14.4可通过 lib/systems/default.nix 中的平台属性确认或参数化覆盖。需要深入参考时可继续阅读 doc/stdenv/stdenv.chapter.md 与 doc/stdenv/cross-compilation.chapter.md 获取 stdenv 与交叉编译的完整背景。【免费下载链接】nixpkgsNix Packages collection NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考