git: 7e67b1988fbd - main - science/py-gemmi: unbreak by backporting new patch to support nanobind-3.x

From: Yuri Victorovich <yuri_at_FreeBSD.org>
Date: Wed, 23 Sep 2026 07:25:57 UTC
The branch main has been updated by yuri:

URL: https://cgit.FreeBSD.org/ports/commit/?id=7e67b1988fbd560c4a05fc19904acddedfc3ed1e

commit 7e67b1988fbd560c4a05fc19904acddedfc3ed1e
Author:     Yuri Victorovich <yuri@FreeBSD.org>
AuthorDate: 2026-09-23 05:44:40 +0000
Commit:     Yuri Victorovich <yuri@FreeBSD.org>
CommitDate: 2026-09-23 07:25:54 +0000

    science/py-gemmi: unbreak by backporting new patch to support nanobind-3.x
    
    Also: fix the only failing testcase
---
 science/py-gemmi/Makefile                          |  13 +--
 .../patch-97c808222f468f8188f2ed87266e0d7c5a854ce2 | 113 +++++++++++++++++++++
 ...e_gemmi_third__party_tao_pegtl_parse__error.hpp |  14 +++
 3 files changed, 132 insertions(+), 8 deletions(-)

diff --git a/science/py-gemmi/Makefile b/science/py-gemmi/Makefile
index e6b1ccec435d..44fa5af9345f 100644
--- a/science/py-gemmi/Makefile
+++ b/science/py-gemmi/Makefile
@@ -1,6 +1,7 @@
 PORTNAME=	gemmi
 DISTVERSIONPREFIX=	v
 DISTVERSION=	0.7.5
+PORTREVISION=	1
 CATEGORIES=	science python
 PKGNAMEPREFIX=	${PYTHON_PKGNAMEPREFIX}
 
@@ -12,8 +13,6 @@ WWW=		https://gemmi.readthedocs.io/en/latest/ \
 LICENSE=	MPL20
 LICENSE_FILE=	${WRKSRC}/LICENSE.txt
 
-BROKEN=		not compatible with nanobind-3.x, see https://github.com/project-gemmi/gemmi/issues/442
-
 BUILD_DEPENDS=	${PYTHON_PKGNAMEPREFIX}pip>0:devel/py-pip@${PY_FLAVOR} \
 		${PYTHON_PKGNAMEPREFIX}nanobind>0:devel/py-nanobind@${PY_FLAVOR} \
 		${PYTHON_PKGNAMEPREFIX}wheel>0:devel/py-wheel@${PY_FLAVOR} \
@@ -22,7 +21,7 @@ LIB_DEPENDS=	libgemmi_cpp.so:science/gemmi
 TEST_DEPENDS=	${PYTHON_PKGNAMEPREFIX}biopython>0:biology/py-biopython@${PY_FLAVOR}
 
 USES=		cmake compiler:c++17-lang localbase:ldflags python
-USE_PYTHON=	flavors
+USE_PYTHON=	flavors pytest
 
 USE_GITHUB=	yes
 GH_ACCOUNT=	project-gemmi
@@ -32,13 +31,11 @@ CMAKE_OFF=	BUILD_GEMMI_PROGRAM STANDALONE_PYTHON_MODULE
 CMAKE_ARGS=	-DPython_EXECUTABLE=${PYTHON_CMD}
 
 TEST_ENV=	${MAKE_ENV} PYTHONPATH=${STAGEDIR}${PYTHONPREFIX_SITELIBDIR}
-TEST_WRKSRC=	${BUILD_WRKSRC}/tests
+TEST_WRKSRC=	${WRKSRC}/tests
+TEST_ARGS=	--disable-plugin-autoload
 
 PLIST_SUB=	VER=${PORTVERSION}
 
-do-test:
-	@cd ${WRKSRC} && ${SETENV} ${TEST_ENV} ${PYTHON_CMD} -m unittest discover -v tests/
-
-# tests as of 0.7.3: test 'test_syntax_error' fails with syntax error, see https://github.com/project-gemmi/gemmi/issues/384
+# tests as of 0.7.5: 204 passed, 4 skipped in 0.54s
 
 .include <bsd.port.mk>
