Skip to content

Commit 9e04027

Browse files
committed
fix: bundle CLI/RabbitMQConfig + missing libs in Linux installer
The v0.0.1 installer shipped only the GUI — cargonetsim-cli was built but excluded from the package, and the GUI's runtime depended on yaml-cpp, KDReports, and ICU shared libraries that were never bundled, so it relied on whatever was on the target machine. * Tag cargonetsim-cli with COMPONENT CargoNetSim_COMP and add it (with RabbitMQConfig) to add_dependencies(CargoNetSimInstaller ...). * Resolve yaml-cpp::yaml-cpp and KDReports::kdreports locations from their imported targets and bundle the .so/.dll/.dylib + symlink chain on all three platforms. * Bundle Qt's libicu* alongside the Qt libraries so Qt6Core's text codecs work on machines with a different ICU. * Fix the Qt manual-fallback to glob libQt6Foo.so* instead of installing bare symlinks, so the SONAMEd file actually ships. * Set CMAKE_INSTALL_RPATH=\$ORIGIN/../lib;\$ORIGIN at the project root and run patchelf at install time to rewrite RPATHs on bundled libs (\$ORIGIN) and Qt plugins (\$ORIGIN/../..). With this in place the three executables launch directly from bin/ — no wrapper scripts. * Drop the redundant static resources/linux/CargoNetSim.desktop install (the IFW CreateDesktopEntry operation is the canonical entry). * Point CPACK_IFW_PACKAGE_RUN_PROGRAM and the CreateDesktopEntry Exec= line at bin/CargoNetSim instead of bin/CargoNetSim.sh.
1 parent 06a0864 commit 9e04027

3 files changed

Lines changed: 176 additions & 48 deletions

File tree

CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ set(CMAKE_CXX_STANDARD 17)
66
set(CMAKE_CXX_STANDARD_REQUIRED ON)
77
set(CMAKE_CXX_EXTENSIONS OFF)
88

9+
# RPATH configuration for portable/relocatable installs.
10+
# Build tree uses link-path RPATHs as usual; install tree gets $ORIGIN
11+
# entries so the installed binaries find their bundled deps without a
12+
# launcher script setting LD_LIBRARY_PATH.
13+
# $ORIGIN/../lib — executables in bin/ resolve libs in lib/
14+
# $ORIGIN — shared libs in lib/ resolve peer libs alongside them
15+
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
16+
if(UNIX AND NOT APPLE)
17+
set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib;$ORIGIN")
18+
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
19+
else()
20+
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
21+
endif()
22+
923
# Option to enable or disable tests
1024
option(CARGONET_BUILD_TESTS "Build the CargoNetSim test suite" ON)
1125
option(CARGONET_BUILD_INSTALLER "Build the CargoNetSim installer package" ON)

