Skip to content

macOS: gyp omits -isysroot when binding.gyp doesn't set SDKROOT, causing clang to resolve SDK via a path that ignores DEVELOPER_DIR/xcode-select #3344

Open

Description

@solarispika

Environment

  • node-gyp version: 9.4.1
  • Node.js version: v20.18.1
  • OS: macOS 26 (Tahoe) 26.5.1
  • Xcode: 16.2.0 (Apple clang 16.0.0), xcode-select -p -> /Applications/Xcode-16.2.0.app/Contents/Developer
  • Command Line Tools: 26.6 (installed via Software Update, independent of Xcode.app)

Bug description

On macOS, node-gyp rebuild invokes clang++ without either -isysroot or --sdk. This makes the clang driver fall back to its own internal call to xcrun --show-sdk-path (with no --sdk argument). Verified via xcrun -v that this specific call variant resolves via a cache key hard-coded to /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -- independent of xcode-select/DEVELOPER_DIR, and independent of whichever Xcode toolchain actually supplies the invoked clang++ binary. (By contrast, xcrun --sdk macosx --show-sdk-path -- i.e. with an explicit SDK name -- does respect DEVELOPER_DIR/xcode-select.)

On systems where the OS has been updated (bumping the standalone CommandLineTools SDK) but the selected Xcode.app has not, this pairs a newer SDK's libc++ headers (which reference builtins like __builtin_ctzg/__builtin_clzg, requiring Clang >= 19) with an older embedded clang (16.0.0 from Xcode 16.2), producing:

error: use of undeclared identifier '__builtin_ctzg'

Root cause in gyp's own code

gyp/pylib/gyp/xcode_emulation.py already contains logic that avoids this, by explicitly naming the SDK when querying xcrun, and by passing -isysroot:

# GetSDKVersionInfoItem
return GetStdoutQuiet(["xcrun", "--sdk", sdk, infoitem])   # explicit --sdk, respects DEVELOPER_DIR

# GetCflags
if "SDKROOT" in self._Settings() and sdk_root:
    cflags.append("-isysroot %s" % sdk_root)               # only appended if SDKROOT is explicitly set in xcode_settings

(Line numbers 525 / 581-582 in the 9.4.1 tag I actually hit this on; confirmed the exact same structure -- same gating -- is still present at line 522 / 578-579 in the current latest release, v13.0.1, published 2026-07-02. Also checked that release's CHANGELOG for any macOS SDK/clang-related fix; found none.)

This mirrors what CMake does (Modules/Platform/Darwin-Initialize.cmake: xcrun --sdk macosx --show-sdk-path), which correctly resolves the SDK paired with the active developer directory, and does not hit this issue on the same machine/OS update.

However, -isysroot is only emitted if the target's binding.gyp (or a shared .gypi) sets SDKROOT under xcode_settings. Neither node-gyp's own addon.gypi, node's cached common.gypi, nor common third-party native modules (checked diskusage, better-sqlite3, node-pty) set SDKROOT. As a result, no -isysroot is passed for ordinary builds, and clang falls through to the SDK-resolution path described above.

Steps to reproduce

  1. On a Mac where the standalone CommandLineTools SDK has been updated (via Software Update) to a version requiring Clang >= 19 builtins, while xcode-select still points at an older Xcode with an older embedded clang.
  2. npm install / yarn install any package with a native addon (e.g. diskusage, better-sqlite3, node-pty).
  3. node-gyp rebuild fails with __builtin_ctzg/__builtin_clzg undeclared identifier errors, even though xcodebuild/CMake-based builds on the same machine succeed.

Expected behavior

The SDK-resolution path gyp uses by default (when SDKROOT is not explicitly configured) should pair the SDK with the actual compiler in use -- e.g. by always querying xcrun --sdk macosx --show-sdk-path instead of the no---sdk variant -- rather than relying on a system-wide default that can silently drift out of sync with the selected Xcode/CLT compiler.

Workaround (confirmed working)

Pin toolchain and SDK together, either direction:

DEVELOPER_DIR=/Library/Developer/CommandLineTools npm install   # newer clang + newer SDK, paired
SDKROOT=/Applications/Xcode-16.2.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.2.sdk npm install   # older clang + older SDK, paired

Related

Activity

  1. cclauss commented on Jul 9, 2026

    @cclauss
    Contributor

    Please create a pull request:
    Commit 1: A GitHub Action that fails because of this problem.
    Commit 2: A fix that also enables the GitHub Action to pass.

  2. cclauss commented on Jul 9, 2026

    @cclauss
    Contributor

    Environment
    node-gyp version: 9.4.1
    Node.js version: v20.18.1

    Node-gyp v13 is current.
    Node.js v20 is no longer supported.
    https://nodejs.org/en/about/previous-releases#looking-for-the-latest-release-of-a-version-branch

    Can you please replicate on current software.

    Steps to reproduce 1 seems like a misconfiguration that even Apple would not support.

  3. solarispika commented on Jul 13, 2026

    @solarispika
    Author

    Reproduced on Node.js v24.15.0 + node-gyp v13.0.1, minimal repro (std::sort on a std::vector<int>, no SDKROOT in binding.gyp):

    node-gyp rebuild                                              # error: no matching function for call to '__countl_zero' / not ok
    DEVELOPER_DIR=/Library/Developer/CommandLineTools node-gyp rebuild   # gyp info ok
    

    So this isn't limited to the originally reported Node/node-gyp versions. The relevant SDKROOT gating in xcode_emulation.py is unchanged between v9.4.1 and v13.0.1, consistent with the same mechanism still applying.

    On the host configuration: the standalone CommandLineTools SDK can be updated by Software Update independently of whichever Xcode.app is selected via xcode-select/DEVELOPER_DIR. The same class of failure (newer CLT SDK headers vs. an older paired compiler) has shown up in other projects too, e.g. winfunc/opcode#233 (worked around with SDKROOT pointing at the older SDK) and terasakisatoshi/CxxFork.jl#24 (fixed by no longer blindly preferring the newer SDK symlink when its libc++ headers are incompatible with the embedded Clang).

    Also, on this machine Xcode 16.2's GUI no longer launches on macOS 26 (spctl -a -vv: "a sealed resource is missing or invalid"). Regardless of that, the command-line toolchain in that same Xcode still builds successfully when tools explicitly resolve the SDK through xcrun --sdk macosx under DEVELOPER_DIR -- our CI does exactly that via CMake, on this same macOS 26 machine, no errors. The observable difference is that node-gyp doesn't pass an explicit SDK path in this case unless SDKROOT is set in binding.gyp.

    Attached a minimal repro repo (binding.gyp + one .cc file), including the setup recipe I used (via xcodes + softwareupdate) for anyone who isn't already in this state.

    node-gyp-sdk-repro.zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions