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..336ba04 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,7 +2,7 @@ Checks: > -*, clang-diagnostic-*, clang-analyzer-optin.*, - llvm-*,-llvm-header-guard, + llvm-*,-llvm-header-guard,-llvm-prefer-static-over-anonymous-namespace, 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, 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/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..9cd0c7c 100644 --- a/include/ppplugin/c/plugin.h +++ b/include/ppplugin/c/plugin.h @@ -44,22 +44,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().value()); } - return { p.error().value() }; + return { result_pointer.error().value() }; } 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().value()) = std::forward(new_value); return {}; } - return { p.error().value() }; + return { result_pointer.error().value() }; } } // namespace ppplugin diff --git a/include/ppplugin/cpp/plugin.h b/include/ppplugin/cpp/plugin.h index 867b3db..723c2d7 100644 --- a/include/ppplugin/cpp/plugin.h +++ b/include/ppplugin/cpp/plugin.h @@ -48,22 +48,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().value()); } - return { p.error().value() }; + return { result_pointer.error().value() }; } 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().value()) = std::forward(new_value); return {}; } - return { p.error().value() }; + return { result_pointer.error().value() }; } } // 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..caf41af --- /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; + +[[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