From c8947337066160bb5be3544f995e9be6395cf496 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 1/9] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 1664443..9659221 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From fd5ebbfd2289f7bef3aa08372aeff35e38e8d4c2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 1 May 2024 14:39:09 -0400 Subject: [PATCH 2/9] xo-pyutil: build: streamline using recent xo-cmake improvements --- CMakeLists.txt | 43 ++++++++------------------------- cmake/xo-bootstrap-macros.cmake | 35 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index c7e3266..a20befe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,24 +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(xo_macros/xo_cxx) -include(xo_macros/code-coverage) # very little to unit test here +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 +24,18 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") 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) add_library(${SELF_LIB} INTERFACE) xo_include_headeronly_options2(${SELF_LIB}) - -# ---------------------------------------------------------------- -# standard install + provide find_package() support - xo_install_library2(${SELF_LIB}) -xo_install_include_tree() +xo_install_include_tree3(include/xo/pyutil) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 0000000..aba3116 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# 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(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_cxx) + +xo_cxx_bootstrap_message() From ecd315c0f2c93cfd699e881931e134cd697cc316 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 16:53:07 -0400 Subject: [PATCH 3/9] xo-pyuitl: ++ .projectile in .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) 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) From cd687d2ac40f92370693e6d1e8698e637eec8a30 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 16:53:23 -0400 Subject: [PATCH 4/9] xo-pyutil: + pycaller assistant for registering function pointers --- include/xo/pyutil/pycaller.hpp | 132 +++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 include/xo/pyutil/pycaller.hpp diff --git a/include/xo/pyutil/pycaller.hpp b/include/xo/pyutil/pycaller.hpp new file mode 100644 index 0000000..85bf665 --- /dev/null +++ b/include/xo/pyutil/pycaller.hpp @@ -0,0 +1,132 @@ +/** @file pycaller.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include + +//#include + +namespace xo { + namespace pyutil { + struct pycaller_base { + virtual ~pycaller_base() = default; +g + 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; + } + }; + + /** Invoke function pointer of type Retval(*)(Args...), + * with 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 (*)(); + + pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pybind11::module & declare_once(pybind11::module & m) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pycaller_base::declare_once(m); + pybind11::class_(m, "pycaller0") + .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); + + pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pybind11::module & declare_once(pybind11::module & m) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pycaller_base::declare_once(m); + pybind11::class_(m, "pycaller1") + .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); + + pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pybind11::module & declare_once(pybind11::module & m) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pycaller_base::declare_once(m); + pybind11::class_(m, "pycaller2") + .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 **/ From bc601c472c484a6e8a1bd7ede2a49948c7b93f28 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 16:54:24 -0400 Subject: [PATCH 5/9] xo-pyutil: remove stray character --- include/xo/pyutil/pycaller.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/pyutil/pycaller.hpp b/include/xo/pyutil/pycaller.hpp index 85bf665..8368c06 100644 --- a/include/xo/pyutil/pycaller.hpp +++ b/include/xo/pyutil/pycaller.hpp @@ -13,7 +13,7 @@ namespace xo { namespace pyutil { struct pycaller_base { virtual ~pycaller_base() = default; -g + static pybind11::module & declare_once(pybind11::module & m) { static bool s_once = false; if (!s_once) { From e169a0d276765f7daa196dd91d60ff105654bc02 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 17:41:50 -0400 Subject: [PATCH 6/9] xo-pyutil: + example (mostly to make LSP happy) --- CMakeLists.txt | 12 ++++-------- example/CMakeLists.txt | 1 + example/ex1/CMakeLists.txt | 11 +++++++++++ example/ex1/pyex1.cpp | 14 ++++++++++++++ example/ex1/pyutilexample.hpp.in | 21 +++++++++++++++++++++ 5 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/pyex1.cpp create mode 100644 example/ex1/pyutilexample.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 0143a02..596c509 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,22 +26,18 @@ add_definitions(${PROJECT_CXX_FLAGS}) # ---------------------------------------------------------------- -#add_subdirectory(example) -#add_subdirectory(utest) - # ---------------------------------------------------------------- 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_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) +add_subdirectory(example) +#add_subdirectory(utest) # ---------------------------------------------------------------- # install any additional components 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 */ From debc2db34c701262db2f095924770fdfe95bbe3c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 19:33:44 -0400 Subject: [PATCH 7/9] xo-pyutil: pycaller: provide type-erased factory functions --- include/xo/pyutil/pycaller.hpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/include/xo/pyutil/pycaller.hpp b/include/xo/pyutil/pycaller.hpp index 8368c06..20ecc92 100644 --- a/include/xo/pyutil/pycaller.hpp +++ b/include/xo/pyutil/pycaller.hpp @@ -12,6 +12,9 @@ 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; static pybind11::module & declare_once(pybind11::module & m) { @@ -40,8 +43,11 @@ namespace xo { struct pycaller : public pycaller_base { using self_type = pycaller; using function_type = Retval (*)(); + using void_function_type = void (*)(); - pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + pycaller(void_function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } static pybind11::module & declare_once(pybind11::module & m) { static bool s_once = false; @@ -68,8 +74,11 @@ namespace xo { struct pycaller : public pycaller_base { using self_type = pycaller; using function_type = Retval (*)(Arg1); + using void_function_type = void (*)(); - pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + pycaller(void_function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } static pybind11::module & declare_once(pybind11::module & m) { static bool s_once = false; @@ -99,8 +108,11 @@ namespace xo { struct pycaller : public pycaller_base { using self_type = pycaller; using function_type = Retval (*)(Arg1, Arg2); + using void_function_type = void (*)(); - pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + pycaller(void_function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } static pybind11::module & declare_once(pybind11::module & m) { static bool s_once = false; From a2cb8ae60fea391259d0b9530ddd3df658958c84 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:30:40 -0400 Subject: [PATCH 8/9] xo-pyutil: bugfix: + prototype_str for unique declare_once() syms --- include/xo/pyutil/pycaller.hpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/include/xo/pyutil/pycaller.hpp b/include/xo/pyutil/pycaller.hpp index 20ecc92..a9715d2 100644 --- a/include/xo/pyutil/pycaller.hpp +++ b/include/xo/pyutil/pycaller.hpp @@ -17,6 +17,7 @@ namespace xo { 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) { @@ -27,8 +28,10 @@ namespace xo { } }; - /** Invoke function pointer of type Retval(*)(Args...), - * with arguments converted from py::object, and return type converted back to py::object. + /** @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) @@ -49,12 +52,14 @@ namespace xo { static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } - static pybind11::module & declare_once(pybind11::module & m) { + /* 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, "pycaller0") + pybind11::class_(m, prototype_str) .def("__call__", [](self_type & self) { @@ -80,12 +85,14 @@ namespace xo { static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } - static pybind11::module & declare_once(pybind11::module & m) { + /* 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, "pycaller1") + pybind11::class_(m, prototype_str) .def("__call__", [](self_type & self, Arg1 arg1) { @@ -114,12 +121,14 @@ namespace xo { static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } - static pybind11::module & declare_once(pybind11::module & m) { + /* 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, "pycaller2") + pybind11::class_(m, prototype_str) .def("__call__", [](self_type & self, Arg1 arg1, Arg2 arg2) { From dfd5229b82733469c6c21ae8487e68221da18ca9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 27 Feb 2026 19:38:53 +1100 Subject: [PATCH 9/9] xo-cmake: setup to make share target available via cmake install --- cmake/xo_pyutilConfig.cmake.in | 1 + 1 file changed, 1 insertion(+) 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@")