diff --git a/science/py-gemmi/files/patch-97c808222f468f8188f2ed87266e0d7c5a854ce2 b/science/py-gemmi/files/patch-97c808222f468f8188f2ed87266e0d7c5a854ce2
new file mode 100644
index 000000000000..a46b2111e814
--- /dev/null
+++ b/science/py-gemmi/files/patch-97c808222f468f8188f2ed87266e0d7c5a854ce2
@@ -0,0 +1,113 @@
+-- nanobind-3.x compatibility patch from https://github.com/project-gemmi/gemmi/commit/97c808222f468f8188f2ed87266e0d7c5a854ce2
+
+diff --git CMakeLists.txt CMakeLists.txt
+index 54e1a4fd6..fbb0a27d3 100644
+--- CMakeLists.txt
++++ CMakeLists.txt
+@@ -31,6 +31,8 @@ option(GENERATE_STUBS "Generate Python type stubs" ON)
+ option(EXTRA_WARNINGS "Set extra warning flags" OFF)
+ option(USE_WMAIN "(Windows only) take Unicode arguments in gemmi program" ON)
+ option(STANDALONE_PYTHON_MODULE "Avoid linking Python module to libgemmi_cpp DLL" ON)
++option(ENABLE_SPLIT_MODE "Use nanobind split mode (BACKEND_MODULE)" OFF)
++option(ENABLE_FREE_THREADED "Enable free-threaded Python support" OFF)
+ if (WIN32)
+   set(GEMMI_INSTALL_CMAKEDIR "cmake" CACHE STRING
+       "Install path for gemmi CMake files")
+@@ -472,10 +474,20 @@ if (USE_PYTHON)
+       COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
+       OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nb_cmake_dir)
+     list(APPEND CMAKE_PREFIX_PATH "${nb_cmake_dir}")
+-    find_package(nanobind 2.4.0 CONFIG REQUIRED)
++    find_package(nanobind CONFIG REQUIRED)
++    if (nanobind_VERSION VERSION_LESS "2.4.0" OR nanobind_VERSION VERSION_GREATER_EQUAL "4.0.0")
++      message(FATAL_ERROR "nanobind version must be >= 2.4.0 and < 4.0.0 (found ${nanobind_VERSION})")
++    endif()
+     message(STATUS "Found nanobind ${nanobind_VERSION}: ${NB_DIR}")
+   endif()
+-  nanobind_add_module(gemmi_py NOMINSIZE
++  set(gemmi_py_flags NOMINSIZE)
++  if (ENABLE_SPLIT_MODE)
++    list(APPEND gemmi_py_flags BACKEND_MODULE nanobind_backend)
++  endif()
++  if (ENABLE_FREE_THREADED)
++    list(APPEND gemmi_py_flags FREE_THREADED)
++  endif()
++  nanobind_add_module(gemmi_py ${gemmi_py_flags}
+           python/gemmi.cpp python/align.cpp
+           python/ccp4.cpp python/chemcomp.cpp python/cif.cpp
+           python/elem.cpp python/grid.cpp python/hkl.cpp
+diff --git python/hkl.cpp python/hkl.cpp
+index f991f90d5..91d725e48 100644
+--- python/hkl.cpp
++++ python/hkl.cpp
+@@ -318,7 +318,7 @@ void add_hkl(nb::module_& m) {
+         self.add_if_valid({h(i, 0), h(i, 1), h(i, 2)}, 0, 0, v(i), s(i));
+       self.type = DataType::Unmerged;
+       self.switch_to_asu_indices();
+-    }, nb::arg("cell"), nb::arg("sg").none(false),
++    }, nb::arg("cell"), nb::arg("sg"),
+        nb::arg("miller_array"), nb::arg("value_array"), nb::arg("sigma_array"))
+     ;
+ 
+diff --git python/make_iterator.h python/make_iterator.h
+index bbb5c4fcf..f800754ad 100644
+--- python/make_iterator.h
++++ python/make_iterator.h
+@@ -4,7 +4,18 @@
+ // This wrapper simplifies the call to nb::make_iterator (by assuming
+ // the name "iterator") and changes the default rv_policy to what it was
+ // in pybind11 and nanobind<2.0.
+-template<nb::rv_policy Policy = nb::rv_policy::reference_internal, typename S, typename T>
++#if defined(NB_VERSION_MAJOR) && NB_VERSION_MAJOR >= 3
++template <typename S, typename T>
++auto usual_iterator(const S&, T& value) {
++  return nb::make_iterator<nb::rv_policy::reference_internal>(nb::type<S>(), "iterator", value);
++}
++template <auto Policy, typename S, typename T>
++auto usual_iterator(const S&, T& value) {
++  return nb::make_iterator<Policy>(nb::type<S>(), "iterator", value);
++}
++#else
++template <nb::rv_policy Policy = nb::rv_policy::reference_internal, typename S, typename T>
+ auto usual_iterator(const S&, T& value) {
+   return nb::make_iterator<Policy>(nb::type<S>(), "iterator", value);
+ }
++#endif
+diff --git python/mol.cpp python/mol.cpp
+index 56eb7dfec..7108965cc 100644
+--- python/mol.cpp
++++ python/mol.cpp
+@@ -36,7 +36,11 @@ struct returns_references {
+     if (!nb::isinstance<nb::sequence>(ret))
+       throw std::runtime_error("return value should be a sequence");
+     for (nb::handle nurse : ret)
++#if defined(NB_VERSION_MAJOR) && NB_VERSION_MAJOR >= 3
++      nb::keep_alive_obj(nurse, args[0]);
++#else
+       nb::detail::keep_alive(nurse.ptr(), args[0]);
++#endif
+   }
+ };
+ 
+diff --git python/scaling.cpp python/scaling.cpp
+index 5bc3690c7..050351b35 100644
+--- python/scaling.cpp
++++ python/scaling.cpp
+@@ -22,7 +22,7 @@ void add_scaling(nb::module_& m) {
+     .def_prop_rw("parameters", &Scaling::get_parameters,
+                   (void (Scaling::*)(const std::vector<double>&)) &Scaling::set_parameters)
+     .def("prepare_points", &Scaling::prepare_points,
+-         nb::arg("calc"), nb::arg("obs"), nb::arg("mask")=static_cast<FPhiData*>(nullptr))
++         nb::arg("calc"), nb::arg("obs"), nb::arg("mask")=nb::none())
+     .def("fit_isotropic_b_approximately", &Scaling::fit_isotropic_b_approximately)
+     .def("fit_b_star_approximately", &Scaling::fit_b_star_approximately)
+     .def("fit_parameters", &Scaling::fit_parameters)
+@@ -36,7 +36,7 @@ void add_scaling(nb::module_& m) {
+     })
+     .def("get_solvent_scale", &Scaling::get_solvent_scale, nb::arg("stol2"))
+     .def("scale_data", &Scaling::scale_data,
+-         nb::arg("asu_data"), nb::arg("mask_data")=static_cast<FPhiData*>(nullptr))
++         nb::arg("asu_data"), nb::arg("mask_data")=nb::none())
+     .def("scale_value", &Scaling::scale_value,
+          nb::arg("hkl"), nb::arg("f_value"), nb::arg("mask_value"))
+     ;
diff --git a/science/py-gemmi/files/patch-include_gemmi_third__party_tao_pegtl_parse__error.hpp b/science/py-gemmi/files/patch-include_gemmi_third__party_tao_pegtl_parse__error.hpp
new file mode 100644
index 000000000000..e405380a2513
--- /dev/null
+++ b/science/py-gemmi/files/patch-include_gemmi_third__party_tao_pegtl_parse__error.hpp
@@ -0,0 +1,14 @@
+-- fix for the bug in the Gemmi project: https://github.com/project-gemmi/gemmi/issues/384#issuecomment-5789847575
+-- this bug causes one testcase to fail due to duplicate C++ typeinfo objects
+
+--- include/gemmi/third_party/tao/pegtl/parse_error.hpp.orig	2026-03-02 03:06:09 UTC
++++ include/gemmi/third_party/tao/pegtl/parse_error.hpp
+@@ -14,7 +14,7 @@ namespace tao
+ {
+    namespace TAO_PEGTL_NAMESPACE
+    {
+-      struct parse_error
++      struct __attribute__((visibility("default"))) parse_error
+          : public std::runtime_error
+       {
+          parse_error( const std::string& msg, std::vector< position >&& in_positions )