[cpp] nostdlib build to be used with WASM and other environments that do not want stdlibc++ linked in.

This commit is contained in:
Mario Zechner 2025-07-20 14:20:40 +02:00
parent 3927ff25ff
commit aaca02ad81
5 changed files with 204 additions and 6 deletions

View File

@ -3,6 +3,8 @@ project(spine-cpp)
include(${CMAKE_CURRENT_LIST_DIR}/../flags.cmake)
option(SPINE_NO_FILE_IO "Disable file I/O operations" OFF)
include_directories(include)
file(GLOB INCLUDES "include/**/*.h")
file(GLOB SOURCES "src/**/*.cpp")
@ -10,6 +12,15 @@ file(GLOB SOURCES "src/**/*.cpp")
add_library(spine-cpp STATIC ${SOURCES} ${INCLUDES})
target_include_directories(spine-cpp PUBLIC include)
if(SPINE_NO_FILE_IO)
target_compile_definitions(spine-cpp PRIVATE SPINE_NO_FILE_IO)
endif()
# nostdcpp variant (no C++ standard library)
file(GLOB NOSTDCPP_SOURCES ${SOURCES} "src/nostdlib.cpp")
add_library(spine-cpp-nostdcpp STATIC ${NOSTDCPP_SOURCES} ${INCLUDES})
target_include_directories(spine-cpp-nostdcpp PUBLIC include)
# Install target
install(TARGETS spine-cpp EXPORT spine-cpp_TARGETS DESTINATION dist/lib)
install(FILES ${INCLUDES} DESTINATION dist/include)
@ -24,4 +35,28 @@ export(
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
add_executable(headless-test ${CMAKE_CURRENT_SOURCE_DIR}/tests/HeadlessTest.cpp)
target_link_libraries(headless-test spine-cpp)
if(SPINE_NO_FILE_IO)
target_compile_definitions(headless-test PRIVATE SPINE_NO_FILE_IO)
endif()
# nostdcpp test executable (no C++ stdlib)
add_executable(headless-test-nostdcpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/HeadlessTest.cpp)
if(MSVC)
# On Windows/MSVC, disable default libraries but keep C runtime
target_link_libraries(headless-test-nostdcpp spine-cpp-nostdcpp)
target_link_options(headless-test-nostdcpp PRIVATE /NODEFAULTLIB)
target_link_libraries(headless-test-nostdcpp msvcrt kernel32)
else()
# Unix/Linux: avoid linking libstdc++ automatically
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_link_libraries(headless-test-nostdcpp spine-cpp-nostdcpp)
target_link_options(headless-test-nostdcpp PRIVATE -nostdlib++ -lc)
else()
# GCC: use -nodefaultlibs and link minimal libraries including libgcc for operator new/delete
target_link_options(headless-test-nostdcpp PRIVATE -nodefaultlibs)
target_link_libraries(headless-test-nostdcpp spine-cpp-nostdcpp -lm -lc -lgcc)
endif()
endif()
endif()

View File

@ -3,11 +3,38 @@ set -e
cd "$(dirname "$0")"
# Clean only if explicitly requested
if [ "$1" = "clean" ]; then
# Parse arguments
BUILD_TYPE="debug"
NOFILEIO=""
CLEAN=""
for arg in "$@"; do
case $arg in
clean)
CLEAN="true"
;;
release)
BUILD_TYPE="release"
;;
debug)
BUILD_TYPE="debug"
;;
nofileio)
NOFILEIO="-DSPINE_NO_FILE_IO=ON"
;;
*)
echo "Unknown argument: $arg"
echo "Usage: $0 [clean] [release|debug] [nofileio]"
exit 1
;;
esac
done
# Clean if requested
if [ "$CLEAN" = "true" ]; then
rm -rf build
fi
# Always build
cmake --preset=debug .
cmake --build --preset=debug
# Configure and build
cmake --preset=$BUILD_TYPE $NOFILEIO .
cmake --build --preset=$BUILD_TYPE

View File

@ -0,0 +1,54 @@
/*
* Minimal runtime stubs for nostdlib spine-cpp build
*
* Provides minimal implementations of C++ runtime symbols required
* for spine-cpp to work without the standard library.
*/
#include <cstddef>
// ============================================================================
// C++ Runtime Stubs
// ============================================================================
// Memory operators - basic malloc/free wrappers
// Note: These require a C library that provides malloc/free
extern "C" {
void* malloc(size_t size);
void free(void* ptr);
}
// Memory operators - use malloc/free but with careful implementation
// Make them weak so system can override if needed
__attribute__((weak)) void* operator new(size_t size) {
return malloc(size);
}
__attribute__((weak)) void operator delete(void* ptr) {
if (ptr) free(ptr);
}
// Static initialization guards (single-threaded stubs)
// Make them weak so system can override if needed
extern "C" __attribute__((weak)) int __cxa_guard_acquire(char* guard) {
return *guard == 0 ? (*guard = 1, 1) : 0;
}
extern "C" __attribute__((weak)) void __cxa_guard_release(char* guard) {
// No-op for single-threaded
(void)guard;
}
// Pure virtual function handler
extern "C" __attribute__((weak)) void __cxa_pure_virtual() {
// In a real implementation, this would abort or throw
// For minimal stub, we'll just return (undefined behavior but won't crash)
// This should never be called in a correctly written program
}
// Stack protection (for GCC -fstack-protector)
// Make it weak so libc can override it in static builds
extern "C" __attribute__((weak)) char __stack_chk_guard = 0;
extern "C" __attribute__((weak)) void __stack_chk_fail() {
// Could call abort() or be no-op for minimal runtime
}

