Skip to content

Commit 712484d

Browse files
author
Marcel Hecko
committed
Fix #237: Add CMake plugin helper and ensure symbol visibility
This commit addresses issue #237 where out-of-tree plugins built with CMake fail to load with 'could not find any application matching configured criteria' error. Changes: 1. cmake/SemsPlugin.cmake - New helper for out-of-tree plugins - Provides sems_add_plugin() function for correct MODULE configuration - Ensures default symbol visibility for dlsym() lookup - Prevents common mistakes (SHARED type, static core linking) 2. cmake/module.rules.txt - Add explicit symbol visibility - Set C_VISIBILITY_PRESET and CXX_VISIBILITY_PRESET to default - Ensure EXPORT_SESSION_FACTORY symbols are exported - Prevents symbols from being hidden by compiler defaults 3. doc/BuildingOutOfTreePlugins.md - Comprehensive documentation - Explains the root cause of the issue - Provides correct CMake configuration examples - Lists common mistakes to avoid - Includes verification steps Root cause: When using static linking of SEMS core libraries with --whole-archive or wrong library type (SHARED vs MODULE), the session_factory_create symbol gets hidden and dlsym() cannot find it. The fix ensures MODULE library type and default symbol visibility, matching the behavior of the Makefile-based build system.
1 parent 6e113db commit 712484d

File tree

3 files changed

+320
-0
lines changed

3 files changed

+320
-0
lines changed

