Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/precompiled-bin-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ jobs:
export LDFLAGS="-static-libstdc++"
# Ensure CMake finds OpenSSL 3 installed by openssl3 packages inside the manylinux container
export EXTRA_CMAKE_FLAGS="-DOPENSSL_ROOT_DIR=/usr/lib64/openssl3 -DOPENSSL_INCLUDE_DIR=/usr/include/openssl3 -DOPENSSL_CRYPTO_LIBRARY=/usr/lib64/openssl3/libcrypto.so -DOPENSSL_SSL_LIBRARY=/usr/lib64/openssl3/libssl.so"
# The static archive is linked by consumers with a newer libstdc++;
# keep its own template instantiations private (see BundleStaticLibrary.cmake).
export EXTRA_CMAKE_FLAGS="$EXTRA_CMAKE_FLAGS -DLBUG_LOCALIZE_STATIC_SYMBOLS=TRUE"
make GEN=Ninja
make install

Expand All @@ -153,6 +156,7 @@ jobs:
CMAKE_CXX_COMPILER_LAUNCHER: ccache
CC: clang-21
CXX: clang++-21
EXTRA_CMAKE_FLAGS: -DLBUG_LOCALIZE_STATIC_SYMBOLS=TRUE
run: |
make GEN=Ninja
make install
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ option(BUILD_SINGLE_FILE_HEADER "Build single file header. Requires Python >= 3.
option(BUILD_LBUG "Build Lbug." TRUE)
option(BUILD_SHARED_LBUG "Build the shared liblbug library." TRUE)
option(BUILD_STATIC_LBUG "Build the bundled static liblbug archive." TRUE)
option(LBUG_LOCALIZE_STATIC_SYMBOLS "Make every symbol of the bundled static liblbug archive except the Ladybug API local, so it can be linked next to code built by a different compiler (ELF only)." FALSE)
option(LBUG_API_USE_PRECOMPILED_LIB "Link language bindings against a precompiled static liblbug archive." FALSE)
set(LBUG_API_PRECOMPILED_LIB_PATH "" CACHE FILEPATH "Path to the precompiled static liblbug archive used by language bindings.")
option(LBUG_NODEJS_USE_PRECOMPILED_LIB "Deprecated alias for LBUG_API_USE_PRECOMPILED_LIB." FALSE)
Expand Down
47 changes: 47 additions & 0 deletions cmake/BundleStaticLibrary.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,53 @@ else()
RESULT_VARIABLE BUNDLE_RESULT
COMMAND_ERROR_IS_FATAL ANY)
file(REMOVE "${MRI_SCRIPT}")

if(LOCALIZE_SYMBOLS)
# The archive bundles its own instantiations of libstdc++ templates
# (std::format, std::string, ...) as COMDAT groups. A consumer built
# with a different GCC has its own, and the linker keeps only one copy
# per group, so code compiled against one libstdc++ layout ends up
# calling the other (e.g. a segfault in Binder::bindCopyToClause when
# the GCC 13 prebuilt is linked on a GCC 15 system). Give the archive
# private copies of everything that isn't the Ladybug API:
# 1. Partially link it into one object, turning COMDAT group members
# into ordinary sections so the final link can't discard them.
# 2. Make every defined global local except `lbug::` (mangled
# `4lbug`) and the C API (`lbug_*`).
# 3. Weaken the remaining STB_GNU_UNIQUE definitions (function-local
# statics, inline variables), which objcopy cannot localize and
# which would otherwise be duplicate symbols. Only defined symbols:
# a weakened undefined reference would resolve to 0.
set(COMBINED_OBJECT "${OUTPUT}.combined.o")
set(UNIQUE_SYMBOLS_FILE "${OUTPUT}.unique.txt")
execute_process(
COMMAND "${LD_EXECUTABLE}" -r --force-group-allocation
--whole-archive "${BUNDLED_LIBRARY}" -o "${COMBINED_OBJECT}"
COMMAND_ERROR_IS_FATAL ANY)
execute_process(
COMMAND "${OBJCOPY_EXECUTABLE}" --wildcard
"--keep-global-symbol=*4lbug*"
"--keep-global-symbol=lbug_*"
"${COMBINED_OBJECT}"
COMMAND_ERROR_IS_FATAL ANY)
execute_process(
COMMAND "${NM_EXECUTABLE}" -g --defined-only "${COMBINED_OBJECT}"
OUTPUT_VARIABLE GLOBAL_SYMBOLS
COMMAND_ERROR_IS_FATAL ANY)
string(REGEX MATCHALL "[^\n]* u [^\n]*" UNIQUE_LINES "${GLOBAL_SYMBOLS}")
list(TRANSFORM UNIQUE_LINES REPLACE "^.* u " "")
list(JOIN UNIQUE_LINES "\n" UNIQUE_SYMBOLS)
file(WRITE "${UNIQUE_SYMBOLS_FILE}" "${UNIQUE_SYMBOLS}\n")
execute_process(
COMMAND "${OBJCOPY_EXECUTABLE}" "--weaken-symbols=${UNIQUE_SYMBOLS_FILE}"
"${COMBINED_OBJECT}"
COMMAND_ERROR_IS_FATAL ANY)
file(REMOVE "${BUNDLED_LIBRARY}")
execute_process(
COMMAND "${AR_EXECUTABLE}" rcs "${BUNDLED_LIBRARY}" "${COMBINED_OBJECT}"
COMMAND_ERROR_IS_FATAL ANY)
file(REMOVE "${COMBINED_OBJECT}" "${UNIQUE_SYMBOLS_FILE}")
endif()
endif()

file(RENAME "${BUNDLED_LIBRARY}" "${OUTPUT}")
Expand Down
7 changes: 7 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ if(TARGET lbug)
list(APPEND LBUG_STATIC_ARCHIVE_PATHS "$<TARGET_FILE:${LIBRARY}>")
endforeach()
set(LBUG_STATIC_ARCHIVE_PATH_FILE "${CMAKE_CURRENT_BINARY_DIR}/lbug_static_archives_$<CONFIG>.txt")
if(LBUG_LOCALIZE_STATIC_SYMBOLS AND NOT CMAKE_EXECUTABLE_FORMAT STREQUAL "ELF")
message(FATAL_ERROR "LBUG_LOCALIZE_STATIC_SYMBOLS is only supported for ELF targets")
endif()
file(GENERATE
OUTPUT "${LBUG_STATIC_ARCHIVE_PATH_FILE}"
CONTENT "$<JOIN:${LBUG_STATIC_ARCHIVE_PATHS},\n>\n")
Expand All @@ -64,6 +67,10 @@ if(TARGET lbug)
"-DSTATIC_LIBRARIES_FILE=${LBUG_STATIC_ARCHIVE_PATH_FILE}"
"-DAR_EXECUTABLE=${CMAKE_AR}"
"-DRANLIB_EXECUTABLE=${CMAKE_RANLIB}"
"-DLOCALIZE_SYMBOLS=${LBUG_LOCALIZE_STATIC_SYMBOLS}"
"-DLD_EXECUTABLE=${CMAKE_LINKER}"
"-DOBJCOPY_EXECUTABLE=${CMAKE_OBJCOPY}"
"-DNM_EXECUTABLE=${CMAKE_NM}"
-P "${PROJECT_SOURCE_DIR}/cmake/BundleStaticLibrary.cmake"
VERBATIM)
endif()
Expand Down
Loading