diff --git a/.gitignore b/.gitignore index 13c0afb..bc625ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# emacs project config +.projectile # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) diff --git a/CMakeLists.txt b/CMakeLists.txt index e412e4a..596c509 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,23 +3,19 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyutil VERSION 0.1) -enable_language(CXX) -# common XO cmake macros (see github:Rconybea/xo-cmake) -include(cmake/xo-bootstrap-macros.cmake) +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) # very little to unit test here + +xo_cxx_toplevel_options2() # ---------------------------------------------------------------- -# unit test setup +# cmake -DCMAKE_BUILD_TYPE=coverage +xo_toplevel_coverage_config2() -enable_testing() -## enable code coverage for all executables+libraries -## (when configured with -DCODE_COVERAGE=ON) -## -add_code_coverage() -add_code_coverage_all_targets( - EXCLUDE - /nix/store/* - ${PROJECT_SOURCE_DIR}/utest/*) +# ---------------------------------------------------------------- +# cmake -DCMAKE_BUILD_TYPE=debug +xo_toplevel_debug_config2() # ---------------------------------------------------------------- # bespoke (usually temporary) c++ settings @@ -29,36 +25,19 @@ set(PROJECT_CXX_FLAGS "") add_definitions(${PROJECT_CXX_FLAGS}) # ---------------------------------------------------------------- -# c++ settings - -xo_toplevel_compile_options() # ---------------------------------------------------------------- -# external dependencies -# -# set CMAKE_INSTALL_PREFIX to analog of /usr -# to use .cmake assistants from /usr/lib/cmake/indentlog -# -# xo_dependency(..) - -# ---------------------------------------------------------------- - -#add_subdirectory(example) -#add_subdirectory(utest) - -# ---------------------------------------------------------------- -# output targets set(SELF_LIB xo_pyutil) + xo_add_headeronly_library(${SELF_LIB}) -#xo_include_headeronly_options2(${SELF_LIB}) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- -# standard install + provide find_package() support -xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) -#xo_install_include_tree() -xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) +add_subdirectory(example) +#add_subdirectory(utest) # ---------------------------------------------------------------- # install any additional components diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 1664443..aba3116 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,12 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/cmake/xo_pyutilConfig.cmake.in b/cmake/xo_pyutilConfig.cmake.in index 9c15f36..eb9a3b1 100644 --- a/cmake/xo_pyutilConfig.cmake.in +++ b/cmake/xo_pyutilConfig.cmake.in @@ -1,4 +1,5 @@ @PACKAGE_INIT@ include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Share.cmake") check_required_components("@PROJECT_NAME@") diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..4151ec2 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ex1) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 0000000..d27232e --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,11 @@ +# xo-pyutil/example/ex1/CMakeLists.txt + +set(SELF_LIB xo_pyutilexample) +set(SELF_SRCS pyex1.cpp) + +if (XO_ENABLE_EXAMPLES) + xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) + xo_pybind11_dependency(${SELF_LIB} refcnt) +endif() + +# end CMakeLists.txt diff --git a/example/ex1/pyex1.cpp b/example/ex1/pyex1.cpp new file mode 100644 index 0000000..385a5b7 --- /dev/null +++ b/example/ex1/pyex1.cpp @@ -0,0 +1,14 @@ +/* @file pyex1.cpp */ + +#include "pyutilexample.hpp" +#include "xo/pyutil/pyutil.hpp" +#include + +namespace xo { + + PYBIND11_MODULE(XO_PYUTILEXAMPLE_MODULE_NAME(), m) { + m.def("sqrt", [](double x) { return ::sqrt(x); }); + } +} + +/* end pyex1.cpp */ diff --git a/example/ex1/pyutilexample.hpp.in b/example/ex1/pyutilexample.hpp.in new file mode 100644 index 0000000..a094415 --- /dev/null +++ b/example/ex1/pyutilexample.hpp.in @@ -0,0 +1,21 @@ +/* @file pyutilexample.hpp */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(XO_PYUTILEXAMPLE_MODULE_NAME(), m) { ... } + */ +#define XO_PYUTILEXAMPLE_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(XO_PYUTILEXAMPLE_MODULE_NAME_STR) + */ +#define XO_PYUTILEXAMPLE_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * XO_PYUTILEXAMPLE_IMPORT_MODULE() + * replaces + * py::module_::import("xo_pyexpression") + */ +#define XO_PYUTILEXAMPLE_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pyutilexample.hpp */ diff --git a/include/xo/pyutil/pycaller.hpp b/include/xo/pyutil/pycaller.hpp new file mode 100644 index 0000000..a9715d2 --- /dev/null +++ b/include/xo/pyutil/pycaller.hpp @@ -0,0 +1,153 @@ +/** @file pycaller.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include + +//#include + +namespace xo { + namespace pyutil { + struct pycaller_base { + using void_function_type = void (*)(); + using factory_function_type = pycaller_base*(*)(void_function_type); + + virtual ~pycaller_base() = default; + + /* note: need inherited class pycaller_base revealed to pybind11 too */ + static pybind11::module & declare_once(pybind11::module & m) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pybind11::class_(m, "pycaller_base"); + } + return m; + } + }; + + /** @class pycaller + * @brief Invoke function pointer of type Retval(*)(Args...) from py::object + * + * Arguments converted from py::object, and return type converted back to py::object. + * + * Each distinct combination of {Retval,Args...} needs to be established at compile time + * (since we need PyCall to be instantiated for particular types) + * + * Use when we don't know function pointer until *runtime*, + * for example getting function pointer from just-compiled code using xo-pyjit + **/ + template + struct pycaller; + + template + struct pycaller : public pycaller_base { + using self_type = pycaller; + using function_type = Retval (*)(); + using void_function_type = void (*)(); + + pycaller(void_function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } + + /* note: prototype_str must be [const char *], pybind11 requirement */ + static pybind11::module & declare_once(pybind11::module & m, + const char * prototype_str) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pycaller_base::declare_once(m); + pybind11::class_(m, prototype_str) + .def("__call__", + [](self_type & self) + { + return pybind11::cast((*self.fptr_)()); + }); + } + return m; + } + + pybind11::object operator()() { return pybind11::cast((*fptr_)()); } + + private: + function_type fptr_; + }; + + template + struct pycaller : public pycaller_base { + using self_type = pycaller; + using function_type = Retval (*)(Arg1); + using void_function_type = void (*)(); + + pycaller(void_function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } + + /* note: prototype_str must be [const char *], pybind11 requirement */ + static pybind11::module & declare_once(pybind11::module & m, + const char * prototype_str) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pycaller_base::declare_once(m); + pybind11::class_(m, prototype_str) + .def("__call__", + [](self_type & self, Arg1 arg1) + { + return pybind11::cast((*self.fptr_)(arg1)); + }) + ; + } + return m; + } + + pybind11::object operator()(pybind11::object arg1) { + return pybind11::cast((*fptr_)(pybind11::cast(arg1))); + } + + private: + function_type fptr_; + }; + + template + struct pycaller : public pycaller_base { + using self_type = pycaller; + using function_type = Retval (*)(Arg1, Arg2); + using void_function_type = void (*)(); + + pycaller(void_function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } + + /* note: prototype_str must be [const char *], pybind11 requirement */ + static pybind11::module & declare_once(pybind11::module & m, + const char * prototype_str) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pycaller_base::declare_once(m); + pybind11::class_(m, prototype_str) + .def("__call__", + [](self_type & self, Arg1 arg1, Arg2 arg2) + { + return pybind11::cast((*self.fptr_)(arg1, arg2)); + }) + ; + } + return m; + } + + pybind11::object operator()(pybind11::object arg1, pybind11::object arg2) { + return pybind11::cast((*fptr_)(pybind11::cast(arg1), + pybind11::cast(arg2))); + } + + private: + function_type fptr_; + }; + } /*namespace pyutil*/ +} + +/** end pycaller.hpp **/