cmake/SemsPlugin.cmake

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# SEMS Plugin CMake Helper
2+
#
3+
# Include this file in your out-of-tree SEMS plugin CMakeLists.txt:
4+
# include(/path/to/sems/cmake/SemsPlugin.cmake)
5+
# sems_add_plugin(myplugin MyPlugin.cpp OtherFile.cpp)
6+
#
7+
# This ensures your plugin is built with the correct settings to be
8+
# loadable by SEMS (proper symbol export, MODULE library type, etc.)
9+
10+
if(NOT DEFINED SEMS_PLUGIN_HELPER_INCLUDED)
11+
set(SEMS_PLUGIN_HELPER_INCLUDED TRUE)
12+
13+
# Function to add a SEMS plugin with proper settings
14+
# Usage: sems_add_plugin(plugin_name source1.cpp source2.cpp ...)
15+
function(sems_add_plugin PLUGIN_NAME)
16+
# Get source files from remaining arguments
17+
set(PLUGIN_SOURCES ${ARGN})
18+
19+
if(NOT PLUGIN_SOURCES)
20+
message(FATAL_ERROR "sems_add_plugin: No source files specified for plugin ${PLUGIN_NAME}")
21+
endif()
22+
23+
# Create MODULE library (not SHARED!) - this is critical for dlopen/dlsym
24+
add_library(sems_${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES})
25+
26+
# Set module name definition
27+
target_compile_definitions(sems_${PLUGIN_NAME} PRIVATE
28+
MODULE_NAME="${PLUGIN_NAME}"
29+
)
30+
31+
# Platform-specific linker flags
32+
if(APPLE)
33+
set_target_properties(sems_${PLUGIN_NAME} PROPERTIES
34+
LINK_FLAGS "-flat_namespace -undefined suppress"
35+
)
36+
endif()
37+
38+
# Set output properties
39+
set_target_properties(sems_${PLUGIN_NAME} PROPERTIES
40+
OUTPUT_NAME ${PLUGIN_NAME} # Output: ${PLUGIN_NAME}.so (no "lib" prefix)
41+
PREFIX "" # No "lib" prefix
42+
C_VISIBILITY_PRESET default # Ensure symbols are exported
43+
CXX_VISIBILITY_PRESET default # Ensure symbols are exported
44+
VISIBILITY_INLINES_HIDDEN OFF # Don't hide inline functions
45+
)
46+
47+
# Link with minimum required libraries
48+
# DO NOT link statically with SEMS core libraries!
49+
# They will be resolved from the SEMS binary at runtime.
50+
target_link_libraries(sems_${PLUGIN_NAME} PRIVATE
51+
${CMAKE_DL_LIBS}
52+
)
53+
54+
# Make the target name available to parent scope
55+
set(SEMS_PLUGIN_TARGET sems_${PLUGIN_NAME} PARENT_SCOPE)
56+
57+
message(STATUS "SEMS Plugin configured: ${PLUGIN_NAME}")
58+
message(STATUS " - Library type: MODULE (for dlopen)")
59+
message(STATUS " - Symbol visibility: default (exports visible)")
60+
message(STATUS " - Output: ${PLUGIN_NAME}.so")
61+
endfunction()
62+
63+
# Function to add external library dependencies to a plugin
64+
# Usage: sems_plugin_link_libraries(plugin_name lib1 lib2 ...)
65+
function(sems_plugin_link_libraries PLUGIN_NAME)
66+
if(NOT TARGET sems_${PLUGIN_NAME})
67+
message(FATAL_ERROR "sems_plugin_link_libraries: Plugin ${PLUGIN_NAME} not found. Call sems_add_plugin first.")
68+
endif()
69+
70+
target_link_libraries(sems_${PLUGIN_NAME} PRIVATE ${ARGN})
71+
endfunction()
72+
73+
# Function to add include directories to a plugin
74+
# Usage: sems_plugin_include_directories(plugin_name dir1 dir2 ...)
75+
function(sems_plugin_include_directories PLUGIN_NAME)
76+
if(NOT TARGET sems_${PLUGIN_NAME})
77+
message(FATAL_ERROR "sems_plugin_include_directories: Plugin ${PLUGIN_NAME} not found. Call sems_add_plugin first.")
78+
endif()
79+
80+
target_include_directories(sems_${PLUGIN_NAME} PRIVATE ${ARGN})
81+
endfunction()
82+
83+
# Function to install a plugin to the standard SEMS location
84+
# Usage: sems_install_plugin(plugin_name)
85+
function(sems_install_plugin PLUGIN_NAME)
86+
if(NOT TARGET sems_${PLUGIN_NAME})
87+
message(FATAL_ERROR "sems_install_plugin: Plugin ${PLUGIN_NAME} not found. Call sems_add_plugin first.")
88+
endif()
89+
90+
if(NOT DEFINED SEMS_PLUGIN_DIR)
91+
set(SEMS_PLUGIN_DIR "lib/sems/plug-in")
92+
endif()
93+
94+
install(
95+
TARGETS sems_${PLUGIN_NAME}
96+
LIBRARY DESTINATION ${SEMS_PLUGIN_DIR}
97+
)
98+
endfunction()
99+
100+
message(STATUS "SEMS Plugin Helper loaded")
101+
message(STATUS "IMPORTANT: Do NOT statically link SEMS core libraries!")
102+
message(STATUS "Symbols will be resolved from the SEMS binary at runtime.")
103+
104+
endif() # SEMS_PLUGIN_HELPER_INCLUDED

