diff --git a/.clang-format b/.clang-format index e0958e5..6214c59 100644 --- a/.clang-format +++ b/.clang-format @@ -10,3 +10,4 @@ QualifierOrder: [ 'const', 'type', ] +SpaceInEmptyBraces: Block diff --git a/.clang-tidy b/.clang-tidy index 143d361..b117b03 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,13 +2,13 @@ Checks: > -*, clang-diagnostic-*, clang-analyzer-optin.*, - llvm-*,-llvm-header-guard, + llvm-*,-llvm-header-guard,-llvm-prefer-static-over-anonymous-namespace,-llvm-use-ranges, google-*,-google-readability-todo,-google-readability-braces-around-statements, hicpp-*,-hicpp-braces-around-statements,-hicpp-use-emplace,-hicpp-named-parameter, -hicpp-no-array-decay,-hicpp-uppercase-literal-suffix, misc-*,-misc-include-cleaner,-misc-no-recursion, bugprone-*, - boost-*, + boost-*,-boost-use-ranges, cert-*,-cert-dcl16-c,-cert-dcl51-cpp,-cert-dcl37-c, concurrency-*, cppcoreguidelines-*,-cppcoreguidelines-avoid-magic-numbers, @@ -18,6 +18,10 @@ Checks: > readability-*,-readability-redundant-access-specifiers CheckOptions: + - key: readability-identifier-length.IgnoredVariableNames + value: '^(id|it)$' + - key: readability-identifier-length.IgnoredParameterNames + value: '^(id|it)$' - key: readability-identifier-naming.AbstractClassCase value: CamelCase - key: readability-identifier-naming.ClassCase diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index af40f50..fcee63c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -61,8 +61,12 @@ jobs: gtest-dev - name: Fixup environment run: | + # add missing final symlink for Lua library ln -s liblua-5.2.so.0 /usr/lib/liblua-5.2.so + # use ninja-build as ninja binary (alternative: install samurai instead) ln -s /usr/lib/ninja-build/bin/ninja /usr/bin/ninja + # patch Boost.process to not use ::close_range() since Alpine's musl implementation does not provide it + sed -i 's/#define BOOST_PROCESS_V2_HAS_CLOSE_RANGE 1/\/\/#define BOOST_PROCESS_V2_HAS_CLOSE_RANGE 1\n#include /g' /usr/include/boost/process/v2/posix/detail/close_handles.ipp shell: alpine.sh --root {0} - name: Prepare @@ -92,7 +96,7 @@ jobs: retention-days: 5 unittest: - name: "unittest" + name: "unittests" needs: build runs-on: ubuntu-latest strategy: @@ -151,7 +155,7 @@ jobs: retention-days: 3 ctest: - name: "ctest" + name: "cmake tests" needs: build runs-on: ubuntu-latest steps: @@ -179,8 +183,12 @@ jobs: gtest-dev - name: Fixup environment run: | - ln -s /usr/lib/ninja-build/bin/ninja /usr/bin/ninja + # add missing final symlink for Lua library ln -s liblua-5.2.so.0 /usr/lib/liblua-5.2.so + # use ninja-build as ninja binary (alternative: install samurai instead) + ln -s /usr/lib/ninja-build/bin/ninja /usr/bin/ninja + # patch Boost.process to not use ::close_range() since Alpine's musl implementation does not provide it + sed -i 's/#define BOOST_PROCESS_V2_HAS_CLOSE_RANGE 1/\/\/#define BOOST_PROCESS_V2_HAS_CLOSE_RANGE 1\n#include /g' /usr/include/boost/process/v2/posix/detail/close_handles.ipp shell: alpine.sh --root {0} - name: Run cmake tests diff --git a/.github/workflows/cpp-lint.yml b/.github/workflows/cpp-lint.yml index b0f2164..b5d96ec 100644 --- a/.github/workflows/cpp-lint.yml +++ b/.github/workflows/cpp-lint.yml @@ -47,8 +47,12 @@ jobs: clang19-extra-tools - name: Fixup environment run: | + # add missing final symlink for Lua library ln -s liblua-5.2.so.0 /usr/lib/liblua-5.2.so + # use ninja-build as ninja binary (alternative: install samurai instead) ln -s /usr/lib/ninja-build/bin/ninja /usr/bin/ninja + # patch Boost.process to not use ::close_range() since Alpine's musl implementation does not provide it + sed -i 's/#define BOOST_PROCESS_V2_HAS_CLOSE_RANGE 1/\/\/#define BOOST_PROCESS_V2_HAS_CLOSE_RANGE 1\n#include /g' /usr/include/boost/process/v2/posix/detail/close_handles.ipp shell: alpine.sh --root {0} - name: Generate compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b49739..509c807 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,16 @@ option(PPPLUGIN_ENABLE_UNDEFINED_SANITIZE option(PPPLUGIN_ENABLE_UNREACHABLE_SANITIZE "Enable compilation with unreachable sanitize flags" OFF) +# required Boost header-only components: dll, algorithm, asio, process, uuid; +# Boost.filesystem is only required because of Boost.dll find_package(Boost 1.70.0 REQUIRED CONFIG COMPONENTS headers filesystem) +if(${Boost_VERSION} VERSION_GREATER_EQUAL 1.85.0) + # Boost.process was header-only until 1.84; requires linking in >= 1.85 + find_package(Boost REQUIRED CONFIG COMPONENTS process) +else() + # add dummy target so that this check is only necessary once + add_library(Boost::process ALIAS Boost::headers) +endif() find_package(Python 3.0 REQUIRED COMPONENTS Development) find_package(Lua 5.2 REQUIRED) diff --git a/cmake/ppplugin-config.cmake.in b/cmake/ppplugin-config.cmake.in index 9d24d6b..5e89629 100644 --- a/cmake/ppplugin-config.cmake.in +++ b/cmake/ppplugin-config.cmake.in @@ -1,12 +1,18 @@ @PACKAGE_INIT@ include(CMakeFindDependencyMacro) -find_dependency(Boost @Boost_VERSION_MAJOR@ COMPONENTS headers filesystem - python) +find_dependency(Boost @Boost_VERSION_MAJOR@ CONFIG COMPONENTS headers filesystem) +if(@Boost_VERSION@ VERSION_GREATER_EQUAL 1.85.0) + # Boost.process was header-only until 1.84; requires linking in >= 1.85 + find_dependency(Boost CONFIG COMPONENTS process) +else() + # add dummy target so that this check is only necessary once + add_library(Boost::process ALIAS Boost::headers) +endif() find_dependency(Python @Python_VERSION_MAJOR@ COMPONENTS Development) find_dependency(Lua @LUA_VERSION_MAJOR@) -if(${PPPLUGIN_ENABLE_CPP17_COMPATIBILITY}) +if(@PPPLUGIN_ENABLE_CPP17_COMPATIBILITY@) find_dependency(fmt @fmt_VERSION_MAJOR@) endif() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 890a974..8b13789 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1 @@ -add_subdirectory(simple_plugin) -add_subdirectory(configurable_plugin) -add_subdirectory(lua_plugin) -add_subdirectory(multi_language_plugin) -add_subdirectory(multi_return_lua_plugin) -add_subdirectory(python_plugin) + diff --git a/examples/configurable_plugin/CMakeLists.txt b/examples/configurable_plugin/CMakeLists.txt deleted file mode 100644 index 01a14ac..0000000 --- a/examples/configurable_plugin/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_executable(configurable_plugin_manager configurable_plugin_manager.cpp) -target_link_libraries(configurable_plugin_manager ${LIBRARY_TARGET}) - -add_library(configurable_plugin SHARED configurable_plugin.cpp) diff --git a/examples/configurable_plugin/configurable_plugin.cpp b/examples/configurable_plugin/configurable_plugin.cpp deleted file mode 100644 index 54d83e1..0000000 --- a/examples/configurable_plugin/configurable_plugin.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "configurable_plugin.h" - -#include - -#include - -BOOST_DLL_ALIAS(ConfigurablePluginA::create, create_a); - -void ConfigurablePluginA::loop(const std::atomic& stop) -{ - while (!stop) { - print(configuration().printValue); - sleep(configuration().printInterval); - } -} diff --git a/examples/configurable_plugin/configurable_plugin.h b/examples/configurable_plugin/configurable_plugin.h deleted file mode 100644 index ecfe589..0000000 --- a/examples/configurable_plugin/configurable_plugin.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef PPPLUGIN_EXAMPLES_CONFIGURABLE_PLUGIN_H -#define PPPLUGIN_EXAMPLES_CONFIGURABLE_PLUGIN_H - -#include -#include -#include - -struct PluginConfiguration { - std::chrono::milliseconds printInterval {}; - std::string printValue; -}; - -class ConfigurablePluginInterface { -public: - explicit ConfigurablePluginInterface(PluginConfiguration config) - : configuration_ { std::move(config) } - { - } - virtual ~ConfigurablePluginInterface() = default; - ConfigurablePluginInterface(const ConfigurablePluginInterface&) = default; - ConfigurablePluginInterface(ConfigurablePluginInterface&&) noexcept = default; - ConfigurablePluginInterface& operator=(const ConfigurablePluginInterface&) = default; - ConfigurablePluginInterface& operator=(ConfigurablePluginInterface&&) noexcept = default; - - virtual void loop(const std::atomic& stop) = 0; - -protected: - template - void print(T&& arg) - { - std::cout << std::forward(arg) << std::flush; - } - static void sleep(std::chrono::milliseconds duration) - { - std::this_thread::sleep_for(duration); - } - [[nodiscard]] const PluginConfiguration& configuration() const { return configuration_; } - -private: - PluginConfiguration configuration_; -}; - -class ConfigurablePluginA : public ConfigurablePluginInterface { -public: - using ConfigurablePluginInterface::ConfigurablePluginInterface; - - static std::shared_ptr create( - PluginConfiguration config) - { - return std::make_shared(std::move(config)); - } - - void loop(const std::atomic& stop) override; -}; - -#endif // PPPLUGIN_EXAMPLES_CONFIGURABLE_PLUGIN_H diff --git a/examples/configurable_plugin/configurable_plugin_manager.cpp b/examples/configurable_plugin/configurable_plugin_manager.cpp deleted file mode 100644 index 8efe1f3..0000000 --- a/examples/configurable_plugin/configurable_plugin_manager.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "configurable_plugin.h" -#include "ppplugin/cpp/plugin.h" -#include "ppplugin/plugin_manager.h" - -int main(int argc, char* argv[]) -{ - try { - if (argc < 1) { - return -1; - } - auto executable_dir = std::filesystem::path { argv[0] }.parent_path(); - auto library = executable_dir / "libconfigurable_plugin.so"; - ppplugin::GenericPluginManager manager; - PluginConfiguration config_1 { std::chrono::milliseconds { 600 }, "1" }; - auto plugin = manager.loadCppPlugin(library).valueOrElse([&library]() -> ppplugin::CppPlugin { - std::cerr << "Unable to load '" << library << "'\n"; - std::exit(1); // NOLINT(concurrency-mt-unsafe) - }); - auto a_1 = plugin.call>("create_a", config_1); - PluginConfiguration config_2 { std::chrono::milliseconds { 200 }, "2" }; - auto a_2 = plugin.call>("create_a", config_2); - - if (!a_1 || !a_2) { - return 2; - } - - std::atomic stop_signal { false }; - std::thread thread_1([plugin = *a_1, &stop_signal]() { plugin->loop(stop_signal); }); - std::thread thread_2([plugin = *a_2, &stop_signal]() { plugin->loop(stop_signal); }); - std::this_thread::sleep_for(std::chrono::seconds { 5 }); - stop_signal.store(true); - thread_1.join(); - thread_2.join(); - } catch (const std::exception& exception) { - std::cerr << "A fatal error occurred: '" << exception.what() << "'\n"; - return 1; - } catch (...) { - std::cerr << "An unknown fatal error occurred!\n"; - return 1; - } - return 0; -} diff --git a/examples/lua_plugin/CMakeLists.txt b/examples/lua_plugin/CMakeLists.txt deleted file mode 100644 index e5ea902..0000000 --- a/examples/lua_plugin/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_executable(lua_plugin_manager lua_plugin_manager.cpp) -target_link_libraries(lua_plugin_manager ${LIBRARY_TARGET}) - -add_custom_command( - TARGET lua_plugin_manager - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugin.lua - $/plugin_1.lua - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugin.lua - $/plugin_2.lua - COMMENT "Copying Lua plugins to output directory...") diff --git a/examples/lua_plugin/lua_plugin_manager.cpp b/examples/lua_plugin/lua_plugin_manager.cpp deleted file mode 100644 index 2e431fa..0000000 --- a/examples/lua_plugin/lua_plugin_manager.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "ppplugin/lua/plugin.h" -#include "ppplugin/plugin_manager.h" - -int main(int argc, char* argv[]) -{ - try { - if (argc < 1) { - return -1; - } - // path to plugins can be passed via command line; - // if no path was specified, the location of the executable is used instead - std::filesystem::path plugin_dir; - if (argc < 2) { - plugin_dir = std::filesystem::path { argv[0] }.parent_path(); - } else { - plugin_dir = std::filesystem::path { argv[1] }; - } - ppplugin::GenericPluginManager manager; - std::vector threads; - - // recursively traverse filesystem to find scripts - const std::filesystem::recursive_directory_iterator dir_iterator { plugin_dir }; - for (const auto& entry : dir_iterator) { - if (!entry.is_regular_file()) { - continue; - } - const auto& path = entry.path(); - // only load files ending with ".lua" and execute in separate thread - if (path.extension() == ".lua") { - if (auto plugin = manager.loadLuaPlugin(path)) { - threads.emplace_back([plugin = std::move(*plugin), plugin_number = threads.size()]() mutable { - // data race due to shared resource "stdout", - // but Lua plugins are otherwise thread-safe - std::ignore = plugin.call("initialize"); - std::ignore = plugin.call("loop", plugin_number); - }); - } - } - } - - for (auto& thread : threads) { - thread.join(); - } - } catch (const std::exception& exception) { - std::cerr << "A fatal error occurred: '" << exception.what() << "'\n"; - return 1; - } catch (...) { - std::cerr << "An unknown fatal error occurred!"; - return 1; - } - return 0; -} diff --git a/examples/lua_plugin/plugin.lua b/examples/lua_plugin/plugin.lua deleted file mode 100644 index 3ff6512..0000000 --- a/examples/lua_plugin/plugin.lua +++ /dev/null @@ -1,17 +0,0 @@ -require 'os' - -function initialize() - print("initialize") -end - -function loop(something) - while true do - local start = os.time() - print("loop: " .. something) - while start + 1 > os.time() do - end - -- TODO: execute('sleep 1') blocks SIGINT signal, would have to check return value - end -end - -print("loading") diff --git a/examples/multi_language_plugin/CMakeLists.txt b/examples/multi_language_plugin/CMakeLists.txt deleted file mode 100644 index f54082a..0000000 --- a/examples/multi_language_plugin/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -add_executable(multi_language_plugin_manager multi_language_plugin_manager.cpp) -target_include_directories(multi_language_plugin_manager - PUBLIC ${CMAKE_SOURCE_DIR}/include) -target_link_libraries(multi_language_plugin_manager ${LIBRARY_TARGET}) - -add_library(cpp_plugin SHARED plugins/cpp_plugin.cpp) -add_library(c_plugin SHARED plugins/c_plugin.c) - -add_custom_command( - TARGET multi_language_plugin_manager - POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugins/lua_plugin.lua - $ - COMMAND - ${CMAKE_COMMAND} -E copy - ${CMAKE_CURRENT_SOURCE_DIR}/plugins/python_plugin.py - $ - COMMENT "Copying plugins to output directory...") diff --git a/examples/multi_language_plugin/multi_language_plugin_manager.cpp b/examples/multi_language_plugin/multi_language_plugin_manager.cpp deleted file mode 100644 index aded76e..0000000 --- a/examples/multi_language_plugin/multi_language_plugin_manager.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ppplugin/errors.h" -#include "ppplugin/noop_plugin.h" -#include "ppplugin/plugin.h" -#include "ppplugin/plugin_manager.h" - -int main(int argc, char* argv[]) -{ - if (argc < 1) { - return -1; - } - try { - auto executable_dir = std::filesystem::path { argv[0] }.parent_path(); - auto cpp_lib_path = executable_dir / "libcpp_plugin.so"; - auto c_lib_path = executable_dir / "libc_plugin.so"; - auto lua_lib_path = executable_dir / "lua_plugin.lua"; - auto python_lib_path = executable_dir / "python_plugin.py"; - auto non_existant_lib_path = executable_dir / "does_not_exist"; - - ppplugin::PluginManager manager; - std::vector plugins; - if (auto plugin = manager.loadCppPlugin(cpp_lib_path)) { - plugins.push_back(std::move(*plugin)); - } - if (auto plugin = manager.loadCPlugin(c_lib_path)) { - plugins.push_back(std::move(*plugin)); - } - if (auto plugin = manager.loadLuaPlugin(lua_lib_path)) { - plugins.push_back(std::move(*plugin)); - } - if (auto plugin = manager.loadPythonPlugin(python_lib_path)) { - plugins.push_back(std::move(*plugin)); - } - plugins.push_back(manager.loadCPlugin(non_existant_lib_path) - .andThen([](const auto& plugin) { - // convert to generic plugin - return ppplugin::Plugin { plugin }; - }) - // silently fail and use no-op plugin instead - .valueOr(ppplugin::NoopPlugin {})); - for (auto& plugin : plugins) { - plugin.call("initialize").valueOrElse([](const ppplugin::CallError& error) { - std::cerr << "Initialize Error: " << error.what() << '\n'; - }); - } - for (int counter {}; counter < std::numeric_limits::max(); ++counter) { - for (auto& plugin : plugins) { - // explicit cast to int to avoid passing by reference - plugin.call("loop", static_cast(counter)) // NOLINT(readability-redundant-casting) - .valueOrElse([](const ppplugin::CallError& error) { - std::cerr << "Loop Error: " << error.what() << '\n'; - }); - } - std::this_thread::sleep_for(std::chrono::milliseconds { 500 }); - } - } catch (const std::exception& exception) { - std::cerr << "A fatal error occurred: '" << exception.what() << "'\n"; - return 1; - } catch (...) { - std::cerr << "An unknown fatal error occurred!"; - return 1; - } - return 0; -} diff --git a/examples/multi_language_plugin/plugins/c_plugin.c b/examples/multi_language_plugin/plugins/c_plugin.c deleted file mode 100644 index 938bc1e..0000000 --- a/examples/multi_language_plugin/plugins/c_plugin.c +++ /dev/null @@ -1,10 +0,0 @@ -#include - -void initialize(void) { - puts("C initialize"); -} - -int loop(int value) { - printf("C: %d\n", value); - return value - 2; -} diff --git a/examples/multi_language_plugin/plugins/cpp_plugin.cpp b/examples/multi_language_plugin/plugins/cpp_plugin.cpp deleted file mode 100644 index 76d8d3a..0000000 --- a/examples/multi_language_plugin/plugins/cpp_plugin.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include - -#include - -extern void initialize() -{ - std::cout << "C++ initialize" << '\n'; -} - -// necessary to circumvent different C++ name mangling schemes -BOOST_DLL_AUTO_ALIAS(initialize); -// this would be the alternative: -// namespace prevent_conflict { -// extern "C" const void* initialize; -// const void* initialize = reinterpret_cast(&::initialize); -// } // namespace prevent_conflict - -class A { -public: - static int loop(int value) - { - std::cout << "C++: " << value++ << '\n'; - return value; - } -}; -BOOST_DLL_ALIAS(A::loop, loop); diff --git a/examples/multi_language_plugin/plugins/lua_plugin.lua b/examples/multi_language_plugin/plugins/lua_plugin.lua deleted file mode 100644 index edf14c5..0000000 --- a/examples/multi_language_plugin/plugins/lua_plugin.lua +++ /dev/null @@ -1,14 +0,0 @@ -function initialize() - print("Lua initialize") -end - -function loop(value) - print("Lua: " .. tostring(value)) - return value * -1.5 -end - -print("Lua loading") - -if not pcall(debug.getlocal, 4, 1) then - print("Lua main") -end diff --git a/examples/multi_language_plugin/plugins/python_plugin.py b/examples/multi_language_plugin/plugins/python_plugin.py deleted file mode 100644 index 9914fdc..0000000 --- a/examples/multi_language_plugin/plugins/python_plugin.py +++ /dev/null @@ -1,11 +0,0 @@ -def initialize(): - print("Python initialize") - -def loop(value): - print("Python: ", value) - return value * -1.5 - -print("Python loading") - -if __name__ == '__main__': - print("Python main") diff --git a/examples/multi_return_lua_plugin/CMakeLists.txt b/examples/multi_return_lua_plugin/CMakeLists.txt deleted file mode 100644 index 9d9ba39..0000000 --- a/examples/multi_return_lua_plugin/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -add_executable(multi_return_lua_plugin_manager - multi_return_lua_plugin_manager.cpp) -target_link_libraries(multi_return_lua_plugin_manager ${LIBRARY_TARGET}) - -add_custom_command( - TARGET multi_return_lua_plugin_manager - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugin.lua - $ - COMMENT "Copying Lua plugin to output directory...") diff --git a/examples/multi_return_lua_plugin/multi_return_lua_plugin_manager.cpp b/examples/multi_return_lua_plugin/multi_return_lua_plugin_manager.cpp deleted file mode 100644 index 8e549fa..0000000 --- a/examples/multi_return_lua_plugin/multi_return_lua_plugin_manager.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include -#include -#include - -#include "ppplugin/plugin_manager.h" - -int main(int argc, char* argv[]) -{ - try { - if (argc < 1) { - return -1; - } - // path to plugins can be passed via command line; - // if no path was specified, the location of the executable is used instead - std::filesystem::path plugin_path; - if (argc < 2) { - plugin_path = std::filesystem::path { argv[0] }.parent_path(); - } else { - plugin_path = std::filesystem::path { argv[1] }; - } - plugin_path /= "plugin.lua"; - - ppplugin::PluginManager manager; - if (auto plugin = manager.loadLuaPlugin(plugin_path)) { - auto [a, b, c] = plugin->call>("values", 10).valueOr(std::make_tuple(-1, -1, -1)); - std::cout << "a, b, c: " << a << ", " << b << ", " << c << '\n'; - } else { - std::cerr << "Unable to load plugin '" << plugin_path << "'\n"; - return 1; - } - } catch (const std::exception& exception) { - std::cerr << "A fatal error occurred: '" << exception.what() << "'\n"; - return 1; - } catch (...) { - std::cerr << "An unknown fatal error occurred!"; - return 1; - } - return 0; -} diff --git a/examples/multi_return_lua_plugin/plugin.lua b/examples/multi_return_lua_plugin/plugin.lua deleted file mode 100644 index 268bede..0000000 --- a/examples/multi_return_lua_plugin/plugin.lua +++ /dev/null @@ -1,7 +0,0 @@ -require 'os' - -function values(value) - return value, value + 1, value + 2 -end - -print("loading") diff --git a/examples/python_plugin/CMakeLists.txt b/examples/python_plugin/CMakeLists.txt deleted file mode 100644 index 75346b5..0000000 --- a/examples/python_plugin/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_executable(python_plugin_manager python_plugin_manager.cpp) -target_link_libraries(python_plugin_manager PRIVATE ${LIBRARY_TARGET}) - -add_custom_command( - TARGET python_plugin_manager - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugin.py - $/plugin_1.py - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/plugin.py - $/plugin_2.py - COMMENT "Copying Python plugins to output directory...") diff --git a/examples/python_plugin/plugin.py b/examples/python_plugin/plugin.py deleted file mode 100644 index 4766ad0..0000000 --- a/examples/python_plugin/plugin.py +++ /dev/null @@ -1,19 +0,0 @@ -import time - - -def initialize(something): - print("initialize: ", something) - - -def loop(something): - # this only works because of the I/O and sleep; - # a loop without any "non-python" operations may block all threads - # wanting to execute python plugins; - # if python 3.12 or newer is used, multiple threads can execute - # python code at the same time - while True: - print("loop: ", something) - time.sleep(1) - - -print("loading") diff --git a/examples/python_plugin/python_plugin_manager.cpp b/examples/python_plugin/python_plugin_manager.cpp deleted file mode 100644 index fcdeca0..0000000 --- a/examples/python_plugin/python_plugin_manager.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ppplugin/plugin_manager.h" -#include "ppplugin/python/plugin.h" - -int main(int argc, char* argv[]) -{ - try { - if (argc < 1) { - return -1; - } - // path to plugins can be passed via command line; - // if no path was specified, the location of the executable is used instead - std::filesystem::path plugin_dir; - if (argc < 2) { - plugin_dir = std::filesystem::path { argv[0] }.parent_path(); - } else { - plugin_dir = std::filesystem::path { argv[1] }; - } - ppplugin::GenericPluginManager manager; - std::vector threads; - int plugin_number {}; - - // recursively traverse filesystem to find scripts - const std::filesystem::recursive_directory_iterator dir_iterator { plugin_dir }; - for (const auto& entry : dir_iterator) { - if (!entry.is_regular_file()) { - continue; - } - const auto& path = entry.path(); - // only load files ending with ".py" and execute in separate thread - if (path.extension() == ".py") { - auto plugin = manager.loadPythonPlugin(path); - if (plugin) { - threads.emplace_back([plugin = std::move(*plugin), plugin_number = plugin_number++]() mutable { - // ignore calling errors - std::ignore = plugin.call("initialize", plugin_number); - std::ignore = plugin.call("loop", std::to_string(plugin_number)); - }); - } else { - std::cerr << "Failed to load " << path << '\n'; - } - } - } - - for (auto& thread : threads) { - thread.join(); - } - } catch (const std::exception& exception) { - std::cerr << "A fatal error occurred: '" << exception.what() << "'\n"; - return 1; - } catch (...) { - std::cerr << "An unknown fatal error occurred!"; - return 1; - } - return 0; -} diff --git a/examples/simple_plugin/CMakeLists.txt b/examples/simple_plugin/CMakeLists.txt deleted file mode 100644 index 6caa1ce..0000000 --- a/examples/simple_plugin/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_executable(simple_plugin_manager simple_plugin_manager.cpp) -target_link_libraries(simple_plugin_manager ${LIBRARY_TARGET}) - -add_library(simple_plugin SHARED simple_plugin.cpp) diff --git a/examples/simple_plugin/simple_plugin.cpp b/examples/simple_plugin/simple_plugin.cpp deleted file mode 100644 index e35f0d1..0000000 --- a/examples/simple_plugin/simple_plugin.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "simple_plugin.h" - -#include -#include - -#include - -BOOST_DLL_ALIAS(SimplePluginA::create, create_a); - -void SimplePluginA::loop() -{ - while (true) { - print(i_); - sleep(std::chrono::milliseconds { 750 }); - } -} - -class SimplePluginB : public SimplePluginInterface { -public: - SimplePluginB() = default; - - static std::shared_ptr create() - { - return std::make_shared(); - } - - void initialize() override { i_ = std::make_unique(1); } - void loop() override - { - while (i_) { - print(*i_); - sleep(std::chrono::milliseconds { 500 }); - } - } - -private: - std::unique_ptr i_; -}; -BOOST_DLL_ALIAS(SimplePluginB::create, create_b); diff --git a/examples/simple_plugin/simple_plugin.h b/examples/simple_plugin/simple_plugin.h deleted file mode 100644 index 8a4b66c..0000000 --- a/examples/simple_plugin/simple_plugin.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef PPPLUGIN_EXAMPLES_SIMPLE_PLUGIN_H -#define PPPLUGIN_EXAMPLES_SIMPLE_PLUGIN_H - -#include -#include -#include - -class SimplePluginInterface { -public: - SimplePluginInterface() = default; - virtual ~SimplePluginInterface() = default; - SimplePluginInterface(const SimplePluginInterface&) = default; - SimplePluginInterface(SimplePluginInterface&&) noexcept = default; - SimplePluginInterface& operator=(const SimplePluginInterface&) = default; - SimplePluginInterface& operator=(SimplePluginInterface&&) noexcept = default; - - virtual void initialize() { } - virtual void loop() = 0; - -protected: - template - void print(T&& arg) - { - std::cout << std::forward(arg) << std::flush; - } - static void sleep(std::chrono::milliseconds duration) - { - std::this_thread::sleep_for(duration); - } -}; - -class SimplePluginA : public SimplePluginInterface { -public: - SimplePluginA() = default; - - static std::shared_ptr create() - { - return std::make_shared(); - } - - void loop() override; - -private: - int i_ { 0 }; -}; - -#endif // PPPLUGIN_EXAMPLES_SIMPLE_PLUGIN_H diff --git a/examples/simple_plugin/simple_plugin_manager.cpp b/examples/simple_plugin/simple_plugin_manager.cpp deleted file mode 100644 index c99a594..0000000 --- a/examples/simple_plugin/simple_plugin_manager.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "ppplugin/cpp/plugin.h" -#include "ppplugin/errors.h" -#include "ppplugin/plugin_manager.h" - -#include "simple_plugin.h" - -namespace { -template -std::thread initializeAndLoop(ppplugin::CppPlugin& plugin, const std::string& function_name) -{ - auto result = plugin.call>(function_name); - if (result) { - auto& plugin = *result; - plugin->initialize(); - return std::thread { [plugin]() { - plugin->loop(); - } }; - } - // NOLINTNEXTLINE(bugprone-unchecked-optional-access); checked in previous if - return std::thread { [function_name, error = *result.error()]() { - std::cerr << "Unable to call '" << function_name - << "' ('" << ppplugin::codeToString(error.code()) << "')!\n"; - } }; -} -} // namespace - -int main(int argc, char* argv[]) -{ - try { - if (argc < 1) { - return -1; - } - auto executable_dir = std::filesystem::path { argv[0] }.parent_path(); - auto library_path = executable_dir / "libsimple_plugin.so"; - // setup manager - has to stay alive for as long as you want to use the plugins - ppplugin::GenericPluginManager manager; - // load plugin library and exit on error - auto plugin_library = manager.loadCppPlugin(std::filesystem::path { library_path }) - .valueOrElse([](const auto& error) -> ppplugin::CppPlugin { - std::cerr << "Unable to load plugin: " << ppplugin::codeToString(error); - std::exit(1); // NOLINT(concurrency-mt-unsafe) - }); - - auto thread_a = initializeAndLoop(plugin_library, "create_a"); - auto thread_b = initializeAndLoop(plugin_library, "create_b"); - thread_a.join(); - thread_b.join(); - } catch (const std::exception& exception) { - std::cerr << "A fatal error occurred: '" << exception.what() << "'\n"; - return 1; - } catch (...) { - std::cerr << "An unknown fatal error occurred!\n"; - return 1; - } - return 0; -} diff --git a/include/ppplugin.cpp b/include/ppplugin.cpp new file mode 100644 index 0000000..8460ca8 --- /dev/null +++ b/include/ppplugin.cpp @@ -0,0 +1,39 @@ +// this source file includes all header files of the library; +// it allows the language server to infer the correct compilation flags for header +// files that do not have a corresponding source file; additionally, this will +// cause the compiler to compile check them in the compilation process, even if +// they are not used elsewhere in the library (external interface) + +#include "ppplugin/errors.h" +#include "ppplugin/expected.h" +#include "ppplugin/noop_plugin.h" +#include "ppplugin/plugin.h" +#include "ppplugin/plugin_manager.h" + +#include "ppplugin/c/plugin.h" + +#include "ppplugin/cpp/plugin.h" + +#include "ppplugin/lua/lua_helpers.h" +#include "ppplugin/lua/lua_script.h" +#include "ppplugin/lua/lua_state.h" +#include "ppplugin/lua/plugin.h" + +#include "ppplugin/shell/plugin.h" +#include "ppplugin/shell/shell_session.h" + +#include "ppplugin/python/plugin.h" +#include "ppplugin/python/python_exception.h" +#include "ppplugin/python/python_forward_defs.h" +#include "ppplugin/python/python_guard.h" +#include "ppplugin/python/python_interpreter.h" +#include "ppplugin/python/python_object.h" +#include "ppplugin/python/python_tuple.h" + +#include "ppplugin/detail/boost_dll_loader.h" +#include "ppplugin/detail/compatibility_utils.h" +#include "ppplugin/detail/compiler_info.h" +#include "ppplugin/detail/function_details.h" +#include "ppplugin/detail/scope_guard.h" +#include "ppplugin/detail/string_utils.h" +#include "ppplugin/detail/template_helpers.h" diff --git a/include/ppplugin/c/plugin.h b/include/ppplugin/c/plugin.h index 94f8ac9..54a8594 100644 --- a/include/ppplugin/c/plugin.h +++ b/include/ppplugin/c/plugin.h @@ -9,8 +9,7 @@ class CPlugin { public: [[nodiscard]] static Expected load(const std::filesystem::path& plugin_library_path); - // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) - operator bool() const; + explicit operator bool() const; template [[nodiscard]] CallResult call(const std::string& function_name, Args&&... args); @@ -44,22 +43,26 @@ CallResult CPlugin::call(const std::string& function_name, Args&&.. template CallResult CPlugin::global(const std::string& variable_name) { - auto p = detail::boost_dll::getSymbol(plugin_, variable_name); - if (p.hasValue()) { - return *reinterpret_cast(p.value().value()); + auto result_pointer = detail::boost_dll::getSymbol(plugin_, variable_name); + if (result_pointer.hasValue()) { + // raw type casting necessary due to lack of type information in shared library + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return *reinterpret_cast(result_pointer.value()); } - return { p.error().value() }; + return { result_pointer.error() }; } template CallResult CPlugin::global(const std::string& variable_name, VariableType&& new_value) { - auto p = detail::boost_dll::getSymbol(plugin_, variable_name); - if (p.hasValue()) { - *reinterpret_cast(p.value().value()) = std::forward(new_value); + auto result_pointer = detail::boost_dll::getSymbol(plugin_, variable_name); + if (result_pointer.hasValue()) { + // raw type casting necessary due to lack of type information in shared library + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + *reinterpret_cast(result_pointer.value()) = std::forward(new_value); return {}; } - return { p.error().value() }; + return { result_pointer.error() }; } } // namespace ppplugin diff --git a/include/ppplugin/cpp/plugin.h b/include/ppplugin/cpp/plugin.h index 867b3db..2ab31df 100644 --- a/include/ppplugin/cpp/plugin.h +++ b/include/ppplugin/cpp/plugin.h @@ -20,8 +20,7 @@ class CppPlugin { CppPlugin& operator=(const CppPlugin&) = default; CppPlugin& operator=(CppPlugin&&) = default; - // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) - operator bool() const; + explicit operator bool() const; template [[nodiscard]] CallResult call(const std::string& function_name, Args&&... args); @@ -48,22 +47,26 @@ CallResult CppPlugin::call(const std::string& function_name, Args&& template CallResult CppPlugin::global(const std::string& variable_name) { - auto p = detail::boost_dll::getSymbol(plugin_, variable_name); - if (p.hasValue()) { - return *reinterpret_cast(p.value().value()); + auto result_pointer = detail::boost_dll::getSymbol(plugin_, variable_name); + if (result_pointer.hasValue()) { + // raw type casting necessary due to lack of type information in shared library + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return *reinterpret_cast(result_pointer.value()); } - return { p.error().value() }; + return { result_pointer.error() }; } template CallResult CppPlugin::global(const std::string& variable_name, VariableType&& new_value) { - auto p = detail::boost_dll::getSymbol(plugin_, variable_name); - if (p.hasValue()) { - *reinterpret_cast(p.value().value()) = std::forward(new_value); + auto result_pointer = detail::boost_dll::getSymbol(plugin_, variable_name); + if (result_pointer.hasValue()) { + // raw type casting necessary due to lack of type information in shared library + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + *reinterpret_cast(result_pointer.value()) = std::forward(new_value); return {}; } - return { p.error().value() }; + return { result_pointer.error() }; } } // namespace ppplugin diff --git a/include/ppplugin/detail/scope_guard.h b/include/ppplugin/detail/scope_guard.h index c2cd92d..d33b52f 100644 --- a/include/ppplugin/detail/scope_guard.h +++ b/include/ppplugin/detail/scope_guard.h @@ -8,7 +8,7 @@ class ScopeGuard final { template >> explicit ScopeGuard(Func&& func) - : function_ { func } + : function_ { std::forward(func) } { } ~ScopeGuard() { call(); } @@ -40,8 +40,7 @@ class ScopeGuard final { void cancel() { function_ = {}; } private: - std::function - function_; + std::function function_; }; } // namespace ppplugin::detail diff --git a/include/ppplugin/detail/string_utils.h b/include/ppplugin/detail/string_utils.h new file mode 100644 index 0000000..0cf22dc --- /dev/null +++ b/include/ppplugin/detail/string_utils.h @@ -0,0 +1,36 @@ +#ifndef PPPLUGIN_DETAIL_STRING_UTILS_H +#define PPPLUGIN_DETAIL_STRING_UTILS_H + +#include "template_helpers.h" + +#include +#include +#include + +namespace ppplugin::detail { +template +constexpr auto IsStringlikeV = // NOLINT(readability-identifier-naming) + templates::IsAnyOfV, std::string, std::string_view, const char*, char*>; + +[[nodiscard]] inline bool endsWith(std::string_view string, std::string_view end) +{ + if (string.size() < end.size()) { + return false; + } + auto result = string.compare(string.size() - end.size(), end.size(), end); + return result == 0; +} + +template +[[nodiscard]] inline std::optional toInteger(std::string_view string) +{ + int value {}; + auto result = std::from_chars(string.begin(), string.end(), value); + if (result.ec == std::errc {} && result.ptr == string.end()) { + return value; + } + return std::nullopt; +} +} // namespace ppplugin::detail + +#endif // PPPLUGIN_DETAIL_STRING_UTILS_H diff --git a/include/ppplugin/detail/template_helpers.h b/include/ppplugin/detail/template_helpers.h index b09f224..90d619f 100644 --- a/include/ppplugin/detail/template_helpers.h +++ b/include/ppplugin/detail/template_helpers.h @@ -185,6 +185,9 @@ struct IsSpecialization : std::false_type { }; template