Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/build-wheel-release-upload.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,18 @@ jobs:
#
# Choose this for open-source packages distributed via PyPI and/or
# conda-forge where broad public availability is the goal.
#
# build_c_ext_with_cibw builds the wheels with cibuildwheel using
# [tool.cibuildwheel] in pyproject.toml.
# TODO: switch back to scikit-package/release-scripts@v0 once the
# cibw change is released there.
build-release:
uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0
uses: SYMSCAE/release-scripts/.github/workflows/_build-wheel-release-upload.yml@cibw
with:
project: pyobjcryst
submodules: "true"
c_extension: true
build_c_ext_with_cibw: true
maintainer_github_username: sbillinge
secrets:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
Expand Down
27 changes: 23 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,21 @@ target_compile_definitions(ObjCryst PUBLIC
target_link_libraries(ObjCryst PUBLIC cctbx newmat)

# Put VFNDebug.cpp first so debug-level constants are defined before use
set_source_files_properties(
"${OBJCRYST_SRC}/Quirks/VFNDebug.cpp"
PROPERTIES COMPILE_FLAGS "-O0"
)
if(NOT MSVC)
set_source_files_properties(
"${OBJCRYST_SRC}/Quirks/VFNDebug.cpp"
PROPERTIES COMPILE_FLAGS "-O0"
)
endif()

# MSVC: same settings as the libobjcryst Windows build (/EHsc, M_PI etc.),
# plus /bigobj for the large binding translation units.
if(MSVC)
foreach(_tgt newmat cctbx ObjCryst)
target_compile_definitions(${_tgt} PUBLIC _USE_MATH_DEFINES NOMINMAX)
target_compile_options(${_tgt} PUBLIC /EHsc /bigobj)
endforeach()
endif()

# ---------------------------------------------------------------------------
# nanobind extension: _pyobjcryst
Expand All @@ -178,3 +189,11 @@ target_link_libraries(_pyobjcryst PRIVATE ObjCryst)

# Install into pyobjcryst package
install(TARGETS _pyobjcryst LIBRARY DESTINATION pyobjcryst)

# Licenses of the statically linked third-party code ship with the wheel.
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/extern/objcryst/LICENSE.txt"
DESTINATION pyobjcryst/licenses/objcryst)
install(FILES "${CCTBX_SRC}/LICENSE_2_0.txt" "${CCTBX_SRC}/COPYRIGHT_2_0.txt"
DESTINATION pyobjcryst/licenses/cctbx)
install(FILES "${NEWMAT_SRC}/readme.txt" "${NEWMAT_SRC}/nm11.htm"
DESTINATION pyobjcryst/licenses/newmat)
19 changes: 11 additions & 8 deletions nanobind_migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,18 @@ Known, accepted gaps — real, not oversights, but still open:
The `Molecule`↔`MolAtom` cycle is fixed. `Crystal`↔`Scatterer`/`Molecule`/`ScatteringPower` cycles
remain (56 leaked instances in the current full-suite run) — not safe to fix the same way, see
[keep_alive Reference Cycles](#keep_alive-reference-cycles). Not blocking functionality.
- **CI has not been exercised against this build.** `.github/workflows/` is inherited from `main`
unmodified (it calls a shared reusable workflow); it has never actually run against the
nanobind/CMake build because nothing on this branch has been pushed yet.
- **Cross-platform**: everything above has been built and tested on Linux only. macOS/Windows are
unverified.
- **Wheel builds**: PyPI wheels are built with `cibuildwheel` by the shared release workflow,
configured in `[tool.cibuildwheel]` in `pyproject.toml`. The old shared workflow stripped the
libraries `auditwheel` had bundled, so those wheels only worked inside a conda environment.
The cibuildwheel version of the shared workflow is not released yet:
`build-wheel-release-upload.yml` points at `SYMSCAE/release-scripts@cibuildwheel` for now and
must be switched back to `scikit-package/release-scripts@v0` once it is.
- **Cross-platform**: Linux, macOS (x86_64/arm64) and Windows wheels are built and tested by
cibuildwheel in the release workflow.
- **Performance**: not benchmarked against Boost.Python.
- **Versioning**: `pyproject.toml` uses a static version (`2026.2.0rc0`) instead of
`main`'s dynamic tag-based versioning (`setuptools-git-versioning`) — scikit-build-core needs a
different mechanism for that; flagged as a `TODO` in `pyproject.toml` itself, not yet resolved.
- **Versioning**: the version comes from the git tag via `setuptools-scm`
(`metadata.version.provider = "scikit_build_core.metadata.setuptools_scm"`), replacing
`main`'s `setuptools-git-versioning`. Builds need the tags (`fetch-depth: 0` in CI).

None of the above blocks day-to-day use of the library from Python — every method that was
reachable under Boost.Python is reachable under nanobind (modulo the override caveat above, which
Expand Down
25 changes: 25 additions & 0 deletions news/cibuildwheel-wheels.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**Added:**

* <news item>

**Changed:**

* Build the PyPI wheels with ``cibuildwheel`` (manylinux, macOS x86_64/arm64 and Windows AMD64), configured in ``[tool.cibuildwheel]`` in ``pyproject.toml``.
* The package version is now derived from the git tag via ``setuptools-scm``.

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* PyPI wheels are now self-contained and no longer need ``libObjCryst`` or ``libboost_python`` from a conda environment; previously ``import pyobjcryst`` failed in a plain virtual environment.
* Declare ``packaging`` as a runtime dependency (used by ``pyobjcryst.powderpattern``).

**Security:**

* <news item>
1 change: 0 additions & 1 deletion news/nanobind-packaging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
**Changed:**

* Boost headers are now taken from the PyPI ``libboost-headers`` package when building with pip, falling back to system/conda Boost headers (or ``-DBOOST_INCLUDE_DIR=<path>``).
* Set package version to ``2026.2.0rc0``.

**Deprecated:**

Expand Down
54 changes: 47 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
[build-system]
requires = ["scikit-build-core>=0.9", "nanobind", "numpy", "libboost-headers"]
requires = [
"scikit-build-core>=0.10",
"setuptools-scm>=8",
"nanobind",
"numpy",
"libboost-headers",
]
build-backend = "scikit_build_core.build"

[project]
name = "pyobjcryst"
# TODO: main uses setuptools-git-versioning (dynamic, tag-based) with
# setuptools.build_meta; scikit-build-core needs a different dynamic-version
# provider to match that exactly. Pinned as a static placeholder for now --
# revisit before release, see nanobind_migration_notes.md.
version = "2026.2.0rc0"
dynamic = ["version"]
authors = [
{ name="Christopher Farrow" },
{ name="Pavol Juhás" },
Expand All @@ -22,7 +24,7 @@ description = "Python bindings to the ObjCryst++ library."
keywords = ['objcryst', 'atom structure crystallography', 'powder diffraction']
readme = "README.rst"
requires-python = ">=3.12, <3.16"
dependencies = ["numpy"]
dependencies = ["numpy", "packaging"]
classifiers = [
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
Expand Down Expand Up @@ -52,8 +54,46 @@ Issues = "https://github.com/diffpy/pyobjcryst/issues/"
pyobjcryst = "pyobjcryst.pyobjcryst_app:main"

[tool.scikit-build]
minimum-version = "build-system.requires"
cmake.build-type = "Release"
wheel.packages = ["src/pyobjcryst"]
sdist.cmake = true
sdist.exclude = [
"build",
"dist",
".pytest_cache",
# Parts of the ObjCryst++ submodule not needed to build the extension.
"extern/objcryst/Fox",
"extern/objcryst/test",
"extern/objcryst/ObjCryst/doc",
"extern/objcryst/ObjCryst/example",
]
metadata.version.provider = "scikit_build_core.metadata.setuptools_scm"

[tool.setuptools_scm]
version_scheme = "no-guess-dev"

# PyPI wheels: ObjCryst++, cctbx and newmat are built from extern/ and linked
# statically, Boost is header-only and nanobind is linked statically, so the
# wheels install into any environment without conda or system libraries.
[tool.cibuildwheel]
build = ["cp312-*", "cp313-*", "cp314-*", "cp315-*"]
skip = ["*-musllinux_*"]
test-requires = ["pytest"]
# The tests open tests/testdata/* relative to the working directory.
test-sources = ["tests"]
test-command = "pytest tests"

[tool.cibuildwheel.linux]
archs = ["x86_64"]

[tool.cibuildwheel.macos]
archs = ["native"]
environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" }

# No Windows ARM64 wheels.
[tool.cibuildwheel.windows]
archs = ["AMD64"]

[tool.codespell]
exclude-file = ".codespell/ignore_lines.txt"
Expand Down
1 change: 1 addition & 0 deletions requirements/pip.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
setuptools
numpy
packaging
libboost-headers
Loading