From 1dee2509e46e04894b7f67a3f031905200dab26b Mon Sep 17 00:00:00 2001 From: Vincent Palancher Date: Mon, 4 Dec 2023 16:21:19 +0100 Subject: [PATCH 1/8] Bump version to v3.1.5.dev0. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 794715e..3b7f54a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "scikit_build_core.build" [project] name = "qi" description = "LibQi Python bindings" -version = "3.1.4" +version = "3.1.5.dev0" readme = "README.rst" requires-python = ">=3.7" license = { "file" = "COPYING" } From a7a91d8707174a5248f69fee9dc66ae485622d95 Mon Sep 17 00:00:00 2001 From: Vincent Palancher Date: Wed, 17 Apr 2024 16:00:38 +0200 Subject: [PATCH 2/8] conan: Update dependencies. libqi now defines a `qi_add_module` macro to replace `qi_create_module`. --- CMakeLists.txt | 2 +- conanfile.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index db1acd8..c4dd4a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,7 +297,7 @@ if(BUILD_TESTING) include(GoogleTest) find_package(qimodule REQUIRED) - qi_create_module(moduletest NO_INSTALL) + qi_add_module(moduletest) target_sources( moduletest PRIVATE diff --git a/conanfile.py b/conanfile.py index 9e16c4c..d2a74aa 100644 --- a/conanfile.py +++ b/conanfile.py @@ -52,11 +52,12 @@ "system", ] + class QiPythonConan(ConanFile): requires = [ - "boost/[~1.78]", - "pybind11/[^2.9]", - "qi/4.0.2", + "boost/[~1.83]", + "pybind11/[^2.11]", + "qi/[~4]", ] test_requires = [ @@ -77,9 +78,9 @@ class QiPythonConan(ConanFile): # Disable every components of Boost unless we actively use them. default_options.update( { - f"boost/*:without_{_name}": False - if _name in USED_BOOST_COMPONENTS - else True + f"boost/*:without_{_name}": ( + False if _name in USED_BOOST_COMPONENTS else True + ) for _name in BOOST_COMPONENTS } ) From dc28693a5664e2369d901a849ef3aaf4d299af17 Mon Sep 17 00:00:00 2001 From: Vincent Palancher Date: Wed, 17 Apr 2024 16:02:49 +0200 Subject: [PATCH 3/8] types: Fix dict iterator dereference values destruction. #SW-7582 --- src/pytypes.cpp | 36 +++++++++++------------------------- tests/test_types.cpp | 26 +++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/pytypes.cpp b/src/pytypes.cpp index fa03102..41795d3 100644 --- a/src/pytypes.cpp +++ b/src/pytypes.cpp @@ -595,20 +595,13 @@ class ListInterface : public ObjectInterfaceBase using DefaultImpl = DefaultTypeImplMethods>; - void* initializeStorage(void* ptr = nullptr) override - { - return DefaultImpl::initializeStorage(ptr); - } - + void* initializeStorage(void* ptr = nullptr) override { return DefaultImpl::initializeStorage(ptr); } void* clone(void* storage) override { return DefaultImpl::clone(storage); } - void destroy(void* storage) override - { - destroyDisownedReferences(storage); - return DefaultImpl::destroy(storage); - } + void destroy(void* storage) override { return DefaultImpl::destroy(storage); } const TypeInfo& info() override { return DefaultImpl::info(); } void* ptrFromStorage(void** s) override { return DefaultImpl::ptrFromStorage(s); } bool less(void* a, void* b) override { return DefaultImpl::less(a, b); } + Iterator* asIterPtr(void** storage) { return static_cast(ptrFromStorage(storage)); } Iterator& asIter(void** storage) { return *asIterPtr(storage); } }; @@ -680,12 +673,13 @@ class DictInterface: public ObjectInterfaceBase std::advance(it, index); const auto key = ::py::reinterpret_borrow<::py::object>(it->first); const auto element = ::py::reinterpret_borrow<::py::object>(it->second); - const auto keyElementPair = std::make_pair(key, element); - auto ref = AnyReference::from(keyElementPair).clone(); + auto keyRef = AnyReference::from(key); + auto elementRef = AnyReference::from(element); + auto pairRef = makeGenericTuple({keyRef, elementRef}); // Store the disowned reference with the list as a context instead of the // iterator because the reference might outlive the iterator. - storeDisownedReference(dictStorage, ref); - return ref; + storeDisownedReference(dictStorage, pairRef); + return pairRef; } void next(void** storage) override { ++asIter(storage).second; } @@ -693,21 +687,13 @@ class DictInterface: public ObjectInterfaceBase using DefaultImpl = DefaultTypeImplMethods>; - void* initializeStorage(void* ptr = nullptr) override - { - return DefaultImpl::initializeStorage(ptr); - } - + void* initializeStorage(void* ptr = nullptr) override { return DefaultImpl::initializeStorage(ptr); } void* clone(void* storage) override { return DefaultImpl::clone(storage); } - void destroy(void* storage) override - { - destroyDisownedReferences(storage); - return DefaultImpl::destroy(storage); - } - + void destroy(void* storage) override { return DefaultImpl::destroy(storage); } const TypeInfo& info() override { return DefaultImpl::info(); } void* ptrFromStorage(void** s) override { return DefaultImpl::ptrFromStorage(s); } bool less(void* a, void* b) override { return DefaultImpl::less(a, b); } + Iterator* asIterPtr(void** storage) { return static_cast(ptrFromStorage(storage)); } Iterator& asIter(void** storage) { return *asIterPtr(storage); } }; diff --git a/tests/test_types.cpp b/tests/test_types.cpp index 5eae486..a3712e1 100644 --- a/tests/test_types.cpp +++ b/tests/test_types.cpp @@ -387,20 +387,40 @@ TEST_F(TypePassing, Recursive) } } +TEST_F(TypePassing, ReverseList) +{ + exec( + "class TestService:\n" + " def func(self, list):\n" + // Test the iterator interface. + " for value in list:\n" + " assert(isinstance(value, str))\n" + " assert(list == ['hello', 'world'])\n" + ); + registerService(); + const std::vector list {"hello", "world"}; + getService().call("func", list); +} + + TEST_F(TypePassing, ReverseDict) { exec( "class TestService:\n" " def func(self, dict):\n" - " return dict == {'one' : 1, 'two' : 2, 'three' : 3}\n" + // Test the iterator interface. + " for key, value in dict.items():\n" + " assert(isinstance(key, str))\n" + " assert(isinstance(value, int))\n" + " assert(dict == {'one' : 1, 'two' : 2, 'three' : 3})\n" ); registerService(); - const std::map expected { + const std::map dict { {"one", 1}, {"two", 2}, {"three", 3}, }; - EXPECT_TRUE(getService().call("func", expected)); + getService().call("func", dict); } TEST_F(TypePassing, LogLevel) From 5641ce74b7dd18a9191ee10936325d5d0bfe4bb1 Mon Sep 17 00:00:00 2001 From: Vincent Palancher Date: Wed, 17 Apr 2024 16:06:05 +0200 Subject: [PATCH 4/8] ci+conan: Detect default and add cppstd17 profiles. --- ci/cibuildwheel_linux_before_all.sh | 3 ++- ci/conan/global.conf | 2 -- ci/conan/profiles/cppstd17 | 2 ++ ci/conan/profiles/default | 8 -------- 4 files changed, 4 insertions(+), 11 deletions(-) create mode 100644 ci/conan/profiles/cppstd17 delete mode 100644 ci/conan/profiles/default diff --git a/ci/cibuildwheel_linux_before_all.sh b/ci/cibuildwheel_linux_before_all.sh index f033f37..199071a 100755 --- a/ci/cibuildwheel_linux_before_all.sh +++ b/ci/cibuildwheel_linux_before_all.sh @@ -9,6 +9,7 @@ pip install 'conan>=2' 'cmake>=3.23' ninja yum install -y perl-IPC-Cmd perl-Digest-SHA # Install Conan configuration. +conan profile detect conan config install "$PACKAGE/ci/conan" # Clone and export libqi to Conan cache. @@ -27,4 +28,4 @@ conan export /work/libqi --version="${QI_VERSION}" # This is because the GLIBC from the manylinux images are often older than the # ones that were used to build the precompiled binaries, which means the binaries # cannot by executed. -conan install "$PACKAGE" --build="*" +conan install "$PACKAGE" --build="*" --profile:all default --profile:all cppstd17 diff --git a/ci/conan/global.conf b/ci/conan/global.conf index 7ad81c5..f90cae0 100644 --- a/ci/conan/global.conf +++ b/ci/conan/global.conf @@ -1,5 +1,3 @@ -core:default_profile=default -core:default_build_profile=default tools.build:skip_test=true tools.cmake.cmaketoolchain:generator=Ninja # Only use the build_type as a variable for the build folder name, so diff --git a/ci/conan/profiles/cppstd17 b/ci/conan/profiles/cppstd17 new file mode 100644 index 0000000..5b1d062 --- /dev/null +++ b/ci/conan/profiles/cppstd17 @@ -0,0 +1,2 @@ +[settings] +compiler.cppstd=gnu17 diff --git a/ci/conan/profiles/default b/ci/conan/profiles/default deleted file mode 100644 index 4ca0f95..0000000 --- a/ci/conan/profiles/default +++ /dev/null @@ -1,8 +0,0 @@ -[settings] -arch=x86_64 -build_type=Release -compiler=gcc -compiler.cppstd=gnu17 -compiler.libcxx=libstdc++ -compiler.version=10 -os=Linux From 9f7f8b77bd3fefe0ceea0daec67ab60da86ce2b6 Mon Sep 17 00:00:00 2001 From: Vincent Palancher Date: Wed, 17 Apr 2024 16:06:57 +0200 Subject: [PATCH 5/8] cibuildwheel: Remove manylinux-x86_64 image specification --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3b7f54a..18de992 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,7 +63,6 @@ build-frontend = "build" environment-pass = ["LIBQI_REPOSITORY_URL"] [tool.cibuildwheel.linux] -manylinux-x86_64-image = "manylinux2014" before-all = ["ci/cibuildwheel_linux_before_all.sh {package}"] [tool.cibuildwheel.linux.config-settings] From ab137259116d52f8c5f9f91ec9570757681b7608 Mon Sep 17 00:00:00 2001 From: Vincent Palancher Date: Wed, 17 Apr 2024 16:15:32 +0200 Subject: [PATCH 6/8] ci: Fetch last version of libqi from GitHub. --- .gitlab-ci.yml | 2 -- ci/cibuildwheel_linux_before_all.sh | 10 ++++------ pyproject.toml | 2 -- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 61b2b8c..be06278 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,8 +17,6 @@ build-wheel: tags: - docker image: python:3.8 - variables: - LIBQI_REPOSITORY_URL: "https://gitlab-ci-token:$CI_JOB_TOKEN@$CI_SERVER_HOST/qi/libqi" script: - curl -sSL https://get.docker.com/ | sh - pip install cibuildwheel==2.14.1 diff --git a/ci/cibuildwheel_linux_before_all.sh b/ci/cibuildwheel_linux_before_all.sh index 199071a..8a5e5b8 100755 --- a/ci/cibuildwheel_linux_before_all.sh +++ b/ci/cibuildwheel_linux_before_all.sh @@ -13,14 +13,12 @@ conan profile detect conan config install "$PACKAGE/ci/conan" # Clone and export libqi to Conan cache. -QI_VERSION=$(sed -nE '/^\s*requires\s*=/,/^\s*]/{ s/\s*"qi\/([^"]+)".*/\1/p }' "$PACKAGE/conanfile.py") - GIT_SSL_NO_VERIFY=true \ - git clone --depth=1 \ - --branch "qi-framework-v${QI_VERSION}" \ - "$LIBQI_REPOSITORY_URL" \ + git clone \ + --branch master \ + https://github.com/aldebaran/libqi.git \ /work/libqi -conan export /work/libqi --version="${QI_VERSION}" +conan export /work/libqi # Install dependencies of libqi-python from Conan, including libqi. # diff --git a/pyproject.toml b/pyproject.toml index 18de992..429db6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,8 +60,6 @@ BUILD_TESTING = "OFF" build = "cp*manylinux*x86_64" build-frontend = "build" -environment-pass = ["LIBQI_REPOSITORY_URL"] - [tool.cibuildwheel.linux] before-all = ["ci/cibuildwheel_linux_before_all.sh {package}"] From d2c4bbc0c96d24aaf689f6d87a425aadbeac0e2d Mon Sep 17 00:00:00 2001 From: Vincent Palancher Date: Wed, 17 Apr 2024 16:44:28 +0200 Subject: [PATCH 7/8] Bump version to v3.1.5. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 429db6c..6ccd0da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "scikit_build_core.build" [project] name = "qi" description = "LibQi Python bindings" -version = "3.1.5.dev0" +version = "3.1.5" readme = "README.rst" requires-python = ">=3.7" license = { "file" = "COPYING" } From 3c1197f321efee652022d4e2acaba3e00e743bff Mon Sep 17 00:00:00 2001 From: Maelic Date: Thu, 2 Apr 2026 15:33:21 +0200 Subject: [PATCH 8/8] feat: add Python 3.12 and 3.13 support - Fix _Py_IsFinalizing removed in Python 3.13; use public Py_IsFinalizing() for Python >= 3.13 and keep _Py_IsFinalizing() for 3.7-3.12 - Add perl-Time-Piece yum dependency required to build OpenSSL 3.6.x - Use --output-folder=/conan-generators in conan install for predictable generator paths with scikit-build-core (replaces --preset=conan-release) - Add Python 3.12 and 3.13 classifiers to pyproject.toml --- ci/cibuildwheel_linux_before_all.sh | 8 ++++++-- pyproject.toml | 4 +++- qipython/common.hpp | 9 +++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ci/cibuildwheel_linux_before_all.sh b/ci/cibuildwheel_linux_before_all.sh index 8a5e5b8..faf4ebd 100755 --- a/ci/cibuildwheel_linux_before_all.sh +++ b/ci/cibuildwheel_linux_before_all.sh @@ -6,7 +6,7 @@ PACKAGE=$1 pip install 'conan>=2' 'cmake>=3.23' ninja # Perl dependencies required to build OpenSSL. -yum install -y perl-IPC-Cmd perl-Digest-SHA +yum install -y perl-IPC-Cmd perl-Digest-SHA perl-Time-Piece # Install Conan configuration. conan profile detect @@ -26,4 +26,8 @@ conan export /work/libqi # This is because the GLIBC from the manylinux images are often older than the # ones that were used to build the precompiled binaries, which means the binaries # cannot by executed. -conan install "$PACKAGE" --build="*" --profile:all default --profile:all cppstd17 +# +# Use a fixed output folder so that the generated toolchain file path is +# predictable and can be referenced by scikit-build-core. +conan install "$PACKAGE" --build="*" --profile:all default --profile:all cppstd17 \ + --output-folder=/conan-generators diff --git a/pyproject.toml b/pyproject.toml index 6ccd0da..52500f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ classifiers=[ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3 :: Only", ] maintainers = [ @@ -64,4 +66,4 @@ build-frontend = "build" before-all = ["ci/cibuildwheel_linux_before_all.sh {package}"] [tool.cibuildwheel.linux.config-settings] -"cmake.args" = ["--preset=conan-release"] +"cmake.args" = ["-DCMAKE_TOOLCHAIN_FILE=/conan-generators/build/release/generators/conan_toolchain.cmake", "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_PREFIX_PATH=/conan-generators/build/release/generators"] diff --git a/qipython/common.hpp b/qipython/common.hpp index 3335f66..ee39c41 100644 --- a/qipython/common.hpp +++ b/qipython/common.hpp @@ -116,8 +116,13 @@ boost::optional extractKeywordArg(pybind11::dict kwargs, /// finalizing or not. inline boost::optional interpreterIsFinalizing() { -// `_Py_IsFinalizing` is only available on CPython 3.7+ -#if PY_VERSION_HEX >= 0x03070000 +// `_Py_IsFinalizing` was a private CPython API available since 3.7. +// It was removed in Python 3.13 (replaced by the public `Py_IsFinalizing`). +// `Py_IsFinalizing` is the public API, available since CPython 3.9, +// but only guaranteed visible outside of Py_LIMITED_API mode from 3.13. +#if PY_VERSION_HEX >= 0x030d0000 + return boost::make_optional(Py_IsFinalizing() != 0); +#elif PY_VERSION_HEX >= 0x03070000 return boost::make_optional(_Py_IsFinalizing() != 0); #else // There is no way of knowing on older versions.