src/CLI/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,7 @@ target_include_directories(cargonetsim-cli-lib PUBLIC
8080
add_executable(cargonetsim-cli main.cpp)
8181
target_link_libraries(cargonetsim-cli PRIVATE cargonetsim-cli-lib)
8282

83-
install(TARGETS cargonetsim-cli RUNTIME DESTINATION bin)
83+
install(TARGETS cargonetsim-cli
84+
RUNTIME DESTINATION bin
85+
COMPONENT CargoNetSim_COMP
86+
)

src/installer/CMakeLists.txt

Lines changed: 158 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,64 @@ function(filter_debug_dlls input_dll_list output_dll_list)
3535
set(${output_dll_list} "${filtered_dlls}" PARENT_SCOPE)
3636
endfunction()
3737

38+
# Resolve the directory containing the shared library file backing an
39+
# imported target. Needed to bundle non-Qt third-party dependencies
40+
# (yaml-cpp, KDReports) whose location is not exposed via dedicated
41+
# CMake variables the way CONTAINER_LIB_DIR / RABBITMQ_SHRD_LIB_DIR are.
42+
function(resolve_imported_lib_dir target out_var)
43+
set(${out_var} "" PARENT_SCOPE)
44+
if(NOT TARGET ${target})
45+
return()
46+
endif()
47+
foreach(prop
48+
IMPORTED_LOCATION_RELEASE
49+
IMPORTED_LOCATION_RELWITHDEBINFO
50+
IMPORTED_LOCATION_MINSIZEREL
51+
IMPORTED_LOCATION_DEBUG
52+
IMPORTED_LOCATION
53+
)
54+
get_target_property(loc ${target} ${prop})
55+
if(loc AND EXISTS "${loc}")
56+
get_filename_component(dir "${loc}" DIRECTORY)
57+
set(${out_var} "${dir}" PARENT_SCOPE)
58+
return()
59+
endif()
60+
endforeach()
61+
endfunction()
62+
63+
# Glob and install the shared library plus any versioned symlinks for
64+
# `target` from its resolved location. `dest` is the destination
65+
# relative to the install prefix; `glob_patterns` is a list of file
66+
# globs (extension-specific, e.g. "libfoo*.so*"). Silently no-ops when
67+
# the target does not exist or its location cannot be resolved.
68+
function(install_imported_runtime_lib target dest)
69+
set(globs ${ARGN})
70+
if(NOT TARGET ${target})
71+
message(STATUS "install_imported_runtime_lib: skipping ${target} (not a target)")
72+
return()
73+
endif()
74+
resolve_imported_lib_dir(${target} _lib_dir)
75+
if(NOT _lib_dir)
76+
message(WARNING "install_imported_runtime_lib: could not resolve lib dir for ${target}")
77+
return()
78+
endif()
79+
set(_collected "")
80+
foreach(_pattern ${globs})
81+
file(GLOB _matches "${_lib_dir}/${_pattern}")
82+
list(APPEND _collected ${_matches})
83+
endforeach()
84+
if(_collected)
85+
list(REMOVE_DUPLICATES _collected)
86+
message(STATUS "Bundling ${target} from ${_lib_dir}: ${_collected}")
87+
install(FILES ${_collected}
88+
DESTINATION ${dest}
89+
COMPONENT CargoNetSim_COMP
90+
)
91+
else()
92+
message(WARNING "install_imported_runtime_lib: no files matched for ${target} in ${_lib_dir}")
93+
endif()
94+
endfunction()
95+
3896
# Check if building of installer is enabled
3997
if(CARGONET_BUILD_INSTALLER)
4098

@@ -43,8 +101,18 @@ if(CARGONET_BUILD_INSTALLER)
43101
COMMENT "Creating an installer for CargoNetSim."
44102
)
45103

46-
# Ensure that CargoNetSim is built first by specifying it as a dependency
47-
add_dependencies(CargoNetSimInstaller CargoNetSim)
104+
# Make sure every binary that ships in the package is built before
105+
# CPack runs. The CLI and the standalone RabbitMQ config tool are
106+
# opt-in but ship under the same component as the GUI, so wire them
107+
# in conditionally.
108+
set(CARGONETSIM_INSTALLER_DEPS CargoNetSim)
109+
if(TARGET cargonetsim-cli)
110+
list(APPEND CARGONETSIM_INSTALLER_DEPS cargonetsim-cli)
111+
endif()
112+
if(CARGONET_BUILD_RABBITMQ_CONFIG AND TARGET RabbitMQConfig)
113+
list(APPEND CARGONETSIM_INSTALLER_DEPS RabbitMQConfig)
114+
endif()
115+
add_dependencies(CargoNetSimInstaller ${CARGONETSIM_INSTALLER_DEPS})
48116

49117
# Install CargoNetSim executable with component
50118
# This is needed because the install in src/CMakeLists.txt may not be processed correctly by CPack
@@ -187,6 +255,11 @@ if(CARGONET_BUILD_INSTALLER)
187255
message(FATAL_ERROR "CRITICAL: RabbitMQ DLLs not found! RABBITMQ_SHRD_LIB_DIR=${RABBITMQ_SHRD_LIB_DIR}")
188256
endif()
189257

