From da7212e8d2e7bc504bebf9e96229833b054bdfa8 Mon Sep 17 00:00:00 2001 From: SYMSCAE Date: Sun, 11 Oct 2026 18:37:22 -0700 Subject: [PATCH 1/7] chore: pin libdiffpy as submodule --- .../workflows/build-wheel-release-upload.yml | 1 + .../matrix-and-codecov-on-merge-to-main.yml | 1 + .github/workflows/publish-docs-on-release.yml | 1 + .github/workflows/tests-on-pr.yml | 1 + .gitmodules | 4 + CMakeLists.txt | 55 +++++- README.rst | 28 +-- cmake/FetchLibdiffpy.cmake | 24 --- cmake/LibdiffpyVersion.cmake | 13 -- cmake/VerifyLibdiffpyRevision.cmake | 27 --- extern/libdiffpy | 1 + news/windows-libdiffpy-submodule.rst | 10 ++ pyproject.toml | 8 + src/extensions/srreal_pickling.hpp | 2 +- src/extensions/wrap_OverlapCalculator.cpp | 2 +- src/extensions/wrap_PDFCalculators.cpp | 4 +- tests/test_build_config.py | 168 ------------------ 17 files changed, 98 insertions(+), 252 deletions(-) create mode 100644 .gitmodules delete mode 100644 cmake/FetchLibdiffpy.cmake delete mode 100644 cmake/LibdiffpyVersion.cmake delete mode 100644 cmake/VerifyLibdiffpyRevision.cmake create mode 160000 extern/libdiffpy create mode 100644 news/windows-libdiffpy-submodule.rst delete mode 100644 tests/test_build_config.py diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index fbf2d28..47eb6f9 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -11,6 +11,7 @@ jobs: uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 with: project: diffpy.srreal + submodules: recursive c_extension: true maintainer_github_username: sbillinge secrets: diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index 85b03e8..6e473f1 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -15,6 +15,7 @@ jobs: uses: scikit-package/release-scripts/.github/workflows/_matrix-and-codecov-on-merge-to-main.yml@v0 with: project: diffpy.srreal + submodules: recursive c_extension: true headless: false run: | diff --git a/.github/workflows/publish-docs-on-release.yml b/.github/workflows/publish-docs-on-release.yml index 45fd601..80b16b2 100644 --- a/.github/workflows/publish-docs-on-release.yml +++ b/.github/workflows/publish-docs-on-release.yml @@ -8,6 +8,7 @@ jobs: uses: scikit-package/release-scripts/.github/workflows/_release-docs.yml@v0 with: project: diffpy.srreal + submodules: recursive c_extension: true python_version: 0 headless: false diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index 7ff7499..620ff88 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -9,6 +9,7 @@ jobs: uses: scikit-package/release-scripts/.github/workflows/_tests-on-pr.yml@v0 with: project: diffpy.srreal + submodules: recursive c_extension: true headless: false run: | diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a16134b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "extern/libdiffpy"] + path = extern/libdiffpy + url = https://github.com/SYMSCAE/libdiffpy.git + branch = win-fix diff --git a/CMakeLists.txt b/CMakeLists.txt index dc25fa2..2c47e51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,58 @@ cmake_minimum_required(VERSION 3.20) -project(diffpy_srreal LANGUAGES CXX) +project(diffpy_srreal LANGUAGES NONE) # Keep this aligned with the pinned libdiffpy's src/SConscript. set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(LIBDIFFPY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/extern/libdiffpy") +if(NOT EXISTS "${LIBDIFFPY_SOURCE_DIR}/src/diffpy/version.tpl") + message(FATAL_ERROR + "Missing libdiffpy sources. Initialize the submodule with " + "'git submodule update --init --recursive', or use a source distribution " + "that includes extern/libdiffpy." + ) +endif() + +# Read version information from the submodule; sdists carry the generated header. +set(LIBDIFFPY_VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/include/diffpy/version.hpp") +set(LIBDIFFPY_SDIST_VERSION_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/_build/libdiffpy-version.hpp") +if(EXISTS "${LIBDIFFPY_SOURCE_DIR}/.git") + file(STRINGS "${LIBDIFFPY_SOURCE_DIR}/site_scons/fallback_version.py" + LIBDIFFPY_FALLBACK_VERSION REGEX "^FALLBACK_VERSION =") + if(NOT LIBDIFFPY_FALLBACK_VERSION MATCHES "([0-9]+)\\.([0-9]+)\\.([0-9]+)") + message(FATAL_ERROR "Cannot read the libdiffpy base version.") + endif() + set(DIFFPY_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(DIFFPY_VERSION_MINOR "${CMAKE_MATCH_2}") + set(DIFFPY_VERSION_MICRO "${CMAKE_MATCH_3}") + set(DIFFPY_VERSION_PATCH 0) + set(DIFFPY_VERSION_STR "${DIFFPY_VERSION_MAJOR}.${DIFFPY_VERSION_MINOR}.${DIFFPY_VERSION_MICRO}") + math(EXPR DIFFPY_VERSION "${DIFFPY_VERSION_MAJOR} * 1000000000 + ${DIFFPY_VERSION_MINOR} * 1000000 + ${DIFFPY_VERSION_MICRO} * 1000") + set(DIFFPY_VERSION "${DIFFPY_VERSION}LL") + find_package(Git REQUIRED) + execute_process( + COMMAND "${GIT_EXECUTABLE}" -C "${LIBDIFFPY_SOURCE_DIR}" log -1 "--format=%H;%ci" + OUTPUT_VARIABLE LIBDIFFPY_COMMIT_INFO + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY + ) + list(GET LIBDIFFPY_COMMIT_INFO 0 DIFFPY_GIT_SHA) + list(GET LIBDIFFPY_COMMIT_INFO 1 DIFFPY_VERSION_DATE) + configure_file("${LIBDIFFPY_SOURCE_DIR}/src/diffpy/version.tpl" + "${LIBDIFFPY_VERSION_HEADER}") +else() + configure_file("${LIBDIFFPY_SDIST_VERSION_HEADER}" + "${LIBDIFFPY_VERSION_HEADER}" COPYONLY) +endif() +if(SKBUILD_STATE STREQUAL "sdist") + configure_file("${LIBDIFFPY_VERSION_HEADER}" + "${LIBDIFFPY_SDIST_VERSION_HEADER}" COPYONLY) + return() +endif() +enable_language(CXX) + # Conda is optional; system packages and CMAKE_PREFIX_PATH work as well. if(DEFINED ENV{CONDA_PREFIX}) list(APPEND CMAKE_PREFIX_PATH "$ENV{CONDA_PREFIX}" "$ENV{CONDA_PREFIX}/Library") @@ -22,9 +69,6 @@ find_package(nanobind 3.1...<4 CONFIG REQUIRED) find_package(Boost CONFIG REQUIRED COMPONENTS serialization) find_package(GSL REQUIRED) -include(cmake/FetchLibdiffpy.cmake) -configure_file("${LIBDIFFPY_SOURCE_DIR}/src/diffpy/version.tpl" - "include/diffpy/version.hpp") set(DIFFPY_HAS_OBJCRYST 0) configure_file("${LIBDIFFPY_SOURCE_DIR}/src/diffpy/features.tpl" "include/diffpy/features.hpp") @@ -49,6 +93,9 @@ target_include_directories(diffpy_core PUBLIC "${LIBDIFFPY_SOURCE_DIR}/src" ) target_compile_definitions(diffpy_core PRIVATE DIFFPYRUNTIMERELPATH=data) +if(WIN32) + target_compile_definitions(diffpy_core PUBLIC _USE_MATH_DEFINES NOMINMAX) +endif() target_link_libraries(diffpy_core PUBLIC Boost::serialization GSL::gsl GSL::gslcblas ${CMAKE_DL_LIBS}) file(GLOB SRREAL_SOURCES CONFIGURE_DEPENDS "src/extensions/*.cpp") diff --git a/README.rst b/README.rst index 0b11416..9cc757d 100644 --- a/README.rst +++ b/README.rst @@ -105,24 +105,28 @@ To install using ``pip`` into your ``diffpy.srreal_env`` environment, type :: pip install diffpy.srreal -If you prefer to install from sources, after installing the dependencies, obtain the source archive from -`GitHub `_. Once installed, ``cd`` into your ``diffpy.srreal`` directory -and run the following :: +To build from Git, install the dependencies, then clone the repository with +its libdiffpy submodule :: + git clone --recurse-submodules https://github.com/diffpy/diffpy.srreal.git + cd diffpy.srreal pip install . -Source builds use CMake to download libdiffpy at the commit recorded in -``cmake/LibdiffpyVersion.cmake`` and verify the archive's SHA-256 checksum. -This applies to both repository checkouts and source distributions; no Git -submodule initialization is needed. A C++23 compiler, Boost.Serialization, -and GSL must be installed separately. +For an existing checkout, initialize or update the submodule before building :: -To build without downloading libdiffpy, extract the pinned archive or check -out that exact commit locally, then supply its absolute path :: + git submodule update --init --recursive - pip install . -Ccmake.define.FETCHCONTENT_SOURCE_DIR_LIBDIFFPY=/absolute/path/to/libdiffpy +The parent repository pins the libdiffpy commit in ``extern/libdiffpy``. +CMake compiles those sources directly without downloading them or updating +the submodule. Version information is read from the submodule and included +in source distributions as a generated header. -Other build dependencies must also be available for a fully offline build. +Packaged source distributions (sdists) include libdiffpy, so they can be +extracted and built with ``pip install .`` without Git or a separate +libdiffpy download. GitHub's automatically generated source archives omit +submodule contents; use a recursive clone or a packaged sdist instead. +A C++23 compiler, Boost.Serialization, GSL, and the Python build dependencies +must still be installed separately, including for an offline build. Getting Started --------------- diff --git a/cmake/FetchLibdiffpy.cmake b/cmake/FetchLibdiffpy.cmake deleted file mode 100644 index a3ca160..0000000 --- a/cmake/FetchLibdiffpy.cmake +++ /dev/null @@ -1,24 +0,0 @@ -include("${CMAKE_CURRENT_LIST_DIR}/LibdiffpyVersion.cmake") -include(FetchContent) - -# Use extraction time so changing the archive also rebuilds its sources. -if(POLICY CMP0135) - cmake_policy(SET CMP0135 NEW) -endif() - -FetchContent_Declare(libdiffpy - URL "https://codeload.github.com/diffpy/libdiffpy/tar.gz/${DIFFPY_GIT_SHA}" - URL_HASH "SHA256=${LIBDIFFPY_ARCHIVE_SHA256}" - TLS_VERIFY TRUE -) -# libdiffpy uses SCons; we compile its sources in our own CMake targets. -FetchContent_MakeAvailable(libdiffpy) -set(LIBDIFFPY_SOURCE_DIR "${libdiffpy_SOURCE_DIR}") - -if(NOT EXISTS "${LIBDIFFPY_SOURCE_DIR}/src/diffpy/version.tpl") - message(FATAL_ERROR "Missing libdiffpy sources in ${LIBDIFFPY_SOURCE_DIR}.") -endif() - -# Also validate Git checkouts supplied through FETCHCONTENT_SOURCE_DIR_LIBDIFFPY. -include("${CMAKE_CURRENT_LIST_DIR}/VerifyLibdiffpyRevision.cmake") -verify_libdiffpy_revision("${LIBDIFFPY_SOURCE_DIR}" "${DIFFPY_GIT_SHA}") diff --git a/cmake/LibdiffpyVersion.cmake b/cmake/LibdiffpyVersion.cmake deleted file mode 100644 index 5890c0b..0000000 --- a/cmake/LibdiffpyVersion.cmake +++ /dev/null @@ -1,13 +0,0 @@ -# Metadata and archive checksum for the pinned libdiffpy commit. Update these -# together when changing the revision; archive builds do not require Git. -set(DIFFPY_VERSION_MAJOR 1) -set(DIFFPY_VERSION_MINOR 4) -set(DIFFPY_VERSION_MICRO 0) -set(DIFFPY_VERSION_PATCH 0) -set(DIFFPY_VERSION 1004000000LL) -set(DIFFPY_VERSION_STR "1.4.0") -set(DIFFPY_VERSION_DATE "2026-06-24 00:40:51 -0600") -set(DIFFPY_GIT_SHA "b191c66df059aa1e4f96260b9df483454f32e084") - -set(LIBDIFFPY_ARCHIVE_SHA256 - "bbb1d36bcbaff041bc87bb96b1f32d5c5682690bcd871f0a31ec30b52e8ded1d") diff --git a/cmake/VerifyLibdiffpyRevision.cmake b/cmake/VerifyLibdiffpyRevision.cmake deleted file mode 100644 index 1c5873d..0000000 --- a/cmake/VerifyLibdiffpyRevision.cmake +++ /dev/null @@ -1,27 +0,0 @@ -# Source archives deliberately have no .git entry. Test for one before running -# Git so an archive unpacked inside another repository cannot inherit its HEAD. -function(verify_libdiffpy_revision source_dir expected_revision) - if(NOT EXISTS "${source_dir}/.git") - return() - endif() - - find_package(Git REQUIRED) - execute_process( - COMMAND "${GIT_EXECUTABLE}" -C "${source_dir}" rev-parse --verify HEAD - RESULT_VARIABLE git_result - OUTPUT_VARIABLE actual_revision - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_VARIABLE git_error - ) - if(NOT git_result EQUAL 0) - message(FATAL_ERROR "Cannot determine the libdiffpy revision: ${git_error}") - endif() - if(NOT actual_revision STREQUAL expected_revision) - message(FATAL_ERROR - "libdiffpy revision mismatch: cmake/LibdiffpyVersion.cmake records " - "${expected_revision}, but ${source_dir} is at ${actual_revision}. " - "Use a checkout of the pinned commit, or unset " - "FETCHCONTENT_SOURCE_DIR_LIBDIFFPY to download the pinned archive." - ) - endif() -endfunction() diff --git a/extern/libdiffpy b/extern/libdiffpy new file mode 160000 index 0000000..9c4f692 --- /dev/null +++ b/extern/libdiffpy @@ -0,0 +1 @@ +Subproject commit 9c4f692e010bd41997fce1284f65d094a65e6c76 diff --git a/news/windows-libdiffpy-submodule.rst b/news/windows-libdiffpy-submodule.rst new file mode 100644 index 0000000..d9c8361 --- /dev/null +++ b/news/windows-libdiffpy-submodule.rst @@ -0,0 +1,10 @@ +**Changed:** + +* Build the embedded libdiffpy core from a pinned Git submodule. Source + distributions include the core sources and runtime data for offline builds. + +**Fixed:** + +* Use libdiffpy's Windows-compatible runtime data lookup and declarations, + enable MSVC mathematical constants, and disambiguate tuple restoration + in calculator pickles. diff --git a/pyproject.toml b/pyproject.toml index db67cf9..d4a0796 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,14 @@ Issues = "https://github.com/diffpy/diffpy.srreal/issues/" minimum-version = "build-system.requires" cmake.build-type = "Release" wheel.packages = ["src/diffpy"] +sdist.cmake = true +sdist.include = [ + "_build/libdiffpy-version.hpp", + "extern/libdiffpy/src/diffpy/**", + "extern/libdiffpy/src/runtime/**", + "extern/libdiffpy/LICENSE.txt", + "extern/libdiffpy/LICENSE_DANSE.txt", +] sdist.exclude = [".agents", ".codex", "cache", "build", "dist"] metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" diff --git a/src/extensions/srreal_pickling.hpp b/src/extensions/srreal_pickling.hpp index 0a79abf..853f9e1 100644 --- a/src/extensions/srreal_pickling.hpp +++ b/src/extensions/srreal_pickling.hpp @@ -455,7 +455,7 @@ class PairQuantityPickleSuite : using namespace diffpy::srreal; ensure_tuple_length(state, 2); // restore the state using boost serialization - nb::tuple state0(state[0]); + nb::tuple state0(nb::handle(state[0].ptr())); Super::setstate(obj, state0); // restore the structure object StructureAdapterPtr pstru = nb::cast(state[1]); diff --git a/src/extensions/wrap_OverlapCalculator.cpp b/src/extensions/wrap_OverlapCalculator.cpp index f1ac6b1..ee01f0f 100644 --- a/src/extensions/wrap_OverlapCalculator.cpp +++ b/src/extensions/wrap_OverlapCalculator.cpp @@ -229,7 +229,7 @@ class OverlapCalculatorPickleSuite : { ensure_tuple_length(state, 2); // restore the state using boost serialization - nb::tuple state0(state[0]); + nb::tuple state0(nb::handle(state[0].ptr())); Super::setstate(obj, state0); // restore component that was kept out of Boost serialization OverlapCalculator& calc = nb::cast(obj); diff --git a/src/extensions/wrap_PDFCalculators.cpp b/src/extensions/wrap_PDFCalculators.cpp index 0190859..9235c71 100644 --- a/src/extensions/wrap_PDFCalculators.cpp +++ b/src/extensions/wrap_PDFCalculators.cpp @@ -492,7 +492,7 @@ class DebyePDFCalculatorPickleSuite : { ensure_tuple_length(state, 4); // restore the state using boost serialization - nb::tuple st0(state[0]); + nb::tuple st0(nb::handle(state[0].ptr())); Super::setstate(obj, st0); // restore components that were kept out of Boost serialization DebyePDFCalculator& calc = nb::cast(obj); @@ -571,7 +571,7 @@ class PDFCalculatorPickleSuite : { ensure_tuple_length(state, 6); // restore the state using boost serialization - nb::tuple st0(state[0]); + nb::tuple st0(nb::handle(state[0].ptr())); Super::setstate(obj, st0); // restore components that were kept out of Boost serialization PDFCalculator& calc = nb::cast(obj); diff --git a/tests/test_build_config.py b/tests/test_build_config.py deleted file mode 100644 index 7065191..0000000 --- a/tests/test_build_config.py +++ /dev/null @@ -1,168 +0,0 @@ -"""Check revision validation with real Git repositories and CMake.""" - -import shutil -import subprocess -from pathlib import Path - -import pytest - -VERIFY_REVISION = ( - Path(__file__).resolve().parents[1] - / "cmake" - / "VerifyLibdiffpyRevision.cmake" -) -FETCH_LIBDIFFPY = VERIFY_REVISION.with_name("FetchLibdiffpy.cmake") - - -@pytest.fixture -def fetch_source(tmp_path): - cmake = shutil.which("cmake") - if not cmake: - pytest.skip("Build configuration checks require CMake.") - project = tmp_path / "fetch-project" - project.mkdir() - (project / "CMakeLists.txt").write_text( - "cmake_minimum_required(VERSION 3.20)\n" - "project(FetchCheck LANGUAGES NONE)\n" - f'include("{FETCH_LIBDIFFPY.as_posix()}")\n' - 'file(WRITE "${CMAKE_BINARY_DIR}/source-dir.txt"\n' - ' "${LIBDIFFPY_SOURCE_DIR}")\n' - ) - - def configure(source): - build = tmp_path / "fetch-build" - result = subprocess.run( - [ - cmake, - "-S", - str(project), - "-B", - str(build), - f"-DFETCHCONTENT_SOURCE_DIR_LIBDIFFPY={source}", - "-DCMAKE_DISABLE_FIND_PACKAGE_Git=TRUE", - ], - capture_output=True, - text=True, - ) - return result, build - - return configure - - -def test_local_archive_override_without_git(tmp_path, fetch_source): - source = tmp_path / "libdiffpy" - template = source / "src" / "diffpy" / "version.tpl" - template.parent.mkdir(parents=True) - template.write_text("// libdiffpy version template\n") - result, build = fetch_source(source) - assert result.returncode == 0, result.stdout + result.stderr - assert (build / "source-dir.txt").read_text() == source.as_posix() - - -def test_local_archive_override_requires_sources(tmp_path, fetch_source): - source = tmp_path / "empty-libdiffpy" - source.mkdir() - result, _ = fetch_source(source) - assert result.returncode != 0 - assert "Missing libdiffpy sources" in result.stderr - - -@pytest.fixture -def revision_check(tmp_path): - cmake = shutil.which("cmake") - git = shutil.which("git") - if not cmake or not git: - pytest.skip("Build configuration checks require CMake and Git.") - repository = tmp_path / "native-source" - subprocess.run( - [git, "init", str(repository)], check=True, capture_output=True - ) - subprocess.run( - [ - git, - "-C", - str(repository), - "-c", - "user.name=Build config test", - "-c", - "user.email=build-test@example.invalid", - "-c", - "commit.gpgsign=false", - "commit", - "--allow-empty", - "-m", - "Native source revision", - ], - check=True, - capture_output=True, - ) - revision = subprocess.check_output( - [git, "-C", str(repository), "rev-parse", "HEAD"], text=True - ).strip() - project = tmp_path / "project" - project.mkdir() - (project / "CMakeLists.txt").write_text( - "cmake_minimum_required(VERSION 3.20)\n" - "project(RevisionCheck LANGUAGES NONE)\n" - f'include("{VERIFY_REVISION.as_posix()}")\n' - 'verify_libdiffpy_revision("${NATIVE_SOURCE}"\n' - ' "${EXPECTED_REVISION}")\n' - ) - - def configure(source, expected, *options): - return subprocess.run( - [ - cmake, - "-S", - str(project), - "-B", - str(tmp_path / "build"), - f"-DNATIVE_SOURCE={source}", - f"-DEXPECTED_REVISION={expected}", - *options, - ], - capture_output=True, - text=True, - ) - - return repository, revision, configure - - -def test_matching_git_revision(revision_check): - repository, revision, configure = revision_check - # A Git worktree can use a .git file pointing to a separate Git directory. - git_dir = repository.parent / "native-git-dir" - (repository / ".git").rename(git_dir) - (repository / ".git").write_text(f"gitdir: {git_dir.as_posix()}\n") - result = configure(repository, revision) - assert result.returncode == 0, result.stdout + result.stderr - - -def test_mismatched_git_revision(revision_check): - repository, revision, configure = revision_check - result = configure(repository, "0" * 40) - assert result.returncode != 0 - assert "libdiffpy revision mismatch" in result.stderr - assert revision in result.stderr - assert "0" * 40 in result.stderr - assert "FETCHCONTENT_SOURCE_DIR_LIBDIFFPY" in result.stderr - - -def test_archive_does_not_use_parent_git_revision(revision_check): - repository, _, configure = revision_check - archive_source = repository / "unpacked-archive" / "libdiffpy" - archive_source.mkdir(parents=True) - result = configure( - archive_source, "0" * 40, "-DCMAKE_DISABLE_FIND_PACKAGE_Git=TRUE" - ) - assert result.returncode == 0, result.stdout + result.stderr - - -def test_broken_git_metadata_is_not_treated_as_archive(revision_check): - repository, revision, configure = revision_check - broken_source = repository / "broken-checkout" - broken_source.mkdir() - (broken_source / ".git").write_text("gitdir: missing-git-directory\n") - result = configure(broken_source, revision) - assert result.returncode != 0 - assert "Cannot determine the libdiffpy revision" in result.stderr From 0c36d4ed59829addb0afd76aec4460935b51858f Mon Sep 17 00:00:00 2001 From: SYMSCAE Date: Tue, 3 Nov 2026 16:37:04 -0800 Subject: [PATCH 2/7] submodule: update libdiffpy to 1.5.0 --- .gitmodules | 3 +-- extern/libdiffpy | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index a16134b..ebc1342 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,3 @@ [submodule "extern/libdiffpy"] path = extern/libdiffpy - url = https://github.com/SYMSCAE/libdiffpy.git - branch = win-fix + url = https://github.com/diffpy/libdiffpy.git diff --git a/extern/libdiffpy b/extern/libdiffpy index 9c4f692..427b5c6 160000 --- a/extern/libdiffpy +++ b/extern/libdiffpy @@ -1 +1 @@ -Subproject commit 9c4f692e010bd41997fce1284f65d094a65e6c76 +Subproject commit 427b5c658de4fa051f204cc17d2ac63165b46c5f From 26f4fab6841c4cf38e449c75caff5166fd35ee9b Mon Sep 17 00:00:00 2001 From: SYMSCAE Date: Tue, 3 Nov 2026 18:18:37 -0800 Subject: [PATCH 3/7] ci: unpin pyobjcryst versions --- .github/workflows/matrix-and-codecov.yml | 6 ------ .github/workflows/tests-on-pr.yml | 6 ------ 2 files changed, 12 deletions(-) diff --git a/.github/workflows/matrix-and-codecov.yml b/.github/workflows/matrix-and-codecov.yml index a9de528..b9c8720 100644 --- a/.github/workflows/matrix-and-codecov.yml +++ b/.github/workflows/matrix-and-codecov.yml @@ -18,11 +18,5 @@ jobs: submodules: recursive c_extension: true headless: false - run: | - set -e - if [[ $(uname -s) == Linux ]]; then - python -m pip install git+https://github.com/diffpy/pyobjcryst.git@nanobind_migration_test - python -c 'import pyobjcryst._pyobjcryst' - fi secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index 620ff88..e8315ce 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -12,11 +12,5 @@ jobs: submodules: recursive c_extension: true headless: false - run: | - set -e - if [[ $(uname -s) == Linux ]]; then - python -m pip install git+https://github.com/diffpy/pyobjcryst.git@nanobind_migration_test - python -c 'import pyobjcryst._pyobjcryst' - fi secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From eef1685179f640b2fe0698ccc3207bcf49da6bee Mon Sep 17 00:00:00 2001 From: SYMSCAE Date: Tue, 3 Nov 2026 22:55:59 -0800 Subject: [PATCH 4/7] refactor: fix pypi wheels --- .../workflows/build-wheel-release-upload.yml | 64 +++++++++++++++---- .github/workflows/build-wheels.yml | 60 +++++++++++++++++ CMakeLists.txt | 32 ++++++++++ news/static-wheels.rst | 30 +++++++++ pyproject.toml | 51 ++++++++++++++- tools/install-vcpkg-deps.ps1 | 42 ++++++++++++ tools/install-vcpkg-deps.sh | 40 ++++++++++++ tools/vcpkg-triplets/srreal-arm64-osx.cmake | 9 +++ tools/vcpkg-triplets/srreal-x64-linux.cmake | 6 ++ tools/vcpkg-triplets/srreal-x64-osx.cmake | 9 +++ tools/vcpkg-triplets/srreal-x64-windows.cmake | 6 ++ vcpkg.json | 12 ++++ 12 files changed, 346 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/build-wheels.yml create mode 100644 news/static-wheels.rst create mode 100644 tools/install-vcpkg-deps.ps1 create mode 100755 tools/install-vcpkg-deps.sh create mode 100644 tools/vcpkg-triplets/srreal-arm64-osx.cmake create mode 100644 tools/vcpkg-triplets/srreal-x64-linux.cmake create mode 100644 tools/vcpkg-triplets/srreal-x64-osx.cmake create mode 100644 tools/vcpkg-triplets/srreal-x64-windows.cmake create mode 100644 vcpkg.json diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index cf361f4..88bcfe3 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -1,7 +1,7 @@ name: Build Wheel and Release # Trigger on tag push or manual dispatch. -# Tag and release privilege are verified inside the reusable workflow. +# Tag and release privilege are verified by the check-* jobs below. on: workflow_dispatch: push: @@ -9,31 +9,69 @@ on: - "*" # ── Release modality ────────────────────────────────────────────────────────── -# Three options are provided below. Only ONE job should be active at a time. -# To switch: comment out the active job and uncomment your preferred option, +# Three options are provided below. Only ONE option should be active at a time. +# To switch: comment out the active jobs and uncomment your preferred option, # then commit the change to main before tagging a release. # ───────────────────────────────────────────────────────────────────────────── jobs: # Option 1 (default): Release to GitHub, publish to PyPI, and deploy docs. # - # The wheel is uploaded to PyPI so users can install with `pip install`. - # A GitHub release is created with the changelog as the release body, and - # the Sphinx documentation is rebuilt and deployed to GitHub Pages. + # The sdist and wheels are uploaded to PyPI so users can install with + # `pip install`. A GitHub release is created with the changelog as the + # release body, and the Sphinx documentation is rebuilt and deployed to + # GitHub Pages. # - # Choose this for open-source packages distributed via PyPI and/or - # conda-forge where broad public availability is the goal. - build-release: - uses: scikit-package/release-scripts/.github/workflows/_build-wheel-release-upload.yml@v0 + # The wheels come from build-wheels.yml rather than the shared + # scikit-package workflow: they link Boost and GSL statically, so they + # work without a conda environment. + check-tag-privilege: + uses: scikit-package/release-scripts/.github/workflows/_check-tag-privilege.yml@v0 with: - project: diffpy.srreal - submodules: recursive - c_extension: true maintainer_github_username: sbillinge + + check-pat-token-validity: + uses: scikit-package/release-scripts/.github/workflows/_check-pat-token-validity.yml@v0 + secrets: + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + + check-pypi-token-validity: + uses: scikit-package/release-scripts/.github/workflows/_check-pypi-token-validity.yml@v0 secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + + get-python-version: + needs: [check-tag-privilege, check-pat-token-validity, check-pypi-token-validity] + uses: scikit-package/release-scripts/.github/workflows/_get-python-version-latest.yml@v0 + + build-wheels: + needs: [check-tag-privilege, check-pat-token-validity, check-pypi-token-validity] + uses: ./.github/workflows/build-wheels.yml + + release-github: + needs: [build-wheels] + uses: scikit-package/release-scripts/.github/workflows/_release-github.yml@v0 + secrets: PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + # Uploads every artifact of this run: the sdist and all wheels. + release-pypi: + needs: [release-github, get-python-version] + uses: scikit-package/release-scripts/.github/workflows/_release-pypi.yml@v0 + with: + python_version: ${{ fromJSON(needs.get-python-version.outputs.latest_python_version) }} + secrets: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + + docs-release: + needs: [release-github, get-python-version] + uses: scikit-package/release-scripts/.github/workflows/_release-docs.yml@v0 + with: + submodules: recursive + project: diffpy.srreal + c_extension: true + python_version: ${{ fromJSON(needs.get-python-version.outputs.latest_python_version) }} + # Option 2: Release to GitHub and deploy docs, without publishing to PyPI. # # A GitHub release is created and the Sphinx docs are deployed, but the diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml new file mode 100644 index 0000000..a8c6ed1 --- /dev/null +++ b/.github/workflows/build-wheels.yml @@ -0,0 +1,60 @@ +name: Build Wheels + +# Builds the sdist and self-contained wheels (Boost and GSL linked +# statically, see [tool.cibuildwheel] in pyproject.toml). Called by the +# release workflow; run it manually to check the wheels before tagging. +on: + workflow_call: + workflow_dispatch: + +jobs: + build-sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v6 + with: + submodules: recursive + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Build sdist + run: | + python -m pip install --upgrade build + python -m build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + build-wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-15-intel + - macos-latest + - windows-latest + steps: + - name: Check out + uses: actions/checkout@v6 + with: + submodules: recursive + fetch-depth: 0 + + - name: Build and test wheels + uses: pypa/cibuildwheel@v4.3.5 + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }} + path: wheelhouse/*.whl diff --git a/CMakeLists.txt b/CMakeLists.txt index 995c669..f1484ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,8 +66,34 @@ execute_process( COMMAND_ERROR_IS_FATAL ANY ) find_package(nanobind 3.1...<4 CONFIG REQUIRED) + +# PyPI wheels link Boost and GSL statically so they install without conda or +# system libraries. Conda and system builds keep the default shared linking. +option(SRREAL_STATIC_DEPS "Link Boost.Serialization and GSL statically" OFF) +set(SRREAL_DEPS_LICENSE_DIR "" CACHE PATH + "Third-party license files to ship in the wheel with SRREAL_STATIC_DEPS") + +if(SRREAL_STATIC_DEPS) + set(Boost_USE_STATIC_LIBS ON) +endif() find_package(Boost CONFIG REQUIRED COMPONENTS serialization) + +if(SRREAL_STATIC_DEPS AND NOT WIN32) + # FindGSL has no static switch; restrict the search to static archives. + set(_srreal_saved_suffixes "${CMAKE_FIND_LIBRARY_SUFFIXES}") + set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +endif() find_package(GSL REQUIRED) +if(DEFINED _srreal_saved_suffixes) + set(CMAKE_FIND_LIBRARY_SUFFIXES "${_srreal_saved_suffixes}") + get_target_property(_srreal_boost_lib Boost::serialization LOCATION) + foreach(_srreal_lib IN ITEMS "${_srreal_boost_lib}" "${GSL_LIBRARY}" "${GSL_CBLAS_LIBRARY}") + if(NOT _srreal_lib MATCHES "\\.a$") + message(FATAL_ERROR + "SRREAL_STATIC_DEPS requires static libraries, found ${_srreal_lib}") + endif() + endforeach() +endif() set(DIFFPY_HAS_OBJCRYST 0) configure_file("${LIBDIFFPY_SOURCE_DIR}/src/diffpy/features.tpl" @@ -110,3 +136,9 @@ install(DIRECTORY "${LIBDIFFPY_SOURCE_DIR}/src/runtime/" install(FILES "${LIBDIFFPY_SOURCE_DIR}/LICENSE.rst" "${LIBDIFFPY_SOURCE_DIR}/LICENSE_DANSE.rst" DESTINATION diffpy/srreal/licenses/libdiffpy) +# Statically linked Boost and GSL code ships inside the extension, so their +# licenses ship with it. +if(SRREAL_STATIC_DEPS AND SRREAL_DEPS_LICENSE_DIR) + install(DIRECTORY "${SRREAL_DEPS_LICENSE_DIR}/" + DESTINATION diffpy/srreal/licenses) +endif() diff --git a/news/static-wheels.rst b/news/static-wheels.rst new file mode 100644 index 0000000..93423e6 --- /dev/null +++ b/news/static-wheels.rst @@ -0,0 +1,30 @@ +**Added:** + +* Publish self-contained wheels to PyPI for Linux (x86_64), + macOS (x86_64, arm64) and Windows (AMD64). The wheels link + Boost.Serialization and GSL statically and install without conda. + ``vcpkg.json`` pins the library versions on all platforms. +* Add the ``SRREAL_STATIC_DEPS`` CMake option to link Boost.Serialization + and GSL statically. It is off by default, so conda and system builds + keep linking the shared libraries. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Include libdiffpy's ``LICENSE.rst`` and ``LICENSE_DANSE.rst`` in the + sdist under their actual file names. + +**Security:** + +* diff --git a/pyproject.toml b/pyproject.toml index 8a03aae..35a0cee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,12 +50,59 @@ sdist.include = [ "_build/libdiffpy-version.hpp", "extern/libdiffpy/src/diffpy/**", "extern/libdiffpy/src/runtime/**", - "extern/libdiffpy/LICENSE.txt", - "extern/libdiffpy/LICENSE_DANSE.txt", + "extern/libdiffpy/LICENSE.rst", + "extern/libdiffpy/LICENSE_DANSE.rst", ] sdist.exclude = [".agents", ".codex", "cache", "build", "dist"] metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" +# PyPI wheels: Boost.Serialization and GSL are linked statically, so the +# wheels install into any environment without conda or system libraries. +# vcpkg.json pins the library versions; tools/install-vcpkg-deps.* install +# them once per platform in before-all (see the overrides below). +[tool.cibuildwheel] +build = ["cp312-*", "cp313-*", "cp314-*"] +skip = ["*-musllinux_*"] +test-extras = ["test"] +test-command = "pytest {project}/tests" + +[tool.cibuildwheel.linux] +archs = ["x86_64"] +config-settings = { "cmake.define.SRREAL_STATIC_DEPS" = "ON", "cmake.define.SRREAL_DEPS_LICENSE_DIR" = "/opt/srreal-deps/licenses", "cmake.define.CMAKE_TOOLCHAIN_FILE" = "/opt/srreal-deps/vcpkg/scripts/buildsystems/vcpkg.cmake", "cmake.define.VCPKG_INSTALLED_DIR" = "/opt/srreal-deps/installed", "cmake.define.VCPKG_MANIFEST_INSTALL" = "OFF" } + +[tool.cibuildwheel.macos] +archs = ["native"] +# Keep in sync with VCPKG_OSX_DEPLOYMENT_TARGET in tools/vcpkg-triplets. +environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } +config-settings = { "cmake.define.SRREAL_STATIC_DEPS" = "ON", "cmake.define.SRREAL_DEPS_LICENSE_DIR" = "/tmp/srreal-deps/licenses", "cmake.define.CMAKE_TOOLCHAIN_FILE" = "/tmp/srreal-deps/vcpkg/scripts/buildsystems/vcpkg.cmake", "cmake.define.VCPKG_INSTALLED_DIR" = "/tmp/srreal-deps/installed", "cmake.define.VCPKG_MANIFEST_INSTALL" = "OFF" } + +# No Windows ARM64 wheels. +[tool.cibuildwheel.windows] +archs = ["AMD64"] +before-all = "powershell -NoProfile -ExecutionPolicy Bypass -File {project}/tools/install-vcpkg-deps.ps1 C:/srreal-deps srreal-x64-windows" +config-settings = { "cmake.define.SRREAL_STATIC_DEPS" = "ON", "cmake.define.SRREAL_DEPS_LICENSE_DIR" = "C:/srreal-deps/licenses", "cmake.define.CMAKE_TOOLCHAIN_FILE" = "C:/srreal-deps/vcpkg/scripts/buildsystems/vcpkg.cmake", "cmake.define.VCPKG_INSTALLED_DIR" = "C:/srreal-deps/installed", "cmake.define.VCPKG_MANIFEST_INSTALL" = "OFF", "cmake.define.VCPKG_TARGET_TRIPLET" = "srreal-x64-windows" } +before-build = "pip install delvewheel" +repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" + +# One vcpkg triplet per architecture; see tools/vcpkg-triplets. +[[tool.cibuildwheel.overrides]] +select = "*-manylinux_x86_64" +inherit.config-settings = "append" +before-all = "bash {project}/tools/install-vcpkg-deps.sh /opt/srreal-deps srreal-x64-linux" +config-settings = { "cmake.define.VCPKG_TARGET_TRIPLET" = "srreal-x64-linux" } + +[[tool.cibuildwheel.overrides]] +select = "*-macosx_x86_64" +inherit.config-settings = "append" +before-all = "bash {project}/tools/install-vcpkg-deps.sh /tmp/srreal-deps srreal-x64-osx" +config-settings = { "cmake.define.VCPKG_TARGET_TRIPLET" = "srreal-x64-osx" } + +[[tool.cibuildwheel.overrides]] +select = "*-macosx_arm64" +inherit.config-settings = "append" +before-all = "bash {project}/tools/install-vcpkg-deps.sh /tmp/srreal-deps srreal-arm64-osx" +config-settings = { "cmake.define.VCPKG_TARGET_TRIPLET" = "srreal-arm64-osx" } + [tool.setuptools_scm] version_scheme = "no-guess-dev" diff --git a/tools/install-vcpkg-deps.ps1 b/tools/install-vcpkg-deps.ps1 new file mode 100644 index 0000000..5a63e07 --- /dev/null +++ b/tools/install-vcpkg-deps.ps1 @@ -0,0 +1,42 @@ +# Install static Boost.Serialization and GSL with vcpkg for Windows PyPI wheels. +# +# Usage: tools/install-vcpkg-deps.ps1 PREFIX TRIPLET +# +# Windows counterpart of install-vcpkg-deps.sh: checks out vcpkg at the +# builtin-baseline commit of vcpkg.json, installs into PREFIX/installed and +# copies license texts to PREFIX/licenses. +param( + [Parameter(Mandatory = $true)] [string] $Prefix, + [Parameter(Mandatory = $true)] [string] $Triplet +) +$ErrorActionPreference = "Stop" + +function Invoke-Checked { + $command, $arguments = $args + & $command @arguments + if ($LASTEXITCODE -ne 0) { throw "$command failed with exit code $LASTEXITCODE" } +} + +$project = Split-Path -Parent $PSScriptRoot +$baseline = (Get-Content (Join-Path $project "vcpkg.json") -Raw | ConvertFrom-Json)."builtin-baseline" +$vcpkgRoot = Join-Path $Prefix "vcpkg" +$vcpkg = Join-Path $vcpkgRoot "vcpkg.exe" + +if (-not (Test-Path $vcpkg)) { + Invoke-Checked git init -q $vcpkgRoot + Invoke-Checked git -C $vcpkgRoot fetch -q --depth 1 https://github.com/microsoft/vcpkg.git $baseline + Invoke-Checked git -C $vcpkgRoot -c advice.detachedHead=false checkout -q FETCH_HEAD + Invoke-Checked (Join-Path $vcpkgRoot "bootstrap-vcpkg.bat") -disableMetrics +} + +Invoke-Checked $vcpkg install ` + "--x-manifest-root=$project" ` + "--x-install-root=$(Join-Path $Prefix 'installed')" ` + "--overlay-triplets=$(Join-Path $project 'tools\vcpkg-triplets')" ` + "--triplet=$Triplet" + +$share = Join-Path $Prefix "installed\$Triplet\share" +$licenses = Join-Path $Prefix "licenses" +New-Item -ItemType Directory -Force -Path (Join-Path $licenses "boost"), (Join-Path $licenses "gsl") | Out-Null +Copy-Item (Join-Path $share "boost-serialization\copyright") (Join-Path $licenses "boost\LICENSE_1_0.txt") +Copy-Item (Join-Path $share "gsl\copyright") (Join-Path $licenses "gsl\COPYING") diff --git a/tools/install-vcpkg-deps.sh b/tools/install-vcpkg-deps.sh new file mode 100755 index 0000000..2f9127a --- /dev/null +++ b/tools/install-vcpkg-deps.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Install static Boost.Serialization and GSL with vcpkg for PyPI wheels. +# +# Usage: tools/install-vcpkg-deps.sh PREFIX TRIPLET +# +# Checks out vcpkg at the builtin-baseline commit of vcpkg.json, which pins +# every library version. Installs into PREFIX/installed and copies license +# texts to PREFIX/licenses. cibuildwheel runs this as `before-all` on Linux +# and macOS; Windows uses install-vcpkg-deps.ps1. +set -euo pipefail + +PREFIX="${1:?usage: $0 PREFIX TRIPLET}" +TRIPLET="${2:?usage: $0 PREFIX TRIPLET}" +project="$(cd "$(dirname "$0")/.." && pwd)" +baseline="$(sed -n 's/.*"builtin-baseline": *"\([0-9a-f]*\)".*/\1/p' "$project/vcpkg.json")" +vcpkg_root="$PREFIX/vcpkg" + +if [[ "$(uname -s)" == Linux ]]; then + # manylinux images lack zip, which vcpkg uses for its binary cache. + command -v zip >/dev/null || dnf install -y zip +fi + +if [[ ! -x "$vcpkg_root/vcpkg" ]]; then + git init -q "$vcpkg_root" + git -C "$vcpkg_root" fetch -q --depth 1 \ + https://github.com/microsoft/vcpkg.git "$baseline" + git -C "$vcpkg_root" -c advice.detachedHead=false checkout -q FETCH_HEAD + "$vcpkg_root/bootstrap-vcpkg.sh" -disableMetrics +fi + +"$vcpkg_root/vcpkg" install \ + --x-manifest-root="$project" \ + --x-install-root="$PREFIX/installed" \ + --overlay-triplets="$project/tools/vcpkg-triplets" \ + --triplet="$TRIPLET" + +share="$PREFIX/installed/$TRIPLET/share" +mkdir -p "$PREFIX/licenses/boost" "$PREFIX/licenses/gsl" +cp "$share/boost-serialization/copyright" "$PREFIX/licenses/boost/LICENSE_1_0.txt" +cp "$share/gsl/copyright" "$PREFIX/licenses/gsl/COPYING" diff --git a/tools/vcpkg-triplets/srreal-arm64-osx.cmake b/tools/vcpkg-triplets/srreal-arm64-osx.cmake new file mode 100644 index 0000000..15d8171 --- /dev/null +++ b/tools/vcpkg-triplets/srreal-arm64-osx.cmake @@ -0,0 +1,9 @@ +# Static, release-only libraries for diffpy.srreal PyPI wheels. +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_BUILD_TYPE release) +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) +set(VCPKG_OSX_ARCHITECTURES arm64) +# Keep in sync with MACOSX_DEPLOYMENT_TARGET in pyproject.toml. +set(VCPKG_OSX_DEPLOYMENT_TARGET 11.0) diff --git a/tools/vcpkg-triplets/srreal-x64-linux.cmake b/tools/vcpkg-triplets/srreal-x64-linux.cmake new file mode 100644 index 0000000..dedbc84 --- /dev/null +++ b/tools/vcpkg-triplets/srreal-x64-linux.cmake @@ -0,0 +1,6 @@ +# Static, release-only libraries for diffpy.srreal PyPI wheels. +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_BUILD_TYPE release) +set(VCPKG_CMAKE_SYSTEM_NAME Linux) diff --git a/tools/vcpkg-triplets/srreal-x64-osx.cmake b/tools/vcpkg-triplets/srreal-x64-osx.cmake new file mode 100644 index 0000000..896bcb1 --- /dev/null +++ b/tools/vcpkg-triplets/srreal-x64-osx.cmake @@ -0,0 +1,9 @@ +# Static, release-only libraries for diffpy.srreal PyPI wheels. +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_BUILD_TYPE release) +set(VCPKG_CMAKE_SYSTEM_NAME Darwin) +set(VCPKG_OSX_ARCHITECTURES x86_64) +# Keep in sync with MACOSX_DEPLOYMENT_TARGET in pyproject.toml. +set(VCPKG_OSX_DEPLOYMENT_TARGET 11.0) diff --git a/tools/vcpkg-triplets/srreal-x64-windows.cmake b/tools/vcpkg-triplets/srreal-x64-windows.cmake new file mode 100644 index 0000000..ca0b7fa --- /dev/null +++ b/tools/vcpkg-triplets/srreal-x64-windows.cmake @@ -0,0 +1,6 @@ +# Static, release-only libraries for diffpy.srreal PyPI wheels. +set(VCPKG_TARGET_ARCHITECTURE x64) +# Shared C runtime (/MD), as CPython extension modules require. +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) +set(VCPKG_BUILD_TYPE release) diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..f8ddd73 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "diffpy-srreal", + "description": "Static Boost.Serialization and GSL for the diffpy.srreal PyPI wheels.", + "dependencies": [ + "boost-container-hash", + "boost-serialization", + "boost-ublas", + "gsl" + ], + "builtin-baseline": "9e593bb18ea69cc5095e012465dcd675a822ed0d" +} From f3db0522df1fd2845004ced133df45f38c49c505 Mon Sep 17 00:00:00 2001 From: SYMSCAE Date: Tue, 3 Nov 2026 23:11:27 -0800 Subject: [PATCH 5/7] fix: build static wheels on macOS and Windows --- src/extensions/srreal_converters.hpp | 5 +++-- tools/install-vcpkg-deps.ps1 | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/extensions/srreal_converters.hpp b/src/extensions/srreal_converters.hpp index cdf261b..1e9f798 100644 --- a/src/extensions/srreal_converters.hpp +++ b/src/extensions/srreal_converters.hpp @@ -280,7 +280,8 @@ inline nb::object convertToNumPyArray(const ::diffpy::srreal::R3::Matrix& mx) { using namespace diffpy::srreal; - size_t sz[2] = {R3::Ndim, R3::Ndim}; + const size_t ndim = static_cast(R3::Ndim); + size_t sz[2] = {ndim, ndim}; NumPyOutput ap = createNumPyDoubleArray(2, sz); double* xo = ap.array.data(); *(xo++) = mx(0, 0); *(xo++) = mx(0, 1); *(xo++) = mx(0, 2); @@ -295,7 +296,7 @@ inline nb::object convertToNumPyArray(const ::std::vector& vr3v) { using namespace diffpy::srreal; - size_t sz[2] = {vr3v.size(), R3::Ndim}; + size_t sz[2] = {vr3v.size(), static_cast(R3::Ndim)}; NumPyOutput ap = createNumPyDoubleArray(2, sz); double* p = ap.array.data(); std::vector::const_iterator v = vr3v.begin(); diff --git a/tools/install-vcpkg-deps.ps1 b/tools/install-vcpkg-deps.ps1 index 5a63e07..41de85c 100644 --- a/tools/install-vcpkg-deps.ps1 +++ b/tools/install-vcpkg-deps.ps1 @@ -13,6 +13,8 @@ $ErrorActionPreference = "Stop" function Invoke-Checked { $command, $arguments = $args + # Destructuring unwraps a single argument; keep splatting an array. + $arguments = @($arguments) & $command @arguments if ($LASTEXITCODE -ne 0) { throw "$command failed with exit code $LASTEXITCODE" } } From 26270d837b75163661212ace108df8725fd2b1f6 Mon Sep 17 00:00:00 2001 From: SYMSCAE Date: Tue, 3 Nov 2026 23:22:20 -0800 Subject: [PATCH 6/7] fix: export embedded libdiffpy symbols on Windows --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f1484ee..f35d368 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,7 +120,8 @@ target_include_directories(diffpy_core PUBLIC ) target_compile_definitions(diffpy_core PRIVATE DIFFPYRUNTIMERELPATH=data) if(WIN32) - target_compile_definitions(diffpy_core PUBLIC _USE_MATH_DEFINES NOMINMAX) + # libdiffpy and its Python wrappers are compiled into the same extension. + target_compile_definitions(diffpy_core PUBLIC BUILD_DLL _USE_MATH_DEFINES NOMINMAX) endif() target_link_libraries(diffpy_core PUBLIC Boost::serialization GSL::gsl GSL::gslcblas ${CMAKE_DL_LIBS}) From d8f35721292ddf06b5ce0d24d10f02f0a8ea05d0 Mon Sep 17 00:00:00 2001 From: SYMSCAE Date: Tue, 3 Nov 2026 23:39:32 -0800 Subject: [PATCH 7/7] fix: load bundled Windows runtime from srreal package --- pyproject.toml | 4 +++- tools/install-vcpkg-deps.ps1 | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 35a0cee..c06b940 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,9 @@ archs = ["AMD64"] before-all = "powershell -NoProfile -ExecutionPolicy Bypass -File {project}/tools/install-vcpkg-deps.ps1 C:/srreal-deps srreal-x64-windows" config-settings = { "cmake.define.SRREAL_STATIC_DEPS" = "ON", "cmake.define.SRREAL_DEPS_LICENSE_DIR" = "C:/srreal-deps/licenses", "cmake.define.CMAKE_TOOLCHAIN_FILE" = "C:/srreal-deps/vcpkg/scripts/buildsystems/vcpkg.cmake", "cmake.define.VCPKG_INSTALLED_DIR" = "C:/srreal-deps/installed", "cmake.define.VCPKG_MANIFEST_INSTALL" = "OFF", "cmake.define.VCPKG_TARGET_TRIPLET" = "srreal-x64-windows" } before-build = "pip install delvewheel" -repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}" +# Other diffpy distributions also install diffpy/__init__.py. Keep the DLL +# loader in diffpy.srreal and use the runtime matching the build toolset. +repair-wheel-command = "delvewheel repair --namespace-pkg diffpy --add-path C:/srreal-deps/redist -w {dest_dir} {wheel}" # One vcpkg triplet per architecture; see tools/vcpkg-triplets. [[tool.cibuildwheel.overrides]] diff --git a/tools/install-vcpkg-deps.ps1 b/tools/install-vcpkg-deps.ps1 index 41de85c..39bbe84 100644 --- a/tools/install-vcpkg-deps.ps1 +++ b/tools/install-vcpkg-deps.ps1 @@ -24,6 +24,19 @@ $baseline = (Get-Content (Join-Path $project "vcpkg.json") -Raw | ConvertFrom-Js $vcpkgRoot = Join-Path $Prefix "vcpkg" $vcpkg = Join-Path $vcpkgRoot "vcpkg.exe" +# Prefer the current compiler's runtime over older DLLs elsewhere on PATH. +$vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio\Installer\vswhere.exe" +$vsRoot = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath +if ($LASTEXITCODE -ne 0 -or -not $vsRoot) { throw "Cannot locate the Visual C++ toolset." } +$redistVersion = (Get-Content (Join-Path $vsRoot "VC\Auxiliary\Build\Microsoft.VCRedistVersion.default.txt") -Raw).Trim() +$redistRoot = Join-Path $vsRoot "VC\Redist\MSVC\$redistVersion\x64" +$crt = Get-ChildItem $redistRoot -Directory -Filter "Microsoft.VC*.CRT" | Select-Object -First 1 +if (-not $crt) { throw "Cannot locate the x64 Visual C++ runtime in $redistRoot." } +$redist = Join-Path $Prefix "redist" +New-Item -ItemType Directory -Force -Path $redist | Out-Null +Copy-Item (Join-Path $crt.FullName "*.dll") $redist -Force +Write-Host "Staged Visual C++ runtime $redistVersion from $($crt.FullName)" + if (-not (Test-Path $vcpkg)) { Invoke-Checked git init -q $vcpkgRoot Invoke-Checked git -C $vcpkgRoot fetch -q --depth 1 https://github.com/microsoft/vcpkg.git $baseline