diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 32f7da6..a917491 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -30,13 +30,63 @@ jobs: name: cibw-sdist path: dist/*.tar.gz + test_sdist: + name: Test SDist with python ${{ matrix.python }} + needs: [make_sdist] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python: ["3.10", "3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + name: Install Python ${{ matrix.python }} + with: + python-version: ${{ matrix.python }} + + - name: Install dependencies + run: | + pip install pytest pytest-cov + + - uses: actions/download-artifact@v4 + with: + name: cibw-sdist + path: dist + + - name: Install SDist + run: | + pip -V + pip install dist/*.tar.gz + rm -rf dist + + - name: Test installed SDist + run: pytest ./tests + + check_dist: + name: Check dist + needs: [build_wheels, make_sdist, test_sdist] + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + path: all + + - run: pipx run twine check --strict all/*/* + build_wheels: name: Wheel on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ubuntu-latest, windows-latest, macos-15-intel] + arch: ["auto"] + + include: + - os: macos-14 + arch: "arm64" steps: - uses: actions/checkout@v5 @@ -44,6 +94,10 @@ jobs: fetch-depth: 0 - uses: pypa/cibuildwheel@v3.2 + env: + CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*" + CIBW_ARCHS: "${{ matrix.arch }}" + CIBW_REPAIR_WHEEL_COMMAND: "" - name: Upload wheels uses: actions/upload-artifact@v4 @@ -52,7 +106,7 @@ jobs: path: wheelhouse/*.whl upload_all: - needs: [build_wheels, make_sdist] + needs: [check_dist] environment: pypi permissions: id-token: write @@ -67,7 +121,3 @@ jobs: merge-multiple: true - uses: pypa/gh-action-pypi-publish@release/v1 - with: - # Remember to tell (test-)pypi about this repo before publishing - # Remove this line to publish to PyPI - repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7bf72f..24aa944 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,23 +40,34 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] runs-on: [ubuntu-latest, macos-latest, windows-latest] - include: - - python-version: pypy-3.10 - runs-on: ubuntu-latest + - os: macos-14 + arch: "arm64" steps: - uses: actions/checkout@v5 with: fetch-depth: 0 + - name: Fetch tags from fork if PR is from a fork + if: + ${{ github.event.pull_request.head.repo.full_name != github.repository + }} + run: | + git remote add fork https://github.com/${{ github.event.pull_request.head.repo.full_name }}.git + git fetch fork --tags + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true + - name: Install OpenGL libraries + if: ${{ matrix.runs-on == 'ubuntu-latest' }} + run: sudo apt-get install --fix-missing libgl-dev + - name: Install package run: python -m pip install .[test] @@ -69,3 +80,29 @@ jobs: uses: codecov/codecov-action@v5.5.1 with: token: ${{ secrets.CODECOV_TOKEN }} + + checks-cibw: + name: > + Check Python Wheel on ${{ matrix.runs-on }} (${{matrix.arch }}) + runs-on: ${{ matrix.runs-on }} + needs: [pre-commit] + strategy: + fail-fast: false + matrix: + runs-on: [ubuntu-latest, windows-latest, macos-15-intel] + arch: ["auto"] + + include: + - runs-on: macos-14 + arch: "arm64" + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: pypa/cibuildwheel@v3.2 + env: + CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*" + CIBW_ARCHS: "${{ matrix.arch }}" + CIBW_REPAIR_WHEEL_COMMAND: "" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f277c4..d2d36c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,6 +60,7 @@ repos: args: [] additional_dependencies: - pytest + - virtualenv - repo: https://github.com/codespell-project/codespell rev: "v2.2.6" diff --git a/CMakeLists.txt b/CMakeLists.txt index 791b1e4..4fb74a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,88 @@ -cmake_minimum_required(VERSION 3.15...3.26) -project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX) +cmake_minimum_required(VERSION 3.21...4.0) -set(PYBIND11_FINDPYTHON ON) -find_package(pybind11 CONFIG REQUIRED) +# Version diagnostics: +# +# * setuptools_scm (overridden) provides a clean nearest tag as project version +# via: git describe --tags --abbrev=0 --match "*[0-9]*" +# * Compare VTK_VERSION to that clean tag (normalized) and warn on mismatch. +set(VTK_VERSION "9.5.0") # major.minor.patch +message(STATUS "SKBUILD_PROJECT_VERSION: ${SKBUILD_PROJECT_VERSION}") +message(STATUS " VTK_VERSION: ${VTK_VERSION}") +if(NOT VTK_VERSION VERSION_EQUAL SKBUILD_PROJECT_VERSION) + message( + WARNING + "Hardcoded VTK version \"${VTK_VERSION}\" disagrees from git tag version \"${SKBUILD_PROJECT_VERSION}\"." + "This check is performed to ensure that the version is the intended one, especially when a specific tag is checked out." + "Please ensure that git tags are up to date and that VTK_VERSION has the expected value." + ) +endif() -pybind11_add_module(_core MODULE src/main.cpp) -install(TARGETS _core DESTINATION ${SKBUILD_PROJECT_NAME}) +project( + vtk-sdk + VERSION ${VTK_VERSION} + DESCRIPTION "VTK SDK python distributions" + HOMEPAGE_URL "https://github.com/Kitware/vtk-sdk-python-distributions" + LANGUAGES NONE) + +find_package( + Python + COMPONENTS Interpreter Development.Module + REQUIRED) + +# ---------------------------------------------------------------------------- +# Download and extract vtk-wheel-sdk archive + +include(cmake/vtk-sdk-urls.cmake) + +set(download_dir ${PROJECT_BINARY_DIR}) +set(extract_dir ${PROJECT_BINARY_DIR}/vtk-wheel-sdk) + +include(FetchContent) +FetchContent_Populate( + vtkwheelsdk + URL ${VTK_SDK_BINARY_URL} + URL_HASH SHA256=${VTK_SDK_EXPECTED_SHA256} + DOWNLOAD_DIR ${download_dir} + SOURCE_DIR ${extract_dir}) + +# ---------------------------------------------------------------------------- +# Install content of the vtk-wheel-sdk archive + +set(VTK_SDK_INSTALL_DIR "content") + +# Append "/" after ${extract_dir} to ensure folder content is copied to the +# destination, instead of the folder itself. +install( + DIRECTORY ${extract_dir}/ + DESTINATION vtk_sdk/${VTK_SDK_INSTALL_DIR} + PATTERN "bin/*" EXCLUDE) + +install( + DIRECTORY ${extract_dir}/bin/ + DESTINATION vtk_sdk/${VTK_SDK_INSTALL_DIR}/bin + PATTERN + "*" + PERMISSIONS + OWNER_READ + OWNER_WRITE + OWNER_EXECUTE + GROUP_READ + GROUP_EXECUTE + WORLD_READ + WORLD_EXECUTE) + +# ---------------------------------------------------------------------------- +# Configure and install "cmake.prefix" entry point files + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/vtk-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/vtk_sdk/cmake/vtk-config.cmake @ONLY) + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/vtk-config-version.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/vtk_sdk/cmake/vtk-config-version.cmake @ONLY) + +install( + FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/vtk_sdk/cmake/__init__.py + ${CMAKE_CURRENT_BINARY_DIR}/vtk_sdk/cmake/vtk-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/vtk_sdk/cmake/vtk-config-version.cmake + DESTINATION vtk_sdk/cmake) diff --git a/README.md b/README.md index bca51b2..5fa6fdd 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ distributed-memory parallel processing for scalability and better performance. This project is intended to distribute the content of the existing VTK wheel SDKs as first-class `vtk-sdk` wheels. -Each `vtk-sdk` Python wheel is equipped with a scikit-build-core `cmake-module` +Each `vtk-sdk` Python wheel is equipped with a scikit-build-core `cmake.prefix` [entrypoint][scikit-build-core-entrypoint], housing the official VTK SDK sourced from the corresponding [archive][wheel-sdks-link]. diff --git a/cmake/vtk-config-version.cmake.in b/cmake/vtk-config-version.cmake.in new file mode 100644 index 0000000..05fd041 --- /dev/null +++ b/cmake/vtk-config-version.cmake.in @@ -0,0 +1 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../@VTK_SDK_INSTALL_DIR@/vtk-@VTK_VERSION@.data/headers/cmake/vtk-config-version.cmake) diff --git a/cmake/vtk-config.cmake.in b/cmake/vtk-config.cmake.in new file mode 100644 index 0000000..d3daf73 --- /dev/null +++ b/cmake/vtk-config.cmake.in @@ -0,0 +1 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../@VTK_SDK_INSTALL_DIR@/vtk-@VTK_VERSION@.data/headers/cmake/vtk-config.cmake) diff --git a/cmake/vtk-sdk-urls.cmake b/cmake/vtk-sdk-urls.cmake new file mode 100644 index 0000000..1cc7c7e --- /dev/null +++ b/cmake/vtk-sdk-urls.cmake @@ -0,0 +1,124 @@ +# ---------------------------------------------------------------------------- +# Set URLs for downloading the VTK SDK stored as a tar.xz archive on Kitware +# hosted server + +message(STATUS "SKBUILD_SOABI: ${SKBUILD_SOABI}") +message(STATUS "Python_SOABI: ${Python_SOABI}") +message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") + +message(STATUS "Setting VTK_SDK_BINARY_URL") + +# Python and ABI tags +set(python_tag "cp${Python_VERSION_MAJOR}${Python_VERSION_MINOR}") +set(abi_tag "${python_tag}") +message(STATUS " python_tag: ${python_tag}") +message(STATUS " abi_tag: ${abi_tag}") + +# Platform tag +if(LINUX) + if(Python_SOABI MATCHES "x86_64") + set(platform_tag "manylinux2014_x86_64.manylinux_2_17_x86_64") + elseif(Python_SOABI MATCHES "aarch64") + set(platform_tag "manylinux_2_28_aarch64") + endif() +elseif(APPLE) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") + set(platform_tag "macosx_10_10_x86_64") + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64") + set(platform_tag "macosx_11_0_arm64") + endif() +elseif(WIN32) + set(platform_tag "win_amd64") +endif() +if(NOT DEFINED platform_tag) + message( + FATAL_ERROR + "Failed to set platform_tag based of Python_SOABI [${Python_SOABI}]") +endif() +message(STATUS " platform_tag: ${platform_tag}") + +set(archive_name + "vtk-wheel-sdk-${VTK_VERSION}-${python_tag}-${abi_tag}-${platform_tag}.tar.xz" +) +set(VTK_SDK_BINARY_URL "https://vtk.org/files/wheel-sdks/${archive_name}") +message(STATUS "Setting VTK_SDK_BINARY_URL: ${VTK_SDK_BINARY_URL}") + +# ---------------------------------------------------------------------------- +# Set expected checksum + +message(STATUS "Setting VTK_SDK_EXPECTED_SHA256") + +set(sha256_9.5.0-cp38-cp38-macosx_10_10_x86_64 + "aa9785854382bf050e8149ab1b0bce4b6aa4c8988c466d9995ba1ff9f46f92e4") +set(sha256_9.5.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64 + "8c77a54b6b9b782d4df12635dc08f9bb952e50d62d218da6848df5ef30608f85") +set(sha256_9.5.0-cp38-cp38-manylinux_2_28_aarch64 + "1fb7a5d5c57e282f2248dfb00b602e4354b6a4c4f71ecc577baf72abd3cedbd3") +set(sha256_9.5.0-cp38-cp38-win_amd64 + "3427a5ae0940a250f1254a664e70b16b33256f09738c397c9023c7868962d319") + +set(sha256_9.5.0-cp39-cp39-macosx_10_10_x86_64 + "b11f9a6269370eeac12832614b14f8fc9a8ad07f2eec4773c6e4fd3c9feae613") +set(sha256_9.5.0-cp39-cp39-macosx_11_0_arm64 + "16f365d6327a03c46c8c0670ac327e7ab9cf09e7e2c54aaba7802fc87256acd2") +set(sha256_9.5.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64 + "c53a78818e5be5097f140ced0958de27b5b00aec4019071643c6757b382ec346") +set(sha256_9.5.0-cp39-cp39-manylinux_2_28_aarch64 + "81383e4ddcba3f36f19faaf08c169a75ba72c87cde25eb714619b77aeb899560") +set(sha256_9.5.0-cp39-cp39-win_amd64 + "29d42bf55b8e0a56c570914bdc19fa893f388b44c7a75adbac6f00c6b728f1e4") + +set(sha256_9.5.0-cp310-cp310-macosx_10_10_x86_64 + "2bea290714bc7bcc2ff4410d347bb5bef1e2dc7fe1e17259dedb26565324a68d") +set(sha256_9.5.0-cp310-cp310-macosx_11_0_arm64 + "499cc8a3fe2e650ba0b3bf280b41ae537ff44a2ce2f1641759a9ebdd03f51ca9") +set(sha256_9.5.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64 + "1eadf905db2a52c712aa5102f3b39b99d948771227f0892fb3e1696d0506893d") +set(sha256_9.5.0-cp310-cp310-manylinux_2_28_aarch64 + "bc3eb9f34415d5cadc30d5b1f25f7779327e48c871aff768e54943ef28cd25e8") +set(sha256_9.5.0-cp310-cp310-win_amd64 + "cccc2dc62a52faf92e6bfc1530bdc2be4a72a859021f284a729971e570a6cee1") + +set(sha256_9.5.0-cp311-cp311-macosx_10_10_x86_64 + "8509be39dcdcfa20f6fcfceeb41ebc4d90d0039e4439d8ba30f3e79ea39eaa53") +set(sha256_9.5.0-cp311-cp311-macosx_11_0_arm64 + "97f727d87ec88b75b52d76d46ecf2d6340ec5985caa60df3bcf0019046d32b52") +set(sha256_9.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64 + "7e21c4463376c16ddbbd9403a751b19553707152a0377cb07548a66838352710") +set(sha256_9.5.0-cp311-cp311-manylinux_2_28_aarch64 + "30f9a53d8d85af0b5c3a489525af32145f446f052d586d832ba162731d992a76") +set(sha256_9.5.0-cp311-cp311-win_amd64 + "dbe5e7a40c13060964dfa002225d808b9a735c52cc014ae3592bf5eec85cdb66") + +set(sha256_9.5.0-cp312-cp312-macosx_10_10_x86_64 + "8751917491635db56a3e877e167dcea5f4465cc8ac9c3e0143d781aaf34a6e8a") +set(sha256_9.5.0-cp312-cp312-macosx_11_0_arm64 + "5d1075389d0a865143c76e3510a4ed17f8e0ae3e420da23e58a129ef260f6ab8") +set(sha256_9.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64 + "a0c629cae58e52fd19401adc8c88ce52e047ade1c200a9faff4fc732a3ab22bd") +set(sha256_9.5.0-cp312-cp312-manylinux_2_28_aarch64 + "67f620ba3a8ca3c5b2d16a548c46283b41e1e042012691b6dcc0066d8a9aa757") +set(sha256_9.5.0-cp312-cp312-win_amd64 + "1ccd922f8e68c06cca116f345a9de21858d9e882706f86eddee1caf93b43a923") + +set(sha256_9.5.0-cp313-cp313-macosx_10_10_x86_64 + "566f1050cbf88b72e66a18a2e1f34265045f62dd134ee867e320cc4907f8951f") +set(sha256_9.5.0-cp313-cp313-macosx_11_0_arm64 + "73419c913f077e721c3ccdf2b638be49356cd30d5bdf7f5dcfafb2064e936008") +set(sha256_9.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64 + "59ad0e395d7e57eb8b850f990ee7bdcb5074c25fa11a2d6da1c46b695a715659") +set(sha256_9.5.0-cp313-cp313-manylinux_2_28_aarch64 + "b221f3c3526ac7c437be3966d3abe80b0e68d141a176d67fe4a69741530038c3") +set(sha256_9.5.0-cp313-cp313-win_amd64 + "0adaf743d9d1882afe512ad3d8182ae43ec37ffc38dd42a6752ab1ad8073b288") + +if(NOT DEFINED sha256_${VTK_VERSION}-${python_tag}-${abi_tag}-${platform_tag}) + message( + FATAL_ERROR + "Variable sha256_${VTK_VERSION}-${python_tag}-${abi_tag}-${platform_tag} is not defined" + ) +endif() + +set(VTK_SDK_EXPECTED_SHA256 + ${sha256_${VTK_VERSION}-${python_tag}-${abi_tag}-${platform_tag}}) +message(STATUS "Setting VTK_SDK_EXPECTED_SHA256: ${VTK_SDK_EXPECTED_SHA256}") diff --git a/pyproject.toml b/pyproject.toml index 0002726..efd6c65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,6 @@ requires = ["pybind11", "scikit-build-core"] build-backend = "scikit_build_core.build" - [project] name = "vtk-sdk" authors = [ @@ -12,7 +11,7 @@ authors = [ description = "Distribution of the VTK wheel SDKs as first-class Python wheels" readme = "README.md" license.file = "LICENSE" -requires-python = ">=3.8" +requires-python = ">=3.10" classifiers = [ "Development Status :: 1 - Planning", "Intended Audience :: Science/Research", @@ -22,11 +21,10 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "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", "Topic :: Scientific/Engineering", "Typing :: Typed", ] @@ -37,6 +35,7 @@ dependencies = [] test = [ "pytest >=6", "pytest-cov >=3", + "virtualenv", ] dev = [ "pytest >=6", @@ -56,24 +55,26 @@ Homepage = "https://github.com/Kitware/vtk-sdk-python-distributions" Discussions = "https://github.com/Kitware/vtk-sdk-python-distributions/discussions" Changelog = "https://github.com/Kitware/vtk-sdk-python-distributions/releases" +[project.entry-points."cmake.prefix"] +any = "vtk_sdk.cmake" [tool.scikit-build] minimum-version = "0.8.2" build-dir = "build/{wheel_tag}" metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" sdist.include = ["src/vtk_sdk/_version.py"] - +wheel.packages = ["src/vtk_sdk"] [tool.setuptools_scm] +git_describe_command = [ "git", "describe", "--tags" ] # we are only interested by last tag name write_to = "src/vtk_sdk/_version.py" - [tool.cibuildwheel] +skip = ["*-win32"] test-command = "pytest {project}/tests" test-extras = ["test"] test-skip = ["*universal2:arm64"] - [tool.pytest.ini_options] minversion = "6.0" addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"] @@ -86,7 +87,6 @@ testpaths = [ "tests", ] - [tool.coverage] run.source = ["vtk_sdk"] report.exclude_also = [ @@ -96,7 +96,7 @@ report.exclude_also = [ [tool.mypy] files = ["src", "tests"] -python_version = "3.8" +python_version = "3.10" warn_unused_configs = true strict = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] @@ -152,7 +152,7 @@ isort.required-imports = ["from __future__ import annotations"] [tool.pylint] -py-version = "3.8" +py-version = "3.10" ignore-paths = [".*/_version.py"] extension-pkg-allow-list = ["vtk_sdk._core"] reports.output-format = "colorized" diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index d570cd0..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -int add(int i, int j) { return i + j; } - -namespace py = pybind11; - -PYBIND11_MODULE(_core, m) { - m.doc() = R"pbdoc( - Pybind11 example plugin - ----------------------- - .. currentmodule:: python_example - .. autosummary:: - :toctree: _generate - add - subtract - )pbdoc"; - - m.def("add", &add, R"pbdoc( - Add two numbers - Some other explanation about the add function. - )pbdoc"); - - m.def("subtract", [](int i, int j) { return i - j; }, R"pbdoc( - Subtract two numbers - Some other explanation about the subtract function. - )pbdoc"); -} diff --git a/src/vtk_sdk/__init__.py b/src/vtk_sdk/__init__.py index d081a1d..d7e1c59 100644 --- a/src/vtk_sdk/__init__.py +++ b/src/vtk_sdk/__init__.py @@ -1,7 +1,7 @@ """ Copyright (c) 2024 Jean-Christophe Fillion-Robin. All rights reserved. -vtk-sdk: Distribution of the VTK wheel SDK with a convenient "cmake-module" scikit-build-core entrypoint +vtk-sdk: Distribution of the VTK wheel SDK with a convenient "cmake.prefix" scikit-build-core entrypoint """ from __future__ import annotations diff --git a/src/vtk_sdk/_core.pyi b/src/vtk_sdk/_core.pyi deleted file mode 100644 index 537c611..0000000 --- a/src/vtk_sdk/_core.pyi +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -def add(_x: int, _y: int) -> int: ... -def subtract(_x: int, _y: int) -> int: ... diff --git a/src/vtk_sdk/cmake/__init__.py b/src/vtk_sdk/cmake/__init__.py new file mode 100644 index 0000000..e0de3db --- /dev/null +++ b/src/vtk_sdk/cmake/__init__.py @@ -0,0 +1,3 @@ +"""This module serves as `cmake.prefix` scikit-build-core entrypoint to +lookup the location of the `vtk-config.cmake` and `vtk-config-version.cmake` files. +""" diff --git a/tests/packages/find_package/CMakeLists.txt b/tests/packages/find_package/CMakeLists.txt new file mode 100644 index 0000000..614d87c --- /dev/null +++ b/tests/packages/find_package/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.21...4.0) + +project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION}) + +find_package(Python COMPONENTS Interpreter Development.Module) + +set(expected_version "9.5.0") +find_package(VTK ${expected_version}) + +set(skipped_components + # Not VTK modules, from + # https://gitlab.kitware.com/vtk/vtk/-/blob/master/CMake/vtkInstallCMakePackage.cmake#L19 + WrapHierarchy + vtkbuild + vtkplatform + vtkpython + pvtkpython + WrapPython + WrapPythonInit + vtkjava + ParseJava + WrapJava + vtkWebAssemblyTestLinkOptions + # Public dependency to OpenXR loader + RenderingOpenXR + RenderingOpenXRRemoting) +foreach(comp IN LISTS VTK_AVAILABLE_COMPONENTS) + if("${skipped_components}" MATCHES "${comp}") + continue() + endif() + if(NOT VTK_${comp}_FOUND) + message(SEND_ERROR "Expected VTK component \"${comp}\" could not be found!") + endif() +endforeach() diff --git a/tests/packages/find_package/pyproject.toml b/tests/packages/find_package/pyproject.toml new file mode 100644 index 0000000..32a838c --- /dev/null +++ b/tests/packages/find_package/pyproject.toml @@ -0,0 +1,12 @@ +[build-system] +requires = ["scikit-build-core", "vtk-sdk"] +build-backend = "scikit_build_core.build" + +[project] +name = "vtk-sdk-test" +version = "1.0.0" +authors = [ + { name = "Alexy Pellegrini", email = "alexy.pellegrini@kitware.com" }, + { name = "Jean-Christophe Fillion-Robin", email = "jchris.fillionr@kitware.com" }, +] +dependencies = [] diff --git a/tests/packages/src/vtk_simple/__init__.py b/tests/packages/src/vtk_simple/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_compiled.py b/tests/test_compiled.py deleted file mode 100644 index d0342ca..0000000 --- a/tests/test_compiled.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -import vtk_sdk._core as m - - -def test_add(): - assert m.add(2, 3) == 5 - - -def test_subtract(): - assert m.subtract(7, 5) == 2 diff --git a/tests/test_find_package.py b/tests/test_find_package.py new file mode 100644 index 0000000..34b93a9 --- /dev/null +++ b/tests/test_find_package.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import os +import subprocess +import sys +from pathlib import Path +from typing import Literal, overload + +import pytest +import virtualenv as _virtualenv # type: ignore[import-untyped] + +# Root test directory and path to the test package +DIR = Path(__file__).parent.resolve() +BASE = DIR / "packages" / "find_package" +ROOT = DIR.parent + + +class VEnv: + """Manages an isolated virtual environment for testing""" + + def __init__(self, env_dir: Path, *, wheelhouse: Path | None = None) -> None: + # Create a virtual environment without setuptools and wheel + cmd = [str(env_dir), "--no-setuptools", "--no-wheel", "--activators", ""] + result = _virtualenv.cli_run(cmd, setup_logging=False) + self.wheelhouse = wheelhouse + self.executable = Path(result.creator.exe) + self.env_dir = env_dir.resolve() + + # Retrieve install locations (used for debugging or inspection) + self.platlib = Path( + self.execute("import sysconfig; print(sysconfig.get_path('platlib'))") + ) + self.purelib = Path( + self.execute("import sysconfig; print(sysconfig.get_path('purelib'))") + ) + + @overload + def run(self, *args: str, capture: Literal[True]) -> str: ... + + @overload + def run(self, *args: str, capture: Literal[False] = ...) -> None: ... + + def run(self, *args: str, capture: bool = False) -> str | None: + __tracebackhide__ = True + + # Prepare environment variables for subprocess + env = os.environ.copy() + paths = {str(self.executable.parent)} + env["PATH"] = os.pathsep.join([*paths, env["PATH"]]) + env["VIRTUAL_ENV"] = str(self.env_dir) + env["PIP_DISABLE_PIP_VERSION_CHECK"] = "ON" + + # Use local wheelhouse if provided + if self.wheelhouse is not None: + env["PIP_NO_INDEX"] = "ON" + env["PIP_FIND_LINKS"] = str(self.wheelhouse) + + str_args = [os.fspath(a) for a in args] + + # Windows does not make a python shortcut in venv + if str_args[0] in {"python", "python3"}: + str_args[0] = str(self.executable) + + # Run and optionally capture output + if capture: + result = subprocess.run( + str_args, + check=False, + capture_output=True, + text=True, + env=env, + ) + if result.returncode != 0: + print(result.stdout, file=sys.stdout) + print(result.stderr, file=sys.stderr) + print("FAILED RUN:", *str_args, file=sys.stderr) + raise SystemExit(result.returncode) + return result.stdout.strip() + + result_bytes = subprocess.run( + str_args, + check=False, + env=env, + ) + if result_bytes.returncode != 0: + print("FAILED RUN:", *str_args, file=sys.stderr) + raise SystemExit(result_bytes.returncode) + return None + + def execute(self, command: str) -> str: + return self.run(str(self.executable), "-c", command, capture=True) + + def module(self, *args: str) -> None: + return self.run(str(self.executable), "-m", *args) + + def install(self, *args: str, isolated: bool = True) -> None: + isolated_flags = [] if isolated else ["--no-build-isolation"] + self.module("pip", "install", *isolated_flags, *args) + + +@pytest.fixture() +def virtualenv(tmp_path: Path) -> VEnv: + """Provides a fresh virtualenv for each test run""" + path = tmp_path / "venv" + return VEnv(path) + + +def test_find_package(virtualenv: VEnv, tmp_path: Path): + """Ensure that the VTK SDK can be found using find_package inside CMake""" + + # Step 1: Build the test wheel using scikit-build-core and VTK SDK + virtualenv.run( + "python", "-m", "pip", "wheel", str(ROOT), "--wheel-dir", str(tmp_path) + ) + + # Step 2: Install the wheel built from the test project (using find_package(VTK)) + virtualenv.run( + "python", "-m", "pip", "install", "--find-links", str(tmp_path), str(BASE) + )