From eafc8c5cc888cd49a3bfd3e9dce53253b8008968 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 3 Jul 2025 09:08:36 +0200 Subject: [PATCH 001/152] Initial bindings support --- cmake/yup_dependencies.cmake | 19 + cmake/yup_modules.cmake | 14 +- examples/graphics/CMakeLists.txt | 1 + examples/graphics/source/main.cpp | 1 + .../bindings/yup_YupCore_bindings.cpp | 2801 ++++++++++++++++ .../bindings/yup_YupCore_bindings.h | 945 ++++++ .../bindings/yup_YupScript_bindings.cpp | 120 + .../modules/yup_ScriptYupModule.cpp | 77 + modules/yup_python/pybind11/attr.h | 690 ++++ modules/yup_python/pybind11/buffer_info.h | 208 ++ modules/yup_python/pybind11/cast.h | 1704 ++++++++++ modules/yup_python/pybind11/chrono.h | 225 ++ modules/yup_python/pybind11/common.h | 2 + modules/yup_python/pybind11/complex.h | 74 + modules/yup_python/pybind11/detail/class.h | 743 +++++ modules/yup_python/pybind11/detail/common.h | 1255 +++++++ modules/yup_python/pybind11/detail/descr.h | 171 + modules/yup_python/pybind11/detail/init.h | 434 +++ .../yup_python/pybind11/detail/internals.h | 656 ++++ .../pybind11/detail/type_caster_base.h | 1177 +++++++ modules/yup_python/pybind11/detail/typeid.h | 65 + modules/yup_python/pybind11/eigen.h | 12 + modules/yup_python/pybind11/eigen/common.h | 9 + modules/yup_python/pybind11/eigen/matrix.h | 714 ++++ modules/yup_python/pybind11/eigen/tensor.h | 516 +++ modules/yup_python/pybind11/embed.h | 316 ++ modules/yup_python/pybind11/eval.h | 156 + modules/yup_python/pybind11/functional.h | 137 + modules/yup_python/pybind11/gil.h | 239 ++ modules/yup_python/pybind11/iostream.h | 265 ++ modules/yup_python/pybind11/numpy.h | 1998 ++++++++++++ modules/yup_python/pybind11/operators.h | 202 ++ modules/yup_python/pybind11/options.h | 92 + modules/yup_python/pybind11/pybind11.h | 2890 +++++++++++++++++ modules/yup_python/pybind11/pytypes.h | 2557 +++++++++++++++ modules/yup_python/pybind11/stl.h | 447 +++ modules/yup_python/pybind11/stl/filesystem.h | 116 + modules/yup_python/pybind11/stl_bind.h | 851 +++++ .../pybind11/type_caster_pyobject_ptr.h | 61 + .../scripting/yup_ScriptBindings.cpp | 60 + .../yup_python/scripting/yup_ScriptBindings.h | 129 + .../yup_python/scripting/yup_ScriptEngine.cpp | 212 ++ .../yup_python/scripting/yup_ScriptEngine.h | 129 + .../scripting/yup_ScriptException.h | 74 + .../scripting/yup_ScriptUtilities.cpp | 46 + .../scripting/yup_ScriptUtilities.h | 58 + .../utilities/yup_ClassDemangling.cpp | 130 + .../utilities/yup_ClassDemangling.h | 80 + .../utilities/yup_CrashHandling.cpp | 113 + .../yup_python/utilities/yup_CrashHandling.h | 42 + .../yup_python/utilities/yup_MacroHelpers.h | 29 + .../utilities/yup_PyBind11Includes.h | 96 + .../yup_python/utilities/yup_PythonInterop.h | 169 + .../yup_python/utilities/yup_PythonTypes.h | 81 + .../utilities/yup_WindowsIncludes.h | 34 + modules/yup_python/yup_python.cpp | 40 + modules/yup_python/yup_python.h | 118 + modules/yup_python/yup_python.mm | 23 + modules/yup_python/yup_python_bindings.cpp | 24 + modules/yup_python/yup_python_core.cpp | 24 + modules/yup_python/yup_python_modules.cpp | 24 + 61 files changed, 24694 insertions(+), 1 deletion(-) create mode 100644 modules/yup_python/bindings/yup_YupCore_bindings.cpp create mode 100644 modules/yup_python/bindings/yup_YupCore_bindings.h create mode 100644 modules/yup_python/bindings/yup_YupScript_bindings.cpp create mode 100644 modules/yup_python/modules/yup_ScriptYupModule.cpp create mode 100644 modules/yup_python/pybind11/attr.h create mode 100644 modules/yup_python/pybind11/buffer_info.h create mode 100644 modules/yup_python/pybind11/cast.h create mode 100644 modules/yup_python/pybind11/chrono.h create mode 100644 modules/yup_python/pybind11/common.h create mode 100644 modules/yup_python/pybind11/complex.h create mode 100644 modules/yup_python/pybind11/detail/class.h create mode 100644 modules/yup_python/pybind11/detail/common.h create mode 100644 modules/yup_python/pybind11/detail/descr.h create mode 100644 modules/yup_python/pybind11/detail/init.h create mode 100644 modules/yup_python/pybind11/detail/internals.h create mode 100644 modules/yup_python/pybind11/detail/type_caster_base.h create mode 100644 modules/yup_python/pybind11/detail/typeid.h create mode 100644 modules/yup_python/pybind11/eigen.h create mode 100644 modules/yup_python/pybind11/eigen/common.h create mode 100644 modules/yup_python/pybind11/eigen/matrix.h create mode 100644 modules/yup_python/pybind11/eigen/tensor.h create mode 100644 modules/yup_python/pybind11/embed.h create mode 100644 modules/yup_python/pybind11/eval.h create mode 100644 modules/yup_python/pybind11/functional.h create mode 100644 modules/yup_python/pybind11/gil.h create mode 100644 modules/yup_python/pybind11/iostream.h create mode 100644 modules/yup_python/pybind11/numpy.h create mode 100644 modules/yup_python/pybind11/operators.h create mode 100644 modules/yup_python/pybind11/options.h create mode 100644 modules/yup_python/pybind11/pybind11.h create mode 100644 modules/yup_python/pybind11/pytypes.h create mode 100644 modules/yup_python/pybind11/stl.h create mode 100644 modules/yup_python/pybind11/stl/filesystem.h create mode 100644 modules/yup_python/pybind11/stl_bind.h create mode 100644 modules/yup_python/pybind11/type_caster_pyobject_ptr.h create mode 100644 modules/yup_python/scripting/yup_ScriptBindings.cpp create mode 100644 modules/yup_python/scripting/yup_ScriptBindings.h create mode 100644 modules/yup_python/scripting/yup_ScriptEngine.cpp create mode 100644 modules/yup_python/scripting/yup_ScriptEngine.h create mode 100644 modules/yup_python/scripting/yup_ScriptException.h create mode 100644 modules/yup_python/scripting/yup_ScriptUtilities.cpp create mode 100644 modules/yup_python/scripting/yup_ScriptUtilities.h create mode 100644 modules/yup_python/utilities/yup_ClassDemangling.cpp create mode 100644 modules/yup_python/utilities/yup_ClassDemangling.h create mode 100644 modules/yup_python/utilities/yup_CrashHandling.cpp create mode 100644 modules/yup_python/utilities/yup_CrashHandling.h create mode 100644 modules/yup_python/utilities/yup_MacroHelpers.h create mode 100644 modules/yup_python/utilities/yup_PyBind11Includes.h create mode 100644 modules/yup_python/utilities/yup_PythonInterop.h create mode 100644 modules/yup_python/utilities/yup_PythonTypes.h create mode 100644 modules/yup_python/utilities/yup_WindowsIncludes.h create mode 100644 modules/yup_python/yup_python.cpp create mode 100644 modules/yup_python/yup_python.h create mode 100644 modules/yup_python/yup_python.mm create mode 100644 modules/yup_python/yup_python_bindings.cpp create mode 100644 modules/yup_python/yup_python_core.cpp create mode 100644 modules/yup_python/yup_python_modules.cpp diff --git a/cmake/yup_dependencies.cmake b/cmake/yup_dependencies.cmake index fd854db4d..ced169d63 100644 --- a/cmake/yup_dependencies.cmake +++ b/cmake/yup_dependencies.cmake @@ -111,3 +111,22 @@ function (_yup_fetch_perfetto) add_library (perfetto::perfetto ALIAS perfetto) endfunction() + +#============================================================================== + +function (_yup_fetch_python include_dir root_dir) + if (TARGET Python::Python) + return() + endif() + + if (NOT "${root_dir}" STREQUAL "") + set (Python_ROOT_DIR "${root_dir}") + endif() + + if ("${include_dir}" STREQUAL "") + set (Python_USE_STATIC_LIBS TRUE) + find_package (Python REQUIRED Development Interpreter) + endif() + +endfunction() + diff --git a/cmake/yup_modules.cmake b/cmake/yup_modules.cmake index 1d9e54c78..a9cac5633 100644 --- a/cmake/yup_modules.cmake +++ b/cmake/yup_modules.cmake @@ -336,7 +336,7 @@ function (yup_add_module module_path module_group) _yup_module_parse_config ("${module_header}" module_configs module_user_configs) # ==== Assign Configurations Dynamically - set (global_properties "dependencies|defines|options|searchpaths") + set (global_properties "dependencies|defines|libs|options|searchpaths") set (platform_properties "^(.*)Deps$|^(.*)Defines$|^(.*)Libs$|^(.*)Frameworks$|^(.*)WeakFrameworks$|^(.*)Options$|^(.*)LinkOptions$|^(.*)Packages$|^(.*)Searchpaths$|^(.*)CppStandard$") set (parsed_config "") @@ -356,11 +356,14 @@ function (yup_add_module module_path module_group) set (module_cpp_standard "${value}") elseif (${key} MATCHES "^enableARC$") _yup_boolean_property ("${value}" module_arc_enabled) + elseif (${key} MATCHES "^needsPython$") + _yup_boolean_property ("${value}" module_needs_python) endif() endforeach() _yup_set_default (module_cpp_standard "17") _yup_set_default (module_arc_enabled OFF) + _yup_set_default (module_needs_python OFF) _yup_resolve_variable_paths ("${module_searchpaths}" module_searchpaths) # ==== Setup Platform-Specific Configurations @@ -508,6 +511,12 @@ function (yup_add_module module_path module_group) endif() endforeach() + # ==== Fetch Python if needed + if (module_needs_python) + _yup_fetch_python ("${Python_INCLUDE_DIRS}" "${Python_ROOT_DIR}") + list (APPEND module_libs Python::Python) + endif() + # ==== Scan sources to include _yup_module_collect_sources ("${module_path}" module_sources) @@ -602,4 +611,7 @@ function (_yup_add_default_modules modules_path) yup_add_module (${modules_path}/modules/yup_gui ${modules_group}) add_library (yup::yup_gui ALIAS yup_gui) + + yup_add_module (${modules_path}/modules/yup_python ${modules_group}) + add_library (yup::yup_python ALIAS yup_python) endfunction() diff --git a/examples/graphics/CMakeLists.txt b/examples/graphics/CMakeLists.txt index 02c32094a..09c61f3a0 100644 --- a/examples/graphics/CMakeLists.txt +++ b/examples/graphics/CMakeLists.txt @@ -66,6 +66,7 @@ yup_standalone_app ( yup::yup_graphics yup::yup_gui yup::yup_audio_processors + yup::yup_python libpng libwebp ${link_libraries}) diff --git a/examples/graphics/source/main.cpp b/examples/graphics/source/main.cpp index 25d8eaa20..198cd97df 100644 --- a/examples/graphics/source/main.cpp +++ b/examples/graphics/source/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include // For sine wave generation diff --git a/modules/yup_python/bindings/yup_YupCore_bindings.cpp b/modules/yup_python/bindings/yup_YupCore_bindings.cpp new file mode 100644 index 000000000..11ccb5859 --- /dev/null +++ b/modules/yup_python/bindings/yup_YupCore_bindings.cpp @@ -0,0 +1,2801 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2025 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include "yup_YupCore_Bindings.h" + +#define YUP_PYTHON_INCLUDE_PYBIND11_FUNCTIONAL +#define YUP_PYTHON_INCLUDE_PYBIND11_STL +#include "../utilities/yup_PyBind11Includes.h" + +#include "../utilities/yup_CrashHandling.h" + +#include +#include + +namespace PYBIND11_NAMESPACE { +namespace detail { + +// ================================================================================================= + +bool type_caster::load (handle src, bool convert) +{ + yup::ignoreUnused (convert); + + if (! src) + return false; + + if (! PyUnicode_Check (src.ptr())) + return load_raw(src); + + Py_ssize_t size = -1; + const auto* buffer = reinterpret_cast (PyUnicode_AsUTF8AndSize (src.ptr(), &size)); + if (buffer == nullptr) + return false; + + value = buffer; + + loader_life_support::add_patient (src); + + return true; +} + +handle type_caster::cast (const yup::StringRef& src, return_value_policy policy, handle parent) +{ + yup::ignoreUnused (policy, parent); + + return PyUnicode_FromStringAndSize (static_cast (src.text), static_cast (src.length())); +} + +bool type_caster::load_raw (handle src) +{ + if (PYBIND11_BYTES_CHECK (src.ptr())) + { + const char* bytes = PYBIND11_BYTES_AS_STRING (src.ptr()); + if (! bytes) + pybind11_fail ("Unexpected PYBIND11_BYTES_AS_STRING() failure."); + + value = bytes; + return true; + } + + if (PyByteArray_Check (src.ptr())) + { + const char* bytearray = PyByteArray_AsString (src.ptr()); + if (! bytearray) + pybind11_fail ("Unexpected PyByteArray_AsString() failure."); + + value = bytearray; + return true; + } + + return false; +} + +// ================================================================================================= + +bool type_caster::load (handle src, bool convert) +{ + yup::ignoreUnused (convert); + + if (! src) + return false; + + if (! PyUnicode_Check (src.ptr())) + return load_raw(src); + + Py_ssize_t size = -1; + const auto* buffer = reinterpret_cast (PyUnicode_AsUTF8AndSize (src.ptr(), &size)); + if (buffer == nullptr) + return false; + + value = yup::String::fromUTF8 (buffer, static_cast (size)); + return true; +} + +handle type_caster::cast (const yup::String& src, return_value_policy policy, handle parent) +{ + yup::ignoreUnused (policy, parent); + + return PyUnicode_FromStringAndSize (src.toRawUTF8(), static_cast (src.getNumBytesAsUTF8())); +} + +bool type_caster::load_raw (handle src) +{ + if (PYBIND11_BYTES_CHECK (src.ptr())) + { + const char* bytes = PYBIND11_BYTES_AS_STRING (src.ptr()); + if (! bytes) + pybind11_fail ("Unexpected PYBIND11_BYTES_AS_STRING() failure."); + + value = yup::String::fromUTF8 (bytes, static_cast (PYBIND11_BYTES_SIZE (src.ptr()))); + return true; + } + + if (PyByteArray_Check (src.ptr())) + { + const char* bytearray = PyByteArray_AsString (src.ptr()); + if (! bytearray) + pybind11_fail ("Unexpected PyByteArray_AsString() failure."); + + value = yup::String::fromUTF8 (bytearray, static_cast (PyByteArray_Size (src.ptr()))); + return true; + } + + return false; +} + +// ================================================================================================= + +bool type_caster::load (handle src, bool convert) +{ + if (! src) + return false; + + if (base_type::load (src, convert)) + { + value = *reinterpret_cast (base_type::value); + return true; + } + + if (! PyUnicode_Check (src.ptr())) + return load_raw(src); + + Py_ssize_t size = -1; + const auto* buffer = reinterpret_cast (PyUnicode_AsUTF8AndSize (src.ptr(), &size)); + if (buffer == nullptr) + return false; + + value = yup::Identifier (yup::String::fromUTF8 (buffer, static_cast (size))); + return true; +} + +handle type_caster::cast (const yup::Identifier& src, return_value_policy policy, handle parent) +{ + yup::ignoreUnused (policy, parent); + + if (auto result = base_type::cast (src, policy, parent)) + return result; + + return make_caster::cast (src.toString(), policy, parent); +} + +bool type_caster::load_raw (handle src) +{ + if (PYBIND11_BYTES_CHECK (src.ptr())) + { + const char* bytes = PYBIND11_BYTES_AS_STRING (src.ptr()); + if (! bytes) + pybind11_fail ("Unexpected PYBIND11_BYTES_AS_STRING() failure."); + + value = yup::Identifier (yup::String::fromUTF8 (bytes, static_cast (PYBIND11_BYTES_SIZE (src.ptr())))); + return true; + } + + if (PyByteArray_Check (src.ptr())) + { + const char* bytearray = PyByteArray_AsString (src.ptr()); + if (! bytearray) + pybind11_fail ("Unexpected PyByteArray_AsString() failure."); + + value = yup::Identifier (yup::String::fromUTF8 (bytearray, static_cast (PyByteArray_Size (src.ptr())))); + return true; + } + + return false; +} + +// ================================================================================================= + +bool type_caster::load (handle src, bool convert) +{ + PyObject* source = src.ptr(); + + if (PyNone_Check(source)) + value = yup::var::undefined(); + + else if (PyBool_Check(source)) + value = PyObject_IsTrue (source) ? true : false; + + else if (PyLong_Check (source)) + value = static_cast (PyLong_AsLong (source)); + + else if (PyFloat_Check (source)) + value = PyFloat_AsDouble (source); + + else if (PyUnicode_Check (source)) + { + Py_ssize_t size = -1; + const auto* buffer = reinterpret_cast (PyUnicode_AsUTF8AndSize (src.ptr(), &size)); + if (buffer == nullptr) + return false; + + value = yup::String::fromUTF8 (buffer, static_cast (size)); + } + + else if (PYBIND11_BYTES_CHECK (source)) + { + const char* bytes = PYBIND11_BYTES_AS_STRING (source); + if (! bytes) + return false; + + value = yup::var (bytes, static_cast (PYBIND11_BYTES_SIZE (source))); + } + + else if (PyByteArray_Check (source)) + { + const char* bytearray = PyByteArray_AsString (source); + if (! bytearray) + return false; + + value = yup::var (bytearray, static_cast (PyByteArray_Size (source))); + } + + else if (PyTuple_Check (source)) + { + value = yup::var(); + + const auto tupleSize = PyTuple_Size(source); + for (Py_ssize_t i = 0; i < tupleSize; ++i) + { + make_caster conv; + + if (! conv.load (PyTuple_GetItem(source, i), convert)) + return false; + + value.append (cast_op (std::move (conv))); + } + } + + else if (PyList_Check (source)) + { + value = yup::var(); + + const auto tupleSize = PyList_Size(source); + for (Py_ssize_t i = 0; i < tupleSize; ++i) + { + make_caster conv; + + if (! conv.load (PyList_GetItem(source, i), convert)) + return false; + + value.append (cast_op (std::move (conv))); + } + } + + else if (PyDict_Check (source)) + { + yup::DynamicObject::Ptr obj = new yup::DynamicObject; + + value = yup::var (obj.get()); + + PyObject* key; + PyObject* val; + Py_ssize_t pos = 0; + + while (PyDict_Next (source, &pos, &key, &val)) + { + make_caster convKey; + make_caster convValue; + + if (! convKey.load (key, convert) || ! convValue.load (val, convert)) + return false; + + obj->setProperty ( + cast_op (std::move (convKey)), + cast_op (std::move (convValue))); + } + } + + else if (isinstance (src)) + { + value = yup::var (src.cast()); + } + + else + { + value = yup::var::undefined(); + } + + // TODO - raise + + return !PyErr_Occurred(); +} + +handle type_caster::cast (const yup::var& src, return_value_policy policy, handle parent) +{ + if (src.isVoid() || src.isUndefined()) + return Py_None; + + if (src.isBool()) + return PyBool_FromLong (static_cast (src)); + + if (src.isInt()) + return PyLong_FromLong (static_cast (src)); + + if (src.isInt64()) + return PyLong_FromLongLong (static_cast (src)); + + if (src.isDouble()) + return PyFloat_FromDouble (static_cast (src)); + + if (src.isString()) + return make_caster::cast (src, policy, parent); + + if (src.isArray()) + { + list list; + + if (auto array = src.getArray()) + { + for (const auto& value : *array) + list.append (value); + } + + return list.release(); + } + + auto dynamicObject = src.getDynamicObject(); + if (src.isObject() && dynamicObject != nullptr) + { + dict result; + + for (const auto& props : dynamicObject->getProperties()) + result [make_caster::cast (props.name.toString(), policy, parent)] = props.value; + + return result.release(); + } + + if (src.isBinaryData()) + { + if (auto data = src.getBinaryData()) + return bytes (static_cast (data->getData()), static_cast (data->getSize())).release(); + } + + if (src.isMethod()) + { + return cpp_function ([src] + { + yup::var::NativeFunctionArgs args (yup::var(), nullptr, 0); + return src.getNativeFunction() (args); + }).release(); + } + + return Py_None; +} + +}} // namespace PYBIND11_NAMESPACE::detail + +namespace yup::Bindings { + +using namespace yup; + +namespace py = pybind11; +using namespace py::literals; + +// ============================================================================================ + +template