乐于分享
好东西不私藏

借助 Claude Code 排查 ios-cmake 工具链编译 nng 时的链接问题

借助 Claude Code 排查 ios-cmake 工具链编译 nng 时的链接问题
近日在使用 GitHub Actions 于 macOS 上编译 nng 库时,遭遇了一个令人困惑的链接错误,最终借助 Claude Code 找到了根因并顺利解决,在此记录分享。
先来看一下相关的构建工作流配置:
  build-apple:    name: build-${{ matrix.rid }}    if: >-      github.event.pull_request.draft == false    runs-on: macOS-latest    strategy:      fail-fast: false      matrix:        include:          - rid: ios-arm64            os: OS64          - rid: iossimulator-arm64            os: SIMULATORARM64          - rid: iossimulator-x64            os: SIMULATOR64          - rid: osx            os: MAC_UNIVERSAL          - rid: osx-arm64            os: MAC_ARM64          - rid: osx-x64            os: MAC    steps:      - name: Checkout        uses: actions/checkout@v6        with:          submodules: true      - name: Clone nng        run: |          git clone https://github.com/nanomsg/nng.git          pushd nng          git checkout ${{ env.NNG_VERSION }}          popd      - name: Run uname        run: uname -m      - name: Configure        run: >          cmake          -S nng          -B build          -DBUILD_SHARED_LIBS=ON          -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/toolchains/ios-cmake/ios.toolchain.cmake          -DPLATFORM=${{ matrix.os }}          -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }}          -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_PREFIX }}          -DCMAKE_MACOSX_BUNDLE=OFF          -DNNG_ELIDE_DEPRECATED=ON          -DNNG_ENABLE_COVERAGE=OFF          -DNNG_ENABLE_NNGCAT=ON          -DNNG_ENABLE_TLS=OFF          -DNNG_TESTS=OFF          -DNNG_TOOLS=ON      - name: Build & Install        run: |          cmake --build build --target install      - name: Run file command        run: file ./${{ env.INSTALL_PREFIX }}/lib/*      - name: Upload artifacts        uses: actions/upload-artifact@v7        with:          name: nng-${{ env.NNG_VERSION }}-${{ matrix.rid }}          path: ${{ env.INSTALL_PREFIX }}
