瀏覽代碼

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

Mario Zechner 1 月之前
父節點
當前提交
aaca02ad81
共有 5 個文件被更改,包括 204 次插入6 次删除
  1. 35 0
      spine-cpp/CMakeLists.txt
  2. 32 5
      spine-cpp/build.sh
  3. 54 0
      spine-cpp/src/nostdlib.cpp
  4. 1 1
      spine-cpp/src/spine/Extension.cpp
  5. 82 0
      spine-cpp/test-nostdlib.sh

+ 35 - 0
spine-cpp/CMakeLists.txt

@@ -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()

+ 32 - 5
spine-cpp/build.sh

@@ -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

+ 54 - 0
spine-cpp/src/nostdlib.cpp

@@ -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
+}

+ 1 - 1
spine-cpp/src/spine/Extension.cpp

@@ -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 - 0
spine-cpp/test-nostdlib.sh

@@ -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
+    "