View File

@ -101,7 +101,7 @@ void DefaultSpineExtension::_free(void *mem, const char *file, int line) {
}
char *DefaultSpineExtension::_readFile(const String &path, int *length) {
#ifndef __EMSCRIPTEN__
#if !defined(__EMSCRIPTEN__) && !defined(SPINE_NO_FILE_IO)
char *data;
FILE *file = fopen(path.buffer(), "rb");
if (!file) return 0;

82
spine-cpp/test-nostdlib.sh Executable file
View File

@ -0,0 +1,82 @@
#!/bin/bash
set -e
cd "$(dirname "$0")"
# Build or reuse Docker image
IMAGE_NAME="spine-cpp-nostdcpp-test"
if ! docker image inspect $IMAGE_NAME >/dev/null 2>&1; then
echo "Building Docker image (one-time setup)..."
docker build -t $IMAGE_NAME - <<'EOF'
FROM ubuntu:22.04
RUN apt-get update >/dev/null 2>&1 && \
apt-get install -y build-essential cmake ninja-build git file >/dev/null 2>&1 && \
rm -rf /var/lib/apt/lists/*
EOF
fi
echo "Running spine-cpp nostdcpp test..."
# Run Docker container with spine-runtimes directory mounted
docker run --rm \
-v "$(pwd)/..:/workspace/spine-runtimes" \
-w /workspace/spine-runtimes/spine-cpp \
$IMAGE_NAME \
bash -c "
# Build everything first
echo '=== Building all variants ==='
./build.sh clean release >/dev/null 2>&1
# Try to build static regular executable
echo 'Building static regular executable...'
g++ -static -o build/headless-test-static build/CMakeFiles/headless-test.dir/tests/HeadlessTest.cpp.o build/libspine-cpp.a >/dev/null 2>&1 || echo 'Static regular build failed'
# Try to build static nostdcpp executable (multiple approaches)
echo 'Building static nostdcpp executable...'
# Approach 1: Try with -static-libgcc and -static-libstdc++ but no libstdc++
if g++ -static -static-libgcc -Wl,--exclude-libs,libstdc++.a -o build/headless-test-nostdcpp-static build/CMakeFiles/headless-test-nostdcpp.dir/tests/HeadlessTest.cpp.o build/libspine-cpp-nostdcpp.a -lm -lc 2>/dev/null; then
echo 'SUCCESS: Static nostdcpp built (approach 1)'
# Approach 2: Try minimal static linking
elif g++ -static -o build/headless-test-nostdcpp-static-minimal build/CMakeFiles/headless-test-nostdcpp.dir/tests/HeadlessTest.cpp.o build/libspine-cpp-nostdcpp.a 2>/dev/null; then
echo 'SUCCESS: Static nostdcpp built (approach 2 - minimal)'
else
echo 'All static nostdcpp approaches failed - static linking may not be practical on this system'
fi
echo ''
echo '=== FINAL RESULTS ==='
echo ''
echo 'File sizes:'
for exe in build/headless-test*; do
if [ -f \"\$exe\" ]; then
ls -lah \"\$exe\" | awk '{printf \"%-30s %s\\n\", \$9, \$5}'
fi
done
echo ''
echo 'Dependencies:'
for exe in build/headless-test*; do
if [ -f \"\$exe\" ]; then
echo \"\$(basename \$exe):\"
ldd \"\$exe\" 2>/dev/null || echo \" (statically linked)\"
echo ''
fi
done
echo 'Functional test:'
if [ -f build/headless-test-nostdcpp ]; then
echo 'Testing headless-test-nostdcpp with spineboy...'
if OUTPUT=\$(./build/headless-test-nostdcpp ../examples/spineboy/export/spineboy-pro.skel ../examples/spineboy/export/spineboy-pma.atlas idle 2>&1); then
echo \"\$OUTPUT\" | head -10
echo '... (output truncated)'
echo 'SUCCESS: nostdcpp executable works!'
else
echo 'FAILED: nostdcpp executable failed to run'
echo \"Error: \$OUTPUT\"
fi
else
echo 'nostdcpp executable not found'
fi
"