上述配置使用 ios-cmake 工具链为 macOS 和 iOS 平台编译 nng 动态库。然而,在实际构建过程中出现了如下错误:
完整错误信息如下:
ld: library 'nsl' not foundclangerror: linker command failed with exit code 1 (use -v to see invocation)make[2]: *** [libnng.2.0.0-dev.dylibError 1make[1]: *** [CMakeFiles/nng.dir/all] Error 2make: *** [all] Error 2
简而言之,链接器在尝试链接 nsl 库时找不到该库,导致构建失败。
追溯到相关的 CMake 代码片段:
# src/platform/posix/CMakeLists.txt    if (NNG_PLATFORM_SUNOS)        nng_defines(NNG_HAVE_SOCKETPAIR)        nng_check_lib(nsl gethostbyname NNG_HAVE_LIBNSL)        nng_check_lib(socket socket NNG_HAVE_LIBSOCKET)    else()        nng_check_lib(nsl gethostbyname NNG_HAVE_LIBNSL)        nng_check_lib(socket socket NNG_HAVE_LIBSOCKET)    endif()
# cmake/NNGHelpers.cmakefunction(nng_check_lib LIB SYM DEF)    check_library_exists(${LIB} ${SYM} "" ${DEF})    if (${DEF})        target_compile_definitions(nng PRIVATE ${DEF}=1)        target_compile_definitions(nng_testing PUBLIC ${DEF}=1)        target_compile_definitions(nng_private INTERFACE ${DEF}=1)        target_link_libraries(nng PRIVATE ${LIB})        target_link_libraries(nng_testing PRIVATE ${LIB})    endif ()endfunction(nng_check_lib)
这段代码的逻辑是:先检测 nsl 库中是否存在 gethostbyname 符号,若存在则将 NNG_HAVE_LIBNSL 置为 ON,并在链接阶段引入 nsl 库。
CMake 配置阶段的日志输出如下:
于是先向 ChatGPT 咨询,得到了如下解释:

在 macOS 上用 CMake 编译时,如果你使用 gethostbyname,通常会遇到和 Linux 不同的情况——不需要额外链接 -lnsl,因为相关符号已经在系统库里(libSystem)提供了。

nng 官方应当也在 macOS 上做过构建测试,于是查阅了官方仓库的构建日志:
对比来看,使用 ios-cmake 工具链时,配置阶段的输出为:
-- Looking for gethostbyname in nsl-- Looking for gethostbyname in nsl - found
而官方仓库(不使用 ios-cmake)的配置阶段输出为:
-- Looking for gethostbyname in nsl-- Looking for gethostbyname in nsl - not found
带着这个疑问,我在 nng-ci-build 仓库中运行了 Claude Code,并提交了以下问题:
通过 GitHub Actions 使用 ios-cmake 在 macOS 构建 nng 动态库时,报错:ld: library 'nsl' not found但是,nng 官方仓库的构建(不使用 ios-cmake)就不会报错。我看了一下,前者 check_library_exist(nsl gethostbyname "") 会提示在 nsl 中找到 gethostbyname,但后者就是没找到。为什么?
Claude 给出了详细的分析:
● 根本原因:CMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY  问题链条  ios-cmake 的 toolchain 文件(几乎所有 iOS 交叉编译 toolchain 都会这样做)通常包含:  set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY")  这是为了让 try_compile 在交叉编译时不需要真正链接可执行文件(因为宿主机无法运行目标平台的二进制)。  但这直接导致了误报:  check_library_exist 的实现方式  # CMake 内部展开大致如下:  try_compile(VARIABLE    ${CMAKE_BINARY_DIR}    test_source.c    CMAKE_FLAGS "-DLINK_LIBRARIES=${LOCATION} ${LIBRARY}"    ...)  ┌────────────────────┬────────────────────────────────────────┬─────────────────────────┐  │        模式        │                实际行为                │          结果           │  ├────────────────────┼────────────────────────────────────────┼─────────────────────────┤  │ 默认(EXECUTABLE) │ 编译 + 链接,-lnsl 不存在 → 链接器报错 │ NOTFOUND ✓              │  ├────────────────────┼────────────────────────────────────────┼─────────────────────────┤  │ STATIC_LIBRARY     │ 只编译 .o,不调用链接器                │ 永远成功 → 误报 FOUND ✗ │  └────────────────────┴────────────────────────────────────────┴─────────────────────────┘  所以 check_library_exist(nsl gethostbyname "") 在 ios-cmake 下"找到"了 nsl,只是因为根本没有真正去链接它。  为什么会进一步报错  nng 的 CMakeLists.txt 中:  check_library_exist(nsl gethostbyname "" NNG_HAVE_NSL)  if(NNG_HAVE_NSL)    target_link_libraries(nng PRIVATE nsl)  # 这里才真正调用链接器  endif()  check_library_exist 误报 → NNG_HAVE_NSL=TRUE → 实际构建时链接器找不到 libnsl → library 'nsl' not found。  验证方法  在你的 CMake 构建日志(CMakeFiles/CMakeOutput.log)中搜索 nsl,会看到 try_compile 的命令里没有 ld 调用,只有 ar 打静态库。  修复方案  在调用 nng 的 CMakeLists 或 workflow 里,在 ios-cmake toolchain 生效之后、nng configure 之前强制覆盖:  set(NNG_HAVE_NSL FALSE CACHE BOOL "" FORCE)  或者在 cmake 命令行加:  - run: cmake ... -DNNG_HAVE_NSL=FALSE  macOS 上 gethostbyname 在 libSystem(libc)里,从来不需要 -lnsl,这是 Linux 特有的库,macOS 根本没有 libnsl。
顺着这条线索,查阅 ios-cmake 中 ios.toolchain.cmake 的源码,搜索 STATIC_LIBRARY 关键字,发现了一个相关配置项:
# ENABLE_STRICT_TRY_COMPILE: (ON|OFF) Enables or disables strict try_compile() on all Check* directives (will run linker#    to actually check if linking is possible). Default OFF (will set CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY)
在 CMake 配置命令中追加该选项:
-DENABLE_STRICT_TRY_COMPILE=ON
至此,问题得到彻底解决。
回顾这次排查过程,Claude Code 之所以能快速切中要害,在于它能够通读项目中的全部代码,从工具链配置到 CMake 脚本,再到 nng 的构建逻辑,形成完整的上下文认知。这种"全局视野"正是它的优势所在——人工排查时,往往需要在多个文件、多层调用之间反复跳转,容易遗漏细节;而 Claude Code 能够将所有相关代码一并纳入分析,从而更准确地识别出跨文件、跨层次的隐性关联,帮助程序员迅速锁定问题根源。
本文草稿由人工撰写,并使用 Claude Code 润色。