From cafd9462c07f45122ba3c61ac422c26fd66af955 Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Sun, 6 Sep 2026 18:45:21 -0700 Subject: [PATCH 1/3] Separate debug symbols for the _lbug extension Enforce hidden visibility for smaller binaries and keep debug info in Release builds (-g / /Zi+/DEBUG) so it can be split into separate symbol files: .dSYM on macOS (dsymutil + strip -S), .pdb on Windows (native), .debug on Linux (objcopy split + gnu-debuglink). Wheel builds consume these via split_debug_symbols.py in the main repo. --- CMakeLists.txt | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ce8c81..da7f108 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,70 @@ set_target_properties(_lbug RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/ladybug" ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/ladybug") +# Ensure hidden visibility is enforced for smaller binary sizes. +set_target_properties(_lbug PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON +) + +# Enable debug symbols even in Release mode so they can be extracted into +# separate symbol files (see below, and +# scripts/pip-package/split_debug_symbols.py used by python-wheel-workflow.yml). +target_compile_options(_lbug PRIVATE + $<$:/Zi> + $<$:-g> +) +target_link_options(_lbug PRIVATE + $<$:/DEBUG> +) + +# --- Cross-Platform Symbol Separation --- +if(APPLE) + # macOS: Extract symbols into a .dSYM bundle and strip the binary. + find_program(DSYMUTIL dsymutil) + find_program(STRIP strip) + if(DSYMUTIL AND STRIP) + add_custom_command(TARGET _lbug POST_BUILD + COMMAND ${DSYMUTIL} $ -o $.dSYM + COMMAND ${STRIP} -S $ + COMMENT "macOS: Creating dSYM bundle and stripping debug symbols..." + ) + else() + message(STATUS "dsymutil/strip not found; _lbug debug symbols will not be split.") + endif() +elseif(WIN32) + # Windows: MSVC natively outputs a separate .pdb file if /DEBUG is set. + # No extra extraction step is needed; the .pyd is already free of debug data. + set_target_properties(_lbug PROPERTIES + PDB_NAME "_lbug" + PDB_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/ladybug" + ) + + # Optional: Automatically optimize and strip unreferenced code/data. + target_link_options(_lbug PRIVATE + $<$:/OPT:REF> + $<$:/OPT:ICF> + ) +elseif(UNIX) + # Linux: Use GNU objcopy to split symbols. The .debug file travels next to + # the extension so wheel builds can publish it as a separate artifact. + find_program(OBJCOPY objcopy) + if(OBJCOPY) + add_custom_command(TARGET _lbug POST_BUILD + COMMAND ${OBJCOPY} --only-keep-debug $ _lbug.debug + COMMAND ${OBJCOPY} --strip-debug $ + COMMAND ${OBJCOPY} --add-gnu-debuglink=_lbug.debug $ + # --add-gnu-debuglink resolves a relative filename against the + # process working directory, so run in the output dir to keep the + # stored link a portable bare filename instead of a build path. + WORKING_DIRECTORY $ + COMMENT "Linux: Splitting debug symbols into .debug file..." + ) + else() + message(STATUS "objcopy not found; _lbug debug symbols will not be split.") + endif() +endif() + if(LBUG_API_USE_PRECOMPILED_LIB) if(NOT LBUG_API_PRECOMPILED_LIB_PATH) message(FATAL_ERROR "LBUG_API_PRECOMPILED_LIB_PATH must be set when LBUG_API_USE_PRECOMPILED_LIB is enabled.") From 87d4dd2215410b386e972a3ca081acbf19acdf20 Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Sun, 6 Sep 2026 20:07:38 -0700 Subject: [PATCH 2/3] Gate build-time symbol split behind LBUG_SPLIT_DEBUG_SYMBOLS Wheel builds set -DLBUG_SPLIT_DEBUG_SYMBOLS=OFF via setup.py: raw .debug/dSYM artifacts packaged inside the wheel break auditwheel repair, and the split instead happens on the final repaired wheel (where the debug info must still be present). Local builds keep the previous split-at-build-time behavior (default ON). --- CMakeLists.txt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index da7f108..ddd2f3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,18 +81,25 @@ target_link_options(_lbug PRIVATE ) # --- Cross-Platform Symbol Separation --- +# NOTE: build-time splitting must be OFF for wheel builds +# (-DLBUG_SPLIT_DEBUG_SYMBOLS=OFF, set by scripts/pip-package/setup.py). +# Raw .debug/dSYM artifacts packaged inside the wheel break auditwheel +# repair, and the split instead happens on the final repaired wheel via +# scripts/pip-package/split_debug_symbols.py, which needs the debug info +# to still be present in the shipped binary. +option(LBUG_SPLIT_DEBUG_SYMBOLS "Split _lbug debug symbols into separate files at build time." ON) if(APPLE) # macOS: Extract symbols into a .dSYM bundle and strip the binary. find_program(DSYMUTIL dsymutil) find_program(STRIP strip) - if(DSYMUTIL AND STRIP) + if(DSYMUTIL AND STRIP AND LBUG_SPLIT_DEBUG_SYMBOLS) add_custom_command(TARGET _lbug POST_BUILD COMMAND ${DSYMUTIL} $ -o $.dSYM COMMAND ${STRIP} -S $ COMMENT "macOS: Creating dSYM bundle and stripping debug symbols..." ) else() - message(STATUS "dsymutil/strip not found; _lbug debug symbols will not be split.") + message(STATUS "_lbug debug symbols will not be split at build time.") endif() elseif(WIN32) # Windows: MSVC natively outputs a separate .pdb file if /DEBUG is set. @@ -111,7 +118,7 @@ elseif(UNIX) # Linux: Use GNU objcopy to split symbols. The .debug file travels next to # the extension so wheel builds can publish it as a separate artifact. find_program(OBJCOPY objcopy) - if(OBJCOPY) + if(OBJCOPY AND LBUG_SPLIT_DEBUG_SYMBOLS) add_custom_command(TARGET _lbug POST_BUILD COMMAND ${OBJCOPY} --only-keep-debug $ _lbug.debug COMMAND ${OBJCOPY} --strip-debug $ @@ -123,7 +130,7 @@ elseif(UNIX) COMMENT "Linux: Splitting debug symbols into .debug file..." ) else() - message(STATUS "objcopy not found; _lbug debug symbols will not be split.") + message(STATUS "_lbug debug symbols will not be split at build time.") endif() endif() From 8016cc474e7759e4d028def8c50a15c33de93c44 Mon Sep 17 00:00:00 2001 From: Arun Sharma Date: Sun, 6 Sep 2026 20:56:58 -0700 Subject: [PATCH 3/3] Neutralize pybind11 auto-strip so symbols survive for splitting pybind11_add_module() unconditionally attaches a POST_BUILD ${CMAKE_STRIP} step -- plain `strip` on Linux, i.e. --strip-all -- which runs before our own split and destroys both symtab and DWARF. Wheel builds therefore produced empty ~3KB .debug stubs (macOS was unaffected: `strip -x` keeps DWARF; Windows: CMAKE_STRIP unset). Stripping is now owned solely by the LBUG_SPLIT_DEBUG_SYMBOLS pipeline (in-build objcopy split, or host-side split_debug_symbols.py for wheels), both of which preserve .symtab so addresses stay mappable to function names. --- CMakeLists.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ddd2f3f..ce462ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,17 @@ project(_lbug LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 20) set(LBUG_SOURCE_DIR "" CACHE PATH "Path to the Ladybug source tree used for pybind builds") +option(LBUG_SPLIT_DEBUG_SYMBOLS "Split _lbug debug symbols into separate files at build time." ON) + +# pybind11_add_module() auto-attaches a POST_BUILD ${CMAKE_STRIP} step, which +# on Linux is a plain `strip` that removes both the symbol table and DWARF -- +# and it would run before our own split below, leaving nothing to separate +# (this produced empty ~3KB .debug stubs in wheel builds). Neutralize it: +# stripping is owned by the LBUG_SPLIT_DEBUG_SYMBOLS pipeline (in-build +# objcopy split, or host-side split_debug_symbols.py for wheels), both of +# which preserve .symtab so addresses stay mappable to function names. +set(CMAKE_STRIP "") + if(NOT TARGET pybind11::module) if(LBUG_SOURCE_DIR) add_subdirectory("${LBUG_SOURCE_DIR}/third_party/pybind11" "${CMAKE_BINARY_DIR}/third_party/pybind11" EXCLUDE_FROM_ALL) @@ -87,7 +98,6 @@ target_link_options(_lbug PRIVATE # repair, and the split instead happens on the final repaired wheel via # scripts/pip-package/split_debug_symbols.py, which needs the debug info # to still be present in the shipped binary. -option(LBUG_SPLIT_DEBUG_SYMBOLS "Split _lbug debug symbols into separate files at build time." ON) if(APPLE) # macOS: Extract symbols into a .dSYM bundle and strip the binary. find_program(DSYMUTIL dsymutil)