diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 5f2f89c071..f01c1ab053 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -12,7 +12,9 @@ jobs: DEPS_INSTALL_DIR: /opt/AliceVision_install BUILD_TYPE: Release CTEST_OUTPUT_ON_FAILURE: 1 + buildDir: '${{ github.workspace }}/build/' ALICEVISION_ROOT: ${{ github.workspace }}/../AV_install + ALICEVISION_BUNDLE: ${{ github.workspace }}/../AV_bundle ALICEVISION_SENSOR_DB: ${{ github.workspace }}/../AV_install/share/aliceVision/cameraSensors.db ALICEVISION_LENS_PROFILE_INFO: "" steps: @@ -45,8 +47,9 @@ jobs: -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ -DBUILD_SHARED_LIBS=ON \ - -DCMAKE_PREFIX_PATH="${DEPS_INSTALL_DIR}" \ - -DCMAKE_INSTALL_PREFIX="${ALICEVISION_ROOT}" \ + -DCMAKE_PREFIX_PATH:PATH="${DEPS_INSTALL_DIR}" \ + -DCMAKE_INSTALL_PREFIX:PATH="${ALICEVISION_ROOT}" \ + -DALICEVISION_BUNDLE_PREFIX:PATH="${ALICEVISION_BUNDLE}" \ -DTARGET_ARCHITECTURE=core \ -DALICEVISION_BUILD_TESTS=ON \ -DALICEVISION_BUILD_SWIG_BINDING=ON \ @@ -56,12 +59,12 @@ jobs: -DALICEVISION_USE_POPSIFT=ON \ -DALICEVISION_USE_ALEMBIC=ON \ -DALICEVISION_USE_USD=ON \ - -DOpenCV_DIR="${DEPS_INSTALL_DIR}/share/OpenCV" \ - -DCeres_DIR="${DEPS_INSTALL_DIR}/share/Ceres" \ - -DAlembic_DIR="${DEPS_INSTALL_DIR}/lib/cmake/Alembic" \ - -DSWIG_DIR="${DEPS_INSTALL_DIR}/share/swig/4.3.0" \ - -DSWIG_EXECUTABLE="${DEPS_INSTALL_DIR}/bin-deps/swig" \ - -DPython3_EXECUTABLE=/usr/bin/python + -DOpenCV_DIR:PATH="${DEPS_INSTALL_DIR}/share/OpenCV" \ + -DCeres_DIR:PATH="${DEPS_INSTALL_DIR}/share/Ceres" \ + -DAlembic_DIR:PATH="${DEPS_INSTALL_DIR}/lib/cmake/Alembic" \ + -DSWIG_DIR:PATH="${DEPS_INSTALL_DIR}/share/swig/4.3.0" \ + -DSWIG_EXECUTABLE:PATH="${DEPS_INSTALL_DIR}/bin-deps/swig" \ + -DPython3_EXECUTABLE:PATH=/usr/bin/python - name: Build working-directory: ./build @@ -177,4 +180,31 @@ jobs: sys.exit(0) sys.exit(1) " | tee test_templatesVersions.py - python test_templatesVersions.py \ No newline at end of file + python test_templatesVersions.py + + # ── Bundle, Archive & Upload for nightly release ──────── + + - name: CMake Bundle + working-directory: ./build + run: | + cmake --build . --config Release --target bundle + + # Only runs when called from nightly.yml; skipped on regular CI. + - name: Create archive + if: github.workflow == 'Nightly' + id: archive + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + COMMIT_SHORT=$(git rev-parse --short=8 HEAD) + DATE=$(date -u +%Y%m%d) + ARCHIVE="AliceVision-nightly-${DATE}-${COMMIT_SHORT}-linux.tar.gz" + tar -czf "${ARCHIVE}" -C "$(dirname ${ALICEVISION_BUNDLE})" "$(basename ${ALICEVISION_BUNDLE})" + echo "filename=${ARCHIVE}" >> $GITHUB_OUTPUT + + - name: Upload archive artifact + if: github.workflow == 'Nightly' + uses: actions/upload-artifact@v4 + with: + name: linux-package + path: ${{ steps.archive.outputs.filename }} + retention-days: 3 diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index b350fb8733..c76d335fcc 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -13,7 +13,8 @@ jobs: archivePath: '${{ github.workspace }}\dependencies\installed.zip' BUILD_TYPE: Release CTEST_OUTPUT_ON_FAILURE: 1 - ALICEVISION_ROOT: '${{ github.workspace }}\install' + ALICEVISION_ROOT: '${{ github.workspace }}/install' + ALICEVISION_BUNDLE: ${{ github.workspace }}/install/bundle ALICEVISION_LIBPATH: '${{ github.workspace }}/install/bundle/bin' PYTHONPATH: '${{ github.workspace }}/install/bundle/lib/python' SCCACHE_GHA_ENABLED: "true" @@ -151,6 +152,7 @@ jobs: -DBUILD_SHARED_LIBS=ON -DTARGET_ARCHITECTURE=core -DCMAKE_INSTALL_PREFIX=${{ env.ALICEVISION_ROOT }} + -DALICEVISION_BUNDLE_PREFIX:PATH=${{ env.ALICEVISION_BUNDLE }} -DALICEVISION_BUILD_TESTS=ON -DALICEVISION_USE_OPENCV=ON -DALICEVISION_USE_CUDA=ON @@ -174,7 +176,7 @@ jobs: cmakeListsTxtPath: '${{ github.workspace }}/CMakeLists.txt' buildDirectory: ${{ env.buildDir }} buildWithCMakeArgs: '--config Release --target bundle --parallel ${{ env.NUM_CORES }}' - cmakeAppendedArgs: -DCMAKE_INSTALL_PREFIX:PATH=${{ env.ALICEVISION_ROOT }} + cmakeAppendedArgs: -DBUNDLE_INSTALL_PREFIX:PATH=${{ env.ALICEVISION_BUNDLE }} cmakeBuildType: Release buildWithCMake: true @@ -196,3 +198,23 @@ jobs: useVcpkgToolchainFile: true buildWithCMake: true + # ── Archive & upload for nightly release ──────── + # Only runs when called from nightly.yml; skipped on regular CI. + - name: Create archive + if: github.workflow == 'Nightly' + id: archive + shell: pwsh + run: | + $commitShort = (git rev-parse --short=8 HEAD).Trim() + $date = (Get-Date -Format "yyyyMMdd") + $archive = "AliceVision-nightly-${date}-${commitShort}-windows.zip" + Compress-Archive -Path "$env:ALICEVISION_BUNDLE\*" -DestinationPath $archive + echo "filename=$archive" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + + - name: Upload archive artifact + if: github.workflow == 'Nightly' + uses: actions/upload-artifact@v4 + with: + name: windows-package + path: ${{ steps.archive.outputs.filename }} + retention-days: 3 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000000..2c8080e031 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,137 @@ +name: Nightly + +on: + schedule: + # Every day at 02:00 UTC + - cron: "0 2 * * *" + workflow_dispatch: + +# Cancel any in-progress nightly run if a new one is triggered +concurrency: + group: nightly + cancel-in-progress: true + +jobs: + + # ─────────────────────────────────────────────── + # 1. Build & archive (archives created inside each build workflow) + # ─────────────────────────────────────────────── + build-linux: + uses: ./.github/workflows/build-linux.yml + + build-windows: + uses: ./.github/workflows/build-windows.yml + + # ─────────────────────────────────────────────── + # 2. Publish to the "nightly" GitHub Release + # ─────────────────────────────────────────────── + publish-nightly: + needs: [build-linux, build-windows] + runs-on: ubuntu-latest + permissions: + contents: write # required to create/edit releases and push tags + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for changelog + + - name: Download Linux package + uses: actions/download-artifact@v4 + with: + name: linux-package + path: dist + + - name: Download Windows package + uses: actions/download-artifact@v4 + with: + name: windows-package + path: dist + + # ── Metadata ──────────────────────────────── + - name: Compute release metadata + id: meta + run: | + SHORT_SHA=$(git rev-parse --short=8 HEAD) + FULL_SHA=$(git rev-parse HEAD) + NOW=$(date -u +'%Y-%m-%d %H:%M UTC') + BUILD_LABEL=$(date -u +%Y%m%d)-${SHORT_SHA} + echo "date=$(date -u +%Y-%m-%d)" >> $GITHUB_OUTPUT + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "full_sha=${FULL_SHA}" >> $GITHUB_OUTPUT + echo "now=${NOW}" >> $GITHUB_OUTPUT + echo "build_label=${BUILD_LABEL}" >> $GITHUB_OUTPUT + + # ── Ensure release exists ──────────────────── + - name: Ensure nightly release exists + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if ! gh release view nightly --repo "${{ github.repository }}" > /dev/null 2>&1; then + gh release create nightly \ + --repo "${{ github.repository }}" \ + --title "Nightly Build" \ + --notes "Nightly build — will be updated by CI." \ + --prerelease + fi + + # ── Delete stale assets ────────────────────── + - name: Delete existing release assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release view nightly \ + --repo "$GITHUB_REPOSITORY" \ + --json assets \ + --jq '.assets[].name' \ + | while read -r ASSET; do + echo " → Deleting asset: $ASSET" + gh release delete-asset nightly "$ASSET" \ + --repo "$GITHUB_REPOSITORY" --yes + done + + # ── Upload fresh assets ────────────────────── + - name: Upload release assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release upload nightly dist/* \ + --repo "${{ github.repository }}" \ + --clobber + + # ── Write release notes to file ────────────── + - name: Write release notes + run: | + NOW="${{ steps.meta.outputs.now }}" + SHORT_SHA="${{ steps.meta.outputs.short_sha }}" + BUILD_LABEL="${{ steps.meta.outputs.build_label }}" + FULL_SHA="${{ steps.meta.outputs.full_sha }}" + cat > nightly_release_notes.md << EOF + > [!WARNING] + > **Automated nightly build** from the \`develop\` branch. May be unstable. + + | | | + |---|---| + | Date | ${NOW} | + | Commit | [${SHORT_SHA}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) | + | Branch | \`develop\` | + + ### Assets + + | File | Platform | + |------|----------| + | \`AliceVision-nightly-${BUILD_LABEL}-linux.tar.gz\` | Linux (Rocky 9, CUDA 12.1.1) | + | \`AliceVision-nightly-${BUILD_LABEL}-windows.zip\` | Windows | + EOF + + # ── Update release from file ────────────────── + - name: Update release notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release edit nightly \ + --repo "${{ github.repository }}" \ + --title "Nightly Build (${{ steps.meta.outputs.build_label }})" \ + --notes-file nightly_release_notes.md \ + --prerelease + diff --git a/CMakeLists.txt b/CMakeLists.txt index 4173b4a581..4926de8c30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,7 @@ set(DEPS_CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type for all external li string(TOLOWER ${DEPS_CMAKE_BUILD_TYPE} DEPS_CMAKE_BUILD_TYPE_LOWERCASE) set(ALICEVISION_BUNDLE_PREFIX "${CMAKE_INSTALL_PREFIX}/bundle" CACHE STRING "Path for bundle installation") set(ALICEVISION_ROOT ${PROJECT_BINARY_DIR}) +option(ALICEVISION_BUNDLE_SEARCH_LIBS_PATHS "Extra paths to search for external libraries to create the bundle installation" "") # ============================================================================== # GNUInstallDirs CMake module @@ -123,8 +124,33 @@ endif() # Note: require that the install rule has been executed # Include VCPKG installed dir for runtime dependencies lookup set(BUNDLE_LIBS_PATHS "") -if (_VCPKG_INSTALLED_DIR) - set(BUNDLE_LIBS_PATHS ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}$<$:/debug>/bin) + +if(_VCPKG_INSTALLED_DIR) + set(_vcpkg_bin_dir_release "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin") + set(_vcpkg_bin_dir_debug "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/debug/bin") + + # Generator expressions are not evaluated at install/bundle time, + # so we resolve the config explicitly. + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + list(APPEND BUNDLE_LIBS_PATHS "${_vcpkg_bin_dir_debug}") + else() + list(APPEND BUNDLE_LIBS_PATHS "${_vcpkg_bin_dir_release}") + endif() +endif() + +if(ALICEVISION_BUNDLE_SEARCH_LIBS_PATHS) + list(APPEND BUNDLE_LIBS_PATHS ${ALICEVISION_BUNDLE_SEARCH_LIBS_PATHS}) +endif() + +if(CMAKE_PREFIX_PATH) + foreach(_prefix IN LISTS CMAKE_PREFIX_PATH) + foreach(_libdir IN ITEMS bin lib lib64) + set(_candidate "${_prefix}/${_libdir}") + if(EXISTS "${_candidate}") + list(APPEND BUNDLE_LIBS_PATHS "${_candidate}") + endif() + endforeach() + endforeach() endif() add_custom_target(bundle @@ -132,6 +158,7 @@ add_custom_target(bundle -DBUNDLE_INSTALL_PREFIX=${ALICEVISION_BUNDLE_PREFIX} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DBUNDLE_LIBS_PATHS=${BUNDLE_LIBS_PATHS} + -DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/src/cmake/MakeBundle.cmake ) diff --git a/src/cmake/MakeBundle.cmake b/src/cmake/MakeBundle.cmake index d195cd7a66..9c65fddcd8 100644 --- a/src/cmake/MakeBundle.cmake +++ b/src/cmake/MakeBundle.cmake @@ -1,162 +1,224 @@ cmake_minimum_required(VERSION 3.30) + # Perform bundle fixup on all executables of an install directory # and generates a standalone bundle with all required runtime dependencies. # -# This scripts accepts the following parameters: -# - CMAKE_INSTALL_PREFIX: install target path -# - BUNDLE_INSTALL_PREFIX: bundle installation path -# - BUNDLE_LIBS_PATHS: additional paths (colon separated) to look for runtime dependencies +# This script accepts the following parameters (pass via -D on the cmake -P command line): +# - CMAKE_INSTALL_PREFIX : install target path (required) +# - BUNDLE_INSTALL_PREFIX : bundle installation path (required) +# - BUNDLE_LIBS_PATHS : additional paths (semicolon-separated) to look for runtime dependencies +# - CMAKE_INSTALL_BINDIR : relative bin dir (default: bin) +# - CMAKE_INSTALL_LIBDIR : relative lib dir (default: lib) +# - CMAKE_INSTALL_DATADIR : relative data dir (default: share) + +# ─── Validate required parameters ──────────────────────────────────────────── + +foreach(_required_var CMAKE_INSTALL_PREFIX BUNDLE_INSTALL_PREFIX) + if(NOT ${_required_var}) + message(FATAL_ERROR "MakeBundle.cmake: ${_required_var} is not set. " + "Pass it with -D${_required_var}=") + endif() +endforeach() + +# ─── Replicate GNUInstallDirs for -P script mode ────────────────────────────── +# include(GNUInstallDirs) cannot resolve lib vs lib64 when invoked via cmake -P, +# so we compute the FULL_ variants manually from whatever the caller passes in. + +if(NOT CMAKE_INSTALL_BINDIR) + set(CMAKE_INSTALL_BINDIR "bin") +endif() + +if(NOT CMAKE_INSTALL_LIBDIR) + # Attempt to auto-detect lib64 (matches GNUInstallDirs heuristic) + if(EXISTS "/etc/debian_version") + # Debian/Ubuntu multiarch – keep plain lib; the actual multiarch tuple + # would need more work, but lib is the safe default. + set(CMAKE_INSTALL_LIBDIR "lib") + elseif(CMAKE_SIZEOF_VOID_P EQUAL 8 AND EXISTS "/usr/lib64") + set(CMAKE_INSTALL_LIBDIR "lib64") + else() + set(CMAKE_INSTALL_LIBDIR "lib") + endif() +endif() + +if(NOT CMAKE_INSTALL_DATADIR) + set(CMAKE_INSTALL_DATADIR "share") +endif() + +# Build absolute paths +if(NOT CMAKE_INSTALL_FULL_BINDIR) + set(CMAKE_INSTALL_FULL_BINDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") +endif() +if(NOT CMAKE_INSTALL_FULL_LIBDIR) + set(CMAKE_INSTALL_FULL_LIBDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") +endif() +if(NOT CMAKE_INSTALL_FULL_DATADIR) + set(CMAKE_INSTALL_FULL_DATADIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}") +endif() + +# ─── Blacklist (AppImage excludelist) ──────────────────────────────────────── +# https://github.com/AppImage/AppImages/blob/master/excludelist -# Blacklist from AppImage: https://github.com/AppImage/AppImages/blob/master/excludelist set(LINUX_OS_LIB_BLACKLIST -ld-linux -ld-linux-x86-64 -libanl -libBrokenLocale -libcidn -libcrypt -libc -libdl -libm -libmvec -libnsl -libnss_compat -libnss_db -libnss_dns -libnss_files -libnss_hesiod -libnss_nisplus -libnss_nis -libpthread -libresolv -librt -libthread_db -libutil -libstdc++ -libGL -libdrm -libglapi -libX11 -libgio-2.0 -libasound -libgdk_pixbuf-2.0 -libfontconfig -libthai -libfreetype -libharfbuzz -libcom_err -libexpat -libgcc_s -libglib-2.0 -libgpg-error -libICE -libkeyutils -libp11-kit -libSM -libusb-1.0 -libuuid -libz -libgobject-2.0 -libpangoft2-1.0 -libpangocairo-1.0 -libpango-1.0 -libgpg-error -libjack - ) + ld-linux + ld-linux-x86-64 + libanl + libBrokenLocale + libcidn + libcrypt + libc + libdl + libm + libmvec + libnsl + libnss_compat + libnss_db + libnss_dns + libnss_files + libnss_hesiod + libnss_nisplus + libnss_nis + libpthread + libresolv + librt + libthread_db + libutil + libstdc++ + libGL + libdrm + libglapi + libX11 + libgio-2.0 + libasound + libgdk_pixbuf-2.0 + libfontconfig + libthai + libfreetype + libharfbuzz + libcom_err + libexpat + libgcc_s + libglib-2.0 + libgpg-error + libICE + libkeyutils + libp11-kit + libSM + libusb-1.0 + libuuid + libz + libgobject-2.0 + libpangoft2-1.0 + libpangocairo-1.0 + libpango-1.0 + libjack +) + +# ─── Platform overrides ─────────────────────────────────────────────────────── function(gp_resolve_item_override context item exepath dirs resolved_item_var resolved_var) - # avoid log flood for those system libraries with non-absolute path - if(item MATCHES "^(api-ms-win-)[^/]+dll") - # resolve item with fake absolute system path to keep them identified as system libs - # By doing this, fixup_bundle: - # - won't complain about those libraries - # - won't embed them in the bundle - set(${resolved_item_var} "$ENV{SystemRoot}/system/${item}" PARENT_SCOPE) - set(${resolved_var} TRUE PARENT_SCOPE) - endif() + # Suppress errors for Windows API sets (api-ms-win-*.dll) — these are + # virtual DLLs provided by the OS and must never be bundled. + if(item MATCHES "^api-ms-win-[^/]+\\.dll$") + set(${resolved_item_var} "$ENV{SystemRoot}/system/${item}" PARENT_SCOPE) + set(${resolved_var} TRUE PARENT_SCOPE) + endif() endfunction() if(UNIX) - function(gp_resolved_file_type_override resolved_file type_var) - # We would like to embed all non-blacklisted "system" libs, - # based on the AppImage blacklist. - if("${${type_var}}" STREQUAL "system") - get_filename_component(basename ${resolved_file} NAME_WE) - # message(STATUS "SYSTEM LIB: ${resolved_file} [${basename}]") - if(NOT basename IN_LIST LINUX_OS_LIB_BLACKLIST) - # message(STATUS "${resolved_file} [${basename}]: SYSTEM => EMBEDDED") - set(${type_var} "embedded" PARENT_SCOPE) - endif() - endif() - endfunction() + function(gp_resolved_file_type_override resolved_file type_var) + # Embed all "system" libs that are NOT on the AppImage blacklist. + if("${${type_var}}" STREQUAL "system") + get_filename_component(basename "${resolved_file}" NAME_WE) + if(NOT basename IN_LIST LINUX_OS_LIB_BLACKLIST) + set(${type_var} "embedded" PARENT_SCOPE) + endif() + endif() + endfunction() endif() -include(BundleUtilities) -include(GNUInstallDirs) - -message(STATUS "Starting Bundle") -message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") -message(STATUS "BUNDLE_INSTALL_PREFIX: ${BUNDLE_INSTALL_PREFIX}") -message(STATUS "BUNDLE_LIBS_PATHS: ${BUNDLE_LIBS_PATHS}") +# ─── Diagnostics ───────────────────────────────────────────────────────────── -message(STATUS "CMAKE_INSTALL_FULL_LIBDIR: ${CMAKE_INSTALL_FULL_LIBDIR}") -message(STATUS "CMAKE_INSTALL_LIBDIR: ${CMAKE_INSTALL_LIBDIR}") +include(BundleUtilities) -# Add installed runtime library folder to dependencies lookup path -if(WIN32) # installed next to binaries on Windows +message(STATUS "=== MakeBundle ===") +message(STATUS " CMAKE_INSTALL_PREFIX : ${CMAKE_INSTALL_PREFIX}") +message(STATUS " BUNDLE_INSTALL_PREFIX : ${BUNDLE_INSTALL_PREFIX}") +message(STATUS " CMAKE_INSTALL_BINDIR : ${CMAKE_INSTALL_BINDIR}") +message(STATUS " CMAKE_INSTALL_LIBDIR : ${CMAKE_INSTALL_LIBDIR}") +message(STATUS " CMAKE_INSTALL_DATADIR : ${CMAKE_INSTALL_DATADIR}") +message(STATUS " CMAKE_INSTALL_FULL_BINDIR : ${CMAKE_INSTALL_FULL_BINDIR}") +message(STATUS " CMAKE_INSTALL_FULL_LIBDIR : ${CMAKE_INSTALL_FULL_LIBDIR}") +message(STATUS " CMAKE_INSTALL_FULL_DATADIR : ${CMAKE_INSTALL_FULL_DATADIR}") +message(STATUS " BUNDLE_LIBS_PATHS : ${BUNDLE_LIBS_PATHS}") + +# ─── Dependency lookup paths ───────────────────────────────────────────────── + +if(WIN32) + # On Windows, DLLs live next to the executables set(LIBS_LOOKUPS_PATHS "${CMAKE_INSTALL_FULL_BINDIR}") -else() # installed in library dir everywhere else +else() set(LIBS_LOOKUPS_PATHS "${CMAKE_INSTALL_FULL_LIBDIR}") - # GNUInstallDirs is not able to resolve between lib and lib64 hen cmake is called as a sub-command line. - # As a workaround we always add a second path with "64" suffix, so it works in all cases. - # In some cases, that will be useless and point to a non-existing directory. - list(APPEND LIBS_LOOKUPS_PATHS ${CMAKE_INSTALL_FULL_LIBDIR}64) + # GNUInstallDirs cannot distinguish lib vs lib64 in -P mode; + # always probe the "64" variant as well (harmless if absent). + list(APPEND LIBS_LOOKUPS_PATHS "${CMAKE_INSTALL_FULL_LIBDIR}64") endif() if(BUNDLE_LIBS_PATHS) -list(APPEND LIBS_LOOKUPS_PATHS ${BUNDLE_LIBS_PATHS}) + list(APPEND LIBS_LOOKUPS_PATHS ${BUNDLE_LIBS_PATHS}) +endif() +message(STATUS " LIBS_LOOKUPS_PATHS : ${LIBS_LOOKUPS_PATHS}") + +# ─── Guard: bin dir must exist ─────────────────────────────────────────────── + +if(NOT EXISTS "${CMAKE_INSTALL_FULL_BINDIR}") + message(FATAL_ERROR "MakeBundle.cmake: bin directory does not exist: " + "${CMAKE_INSTALL_FULL_BINDIR}") +endif() + +# ─── Copy install tree into bundle ─────────────────────────────────────────── + +foreach(_dir + "${CMAKE_INSTALL_FULL_BINDIR}" + "${CMAKE_INSTALL_FULL_LIBDIR}" + "${CMAKE_INSTALL_FULL_DATADIR}") + if(EXISTS "${_dir}") + file(COPY "${_dir}" + DESTINATION "${BUNDLE_INSTALL_PREFIX}" + USE_SOURCE_PERMISSIONS) + else() + message(STATUS " Skipping non-existent directory: ${_dir}") + endif() +endforeach() + +# ─── fixup_bundle ──────────────────────────────────────────────────────────── + +get_bundle_all_executables("${BUNDLE_INSTALL_PREFIX}" BUNDLE_APPS) + +if(NOT BUNDLE_APPS) + message(FATAL_ERROR "MakeBundle.cmake: no executables found under " + "${BUNDLE_INSTALL_PREFIX}") endif() -message(STATUS "LIBS_LOOKUPS_PATHS: ${LIBS_LOOKUPS_PATHS}") - -get_bundle_all_executables(${CMAKE_INSTALL_FULL_BINDIR} BUNDLE_APPS) - -file(COPY - ${CMAKE_INSTALL_FULL_BINDIR} - DESTINATION ${BUNDLE_INSTALL_PREFIX} - USE_SOURCE_PERMISSIONS - ) - -file(COPY - ${CMAKE_INSTALL_FULL_LIBDIR} - DESTINATION ${BUNDLE_INSTALL_PREFIX} - USE_SOURCE_PERMISSIONS - ) - -file(COPY - ${CMAKE_INSTALL_FULL_DATADIR} - DESTINATION ${BUNDLE_INSTALL_PREFIX} - USE_SOURCE_PERMISSIONS - ) - -# Get first bundled executable as reference app -# fixup_bundle will automatically fixup all the others executable in the bundle -get_bundle_all_executables(${BUNDLE_INSTALL_PREFIX} BUNDLE_APPS) + +# fixup_bundle uses the first app as anchor; it fixes up all others automatically. list(GET BUNDLE_APPS 0 MAIN_APP) -fixup_bundle(${MAIN_APP} "" "${LIBS_LOOKUPS_PATHS}") +message(STATUS " Main app for fixup_bundle: ${MAIN_APP}") +fixup_bundle("${MAIN_APP}" "" "${LIBS_LOOKUPS_PATHS}") + +# ─── Move stray libs that fixup_bundle dropped into bin/ back into lib/ ────── if(UNIX) - file(GLOB _LIBS_TO_MOVE "${BUNDLE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/lib*.so*") - file(COPY - ${_LIBS_TO_MOVE} - DESTINATION ${BUNDLE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR} - USE_SOURCE_PERMISSIONS - FILES_MATCHING PATTERN "lib*.so*" - ) - file(REMOVE - ${_LIBS_TO_MOVE} - ) - + set(_bundle_bindir "${BUNDLE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") + set(_bundle_libdir "${BUNDLE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + + file(GLOB _LIBS_TO_MOVE "${_bundle_bindir}/lib*.so*") + if(_LIBS_TO_MOVE) + message(STATUS " Moving ${CMAKE_LIST_LENGTH} stray libs from bin/ to lib/") + file(COPY ${_LIBS_TO_MOVE} + DESTINATION "${_bundle_libdir}" + USE_SOURCE_PERMISSIONS) + file(REMOVE ${_LIBS_TO_MOVE}) + endif() endif() -message(STATUS "Bundle done: ${BUNDLE_INSTALL_PREFIX}") - +message(STATUS "=== Bundle done: ${BUNDLE_INSTALL_PREFIX} ===") \ No newline at end of file