diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 42b91f7..375e248 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -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 }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 26b23b8..1106534 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 @@ -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) diff --git a/nanobind_migration_notes.md b/nanobind_migration_notes.md index 57eb662..5f2c75c 100644 --- a/nanobind_migration_notes.md +++ b/nanobind_migration_notes.md @@ -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 diff --git a/news/cibuildwheel-wheels.rst b/news/cibuildwheel-wheels.rst new file mode 100644 index 0000000..87862f2 --- /dev/null +++ b/news/cibuildwheel-wheels.rst @@ -0,0 +1,25 @@ +**Added:** + +* + +**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:** + +* + +**Removed:** + +* + +**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:** + +* diff --git a/news/nanobind-packaging.rst b/news/nanobind-packaging.rst index 6419c7f..6d6fa3f 100644 --- a/news/nanobind-packaging.rst +++ b/news/nanobind-packaging.rst @@ -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=``). -* Set package version to ``2026.2.0rc0``. **Deprecated:** diff --git a/pyproject.toml b/pyproject.toml index ea0624b..d70a14b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" }, @@ -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', @@ -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" diff --git a/requirements/pip.txt b/requirements/pip.txt index e1eb255..f70a1e2 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1,3 +1,4 @@ setuptools numpy +packaging libboost-headers