diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f1bcdabb..493f32af5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,8 +22,11 @@ option(ENABLE_LOGGING "Enable logging with google-glog library" ON) option(BOOST_USE_CXX11 "Boost has been built with C++11 support" OFF) option(BOOST_USE_SIGNALS2 "Boost use signals2 instead of signals" ON) option(ENABLE_ASAN "Enable Address Sanitizer (Unix Only)" OFF) +option(INSTALL_PRIVATE_HEADERS "Install private headers (usually needed for externally built Rime plugins)" OFF) +option(ENABLE_EXTERNAL_PLUGINS "Enable loading of externally built Rime plugins (from directory set by RIME_PLUGINS_DIR variable)" OFF) set(RIME_DATA_DIR "${CMAKE_INSTALL_FULL_DATADIR}/rime-data" CACHE STRING "Target directory for Rime data") +set(RIME_PLUGINS_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/rime-plugins" CACHE STRING "Target directory for externally built Rime plugins") if(WIN32) set(ext ".exe") @@ -183,6 +186,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux|FreeBSD|DragonFly|GNU") set(bindir "${CMAKE_INSTALL_FULL_BINDIR}") set(libdir "${CMAKE_INSTALL_FULL_LIBDIR}") set(pkgdatadir "${RIME_DATA_DIR}") + set(pluginsdir "${RIME_PLUGINS_DIR}") set(includedir "${CMAKE_INSTALL_FULL_INCLUDEDIR}") configure_file( ${PROJECT_SOURCE_DIR}/rime.pc.in @@ -198,6 +202,19 @@ install(FILES cmake/RimeConfig.cmake file(GLOB rime_public_header_files ${PROJECT_SOURCE_DIR}/src/*.h) install(FILES ${rime_public_header_files} DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}) +if(INSTALL_PRIVATE_HEADERS) + file(GLOB rime_private_header_files + ${PROJECT_SOURCE_DIR}/src/rime/*.h + ${PROJECT_BINARY_DIR}/src/rime/*.h) + install(FILES ${rime_private_header_files} + DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/rime) + foreach(rime_private_header_files_dir algo config dict gear lever) + file(GLOB rime_private_header_files + ${PROJECT_SOURCE_DIR}/src/rime/${rime_private_header_files_dir}/*.h) + install(FILES ${rime_private_header_files} + DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/rime/${rime_private_header_files_dir}) + endforeach() +endif() if(BUILD_DATA) file(GLOB rime_preset_data_files ${PROJECT_SOURCE_DIR}/data/preset/*.yaml) diff --git a/Makefile b/Makefile index 060a8f884..b8d881ff5 100644 --- a/Makefile +++ b/Makefile @@ -42,21 +42,24 @@ release: cmake . -B$(build) \ -DCMAKE_INSTALL_PREFIX=$(prefix) \ -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_MERGED_PLUGINS=OFF + -DBUILD_MERGED_PLUGINS=OFF \ + -DENABLE_EXTERNAL_PLUGINS=ON cmake --build $(build) merged-plugins: cmake . -B$(build) \ -DCMAKE_INSTALL_PREFIX=$(prefix) \ -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_MERGED_PLUGINS=ON + -DBUILD_MERGED_PLUGINS=ON \ + -DENABLE_EXTERNAL_PLUGINS=OFF cmake --build $(build) debug: cmake . -B$(build) \ -DCMAKE_INSTALL_PREFIX=$(prefix) \ -DCMAKE_BUILD_TYPE=Debug \ - -DBUILD_MERGED_PLUGINS=OFF + -DBUILD_MERGED_PLUGINS=OFF \ + -DENABLE_EXTERNAL_PLUGINS=ON cmake --build $(build) install: diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index f8a505b89..e70438f5a 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -2,12 +2,26 @@ set(RIME_SOURCE_DIR ${PROJECT_SOURCE_DIR}) set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) # work around CMake build issues on macOS -aux_source_directory(. rime_plugins_src) +set(rime_plugin_boilerplate_src "plugin.cc") + +set(plugins_module_src "plugins_module.cc") unset(plugins_objs) unset(plugins_deps) unset(plugins_modules) +if(ENABLE_EXTERNAL_PLUGINS) + add_library(rime-plugins-objs OBJECT ${plugins_module_src}) + if(BUILD_SHARED_LIBS) + set_target_properties(rime-plugins-objs + PROPERTIES + POSITION_INDEPENDENT_CODE ON) + endif() + + set(plugins_objs ${plugins_objs} $) + set(plugins_modules ${plugins_modules} "plugins") +endif() + if(DEFINED ENV{RIME_PLUGINS}) set(plugins $ENV{RIME_PLUGINS}) message(STATUS "Prescribed plugins: ${plugins}") @@ -38,12 +52,12 @@ foreach(plugin ${plugins}) set(plugins_modules ${plugins_modules} ${plugin_modules}) else() message(STATUS "Plugin ${plugin_name} provides modules: ${plugin_modules}") - add_library(${plugin_name} ${rime_plugins_src} ${plugin_objs}) + add_library(${plugin_name} ${rime_plugin_boilerplate_src} ${plugin_objs}) target_link_libraries(${plugin_name} ${plugin_deps}) if(XCODE_VERSION) set_target_properties(${plugin_name} PROPERTIES INSTALL_NAME_DIR "@rpath") endif(XCODE_VERSION) - install(TARGETS ${plugin_name} DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}) + install(TARGETS ${plugin_name} DESTINATION ${RIME_PLUGINS_DIR}) endif() endforeach(plugin) diff --git a/plugins/plugins_module.cc b/plugins/plugins_module.cc new file mode 100644 index 000000000..7a567be48 --- /dev/null +++ b/plugins/plugins_module.cc @@ -0,0 +1,103 @@ +// +// Copyright RIME Developers +// Distributed under the BSD License +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = boost::filesystem; + +namespace rime { + +class PluginManager { + public: + void LoadPlugins(fs::path plugins_dir); + + static string plugin_name_of(fs::path plugin_file); + + static PluginManager& instance(); + + private: + PluginManager() = default; + + map plugin_libs_; +}; + +void PluginManager::LoadPlugins(fs::path plugins_dir) { + const fs::perms exe_perms = (fs::owner_exe | fs::group_exe | fs::others_exe); + ModuleManager& mm = ModuleManager::instance(); + if (!fs::is_directory(plugins_dir)) { + return; + } + LOG(INFO) << "loading plugins from " << plugins_dir; + for (fs::directory_iterator iter(plugins_dir), end; iter != end; ++iter) { + fs::path plugin_file = iter->path(); + if (plugin_file.extension() == boost::dll::shared_library::suffix()) { + fs::file_status plugin_file_status = fs::status(plugin_file); + if (fs::is_regular_file(plugin_file_status) && + fs::status(plugin_file).permissions() & exe_perms) { + DLOG(INFO) << "found plugin: " << plugin_file; + string plugin_name = plugin_name_of(plugin_file); + if (plugin_libs_.find(plugin_name) == plugin_libs_.end()) { + LOG(INFO) << "loading plugin '" << plugin_name + << "' from " << plugin_file; + try { + auto plugin_lib = boost::dll::shared_library(plugin_file); + plugin_libs_[plugin_name] = plugin_lib; + } catch (const std::exception& ex) { + LOG(ERROR) << "error loading plugin " << plugin_name << ": " + << ex.what(); + continue; + } + } + if (RimeModule* module = mm.Find(plugin_name)) { + mm.LoadModule(module); + LOG(INFO) << "loaded plugin: " << plugin_name; + } else { + LOG(WARNING) << "module '" << plugin_name + << "' is not provided by plugin library " << plugin_file; + } + } + } + } +} + +string PluginManager::plugin_name_of(fs::path plugin_file) { + string name = plugin_file.stem().string(); + // remove prefix "(lib)rime-" + if (boost::starts_with(name, "librime-")) { + boost::erase_first(name, "librime-"); + } else if (boost::starts_with(name, "rime-")) { + boost::erase_first(name, "rime-"); + } + // replace dash with underscore, for the plugin name is part of the module + // initializer function name. + boost::replace_all(name, "-", "_"); + return name; +} + +PluginManager& PluginManager::instance() { + static the s_instance; + if (!s_instance) { + s_instance.reset(new PluginManager); + } + return *s_instance; +} + +} // namespace rime + +static void rime_plugins_initialize() { + rime::PluginManager::instance().LoadPlugins(RIME_PLUGINS_DIR); +} + +static void rime_plugins_finalize() {} + +RIME_REGISTER_MODULE(plugins) diff --git a/rime.pc.in b/rime.pc.in index 7291e9f85..1a8c54318 100644 --- a/rime.pc.in +++ b/rime.pc.in @@ -3,6 +3,7 @@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ pkgdatadir=@pkgdatadir@ +pluginsdir=@pluginsdir@ Name: Rime Description: Rime Input Method Engine diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b667fa6bf..78e826d48 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,6 +36,9 @@ set(rime_optional_deps "") if(Gflags_FOUND) set(rime_optional_deps ${rime_optional_deps} ${Gflags_LIBRARY}) endif() +if(ENABLE_EXTERNAL_PLUGINS) + set(rime_optional_deps ${rime_optional_deps} dl) +endif() set(rime_core_deps ${Boost_LIBRARIES} diff --git a/src/rime/build_config.h.in b/src/rime/build_config.h.in index ebd16527e..760059086 100644 --- a/src/rime/build_config.h.in +++ b/src/rime/build_config.h.in @@ -8,4 +8,7 @@ #cmakedefine RIME_BOOST_SIGNALS2 #cmakedefine RIME_ENABLE_LOGGING +#cmakedefine RIME_DATA_DIR "@RIME_DATA_DIR@" +#cmakedefine RIME_PLUGINS_DIR "@RIME_PLUGINS_DIR@" + #endif // RIME_BUILD_CONFIG_H_