258+
# yaml-cpp and KDReports DLLs (REQUIRED at runtime — Backend
259+
# links yaml-cpp::yaml-cpp, GUI links KDReports::kdreports).
260+
install_imported_runtime_lib(yaml-cpp::yaml-cpp bin "yaml-cpp*.dll")
261+
install_imported_runtime_lib(KDReports::kdreports bin "kdreports*.dll" "KDReports*.dll")
262+
190263
# Download RabbitMQ installer
191264
if(NOT EXISTS "${RABBITMQ_INSTALLER_FILE}")
192265
message(STATUS "Downloading RabbitMQ Server installer...")
@@ -535,6 +608,15 @@ if(CARGONET_BUILD_INSTALLER)
535608
endif()
536609
endif()
537610

611+
# yaml-cpp and KDReports dylibs (REQUIRED at runtime — Backend
612+
# links yaml-cpp::yaml-cpp, GUI links KDReports::kdreports).
613+
install_imported_runtime_lib(yaml-cpp::yaml-cpp
614+
CargoNetSim.app/Contents/Frameworks
615+
"libyaml-cpp*.dylib")
616+
install_imported_runtime_lib(KDReports::kdreports
617+
CargoNetSim.app/Contents/Frameworks
618+
"libkdreports*.dylib" "libKDReports*.dylib")
619+
538620
# Create qt.conf for macOS bundle
539621
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/qt.conf.macos"
540622
"[Paths]\nPlugins = PlugIns\n")
@@ -567,37 +649,40 @@ if(CARGONET_BUILD_INSTALLER)
567649
)
568650
else()
569651
message(WARNING "Qt version < 6.3 detected. Manual Linux deployment may be required.")
570-
# Fallback: Install Qt libraries manually
571-
set(REQUIRED_QT_LIBS
572-
"${QT_LIB_DIR}/libQt6Core.so.6"
573-
"${QT_LIB_DIR}/libQt6Network.so.6"
574-
"${QT_LIB_DIR}/libQt6Xml.so.6"
575-
"${QT_LIB_DIR}/libQt6Gui.so.6"
576-
"${QT_LIB_DIR}/libQt6Widgets.so.6"
577-
"${QT_LIB_DIR}/libQt6PrintSupport.so.6"
578-
"${QT_LIB_DIR}/libQt6Sql.so.6"
579-
"${QT_LIB_DIR}/libQt6DBus.so.6"
580-
"${QT_LIB_DIR}/libQt6XcbQpa.so.6"
652+
# Fallback: install each required Qt module by globbing the
653+
# full symlink chain (`.so`, `.so.6`, `.so.6.10.1`). Without
654+
# the SONAMEd file the symlinks point to nothing and the
655+
# binary fails to start with "cannot open shared object".
656+
set(REQUIRED_QT_MODULES
657+
Core Network Xml Gui Widgets PrintSupport Sql DBus XcbQpa
581658
)
582-
foreach(QT_LIB ${REQUIRED_QT_LIBS})
583-
if(EXISTS "${QT_LIB}")
584-
install(FILES "${QT_LIB}"
659+
foreach(QT_MOD ${REQUIRED_QT_MODULES})
660+
file(GLOB QT_LIB_FILES "${QT_LIB_DIR}/libQt6${QT_MOD}.so*")
661+
if(QT_LIB_FILES)
662+
install(FILES ${QT_LIB_FILES}
585663
DESTINATION lib
586664
COMPONENT CargoNetSim_COMP
587665
)
588-
# Also install the .so symlinks
589-
get_filename_component(LIB_NAME "${QT_LIB}" NAME)
590-
string(REGEX REPLACE "\\.so\\.6$" ".so" LIB_SYMLINK "${LIB_NAME}")
591-
if(EXISTS "${QT_LIB_DIR}/${LIB_SYMLINK}")
592-
install(FILES "${QT_LIB_DIR}/${LIB_SYMLINK}"
593-
DESTINATION lib
594-
COMPONENT CargoNetSim_COMP
595-
)
596-
endif()
666+
else()
667+
message(WARNING "Qt module not found in ${QT_LIB_DIR}: libQt6${QT_MOD}.so*")
597668
endif()
598669
endforeach()
599670
endif()
600671

672+
# ICU shared libraries: Qt's text codecs (Qt6Core) link against
673+
# libicui18n / libicuuc / libicudata at the version Qt was built
674+
# against. The target machine may not have a matching ICU, so
675+
# bundle whatever Qt was linked against.
676+
file(GLOB QT_ICU_LIBS "${QT_LIB_DIR}/libicu*.so*")
677+
if(QT_ICU_LIBS)
678+
install(FILES ${QT_ICU_LIBS}
679+
DESTINATION lib
680+
COMPONENT CargoNetSim_COMP
681+
)
682+
else()
683+
message(STATUS "Qt ICU libraries not found in ${QT_LIB_DIR} (system ICU will be used at runtime)")
684+
endif()
685+
601686
# Install Qt plugins
602687
if(EXISTS "${QT_PARENT_DIR}/plugins/platforms/")
603688
install(DIRECTORY "${QT_PARENT_DIR}/plugins/platforms/"
@@ -654,6 +739,12 @@ if(CARGONET_BUILD_INSTALLER)
654739
endif()
655740
endif()
656741

742+
# yaml-cpp and KDReports shared libraries (REQUIRED at runtime —
743+
# Backend links yaml-cpp::yaml-cpp, GUI links KDReports::kdreports).
744+
install_imported_runtime_lib(yaml-cpp::yaml-cpp lib "libyaml-cpp.so*")
745+
install_imported_runtime_lib(KDReports::kdreports lib
746+
"libkdreports*.so*" "libKDReports*.so*")
747+
657748
# Create qt.conf for Linux
658749
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/qt.conf.linux"
659750
"[Paths]\nPrefix = ..\nPlugins = lib/plugins\nLibraries = lib\n")
@@ -663,13 +754,10 @@ if(CARGONET_BUILD_INSTALLER)
663754
COMPONENT CargoNetSim_COMP
664755
)
665756

666-
# Install .desktop file
667-
if(EXISTS "${CMAKE_SOURCE_DIR}/resources/linux/CargoNetSim.desktop")
668-
install(FILES "${CMAKE_SOURCE_DIR}/resources/linux/CargoNetSim.desktop"
669-
DESTINATION share/applications
670-
COMPONENT CargoNetSim_COMP
671-
)
672-
endif()
757+
# The desktop entry is created at install time by the IFW
758+
# CreateDesktopEntry operation (see Linux component script
759+
# below). It points at the CargoNetSim binary and at the
760+
# bundled icon, so no static .desktop file ships in the payload.
673761

674762
# Install icons for XDG compliance
675763
if(EXISTS "${CMAKE_SOURCE_DIR}/resources/linux/CargoNetSim.png")
@@ -684,19 +772,42 @@ if(CARGONET_BUILD_INSTALLER)
684772
)
685773
endif()
686774

