Skip to content

Commit b2abd09

Browse files
committed
feat(setup): find and load external RIME plugins as shared libs #431
1 parent ac37dfc commit b2abd09

7 files changed

+150
-6
lines changed

CMakeLists.txt

+17
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ option(ENABLE_LOGGING "Enable logging with google-glog library" ON)
2222
option(BOOST_USE_CXX11 "Boost has been built with C++11 support" OFF)
2323
option(BOOST_USE_SIGNALS2 "Boost use signals2 instead of signals" ON)
2424
option(ENABLE_ASAN "Enable Address Sanitizer (Unix Only)" OFF)
25+
option(INSTALL_PRIVATE_HEADERS "Install private headers (usually needed for externally built Rime plugins)" OFF)
26+
option(ENABLE_EXTERNAL_PLUGINS "Enable loading of externally built Rime plugins (from directory set by RIME_PLUGINS_DIR variable)" OFF)
2527

2628
set(RIME_DATA_DIR "${CMAKE_INSTALL_FULL_DATADIR}/rime-data" CACHE STRING "Target directory for Rime data")
29+
set(RIME_PLUGINS_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/rime-plugins" CACHE STRING "Target directory for externally built Rime plugins")
2730

2831
if(WIN32)
2932
set(ext ".exe")
@@ -183,6 +186,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux|FreeBSD|DragonFly|GNU")
183186
set(bindir "${CMAKE_INSTALL_FULL_BINDIR}")
184187
set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}")
185188
set(pkgdatadir "${RIME_DATA_DIR}")
189+
set(pluginsdir "${RIME_PLUGINS_DIR}")
186190
set(includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}")
187191
configure_file(
188192
${PROJECT_SOURCE_DIR}/rime.pc.in
@@ -198,6 +202,19 @@ install(FILES cmake/RimeConfig.cmake
198202
file(GLOB rime_public_header_files ${PROJECT_SOURCE_DIR}/src/*.h)
199203
install(FILES ${rime_public_header_files}
200204
DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR})
205+
if(INSTALL_PRIVATE_HEADERS)
206+
file(GLOB rime_private_header_files
207+
${PROJECT_SOURCE_DIR}/src/rime/*.h
208+
${PROJECT_BINARY_DIR}/src/rime/*.h)
209+
install(FILES ${rime_private_header_files}
210+
DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/rime)
211+
foreach(rime_private_header_files_dir algo config dict gear lever)
212+
file(GLOB rime_private_header_files
213+
${PROJECT_SOURCE_DIR}/src/rime/${rime_private_header_files_dir}/*.h)
214+
install(FILES ${rime_private_header_files}
215+
DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/rime/${rime_private_header_files_dir})
216+
endforeach()
217+
endif()
201218

202219
if(BUILD_DATA)
203220
file(GLOB rime_preset_data_files ${PROJECT_SOURCE_DIR}/data/preset/*.yaml)

Makefile

+6-3
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,24 @@ release:
4242
cmake . -B$(build) \
4343
-DCMAKE_INSTALL_PREFIX=$(prefix) \
4444
-DCMAKE_BUILD_TYPE=Release \
45-
-DBUILD_MERGED_PLUGINS=OFF
45+
-DBUILD_MERGED_PLUGINS=OFF \
46+
-DENABLE_EXTERNAL_PLUGINS=ON
4647
cmake --build $(build)
4748

4849
merged-plugins:
4950
cmake . -B$(build) \
5051
-DCMAKE_INSTALL_PREFIX=$(prefix) \
5152
-DCMAKE_BUILD_TYPE=Release \
52-
-DBUILD_MERGED_PLUGINS=ON
53+
-DBUILD_MERGED_PLUGINS=ON \
54+
-DENABLE_EXTERNAL_PLUGINS=OFF
5355
cmake --build $(build)
5456

5557
debug:
5658
cmake . -B$(build) \
5759
-DCMAKE_INSTALL_PREFIX=$(prefix) \
5860
-DCMAKE_BUILD_TYPE=Debug \
59-
-DBUILD_MERGED_PLUGINS=OFF
61+
-DBUILD_MERGED_PLUGINS=OFF \
62+
-DENABLE_EXTERNAL_PLUGINS=ON
6063
cmake --build $(build)
6164

6265
install:

plugins/CMakeLists.txt

+17-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,26 @@ set(RIME_SOURCE_DIR ${PROJECT_SOURCE_DIR})
22
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
33

44
# work around CMake build issues on macOS
5-
aux_source_directory(. rime_plugins_src)
5+
set(rime_plugin_boilerplate_src "plugin.cc")
6+
7+
set(plugins_module_src "plugins_module.cc")
68

79
unset(plugins_objs)
810
unset(plugins_deps)
911
unset(plugins_modules)
1012

13+
if(ENABLE_EXTERNAL_PLUGINS)
14+
add_library(rime-plugins-objs OBJECT ${plugins_module_src})
15+
if(BUILD_SHARED_LIBS)
16+
set_target_properties(rime-plugins-objs
17+
PROPERTIES
18+
POSITION_INDEPENDENT_CODE ON)
19+
endif()
20+
21+
set(plugins_objs ${plugins_objs} $<TARGET_OBJECTS:rime-plugins-objs>)
22+
set(plugins_modules ${plugins_modules} "plugins")
23+
endif()
24+
1125
if(DEFINED ENV{RIME_PLUGINS})
1226
set(plugins $ENV{RIME_PLUGINS})
1327
message(STATUS "Prescribed plugins: ${plugins}")
@@ -38,12 +52,12 @@ foreach(plugin ${plugins})
3852
set(plugins_modules ${plugins_modules} ${plugin_modules})
3953
else()
4054
message(STATUS "Plugin ${plugin_name} provides modules: ${plugin_modules}")
41-
add_library(${plugin_name} ${rime_plugins_src} ${plugin_objs})
55+
add_library(${plugin_name} ${rime_plugin_boilerplate_src} ${plugin_objs})
4256
target_link_libraries(${plugin_name} ${plugin_deps})
4357
if(XCODE_VERSION)
4458
set_target_properties(${plugin_name} PROPERTIES INSTALL_NAME_DIR "@rpath")
4559
endif(XCODE_VERSION)
46-
install(TARGETS ${plugin_name} DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR})
60+
install(TARGETS ${plugin_name} DESTINATION ${RIME_PLUGINS_DIR})
4761
endif()
4862
endforeach(plugin)
4963

plugins/plugins_module.cc

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// Copyright RIME Developers
3+
// Distributed under the BSD License
4+
//
5+
6+
#include <boost/algorithm/string.hpp>
7+
#include <boost/dll.hpp>
8+
#include <boost/filesystem.hpp>
9+
#include <rime/build_config.h>
10+
#include <rime/common.h>
11+
#include <rime/component.h>
12+
#include <rime/module.h>
13+
#include <rime/registry.h>
14+
#include <rime_api.h>
15+
16+
namespace fs = boost::filesystem;
17+
18+
namespace rime {
19+
20+
class PluginManager {
21+
public:
22+
void LoadPlugins(fs::path plugins_dir);
23+
24+
static string plugin_name_of(fs::path plugin_file);
25+
26+
static PluginManager& instance();
27+
28+
private:
29+
PluginManager() = default;
30+
31+
map<string, boost::dll::shared_library> plugin_libs_;
32+
};
33+
34+
void PluginManager::LoadPlugins(fs::path plugins_dir) {
35+
const fs::perms exe_perms = (fs::owner_exe | fs::group_exe | fs::others_exe);
36+
ModuleManager& mm = ModuleManager::instance();
37+
if (!fs::is_directory(plugins_dir)) {
38+
return;
39+
}
40+
LOG(INFO) << "loading plugins from " << plugins_dir;
41+
for (fs::directory_iterator iter(plugins_dir), end; iter != end; ++iter) {
42+
fs::path plugin_file = iter->path();
43+
if (plugin_file.extension() == boost::dll::shared_library::suffix()) {
44+
fs::file_status plugin_file_status = fs::status(plugin_file);
45+
if (fs::is_regular_file(plugin_file_status) &&
46+
fs::status(plugin_file).permissions() & exe_perms) {
47+
DLOG(INFO) << "found plugin: " << plugin_file;
48+
string plugin_name = plugin_name_of(plugin_file);
49+
if (plugin_libs_.find(plugin_name) == plugin_libs_.end()) {
50+
LOG(INFO) << "loading plugin '" << plugin_name
51+
<< "' from " << plugin_file;
52+
try {
53+
auto plugin_lib = boost::dll::shared_library(plugin_file);
54+
plugin_libs_[plugin_name] = plugin_lib;
55+
} catch (const std::exception& ex) {
56+
LOG(ERROR) << "error loading plugin " << plugin_name << ": "
57+
<< ex.what();
58+
continue;
59+
}
60+
}
61+
if (RimeModule* module = mm.Find(plugin_name)) {
62+
mm.LoadModule(module);
63+
LOG(INFO) << "loaded plugin: " << plugin_name;
64+
} else {
65+
LOG(WARNING) << "module '" << plugin_name
66+
<< "' is not provided by plugin library " << plugin_file;
67+
}
68+
}
69+
}
70+
}
71+
}
72+
73+
string PluginManager::plugin_name_of(fs::path plugin_file) {
74+
string name = plugin_file.stem().string();
75+
// remove prefix "(lib)rime-"
76+
if (boost::starts_with(name, "librime-")) {
77+
boost::erase_first(name, "librime-");
78+
} else if (boost::starts_with(name, "rime-")) {
79+
boost::erase_first(name, "rime-");
80+
}
81+
// replace dash with underscore, for the plugin name is part of the module
82+
// initializer function name.
83+
boost::replace_all(name, "-", "_");
84+
return name;
85+
}
86+
87+
PluginManager& PluginManager::instance() {
88+
static the<PluginManager> s_instance;
89+
if (!s_instance) {
90+
s_instance.reset(new PluginManager);
91+
}
92+
return *s_instance;
93+
}
94+
95+
} // namespace rime
96+
97+
static void rime_plugins_initialize() {
98+
rime::PluginManager::instance().LoadPlugins(RIME_PLUGINS_DIR);
99+
}
100+
101+
static void rime_plugins_finalize() {}
102+
103+
RIME_REGISTER_MODULE(plugins)

rime.pc.in

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ exec_prefix=@exec_prefix@
33
libdir=@libdir@
44
includedir=@includedir@
55
pkgdatadir=@pkgdatadir@
6+
pluginsdir=@pluginsdir@
67

78
Name: Rime
89
Description: Rime Input Method Engine

src/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ set(rime_optional_deps "")
3636
if(Gflags_FOUND)
3737
set(rime_optional_deps ${rime_optional_deps} ${Gflags_LIBRARY})
3838
endif()
39+
if(ENABLE_EXTERNAL_PLUGINS)
40+
set(rime_optional_deps ${rime_optional_deps} dl)
41+
endif()
3942

4043
set(rime_core_deps
4144
${Boost_LIBRARIES}

src/rime/build_config.h.in

+3
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@
88
#cmakedefine RIME_BOOST_SIGNALS2
99
#cmakedefine RIME_ENABLE_LOGGING
1010

11+
#cmakedefine RIME_DATA_DIR "@RIME_DATA_DIR@"
12+
#cmakedefine RIME_PLUGINS_DIR "@RIME_PLUGINS_DIR@"
13+
1114
#endif // RIME_BUILD_CONFIG_H_

0 commit comments

Comments
 (0)