cmake/module.rules.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ TARGET_LINK_LIBRARIES(sems_${sems_module_name} ${CMAKE_DL_LIBS} ${sems_module_li
2626
SET_TARGET_PROPERTIES(sems_${sems_module_name} PROPERTIES OUTPUT_NAME ${sems_module_name})
2727
SET_TARGET_PROPERTIES(sems_${sems_module_name} PROPERTIES PREFIX "")
2828

29+
# Ensure symbols are exported (critical for EXPORT_SESSION_FACTORY macro)
30+
# Default visibility ensures extern "C" symbols from plugins are visible to dlsym()
31+
SET_TARGET_PROPERTIES(sems_${sems_module_name} PROPERTIES
32+
C_VISIBILITY_PRESET default
33+
CXX_VISIBILITY_PRESET default
34+
VISIBILITY_INLINES_HIDDEN OFF
35+
)
36+
2937
INSTALL(
3038
TARGETS sems_${sems_module_name}
3139
LIBRARY DESTINATION ${SEMS_EXEC_PREFIX}/${SEMS_LIBDIR}/sems/plug-in/

doc/BuildingOutOfTreePlugins.md

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# Building Out-of-Tree SEMS Plugins with CMake
2+
3+
This document explains how to correctly build SEMS plugins using CMake when building outside the main SEMS source tree.
4+
5+
## The Problem (Issue #237)
6+
7+
When building SEMS plugins with CMake, a common mistake causes the plugin to compile successfully but fail at runtime with:
8+
9+
```
10+
[AmPlugIn.cpp:818] INFO: could not find any application matching configured criteria
11+
[AmSessionContainer.cpp:518] ERROR: No session factory for application
12+
```
13+
14+
### Root Cause
15+
16+
The `EXPORT_SESSION_FACTORY` macro creates an `extern "C"` function called `session_factory_create` that SEMS calls via `dlsym()` when loading the plugin. If this symbol is not properly exported from the shared library, SEMS cannot find it.
17+
18+
Common mistakes that hide this symbol:
19+
20+
1. **Using `SHARED` instead of `MODULE` library type**
21+
- `SHARED` libraries are for regular dynamic linking
22+
- `MODULE` libraries are specifically for `dlopen()` plugins
23+
24+
2. **Statically linking SEMS core libraries**
25+
- Using `-Wl,--whole-archive libsems_core.a` embeds core symbols in the plugin
26+
- This creates symbol conflicts and hides plugin exports
27+
- SEMS binary already contains all core symbols
28+
29+
3. **Incorrect symbol visibility settings**
30+
- Using `CXX_VISIBILITY_PRESET hidden` hides the factory function
31+
- Using `-Wl,--no-undefined` prevents runtime symbol resolution
32+
33+
## The Solution
34+
35+
### Option 1: Use the SEMS Plugin Helper (Recommended)
36+
37+
The easiest way is to use `cmake/SemsPlugin.cmake`:
38+
39+
```cmake
40+
cmake_minimum_required(VERSION 3.15)
41+
project(myplugin)
42+
43+
set(CMAKE_CXX_STANDARD 17)
44+
45+
# Include the SEMS plugin helper
46+
include(/path/to/sems/cmake/SemsPlugin.cmake)
47+
48+
# Add your plugin
49+
sems_add_plugin(myplugin
50+
MyPlugin.cpp
51+
MyFactory.cpp
52+
MyDialog.cpp
53+
)
54+
55+
# Add SEMS core include directory
56+
sems_plugin_include_directories(myplugin
57+
/path/to/sems/core
58+
)
59+
60+
# Link external libraries (NOT SEMS core!)
61+
sems_plugin_link_libraries(myplugin
62+
pthread
63+
PocoNet
64+
# your other dependencies
65+
)
66+
67+
# Install to standard location
68+
sems_install_plugin(myplugin)
69+
```
70+
71+
### Option 2: Manual Configuration
72+
73+
If you prefer to configure manually:
74+
75+
```cmake
76+
cmake_minimum_required(VERSION 3.15)
77+
project(myplugin)
78+
79+
set(CMAKE_CXX_STANDARD 17)
80+
set(MODULE_NAME "myplugin")
81+
82+
# CRITICAL: Use MODULE, not SHARED
83+
add_library(sems_${MODULE_NAME} MODULE
84+
MyPlugin.cpp
85+
MyFactory.cpp
86+
)
87+
88+
# Define MODULE_NAME
89+
target_compile_definitions(sems_${MODULE_NAME} PRIVATE
90+
MODULE_NAME="${MODULE_NAME}"
91+
)
92+
93+
# Include SEMS headers
94+
target_include_directories(sems_${MODULE_NAME} PRIVATE
95+
/path/to/sems/core
96+
)
97+
98+
# CRITICAL: Only link external libraries
99+
# DO NOT statically link SEMS core!
100+
target_link_libraries(sems_${MODULE_NAME} PRIVATE
101+
${CMAKE_DL_LIBS}
102+
pthread
103+
# Add your external dependencies here
104+
)
105+
106+
# Ensure symbol visibility
107+
set_target_properties(sems_${MODULE_NAME} PROPERTIES
108+
OUTPUT_NAME ${MODULE_NAME}
109+
PREFIX ""
110+
C_VISIBILITY_PRESET default
111+
CXX_VISIBILITY_PRESET default
112+
VISIBILITY_INLINES_HIDDEN OFF
113+
)
114+
115+
# macOS specific
116+
if(APPLE)
117+
set_target_properties(sems_${MODULE_NAME} PROPERTIES
118+
LINK_FLAGS "-flat_namespace -undefined suppress"
119+
)
120+
endif()
121+
122+
# Install
123+
install(TARGETS sems_${MODULE_NAME}
124+
LIBRARY DESTINATION lib/sems/plug-in
125+
)
126+
```
127+
128+
## What NOT To Do
129+
130+
### ❌ Wrong: Using SHARED library
131+
132+
```cmake
133+
add_library(myplugin SHARED ...) # WRONG!
134+
```
135+
136+
### ❌ Wrong: Statically linking SEMS core
137+
138+
```cmake
139+
target_link_libraries(myplugin
140+
"-Wl,--whole-archive"
141+
${SEMS_DIR}/core/libsems_core.a
142+
${SEMS_DIR}/core/libsems_sip.a
143+
"-Wl,--no-whole-archive"
144+
) # WRONG!
145+
```
146+
147+
### ❌ Wrong: Hiding symbols
148+
149+
```cmake
150+
set_target_properties(myplugin PROPERTIES
151+
CXX_VISIBILITY_PRESET hidden # WRONG!
152+
)
153+
```
154+
155+
### ❌ Wrong: No undefined symbols
156+
157+
```cmake
158+
target_link_options(myplugin PRIVATE
159+
"-Wl,--no-undefined" # WRONG!
160+
)
161+
```
162+
163+
## Verification
164+
165+
After building, verify the symbol is exported:
166+
167+
```bash
168+
nm -D myplugin.so | grep session_factory_create
169+
```
170+
171+
Expected output:
172+
```
173+
00000000000XXXXX T session_factory_create
174+
```
175+
176+
The `T` means the symbol is in the text (code) section and exported.
177+
178+
## Why This Works
179+
180+
1. **MODULE library type**: Specifically designed for `dlopen()` plugins, ensures proper symbol export
181+
2. **Dynamic linking**: SEMS binary already has core symbols, plugin resolves them at runtime
182+
3. **Default visibility**: The `extern "C"` from `EXPORT_SESSION_FACTORY` makes the symbol externally visible
183+
4. **No whole-archive**: Avoids embedding duplicate symbols that would conflict with SEMS binary
184+
185+
## Comparison with Makefile Approach
186+
187+
The SEMS Makefile uses:
188+
189+
```makefile
190+
LIB_LDFLAGS = -shared
191+
$(LD) -o $(plugin).so $(objs) $(LIB_LDFLAGS)
192+
```
193+
194+
This creates a simple shared library without static linking, allowing clean symbol export.
195+
196+
The CMake equivalent is:
197+
198+
```cmake
199+
add_library(sems_plugin MODULE ${sources})
200+
# No static core linking!
201+
```
202+
203+
## See Also
204+
205+
- `cmake/SemsPlugin.cmake` - Helper functions for out-of-tree plugins
206+
- `cmake/module.rules.txt` - How SEMS builds its own plugins
207+
- `core/AmApi.h` - Definition of `EXPORT_SESSION_FACTORY` macro
208+
- `core/AmPlugIn.cpp` - Plugin loading mechanism

0 commit comments

Comments
 (0)