Repository navigation
Description
ldflags support landed in #476, and it shares its path with cflags. Both fields produce broken builds for valid binding.gyp input, and cflags has every problem below except where noted.
Line references are to transformer.ts at ff237c0. The flags go through .flatMap(mapExpansion).map(escapeSpaces) (lines 122-123) and are then joined unquoted into target_compile_options / target_link_options (lines 225-233).
Reproduced by generating a CMakeLists from bindingGypToCmakeLists and building it with CMake 4.4.3 and Ninja on macOS arm64. The ldflags Android result used NDK 27.1.
Problems
1. Flags are emitted on platforms where gyp ignores them
On mac, gyp's make.py takes both flag sets from xcode_settings (GetCflags at line 1416, GetLdflags at line 1717). It only reads the target-level cflags and ldflags on other platforms. We emit them everywhere, so flags written for Linux reach Apple builds.
| Field | Input | Result |
|---|---|---|
ldflags |
-Wl,--as-needed |
macOS: ld: unknown options: --as-needed |
ldflags |
-Wl,-dead_strip |
Android: ld.lld: error: unknown argument '-dead_strip' |
cflags |
-msse2, -mfpu=neon, -fno-gnu-unique |
Rejected by Apple clang on arm64 (checked by compiling directly, not through the generator) |
2. CMake de-duplicates options, which corrupts multi-token flags
| Field | Input | Result |
|---|---|---|
cflags |
["-include", "a.h", "-include", "b.h"] |
no such file or directory: 'b.h' |
cflags |
["-Xclang", "-fno-pch-timestamp", "-Xclang", "-fno-validate-pch"] |
Second -Xclang dropped: unknown argument '-fno-validate-pch' |
cflags |
<!@echo -include a.h -include b.h |
Same as the first row |
ldflags |
["-Xlinker", "-rpath", "-Xlinker", "/a/b"] |
Becomes -Xlinker -rpath /a/b |
ldflags |
<!@echo -framework CoreFoundation -framework Foundation |
Becomes -framework CoreFoundation Foundation |
3. escapeSpaces turns a multi-word flag into one argument
gyp's QuoteIfNecessary only quotes flags containing ", so a flag with spaces is split by the shell. We keep it as one argument.
| Field | Input | Result |
|---|---|---|
cflags |
-include a.h |
fatal error: ' a.h' file not found |
ldflags |
-framework CoreFoundation |
unknown argument: '-framework CoreFoundation' |
The existing tests assert this escaped output as correct, including -DNAME=value\ with\ space for cflags.
4. Flags aren't quoted as CMake arguments
| Field | Input | Result |
|---|---|---|
cflags |
-DX=a#b |
CMake configure error |
cflags |
-DX=a;b |
Split into two arguments, build fails |
cflags |
-DX=(1) |
Split at the parentheses, build fails |
cflags |
-IC:\inc |
CMake configure error, because cflags and ldflags skip transformPath |
ldflags |
-Wl,-rpath,"/a b" |
Builds, but the rpath contains literal quotes |
-DGREETING="hi" in cflags builds correctly.
5. gyp's $$ escape isn't unescaped
-Wl,-rpath,'$$ORIGIN' builds, but the rpath is literally '$$ORIGIN'. There's no build error, so it only shows up at runtime. This is specific to ldflags in practice.
6. gyp expansions inside a string aren't handled
isCmdExpansion (line 22) only checks the start of the value, and <(var) isn't handled at all. Both -I<!(pwd)/inc and -I<(module_root_dir)/inc pass straight through and fail with no such file or directory: '('. The same applies to -L in ldflags.
Suggested fix
-
Wrap the emitted
cflagsandldflagslines inif(NOT APPLE)to match gyp. This has not been tested. Apple builds then get none of these flags untilxcode_settingsis read. -
Emit all flags for a target as one quoted
SHELL:argument, which fixes 2 and 3 and most of 4. This form built correctly on macOS for repeated-include,-frameworkand-Xlinkergroups, for#and(), and for;when escaped as\;:target_compile_options(foo PRIVATE "SHELL:-include a.h -include b.h") target_link_options(foo PRIVATE "SHELL:-framework CoreFoundation -framework Foundation")
Those cases were hand-written and shell-quoted, so the generator still needs an escaping function that handles shell and CMake quoting. It also needs a decision for a flag that really is one argument containing spaces, since word-splitting matches gyp but changes the current
-DNAME=value with spacebehaviour. -
Add tests that run
cmakeconfigure on the generated CMakeLists and assert on the flags CMake resolves, in place of comparing strings. No build or addon compile is needed: CMake applies de-duplication and argument parsing at configure time. Generating the example from problem 2 and only configuring gives these link fragments from the File API:["-framework", "CoreFoundation", "Foundation"]
- Read the result through
cmake-file-api(compileCommandFragmentsandlink.commandFragments), added as a dev dependency.compile_commands.jsonalso shows compile flags, but not link flags. - This should catch problems 2 to 6 by asserting on the resolved flags and on configure failures. Problem 1 needs an assertion per host, so that the flags are absent on
macos-latestand present onubuntu-latest. project()still detects a compiler during configure, andunit-testsruns on Ubuntu, Windows and macOS, so check that CMake is available on all three. Assertions shouldn't depend on how each toolchain groups fragments.
- Read the result through
Not covered
- Not read at all:
conditions(Improvegyp-to-cmaketo handle "conditions" #98),xcode_settings(OTHER_CFLAGS,OTHER_LDFLAGS),cflags_ccandlibraries. Issues Improvegyp-to-cmaketo handle "cflags" #99 and Improvegyp-to-cmaketo handle "ldflags" #100 can't really close until those are handled. sources,include_dirsanddefinesalso useescapeSpacesand unquoted arguments. They weren't tested.- Not tested: real iOS SDK builds, Windows, the Makefile generator, or a side-by-side build with node-gyp.
Activity
fix(gyp-to-cmake): resolve cflags and ldflags like gyp