687-
# Create a wrapper script that sets LD_LIBRARY_PATH
688-
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/CargoNetSim.sh"
689-
"#!/bin/bash
690-
# CargoNetSim launcher script
691-
SCRIPT_DIR=\"$(cd \"$(dirname \"\${BASH_SOURCE[0]}\")\" && pwd)\"
692-
export LD_LIBRARY_PATH=\"\${SCRIPT_DIR}/../lib:\${LD_LIBRARY_PATH}\"
693-
export QT_PLUGIN_PATH=\"\${SCRIPT_DIR}/../lib/plugins\"
694-
exec \"\${SCRIPT_DIR}/CargoNetSim\" \"$@\"
695-
")
696-
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/CargoNetSim.sh"
697-
DESTINATION bin
698-
COMPONENT CargoNetSim_COMP
699-
)
775+
# Bundled third-party .so files (Container, RabbitMQ-C,
776+
# KDReports, yaml-cpp, Qt itself) carry hardcoded RPATHs from
777+
# their original install prefix. Rewrite them at install time
778+
# to $ORIGIN-based paths so the bundled libraries find their
779+
# peers and so Qt plugins find the bundled Qt libs. Combined
780+
# with the project-wide CMAKE_INSTALL_RPATH set at the root
781+
# (executables get $ORIGIN/../lib;$ORIGIN), this lets users
782+
# invoke the binaries directly — no wrapper script, no
783+
# LD_LIBRARY_PATH dance.
784+
find_program(PATCHELF_EXECUTABLE patchelf)
785+
if(PATCHELF_EXECUTABLE)
786+
message(STATUS "Found patchelf: ${PATCHELF_EXECUTABLE}")
787+
install(CODE "
788+
file(GLOB _all_libs \"\${CMAKE_INSTALL_PREFIX}/lib/*.so*\")
789+
foreach(_lib \${_all_libs})
790+
if(IS_SYMLINK \"\${_lib}\")
791+
continue()
792+
endif()
793+
execute_process(
794+
COMMAND \"${PATCHELF_EXECUTABLE}\" --set-rpath \\$ORIGIN \"\${_lib}\"
795+
ERROR_QUIET)
796+
endforeach()
797+
798+
# Qt plugins live at lib/plugins/<category>/*.so so
799+
# $ORIGIN/../.. resolves to lib/, where the bundled Qt
800+
# libs live.
801+
file(GLOB_RECURSE _qt_plugins \"\${CMAKE_INSTALL_PREFIX}/lib/plugins/*.so\")
802+
foreach(_plugin \${_qt_plugins})
803+
execute_process(
804+
COMMAND \"${PATCHELF_EXECUTABLE}\" --set-rpath \\$ORIGIN/../.. \"\${_plugin}\"
805+
ERROR_QUIET)
806+
endforeach()
807+
" COMPONENT CargoNetSim_COMP)
808+
else()
809+
message(WARNING "patchelf not found. Bundled .so RPATHs will not be relocatable. Install with: sudo apt install patchelf")
810+
endif()
700811

701812
endif() # Platform-specific deployment
702813

@@ -1162,7 +1273,7 @@ Component.prototype.createOperations = function() {
11621273
set(CPACK_IFW_PACKAGE_ALLOW_SPACE_IN_PATH ON)
11631274

11641275
# Linux-specific settings
1165-
set(CPACK_IFW_PACKAGE_RUN_PROGRAM "@TargetDir@/bin/CargoNetSim.sh")
1276+
set(CPACK_IFW_PACKAGE_RUN_PROGRAM "@TargetDir@/bin/CargoNetSim")
11661277
set(CPACK_IFW_PACKAGE_RUN_PROGRAM_DESCRIPTION "Launch CargoNetSim now")
11671278

11681279
# Use PNG icon for Linux
@@ -1225,7 +1336,7 @@ Component.prototype.createOperations = function() {
12251336
'Name=CargoNetSim\\n' +
12261337
'GenericName=Cargo Network Simulator\\n' +
12271338
'Comment=Simulation software for cargo networks\\n' +
1228-
'Exec=' + targetDir + '/bin/CargoNetSim.sh\\n' +
1339+
'Exec=' + targetDir + '/bin/CargoNetSim\\n' +
12291340
'Icon=' + targetDir + '/share/icons/hicolor/256x256/apps/CargoNetSim.png\\n' +
12301341
'Terminal=false\\n' +
12311342
'Categories=Science;Simulation;Education;\\n'

0 commit comments

Comments
 (0)