From df0f18410f7921688a733aa21372b066b9c6aaca Mon Sep 17 00:00:00 2001 From: edompeng Date: Fri, 5 Jun 2026 10:00:44 +0800 Subject: [PATCH 1/8] ci: create musl package for linux platform --- .github/workflows/ci.yml | 76 +++++++++++++++++++++------------ scripts/deploy/package_linux.sh | 44 +++++++++---------- 2 files changed, 70 insertions(+), 50 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d14e3f7..6a6d4dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,10 +31,6 @@ jobs: include: - os: ubuntu-22.04 job_name: linux - qt_host: 'linux' - qt_arch: 'linux_gcc_64' - vcpkg_triplet: 'x64-linux' - qt_version: '6.9.*' artifact_name: 'app-linux.tar.gz' - os: macos-14 @@ -64,18 +60,13 @@ jobs: with: submodules: recursive - - name: Install System Dependencies (Linux) - if: matrix.os == 'ubuntu-22.04' - run: | - sudo apt-get update - sudo apt-get install -y libgl1-mesa-dev libglu1-mesa-dev xvfb patchelf libproj-dev libunwind-dev libgoogle-perftools-dev - - name: Install System Dependencies (macOS) if: matrix.os == 'macos-14' run: | brew install proj - name: Install Qt + if: matrix.os != 'ubuntu-22.04' uses: jurplel/install-qt-action@v4 with: version: ${{ matrix.qt_version }} @@ -133,20 +124,18 @@ jobs: VCPKG_DOWNLOADS: ${{ env.VCPKG_DOWNLOADS }} - name: Set Environment Variables + if: matrix.os != 'ubuntu-22.04' shell: bash run: | echo "QT6_ROOT=${{ env.QT_ROOT_DIR }}" >> $GITHUB_ENV - if [ "${{ matrix.os }}" = "ubuntu-22.04" ]; then - echo "PROJ_ROOT=/usr" >> $GITHUB_ENV - elif [ "${{ matrix.os }}" = "macos-14" ]; then + if [ "${{ matrix.os }}" = "macos-14" ]; then echo "PROJ_ROOT=$(brew --prefix proj)" >> $GITHUB_ENV elif [ "${{ matrix.os }}" = "windows-2022" ]; then - # Elegant solution: vcpkg in manifest mode puts artifacts in vcpkg_installed/. - # The packaging script will find all DLLs inside the 'bin' subdirectory. echo "PROJ_ROOT=${{ github.workspace }}/vcpkg_installed/${{ matrix.vcpkg_triplet }}" >> $GITHUB_ENV fi - name: Cache Bazel + if: matrix.os != 'ubuntu-22.04' uses: actions/cache@v5 with: path: | @@ -157,18 +146,57 @@ jobs: restore-keys: | bazel-${{ matrix.os }}- + - name: Cache CMake FetchContent (Linux Musl) + if: matrix.os == 'ubuntu-22.04' + uses: actions/cache@v5 + with: + path: ${{ github.workspace }}/cmake_deps + key: cmake-deps-alpine-${{ hashFiles('CMakeLists.txt', 'src/CMakeLists.txt') }} + restore-keys: | + cmake-deps-alpine- + + - name: Build, Test & Package (Linux Musl) + if: matrix.os == 'ubuntu-22.04' + run: | + mkdir -p ${{ github.workspace }}/cmake_deps + docker run --rm -v ${{ github.workspace }}:/workspace -v ${{ github.workspace }}/cmake_deps:/cmake_deps -w /workspace alpine:3.20 sh << 'EOF' + set -e + apk update + apk add --no-cache alpine-sdk build-base cmake ninja qt6-qtbase-dev qt6-qtbase-x11 proj-dev patchelf libunwind-dev bash xvfb xvfb-run + apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing gperftools-dev || true + + # Configure and Build + cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DFETCHCONTENT_BASE_DIR="/cmake_deps" + cmake --build build --config RelWithDebInfo + + # Test + cd build + xvfb-run --auto-servernum ctest -C RelWithDebInfo --output-on-failure + cd .. + + # Package if it's a release tag + if [ "${{ startsWith(github.ref, 'refs/tags/v') }}" = "true" ] || [ "${{ github.ref_type }}" = "tag" ]; then + export QT6_ROOT=/usr/lib/qt6 + export PROJ_ROOT=/usr + chmod +x scripts/deploy/package_linux.sh + ./scripts/deploy/package_linux.sh + fi + EOF + + # Create archive for release + if [ "${{ startsWith(github.ref, 'refs/tags/v') }}" = "true" ] || [ "${{ github.ref_type }}" = "tag" ]; then + tar -czvf ${{ github.workspace }}/${{ matrix.artifact_name }} -C dist/linux OpenDriveViewer_linux_x64 + fi + - name: Build and Test with Bazel + if: matrix.os != 'ubuntu-22.04' shell: bash run: | BAZEL_STARTUP_OPTS="--host_jvm_args=-Djava.net.preferIPv4Stack=true" BAZEL_OPTS="--repository_cache=$HOME/.cache/bazel-repo --disk_cache=$HOME/.cache/bazel-disk --keep_going --show_result=10 --registry=https://bcr.bazel.build" COMMON_FLAGS="-c opt" - if [ "${{ matrix.os }}" = "ubuntu-22.04" ]; then - xvfb-run --auto-servernum bazel $BAZEL_STARTUP_OPTS test $COMMON_FLAGS //... --test_output=errors $BAZEL_OPTS --config=linux \ - --action_env=QT6_ROOT="${{ env.QT_ROOT_DIR }}" \ - --action_env=PROJ_ROOT="${{ env.PROJ_ROOT }}" - elif [ "${{ matrix.os }}" = "windows-2022" ]; then + if [ "${{ matrix.os }}" = "windows-2022" ]; then TCMALLOC_LIB=$(find "${{ env.PROJ_ROOT }}/lib" -name "*tcmalloc*.lib" | head -n 1 | tr '/' '\\') export PATH="${{ env.PROJ_ROOT }}/bin:$PATH" bazel $BAZEL_STARTUP_OPTS test $COMMON_FLAGS --linkopt="${TCMALLOC_LIB}" //... --test_output=errors $BAZEL_OPTS --config=windows \ @@ -181,14 +209,6 @@ jobs: --action_env=QT6_ROOT="${{ env.QT_ROOT_DIR }}" \ --action_env=PROJ_ROOT="${{ env.PROJ_ROOT }}" fi - - - name: Package (Linux) - if: matrix.os == 'ubuntu-22.04' && startsWith(github.ref, 'refs/tags/v') - run: | - chmod +x scripts/deploy/package_linux.sh - ./scripts/deploy/package_linux.sh - # Create archive for release - tar -czvf ${{ github.workspace }}/${{ matrix.artifact_name }} -C dist/linux OpenDriveViewer_linux_x64 - name: Package (macOS) if: matrix.os == 'macos-14' && startsWith(github.ref, 'refs/tags/v') diff --git a/scripts/deploy/package_linux.sh b/scripts/deploy/package_linux.sh index 703027a..e63f39c 100755 --- a/scripts/deploy/package_linux.sh +++ b/scripts/deploy/package_linux.sh @@ -3,20 +3,12 @@ set -e # --- Configuration --- BINARY_NAME="OpenDriveViewer" -TARGET="//src/app:OpenDriveViewer" DIST_DIR="dist/linux" BUNDLE_DIR="${DIST_DIR}/${BINARY_NAME}_linux_x64" # --- Check Environment --- -if [ -z "$QT6_ROOT" ]; then - echo "Error: QT6_ROOT environment variable is not set." - exit 1 -fi - -if [ -z "$PROJ_ROOT" ]; then - echo "Error: PROJ_ROOT environment variable is not set." - exit 1 -fi +: "${QT6_ROOT:?Error: QT6_ROOT environment variable is not set.}" +: "${PROJ_ROOT:?Error: PROJ_ROOT environment variable is not set.}" # --- Setup Bundle --- echo "Preparing Linux Bundle..." @@ -27,7 +19,14 @@ mkdir -p "${BUNDLE_DIR}/lib/plugins" mkdir -p "${BUNDLE_DIR}/share/proj" # Copy binary -cp "bazel-bin/src/app/${BINARY_NAME}" "${BUNDLE_DIR}/bin/" +if [ -f "bazel-bin/src/app/${BINARY_NAME}" ]; then + cp "bazel-bin/src/app/${BINARY_NAME}" "${BUNDLE_DIR}/bin/" +elif [ -f "build/bin/${BINARY_NAME}" ]; then + cp "build/bin/${BINARY_NAME}" "${BUNDLE_DIR}/bin/" +else + echo "Error: Binary ${BINARY_NAME} not found in bazel-bin/ or build/bin/." + exit 1 +fi chmod +w "${BUNDLE_DIR}/bin/${BINARY_NAME}" # Extract symbols @@ -41,21 +40,24 @@ tar -czvf "${BINARY_NAME}_linux_symbols.tar.gz" -C "${DIST_DIR}" "${BINARY_NAME} # --- Copy Dependencies --- echo "Collecting shared libraries..." -# Use a temporary file to collect all dependent libraries -LIBS_FILE=$(mktemp) -ldd "bazel-bin/src/app/${BINARY_NAME}" | grep "=> /" | awk '{print $3}' > "$LIBS_FILE" - -# Copy found libraries +# Copy found libraries, skipping standard system libraries to avoid startup crashes while read -r lib; do - cp "$lib" "${BUNDLE_DIR}/lib/" - # Strip libraries to save space lib_name=$(basename "$lib") + case "$lib_name" in + ld-linux*|ld-musl*|libc.*|libpthread.*|libdl.*|libm.*|librt.*|libgcc_s.*|libstdc++.*|libresolv.*|libutil.*|\ + libGL.*|libEGL.*|libGLdispatch.*|libGLX.*|libOpenGL.*|libdrm.*|libglapi.*|libgbm.*|\ + libxcb*|libX11*|libX11-xcb*|libwayland*|libasound*|libfontconfig*|libfreetype*|libdbus*|libuuid*|libudev*|libz.*|\ + libglib-*|libgobject-*|libgthread-*|libgmodule-*|libgio-*) + continue + ;; + esac + + cp "$lib" "${BUNDLE_DIR}/lib/" if [ ! -L "${BUNDLE_DIR}/lib/$lib_name" ]; then chmod +w "${BUNDLE_DIR}/lib/$lib_name" strip --strip-unneeded "${BUNDLE_DIR}/lib/$lib_name" 2>/dev/null || true fi -done < "$LIBS_FILE" -rm "$LIBS_FILE" +done < <(ldd "${BUNDLE_DIR}/bin/${BINARY_NAME}" | grep "=> /" | awk '{print $3}') # --- Copy Qt Plugins (Essential for display/platform) --- echo "Copying Qt plugins..." @@ -65,8 +67,6 @@ find "${BUNDLE_DIR}/lib/plugins" -type f -name "*.so" -exec strip --strip-unneed # --- Copy PROJ Data (selective) --- echo "Copying PROJ data..." -cp "${PROJ_ROOT}/share/proj/proj.db" "${BUNDLE_DIR}/share/proj/" -cp "${PROJ_ROOT}/share/proj/proj.ini" "${BUNDLE_DIR}/share/proj/" find "${PROJ_ROOT}/share/proj/" -maxdepth 1 -type f -not -name "*.tif" -not -name "*.tiff" -not -name "*.gtiff" -exec cp {} "${BUNDLE_DIR}/share/proj/" \; # --- Relink --- From 494b53366c175f608fcd1595fafe653efdfa2474 Mon Sep 17 00:00:00 2001 From: edompeng Date: Fri, 5 Jun 2026 10:17:54 +0800 Subject: [PATCH 2/8] feat: add ccache support for Musl builds and introduce Bazel build verification to CI pipeline --- .github/workflows/ci.yml | 85 +++++++++++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a6d4dd..88d1cc8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,9 +81,6 @@ jobs: # We do not manually cache the vcpkg tool/repository itself since bootstrapping the exe is extremely fast (< 1s), # and this avoids hitting GitHub's 10GB cache limit or caching corrupt/outdated repositories. - - - - name: Cache vcpkg binary packages if: matrix.os == 'windows-2022' uses: actions/cache@v4 @@ -155,20 +152,45 @@ jobs: restore-keys: | cmake-deps-alpine- + - name: Cache Ccache (Linux Musl) + if: matrix.os == 'ubuntu-22.04' + uses: actions/cache@v5 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-alpine-${{ github.sha }} + restore-keys: | + ccache-alpine- + - name: Build, Test & Package (Linux Musl) if: matrix.os == 'ubuntu-22.04' run: | mkdir -p ${{ github.workspace }}/cmake_deps - docker run --rm -v ${{ github.workspace }}:/workspace -v ${{ github.workspace }}/cmake_deps:/cmake_deps -w /workspace alpine:3.20 sh << 'EOF' + mkdir -p ${{ github.workspace }}/.ccache + docker run -i --rm \ + -v ${{ github.workspace }}:/workspace \ + -v ${{ github.workspace }}/.ccache:/root/.ccache \ + -w /workspace alpine:3.20 sh << 'EOF' set -e apk update - apk add --no-cache alpine-sdk build-base cmake ninja qt6-qtbase-dev qt6-qtbase-x11 proj-dev patchelf libunwind-dev bash xvfb xvfb-run + apk add --no-cache alpine-sdk build-base cmake ninja ccache qt6-qtbase-dev qt6-qtbase-x11 mesa-dev qt6-qttools-dev proj-dev patchelf libunwind-dev bash xvfb xvfb-run apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing gperftools-dev || true - # Configure and Build - cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DFETCHCONTENT_BASE_DIR="/cmake_deps" + # Configure ccache + export CCACHE_DIR=/root/.ccache + export CCACHE_MAXSIZE=500M + ccache --zero-stats + + # Configure and Build with ccache + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DFETCHCONTENT_BASE_DIR="/workspace/cmake_deps" \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache cmake --build build --config RelWithDebInfo + # Show ccache stats + ccache --show-stats + # Test cd build xvfb-run --auto-servernum ctest -C RelWithDebInfo --output-on-failure @@ -263,23 +285,30 @@ jobs: env: GH_TOKEN: ${{ github.token }} - build-cmake: - name: CMake Build on ${{ matrix.os }} + build-verify: + name: Verify ${{ matrix.job_name }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: + - os: ubuntu-22.04 + job_name: linux-bazel + build_system: bazel + qt_version: '6.9.*' - os: ubuntu-22.04 job_name: linux-cmake + build_system: cmake vcpkg_triplet: 'x64-linux' qt_version: '6.9.*' - os: macos-14 job_name: macos-cmake + build_system: cmake vcpkg_triplet: 'arm64-osx' qt_version: '6.9.1' - os: windows-2022 job_name: windows-cmake + build_system: cmake vcpkg_triplet: 'x64-windows' qt_version: '6.9.1' @@ -303,18 +332,16 @@ jobs: fi - name: Setup Ccache + if: matrix.build_system == 'cmake' uses: hendrikmuhs/ccache-action@v1.2.23 with: - key: ${{ matrix.os }}-ccache + key: ${{ matrix.job_name }}-ccache variant: ccache append-timestamp: true # Cache built library packages (vcpkg ports) for maximum compilation speedup. # We do not manually cache the vcpkg tool/repository itself since bootstrapping the exe is extremely fast (< 1s), # and this avoids hitting GitHub's 10GB cache limit or caching corrupt/outdated repositories. - - - - name: Cache vcpkg binary packages if: matrix.os == 'windows-2022' @@ -376,7 +403,9 @@ jobs: echo "PROJ_ROOT=${{ github.workspace }}/vcpkg_installed/${{ matrix.vcpkg_triplet }}" >> $GITHUB_ENV fi + # --- CMake-specific steps --- - name: Cache CMake FetchContent + if: matrix.build_system == 'cmake' uses: actions/cache@v5 with: path: ${{ github.workspace }}/cmake_deps @@ -384,7 +413,8 @@ jobs: restore-keys: | cmake-deps-${{ matrix.os }}- - - name: Configure and Build + - name: Configure and Build (CMake) + if: matrix.build_system == 'cmake' shell: bash run: | cmake -B build -G Ninja \ @@ -396,7 +426,8 @@ jobs: -DCMAKE_CXX_COMPILER_LAUNCHER=ccache cmake --build build --config RelWithDebInfo - - name: Test + - name: Test (CMake) + if: matrix.build_system == 'cmake' shell: bash run: | cd build @@ -406,6 +437,30 @@ jobs: ctest -C RelWithDebInfo --output-on-failure fi + # --- Bazel-specific steps --- + - name: Cache Bazel + if: matrix.build_system == 'bazel' + uses: actions/cache@v5 + with: + path: | + ~/.cache/bazel-repo + ~/.cache/bazel-disk + key: bazel-${{ matrix.os }}-${{ hashFiles('**/BUILD*', '**/WORKSPACE*', 'MODULE.bazel', '*.bzl') }} + restore-keys: | + bazel-${{ matrix.os }}- + + - name: Build and Test (Bazel) + if: matrix.build_system == 'bazel' + shell: bash + run: | + BAZEL_STARTUP_OPTS="--host_jvm_args=-Djava.net.preferIPv4Stack=true" + BAZEL_OPTS="--repository_cache=$HOME/.cache/bazel-repo --disk_cache=$HOME/.cache/bazel-disk --keep_going --show_result=10 --registry=https://bcr.bazel.build" + COMMON_FLAGS="-c opt" + + xvfb-run --auto-servernum bazel $BAZEL_STARTUP_OPTS test $COMMON_FLAGS //... --test_output=errors $BAZEL_OPTS --config=linux \ + --action_env=QT6_ROOT="${{ env.QT_ROOT_DIR }}" \ + --action_env=PROJ_ROOT="${{ env.PROJ_ROOT }}" + - name: Cleanup Old Caches if: always() shell: bash From 8d13fa7f004ce1d5f135ed26e91b664d2d24b0ac Mon Sep 17 00:00:00 2001 From: edompeng Date: Fri, 5 Jun 2026 10:22:58 +0800 Subject: [PATCH 3/8] feat: implement libunwind backtrace support for musl-based Linux systems --- .github/workflows/ci.yml | 1 + src/core/CMakeLists.txt | 12 +++ src/core/crash_handler.cpp | 142 ++++++++++++++++++++++++++---- src/ui/widgets/async_map_loader.h | 1 + 4 files changed, 141 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88d1cc8..f313f02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -395,6 +395,7 @@ jobs: - name: Set Environment Variables shell: bash run: | + echo "QT6_ROOT=${{ env.QT_ROOT_DIR }}" >> $GITHUB_ENV if [ "${{ matrix.os }}" = "ubuntu-22.04" ]; then echo "PROJ_ROOT=/usr" >> $GITHUB_ENV elif [ "${{ matrix.os }}" = "macos-14" ]; then diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f99eb47..8e5f89f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -37,3 +37,15 @@ else() endif() target_link_libraries(GeoViewerCore PUBLIC Qt6::Core Qt6::Gui OpenDrive) + +# On musl/non-glibc Linux, link libunwind for backtrace support in crash_handler +if(UNIX AND NOT APPLE) + include(CheckSymbolExists) + check_symbol_exists(backtrace "execinfo.h" HAS_EXECINFO) + if(NOT HAS_EXECINFO) + find_library(UNWIND_LIB unwind) + if(UNWIND_LIB) + target_link_libraries(GeoViewerCore PRIVATE ${UNWIND_LIB}) + endif() + endif() +endif() diff --git a/src/core/crash_handler.cpp b/src/core/crash_handler.cpp index 2e5d1e5..76209b2 100644 --- a/src/core/crash_handler.cpp +++ b/src/core/crash_handler.cpp @@ -15,13 +15,22 @@ // clang-format on # pragma comment(lib, "dbghelp.lib") #else -# include # include # include # include # include # include # include + +// Use execinfo.h on glibc; fall back to libunwind on musl/others. +# if defined(__GLIBC__) +# include +# define GEOVIEWER_HAS_EXECINFO 1 +# elif __has_include() +# define UNW_LOCAL_ONLY +# include +# define GEOVIEWER_HAS_LIBUNWIND 1 +# endif #endif namespace geoviewer::core { @@ -69,7 +78,122 @@ void SafeAppendInt(char* buf, size_t& pos, size_t max_len, long long val) { SafeAppendChar(buf, pos, max_len, temp[--temp_pos]); } } + +#if defined(GEOVIEWER_HAS_LIBUNWIND) +// Async-signal-safe hex conversion for pointer values +void SafeAppendHex(char* buf, size_t& pos, size_t max_len, uintptr_t val) { + SafeAppendString(buf, pos, max_len, "0x"); + if (val == 0) { + SafeAppendChar(buf, pos, max_len, '0'); + return; + } + char temp[32]; + int temp_pos = 0; + while (val > 0 && temp_pos < 31) { + int digit = val & 0xf; + temp[temp_pos++] = digit < 10 ? ('0' + digit) : ('a' + digit - 10); + val >>= 4; + } + while (temp_pos > 0) { + SafeAppendChar(buf, pos, max_len, temp[--temp_pos]); + } +} + + +// Async-signal-safe backtrace writing via libunwind (for musl/non-glibc). +void WriteBacktraceWithLibunwind(int fd) { + unw_cursor_t cursor; + unw_context_t context; + unw_getcontext(&context); + unw_init_local(&cursor, &context); + + int frame_idx = 0; + char line_buf[256]; + char sym_buf[128]; + + while (unw_step(&cursor) > 0) { + unw_word_t ip = 0; + unw_get_reg(&cursor, UNW_REG_IP, &ip); + + size_t pos = 0; + line_buf[0] = '\0'; + SafeAppendInt(line_buf, pos, sizeof(line_buf), frame_idx); + SafeAppendString(line_buf, pos, sizeof(line_buf), ": "); + + unw_word_t offset = 0; + if (unw_get_proc_name(&cursor, sym_buf, sizeof(sym_buf), &offset) == 0) { + SafeAppendString(line_buf, pos, sizeof(line_buf), sym_buf); + SafeAppendString(line_buf, pos, sizeof(line_buf), "+"); + SafeAppendHex(line_buf, pos, sizeof(line_buf), + static_cast(offset)); + } else { + SafeAppendString(line_buf, pos, sizeof(line_buf), "["); + SafeAppendHex(line_buf, pos, sizeof(line_buf), + static_cast(ip)); + SafeAppendString(line_buf, pos, sizeof(line_buf), "]"); + } + SafeAppendChar(line_buf, pos, sizeof(line_buf), '\n'); + [[maybe_unused]] auto res = write(fd, line_buf, pos); + ++frame_idx; + } +} +#endif + +// Write stack trace to file descriptor (async-signal-safe). +void WriteBacktraceToFd(int fd) { +#if defined(GEOVIEWER_HAS_EXECINFO) + void* array[100]; + int size = backtrace(array, 100); + backtrace_symbols_fd(array, size, fd); +#elif defined(GEOVIEWER_HAS_LIBUNWIND) + WriteBacktraceWithLibunwind(fd); +#else + const char* msg = "(no backtrace support on this platform)\n"; + [[maybe_unused]] auto res = write(fd, msg, strlen(msg)); +#endif +} + +// Collect stack trace as string (for C++ exception handler, not signal-safe). +void WriteBacktraceToStream(std::ostream& out) { +#if defined(GEOVIEWER_HAS_EXECINFO) + void* array[100]; + int size = backtrace(array, 100); + char** messages = backtrace_symbols(array, size); + if (messages != nullptr) { + out << "Stack trace:\n"; + for (int i = 0; i < size; ++i) { + out << i << ": " << messages[i] << "\n"; + } + free(messages); + } +#elif defined(GEOVIEWER_HAS_LIBUNWIND) + unw_cursor_t cursor; + unw_context_t context; + unw_getcontext(&context); + unw_init_local(&cursor, &context); + + out << "Stack trace:\n"; + int frame_idx = 0; + char sym_buf[256]; + while (unw_step(&cursor) > 0) { + unw_word_t ip = 0; + unw_get_reg(&cursor, UNW_REG_IP, &ip); + + unw_word_t offset = 0; + if (unw_get_proc_name(&cursor, sym_buf, sizeof(sym_buf), &offset) == 0) { + out << frame_idx << ": " << sym_buf << "+0x" << std::hex << offset + << std::dec << "\n"; + } else { + out << frame_idx << ": [0x" << std::hex << ip << std::dec << "]\n"; + } + ++frame_idx; + } +#else + out << "(no backtrace support on this platform)\n"; #endif +} + +#endif // !_WIN32 std::string GetTimestampString() { auto now = std::chrono::system_clock::now(); @@ -139,9 +263,7 @@ void PosixSignalHandler(int sig, siginfo_t* /*info*/, void* /*secret*/) { const char* trace_msg = "Stack trace:\n"; [[maybe_unused]] auto res3 = write(fd, trace_msg, strlen(trace_msg)); - void* array[100]; - int size = backtrace(array, 100); - backtrace_symbols_fd(array, size, fd); + WriteBacktraceToFd(fd); close(fd); @@ -182,17 +304,7 @@ void TerminateHandler() { } #ifndef _WIN32 - void* array[100]; - int size = backtrace(array, 100); - char** messages = backtrace_symbols(array, size); - - if (messages != nullptr) { - out << "Stack trace:\n"; - for (int i = 0; i < size; ++i) { - out << i << ": " << messages[i] << "\n"; - } - free(messages); - } + WriteBacktraceToStream(out); #endif out.close(); } diff --git a/src/ui/widgets/async_map_loader.h b/src/ui/widgets/async_map_loader.h index fd437c8..042e341 100644 --- a/src/ui/widgets/async_map_loader.h +++ b/src/ui/widgets/async_map_loader.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include From 6e0ca03495eb0eac1bfabd75f050abb65ac6920a Mon Sep 17 00:00:00 2001 From: edompeng Date: Fri, 5 Jun 2026 10:47:07 +0800 Subject: [PATCH 4/8] feat: add support for bundling musl-linked binaries in Linux deployment script --- scripts/deploy/package_linux.sh | 69 +++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/scripts/deploy/package_linux.sh b/scripts/deploy/package_linux.sh index e63f39c..5f492ed 100755 --- a/scripts/deploy/package_linux.sh +++ b/scripts/deploy/package_linux.sh @@ -38,19 +38,44 @@ objcopy --add-gnu-debuglink="${DIST_DIR}/${BINARY_NAME}.debug" "${BUNDLE_DIR}/bi # Package symbols tar -czvf "${BINARY_NAME}_linux_symbols.tar.gz" -C "${DIST_DIR}" "${BINARY_NAME}.debug" +# --- Detect musl vs glibc --- +# Determine if this is a musl-linked binary by checking the ELF interpreter +INTERP=$(readelf -l "${BUNDLE_DIR}/bin/${BINARY_NAME}" 2>/dev/null | grep "interpreter:" | sed 's/.*: \(.*\)]/\1/') +IS_MUSL=false +if echo "$INTERP" | grep -q "ld-musl"; then + IS_MUSL=true + echo "Detected musl-linked binary (interpreter: ${INTERP})" +fi + # --- Copy Dependencies --- echo "Collecting shared libraries..." -# Copy found libraries, skipping standard system libraries to avoid startup crashes +# For musl builds: bundle the C runtime, libstdc++, and libgcc_s so the +# package is self-contained and can run on any Linux distribution (including +# glibc-based ones) via the bundled musl dynamic linker. while read -r lib; do lib_name=$(basename "$lib") - case "$lib_name" in - ld-linux*|ld-musl*|libc.*|libpthread.*|libdl.*|libm.*|librt.*|libgcc_s.*|libstdc++.*|libresolv.*|libutil.*|\ - libGL.*|libEGL.*|libGLdispatch.*|libGLX.*|libOpenGL.*|libdrm.*|libglapi.*|libgbm.*|\ - libxcb*|libX11*|libX11-xcb*|libwayland*|libasound*|libfontconfig*|libfreetype*|libdbus*|libuuid*|libudev*|libz.*|\ - libglib-*|libgobject-*|libgthread-*|libgmodule-*|libgio-*) - continue - ;; - esac + + if [ "$IS_MUSL" = true ]; then + # Musl build: only skip graphics/system-specific libs that must come + # from the host. Bundle everything else including libc, libstdc++, etc. + case "$lib_name" in + libGL.*|libEGL.*|libGLdispatch.*|libGLX.*|libOpenGL.*|libdrm.*|libglapi.*|libgbm.*|\ + libxcb*|libX11*|libX11-xcb*|libwayland*|libasound*|libfontconfig*|libfreetype*|libdbus*|libuuid*|libudev*|\ + libglib-*|libgobject-*|libgthread-*|libgmodule-*|libgio-*) + continue + ;; + esac + else + # Glibc build: skip standard system libraries to avoid startup crashes + case "$lib_name" in + ld-linux*|libc.*|libpthread.*|libdl.*|libm.*|librt.*|libgcc_s.*|libstdc++.*|libresolv.*|libutil.*|\ + libGL.*|libEGL.*|libGLdispatch.*|libGLX.*|libOpenGL.*|libdrm.*|libglapi.*|libgbm.*|\ + libxcb*|libX11*|libX11-xcb*|libwayland*|libasound*|libfontconfig*|libfreetype*|libdbus*|libuuid*|libudev*|libz.*|\ + libglib-*|libgobject-*|libgthread-*|libgmodule-*|libgio-*) + continue + ;; + esac + fi cp "$lib" "${BUNDLE_DIR}/lib/" if [ ! -L "${BUNDLE_DIR}/lib/$lib_name" ]; then @@ -59,6 +84,13 @@ while read -r lib; do fi done < <(ldd "${BUNDLE_DIR}/bin/${BINARY_NAME}" | grep "=> /" | awk '{print $3}') +# For musl: also bundle the dynamic linker itself +if [ "$IS_MUSL" = true ] && [ -n "$INTERP" ] && [ -f "$INTERP" ]; then + echo "Bundling musl dynamic linker: ${INTERP}" + cp "$INTERP" "${BUNDLE_DIR}/lib/" + chmod +w "${BUNDLE_DIR}/lib/$(basename "$INTERP")" +fi + # --- Copy Qt Plugins (Essential for display/platform) --- echo "Copying Qt plugins..." cp -R "${QT6_ROOT}/plugins/"* "${BUNDLE_DIR}/lib/plugins/" @@ -86,15 +118,30 @@ fi # --- Create Launcher --- echo "Creating launcher script..." -cat > "${BUNDLE_DIR}/run_geoviewer.sh" < "${BUNDLE_DIR}/run_geoviewer.sh" < "${BUNDLE_DIR}/run_geoviewer.sh" < Date: Fri, 5 Jun 2026 10:58:51 +0800 Subject: [PATCH 5/8] refactor: enhance Linux packaging to recursively collect shared dependencies and bundle Mesa DRI drivers for improved musl compatibility. --- scripts/deploy/package_linux.sh | 105 ++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 25 deletions(-) diff --git a/scripts/deploy/package_linux.sh b/scripts/deploy/package_linux.sh index 5f492ed..920d189 100755 --- a/scripts/deploy/package_linux.sh +++ b/scripts/deploy/package_linux.sh @@ -39,7 +39,6 @@ objcopy --add-gnu-debuglink="${DIST_DIR}/${BINARY_NAME}.debug" "${BUNDLE_DIR}/bi tar -czvf "${BINARY_NAME}_linux_symbols.tar.gz" -C "${DIST_DIR}" "${BINARY_NAME}.debug" # --- Detect musl vs glibc --- -# Determine if this is a musl-linked binary by checking the ELF interpreter INTERP=$(readelf -l "${BUNDLE_DIR}/bin/${BINARY_NAME}" 2>/dev/null | grep "interpreter:" | sed 's/.*: \(.*\)]/\1/') IS_MUSL=false if echo "$INTERP" | grep -q "ld-musl"; then @@ -47,21 +46,42 @@ if echo "$INTERP" | grep -q "ld-musl"; then echo "Detected musl-linked binary (interpreter: ${INTERP})" fi -# --- Copy Dependencies --- +# --- Collect ALL shared library dependencies --- echo "Collecting shared libraries..." -# For musl builds: bundle the C runtime, libstdc++, and libgcc_s so the -# package is self-contained and can run on any Linux distribution (including -# glibc-based ones) via the bundled musl dynamic linker. +DEPS_FILE=$(mktemp) + +# Helper: extract shared lib paths from ldd output +collect_deps() { + ldd "$1" 2>/dev/null | grep "=> /" | awk '{print $3}' +} + +# 1. Collect deps from main binary +collect_deps "${BUNDLE_DIR}/bin/${BINARY_NAME}" >> "$DEPS_FILE" + +# 2. Copy Qt plugins first so we can scan their deps too +echo "Copying Qt plugins..." +cp -R "${QT6_ROOT}/plugins/"* "${BUNDLE_DIR}/lib/plugins/" + +# 3. Collect deps from all Qt plugins (they dlopen additional libs at runtime) +find "${BUNDLE_DIR}/lib/plugins" -name "*.so" 2>/dev/null | while read -r plugin; do + collect_deps "$plugin" >> "$DEPS_FILE" +done + +# De-duplicate +UNIQUE_DEPS=$(sort -u "$DEPS_FILE") +rm -f "$DEPS_FILE" + +# Copy libraries with platform-appropriate exclusion while read -r lib; do + [ -z "$lib" ] && continue lib_name=$(basename "$lib") if [ "$IS_MUSL" = true ]; then - # Musl build: only skip graphics/system-specific libs that must come - # from the host. Bundle everything else including libc, libstdc++, etc. + # Musl build: bundle EVERYTHING except the dynamic linker itself + # (handled separately below). This ensures the entire process uses + # only musl-linked libraries, avoiding glibc/musl ABI conflicts. case "$lib_name" in - libGL.*|libEGL.*|libGLdispatch.*|libGLX.*|libOpenGL.*|libdrm.*|libglapi.*|libgbm.*|\ - libxcb*|libX11*|libX11-xcb*|libwayland*|libasound*|libfontconfig*|libfreetype*|libdbus*|libuuid*|libudev*|\ - libglib-*|libgobject-*|libgthread-*|libgmodule-*|libgio-*) + ld-musl*) continue ;; esac @@ -77,24 +97,53 @@ while read -r lib; do esac fi + # Skip if already bundled + [ -f "${BUNDLE_DIR}/lib/$lib_name" ] && continue + cp "$lib" "${BUNDLE_DIR}/lib/" if [ ! -L "${BUNDLE_DIR}/lib/$lib_name" ]; then chmod +w "${BUNDLE_DIR}/lib/$lib_name" - strip --strip-unneeded "${BUNDLE_DIR}/lib/$lib_name" 2>/dev/null || true + # Never strip musl libc — it's also the dynamic linker + case "$lib_name" in + libc.musl*) + ;; + *) + strip --strip-unneeded "${BUNDLE_DIR}/lib/$lib_name" 2>/dev/null || true + ;; + esac + fi +done <<< "$UNIQUE_DEPS" + +# --- Musl-specific: bundle dynamic linker and Mesa DRI drivers --- +if [ "$IS_MUSL" = true ]; then + # Bundle the musl dynamic linker + if [ -n "$INTERP" ] && [ -f "$INTERP" ]; then + echo "Bundling musl dynamic linker: ${INTERP}" + cp "$INTERP" "${BUNDLE_DIR}/lib/" + # Do NOT strip or patchelf the linker fi -done < <(ldd "${BUNDLE_DIR}/bin/${BINARY_NAME}" | grep "=> /" | awk '{print $3}') -# For musl: also bundle the dynamic linker itself -if [ "$IS_MUSL" = true ] && [ -n "$INTERP" ] && [ -f "$INTERP" ]; then - echo "Bundling musl dynamic linker: ${INTERP}" - cp "$INTERP" "${BUNDLE_DIR}/lib/" - chmod +w "${BUNDLE_DIR}/lib/$(basename "$INTERP")" + # Bundle Mesa DRI drivers so OpenGL works on non-musl hosts via software + # rendering (the host's hardware DRI drivers are glibc-linked and cannot + # be loaded into a musl process). + MESA_DRI_DIR="" + for dir in /usr/lib/xorg/modules/dri /usr/lib/dri; do + if [ -d "$dir" ]; then + MESA_DRI_DIR="$dir" + break + fi + done + + if [ -n "$MESA_DRI_DIR" ]; then + echo "Bundling Mesa DRI drivers from ${MESA_DRI_DIR}..." + mkdir -p "${BUNDLE_DIR}/lib/dri" + cp -a "${MESA_DRI_DIR}/"*.so "${BUNDLE_DIR}/lib/dri/" 2>/dev/null || true + # Strip DRI drivers + find "${BUNDLE_DIR}/lib/dri" -type f -name "*.so" -exec strip --strip-unneeded {} + 2>/dev/null || true + fi fi -# --- Copy Qt Plugins (Essential for display/platform) --- -echo "Copying Qt plugins..." -cp -R "${QT6_ROOT}/plugins/"* "${BUNDLE_DIR}/lib/plugins/" -# Strip plugins too +# Strip Qt plugins find "${BUNDLE_DIR}/lib/plugins" -type f -name "*.so" -exec strip --strip-unneeded {} + 2>/dev/null || true # --- Copy PROJ Data (selective) --- @@ -103,12 +152,17 @@ find "${PROJ_ROOT}/share/proj/" -maxdepth 1 -type f -not -name "*.tif" -not -nam # --- Relink --- echo "Adjusting rpaths..." -# Check if patchelf is available if command -v patchelf >/dev/null 2>&1; then patchelf --set-rpath '$ORIGIN/../lib' "${BUNDLE_DIR}/bin/${BINARY_NAME}" - # Also fixup libraries themselves for lib in "${BUNDLE_DIR}/lib/"*.so*; do if [ ! -L "$lib" ]; then + lib_name=$(basename "$lib") + # Never patchelf the musl dynamic linker or libc + case "$lib_name" in + ld-musl*|libc.musl*) + continue + ;; + esac patchelf --set-rpath '$ORIGIN' "$lib" 2>/dev/null || true fi done @@ -119,8 +173,6 @@ fi # --- Create Launcher --- echo "Creating launcher script..." if [ "$IS_MUSL" = true ]; then - # Musl build: use the bundled musl dynamic linker so the binary can run - # on any Linux distribution regardless of libc variant. INTERP_NAME=$(basename "$INTERP") cat > "${BUNDLE_DIR}/run_geoviewer.sh" < Date: Fri, 5 Jun 2026 11:14:33 +0800 Subject: [PATCH 6/8] feat: include Mesa DRI drivers in musl packaging and update CI dependencies for rendering support --- .github/workflows/ci.yml | 2 +- scripts/deploy/package_linux.sh | 61 +++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f313f02..6ee7e36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -172,7 +172,7 @@ jobs: -w /workspace alpine:3.20 sh << 'EOF' set -e apk update - apk add --no-cache alpine-sdk build-base cmake ninja ccache qt6-qtbase-dev qt6-qtbase-x11 mesa-dev qt6-qttools-dev proj-dev patchelf libunwind-dev bash xvfb xvfb-run + apk add --no-cache alpine-sdk build-base cmake ninja ccache qt6-qtbase-dev qt6-qtbase-x11 mesa-dev qt6-qttools-dev proj-dev patchelf libunwind-dev bash xvfb xvfb-run mesa-dri-gallium apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing gperftools-dev || true # Configure ccache diff --git a/scripts/deploy/package_linux.sh b/scripts/deploy/package_linux.sh index 920d189..16ca2b9 100755 --- a/scripts/deploy/package_linux.sh +++ b/scripts/deploy/package_linux.sh @@ -62,11 +62,35 @@ collect_deps "${BUNDLE_DIR}/bin/${BINARY_NAME}" >> "$DEPS_FILE" echo "Copying Qt plugins..." cp -R "${QT6_ROOT}/plugins/"* "${BUNDLE_DIR}/lib/plugins/" -# 3. Collect deps from all Qt plugins (they dlopen additional libs at runtime) +# 3. Copy Mesa DRI drivers (if musl) so we can scan their deps too +if [ "$IS_MUSL" = true ]; then + MESA_DRI_DIR="" + for dir in /usr/lib/xorg/modules/dri /usr/lib/dri; do + if [ -d "$dir" ]; then + MESA_DRI_DIR="$dir" + break + fi + done + + if [ -n "$MESA_DRI_DIR" ]; then + echo "Bundling Mesa DRI drivers from ${MESA_DRI_DIR}..." + mkdir -p "${BUNDLE_DIR}/lib/dri" + cp -a "${MESA_DRI_DIR}/"*.so "${BUNDLE_DIR}/lib/dri/" 2>/dev/null || true + fi +fi + +# 4. Collect deps from all Qt plugins (they dlopen additional libs at runtime) find "${BUNDLE_DIR}/lib/plugins" -name "*.so" 2>/dev/null | while read -r plugin; do collect_deps "$plugin" >> "$DEPS_FILE" done +# 5. Collect deps from all Mesa DRI drivers (if musl) +if [ "$IS_MUSL" = true ] && [ -d "${BUNDLE_DIR}/lib/dri" ]; then + find "${BUNDLE_DIR}/lib/dri" -name "*.so" 2>/dev/null | while read -r driver; do + collect_deps "$driver" >> "$DEPS_FILE" + done +fi + # De-duplicate UNIQUE_DEPS=$(sort -u "$DEPS_FILE") rm -f "$DEPS_FILE" @@ -114,7 +138,7 @@ while read -r lib; do fi done <<< "$UNIQUE_DEPS" -# --- Musl-specific: bundle dynamic linker and Mesa DRI drivers --- +# --- Musl-specific: bundle dynamic linker --- if [ "$IS_MUSL" = true ]; then # Bundle the musl dynamic linker if [ -n "$INTERP" ] && [ -f "$INTERP" ]; then @@ -122,30 +146,16 @@ if [ "$IS_MUSL" = true ]; then cp "$INTERP" "${BUNDLE_DIR}/lib/" # Do NOT strip or patchelf the linker fi - - # Bundle Mesa DRI drivers so OpenGL works on non-musl hosts via software - # rendering (the host's hardware DRI drivers are glibc-linked and cannot - # be loaded into a musl process). - MESA_DRI_DIR="" - for dir in /usr/lib/xorg/modules/dri /usr/lib/dri; do - if [ -d "$dir" ]; then - MESA_DRI_DIR="$dir" - break - fi - done - - if [ -n "$MESA_DRI_DIR" ]; then - echo "Bundling Mesa DRI drivers from ${MESA_DRI_DIR}..." - mkdir -p "${BUNDLE_DIR}/lib/dri" - cp -a "${MESA_DRI_DIR}/"*.so "${BUNDLE_DIR}/lib/dri/" 2>/dev/null || true - # Strip DRI drivers - find "${BUNDLE_DIR}/lib/dri" -type f -name "*.so" -exec strip --strip-unneeded {} + 2>/dev/null || true - fi fi # Strip Qt plugins find "${BUNDLE_DIR}/lib/plugins" -type f -name "*.so" -exec strip --strip-unneeded {} + 2>/dev/null || true +# Strip Mesa DRI drivers (if musl) +if [ "$IS_MUSL" = true ] && [ -d "${BUNDLE_DIR}/lib/dri" ]; then + find "${BUNDLE_DIR}/lib/dri" -type f -name "*.so" -exec strip --strip-unneeded {} + 2>/dev/null || true +fi + # --- Copy PROJ Data (selective) --- echo "Copying PROJ data..." find "${PROJ_ROOT}/share/proj/" -maxdepth 1 -type f -not -name "*.tif" -not -name "*.tiff" -not -name "*.gtiff" -exec cp {} "${BUNDLE_DIR}/share/proj/" \; @@ -166,6 +176,15 @@ if command -v patchelf >/dev/null 2>&1; then patchelf --set-rpath '$ORIGIN' "$lib" 2>/dev/null || true fi done + + # Adjust rpaths for Mesa DRI drivers to look in '$ORIGIN/..' (which is the lib folder) + if [ "$IS_MUSL" = true ] && [ -d "${BUNDLE_DIR}/lib/dri" ]; then + for driver in "${BUNDLE_DIR}/lib/dri/"*.so*; do + if [ -f "$driver" ] && [ ! -L "$driver" ]; then + patchelf --set-rpath '$ORIGIN/..' "$driver" 2>/dev/null || true + fi + done + fi else echo "Warning: patchelf not found. Binary might not find bundled libs automatically." fi From 99be8968050d21e249fc4df60ecc830f074c44e9 Mon Sep 17 00:00:00 2001 From: edompeng Date: Fri, 5 Jun 2026 11:29:16 +0800 Subject: [PATCH 7/8] ci: install missing Mesa libraries and include EGL/GLX vendor files in Linux deployment package --- .github/workflows/ci.yml | 2 +- scripts/deploy/package_linux.sh | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ee7e36..ed7b96e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -172,7 +172,7 @@ jobs: -w /workspace alpine:3.20 sh << 'EOF' set -e apk update - apk add --no-cache alpine-sdk build-base cmake ninja ccache qt6-qtbase-dev qt6-qtbase-x11 mesa-dev qt6-qttools-dev proj-dev patchelf libunwind-dev bash xvfb xvfb-run mesa-dri-gallium + apk add --no-cache alpine-sdk build-base cmake ninja ccache qt6-qtbase-dev qt6-qtbase-x11 mesa-dev qt6-qttools-dev proj-dev patchelf libunwind-dev bash xvfb xvfb-run mesa-dri-gallium mesa-gl mesa-egl apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing gperftools-dev || true # Configure ccache diff --git a/scripts/deploy/package_linux.sh b/scripts/deploy/package_linux.sh index 16ca2b9..787eb2c 100755 --- a/scripts/deploy/package_linux.sh +++ b/scripts/deploy/package_linux.sh @@ -62,7 +62,7 @@ collect_deps "${BUNDLE_DIR}/bin/${BINARY_NAME}" >> "$DEPS_FILE" echo "Copying Qt plugins..." cp -R "${QT6_ROOT}/plugins/"* "${BUNDLE_DIR}/lib/plugins/" -# 3. Copy Mesa DRI drivers (if musl) so we can scan their deps too +# 3. Copy Mesa DRI drivers and vendor libraries (if musl) so we can scan their deps too if [ "$IS_MUSL" = true ]; then MESA_DRI_DIR="" for dir in /usr/lib/xorg/modules/dri /usr/lib/dri; do @@ -77,6 +77,23 @@ if [ "$IS_MUSL" = true ]; then mkdir -p "${BUNDLE_DIR}/lib/dri" cp -a "${MESA_DRI_DIR}/"*.so "${BUNDLE_DIR}/lib/dri/" 2>/dev/null || true fi + + # Copy Mesa GLX/EGL vendor libraries (libGLX_mesa and libEGL_mesa) if they exist, + # as they are loaded via dlopen by libGL/libEGL and not detected by ldd on the binary. + for libpath in /usr/lib/libGLX_mesa.so* /usr/lib/libEGL_mesa.so*; do + if [ -e "$libpath" ]; then + echo "Bundling Mesa vendor library: $libpath" + cp -a "$libpath" "${BUNDLE_DIR}/lib/" + collect_deps "$libpath" >> "$DEPS_FILE" + fi + done + + # Copy EGL vendor JSON configuration files + if [ -d "/usr/share/glvnd/egl_vendor.d" ]; then + echo "Bundling EGL vendor configuration files..." + mkdir -p "${BUNDLE_DIR}/share/glvnd/egl_vendor.d" + cp -a /usr/share/glvnd/egl_vendor.d/*.json "${BUNDLE_DIR}/share/glvnd/egl_vendor.d/" 2>/dev/null || true + fi fi # 4. Collect deps from all Qt plugins (they dlopen additional libs at runtime) @@ -202,6 +219,8 @@ export PROJ_LIB="\$DIR/share/proj" export XDG_SESSION_TYPE=x11 # Use bundled Mesa DRI drivers to avoid loading glibc-linked host drivers export LIBGL_DRIVERS_PATH="\$DIR/lib/dri" +# Point EGL loaders to the bundled vendor configuration files +export __EGL_VENDOR_LIBRARY_DIRS="\$DIR/share/glvnd/egl_vendor.d" exec "\$DIR/lib/${INTERP_NAME}" --library-path "\$DIR/lib" "\$DIR/bin/${BINARY_NAME}" "\$@" EOF else From 1e5a683dbd2d5b5a409dc624686b673249d04075 Mon Sep 17 00:00:00 2001 From: edompeng Date: Fri, 5 Jun 2026 11:51:48 +0800 Subject: [PATCH 8/8] chore: force software rendering and EGL integration in Linux deployment script --- scripts/deploy/package_linux.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/deploy/package_linux.sh b/scripts/deploy/package_linux.sh index 787eb2c..8da39a0 100755 --- a/scripts/deploy/package_linux.sh +++ b/scripts/deploy/package_linux.sh @@ -221,6 +221,10 @@ export XDG_SESSION_TYPE=x11 export LIBGL_DRIVERS_PATH="\$DIR/lib/dri" # Point EGL loaders to the bundled vendor configuration files export __EGL_VENDOR_LIBRARY_DIRS="\$DIR/share/glvnd/egl_vendor.d" +# Force software rendering for maximum compatibility on glibc hosts +export LIBGL_ALWAYS_SOFTWARE=1 +# Force Qt to use EGL instead of GLX to avoid GLX/fbconfig mismatches under software rendering +export QT_XCB_GL_INTEGRATION=xcb_egl exec "\$DIR/lib/${INTERP_NAME}" --library-path "\$DIR/lib" "\$DIR/bin/${BINARY_NAME}" "\$@" EOF else