From 1ee1ddf83b78d89ed8088240e9343f52d991c041 Mon Sep 17 00:00:00 2001 From: Jack Lovell Date: Fri, 25 Oct 2024 17:50:14 +0100 Subject: [PATCH] Build: Switch from setuptools to scikit-build-core Move away from the setuptools-based build procedure, and use a modern framework, scikit-build-core, which is PEP518-compliant and uses CMake and Ninja for building the cython extensions. Notable improvements: * Automatically rebuilds extensions when Cython sources change, when installed in editable mode. * Parallel by default, doesn't require hacking setuptools to do so. * Out of tree builds, keeps the source tree clean. * Profiling (and line profiling) can be enabled for automatic rebuilds without having to specify `--profile` or `--line-profile` on the command line every time. * No more deprecated calls to `python setup.py `, e.g. in `./dev/build.sh`. Other miscellanous changes: * Use Ubuntu 22.04 in CI, since 24.04 no longer includes Python 3.7. * CI on Python 3.7 through 3.12, which are supported by upstream Cherab and Raysect. --- .github/workflows/ci.yml | 8 +- CMakeLists.txt | 70 ++++++++ README.md | 32 +++- cherab/__init__.py | 19 --- cherab/solps/CMakeLists.txt | 9 ++ cherab/solps/models/CMakeLists.txt | 7 + cmake/FindCython.cmake | 113 +++++++++++++ cmake/UseCython.cmake | 251 +++++++++++++++++++++++++++++ dev/build.sh | 6 - dev/build_wheels.sh | 26 +-- dev/test.sh | 5 +- pyproject.toml | 61 ++++++- requirements.txt | 2 + setup.py | 84 ---------- 14 files changed, 553 insertions(+), 140 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 cherab/__init__.py create mode 100644 cherab/solps/CMakeLists.txt create mode 100644 cherab/solps/models/CMakeLists.txt create mode 100644 cmake/FindCython.cmake create mode 100644 cmake/UseCython.cmake delete mode 100755 dev/build.sh delete mode 100644 setup.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83fa8ce..d744548 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,18 +7,18 @@ on: jobs: tests: name: Run tests - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install cherab-solps and dependencies diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ccae05c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.21) +project(${SKBUILD_PROJECT_NAME} LANGUAGES C) + +find_package( + Python + COMPONENTS Interpreter Development.Module NumPy + REQUIRED) + +# Cython support uses cython-cmake 0.2.0: +# https://pypi.org/project/cython-cmake/. +# We use a vendored copy as upstream doesn't support Python 3.7, but this +# isn't a problem as the vendored files are cmake not python files. +# Ironically the vendoring scripts are the only part of the project which +# would potentially cause Python incompatibilities... +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +find_package(Cython MODULE REQUIRED VERSION 3.0) +include(UseCython) + +# Out-of-tree build means Cython needs to be told to look in +# the source tree for pxd files. +set(CYTHON_ARGS -3 -Wextra -I${CMAKE_SOURCE_DIR}) + +# Don't use deprecated Numpy API. +add_compile_definitions(NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION) + +# Demo files are stored separately and must be copied manually. +file(GLOB_RECURSE demos RELATIVE ${CMAKE_SOURCE_DIR} "${CMAKE_SOURCE_DIR}/demos/*") +foreach(demo_file ${demos}) + get_filename_component(dir ${demo_file} DIRECTORY) + install(FILES ${demo_file} DESTINATION ${SKBUILD_DATA_DIR}/share/cherab/demos/solps/${dir}) +endforeach() + +# Dynamic options ported over from original setup.py. +# CMake's caching means when we use option() here along with +# -Ccmake.define.= (pypa-build) +# or --config-settings=cmake.define.= (pip), +# these persist across rebuilds until specified again on the command line with +# different values. +# +# This means --force won't work properly, since specifying --force for two builds in +# succession would be treated as the same build (the cache value doesn't change) and +# would not lead to calling cython at all. For other options such as --profile, +# again they will only be turned on or off if the command line arguments are changed. +# +# TODO: decide whether CMake defines or environment variables are the better approach +# for dynamic configuration. + +option(profile "Enable profiling in Cython modules." OFF) +if(profile) + list(APPEND CYTHON_ARGS -Xprofile=True) +endif() + +option(line-profile "Enable line-by-line profiling in Cython modules." OFF) +if(line-profile) + list(APPEND CYTHON_ARGS -Xlinetrace=True) + add_compile_definitions(CYTHON_TRACE=1) + add_compile_definitions(CYTHON_TRACE_NOGIL=1) +endif() + +# An example of using an environment variable instead of a CMake option. +# Leave this undocumented for now: do we really want to include support for +# annotations? Probably fine to just use `cython --annotate` on individual files. +if(DEFINED ENV{CYTHON_ANNOTATE}) + list(APPEND CYTHON_ARGS --annotate) +endif() + +# Cython extensions are all stored in cherab/solps and subdirectories. Note: all +# modifications to CYTHON_ARGS should be done before the call to add_subdirectory to +# ensure subdirectories pick up the final list of arguments. +add_subdirectory(cherab/solps) diff --git a/README.md b/README.md index ef09fb3..4a829c7 100644 --- a/README.md +++ b/README.md @@ -49,15 +49,37 @@ This will pull in `cherab-core`, `raysect` `numpy` and other dependencies, then ### Developers -For developing the code, it is recommended to use local checkouts of `cherab-core` and `raysect`, as well as `cherab-solps`. Development should be done against the `development` branch of this repository, and any modifications submitted as pull requests to be merged back into `development`. -To install the package in develop mode, so that local changes are immediately visible without needing to reinstall, install with: +To install the package in editable mode, so that local changes are immediately visible without needing to reinstall, install the project dependencies into your development environment. +You should also enable auto rebuilds. +From the cherab-solps directory, run: ``` -pip install -e +pip install -r requirements.txt +pip install --no-build-isolation --config-settings=editable.rebuild=true -e . ``` -If you are modifying Cython files you will need to run `./dev/build.sh` from this directory in order to rebuild the extension modules. -They will then be used when Python is restarted. +If you are modifying Cython files these will then be automatically rebuilt and the modified versions used when Python is restarted. +Pure Python files will be automatically included in the distribution, but if you add any Cython files you will need to add (or modify) a CMakeLists.txt file to ensure these modules are built and included in the distribution. +See the existing CMakeLists.txt files for examples of how to do this. + +#### Profiling + +It is possible to turn on profiling and line tracing support in the Cython extensions, which may be useful for performance optimisation. +These features do incur a performance overhead so they are disabled by default. + +To enable function-level profiling after installing the project in editable mode (see above), reinstall it with the following `pip` command: + +``` +pip install --no-build-isolation --config-settings=editable.rebuild=true --config-settings=cmake.define.profile=ON -e +``` + +To enable line-by-line profiling, use: + +``` +pip install --no-build-isolation --config-settings=editable.rebuild=true --config-settings=cmake.define.line-profile=ON -e +``` + +**Important:** the profile and line-profile settings will persist across subsequent (manual or automatic) rebuilds until they are turned off by running `pip install` with the corresponding definition set to `OFF`. diff --git a/cherab/__init__.py b/cherab/__init__.py deleted file mode 100644 index bfb1512..0000000 --- a/cherab/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2016-2018 Euratom -# Copyright 2016-2018 United Kingdom Atomic Energy Authority -# Copyright 2016-2018 Centro de Investigaciones Energéticas, Medioambientales y Tecnológicas -# -# Licensed under the EUPL, Version 1.1 or – as soon they will be approved by the -# European Commission - subsequent versions of the EUPL (the "Licence"); -# You may not use this work except in compliance with the Licence. -# You may obtain a copy of the Licence at: -# -# https://joinup.ec.europa.eu/software/page/eupl5 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. -# -# See the Licence for the specific language governing permissions and limitations -# under the Licence. - -__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file diff --git a/cherab/solps/CMakeLists.txt b/cherab/solps/CMakeLists.txt new file mode 100644 index 0000000..bf44139 --- /dev/null +++ b/cherab/solps/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_path(RELATIVE_PATH CMAKE_CURRENT_BINARY_DIR BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE libdir) + +cython_transpile(solps_2d_functions.pyx LANGUAGE C OUTPUT_VARIABLE solps_2d_functions_c) +python_add_library(solps_2d_functions MODULE "${solps_2d_functions_c}" WITH_SOABI) +target_link_libraries(solps_2d_functions PRIVATE Python::NumPy) + +install(TARGETS solps_2d_functions DESTINATION ${libdir}) + +add_subdirectory(models) diff --git a/cherab/solps/models/CMakeLists.txt b/cherab/solps/models/CMakeLists.txt new file mode 100644 index 0000000..29d8a84 --- /dev/null +++ b/cherab/solps/models/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_path(RELATIVE_PATH CMAKE_CURRENT_BINARY_DIR BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE libdir) + +cython_transpile(line_emitter.pyx LANGUAGE C OUTPUT_VARIABLE line_emitter_c) +python_add_library(line_emitter MODULE "${line_emitter_c}" WITH_SOABI) +target_link_libraries(line_emitter PRIVATE Python::NumPy) + +install(TARGETS line_emitter DESTINATION ${libdir}) diff --git a/cmake/FindCython.cmake b/cmake/FindCython.cmake new file mode 100644 index 0000000..7507dd7 --- /dev/null +++ b/cmake/FindCython.cmake @@ -0,0 +1,113 @@ +#.rst: +# +# Find ``cython`` executable. +# +# This module will set the following variables in your project: +# +# ``CYTHON_EXECUTABLE`` +# path to the ``cython`` program +# +# ``CYTHON_VERSION`` +# version of ``cython`` +# +# ``CYTHON_FOUND`` +# true if the program was found +# +# And the following target: +# +# ``Cython::Cython`` +# The Cython executable +# +# A range of versions is supported on CMake 3.19+. See also UseCython. +# +# For more information on the Cython project, see https://cython.org/. +# +# *Cython is a language that makes writing C extensions for the Python language +# as easy as Python itself.* +# +#============================================================================= +# Copyright 2011 Kitware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +# SPDX-License-Identifier: Apache-2.0 + +# Use the Cython executable that lives next to the Python executable +# if it is a local installation. +if(Python_EXECUTABLE) + get_filename_component(_python_path ${Python_EXECUTABLE} PATH) +elseif(Python3_EXECUTABLE) + get_filename_component(_python_path ${Python3_EXECUTABLE} PATH) +elseif(DEFINED PYTHON_EXECUTABLE) + get_filename_component(_python_path ${PYTHON_EXECUTABLE} PATH) +endif() + +if(DEFINED _python_path) + find_program(CYTHON_EXECUTABLE + NAMES cython cython.bat cython3 + HINTS ${_python_path} + DOC "path to the cython executable") +else() + find_program(CYTHON_EXECUTABLE + NAMES cython cython.bat cython3 + DOC "path to the cython executable") +endif() + +if(CYTHON_EXECUTABLE) + set(CYTHON_version_command "${CYTHON_EXECUTABLE}" --version) + + execute_process(COMMAND ${CYTHON_version_command} + OUTPUT_VARIABLE CYTHON_version_output + ERROR_VARIABLE CYTHON_version_error + RESULT_VARIABLE CYTHON_version_result + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE) + + if(NOT ${CYTHON_version_result} EQUAL 0) + set(_error_msg "Command \"${CYTHON_version_command}\" failed with") + set(_error_msg "${_error_msg} output:\n${CYTHON_version_error}") + message(FATAL_ERROR "${_error_msg}") + elseif("${CYTHON_version_output}" MATCHES "^[Cc]ython version ([^,]+)") + set(CYTHON_VERSION "${CMAKE_MATCH_1}") + elseif("${CYTHON_version_error}" MATCHES "^[Cc]ython version ([^,]+)") + set(CYTHON_VERSION "${CMAKE_MATCH_1}") + else() + message(FATAL_ERROR "Invalid Cython version output") + endif() +endif() + +include(FindPackageHandleStandardArgs) + +if(CMAKE_VERSION VERSION_LESS 3.19) + set(_handle_version_range) +else() + set(_handle_version_range HANDLE_VERSION_RANGE) +endif() + +find_package_handle_standard_args(Cython + REQUIRED_VARS CYTHON_EXECUTABLE + VERSION_VAR ${CYTHON_VERSION} + ${_handle_version_range} +) + +if(CYTHON_FOUND) + if(NOT DEFINED Cython::Cython) + add_executable(Cython::Cython IMPORTED) + set_target_properties(Cython::Cython PROPERTIES + IMPORTED_LOCATION "${CYTHON_EXECUTABLE}" + ) + endif() +endif() + +mark_as_advanced(CYTHON_EXECUTABLE) diff --git a/cmake/UseCython.cmake b/cmake/UseCython.cmake new file mode 100644 index 0000000..08198b4 --- /dev/null +++ b/cmake/UseCython.cmake @@ -0,0 +1,251 @@ +#.rst: +# +# The following functions are defined: +# +# .. cmake:command:: Cython_transpile +# +# Create custom rules to generate the source code for a Python extension module +# using cython. +# +# Cython_transpile( +# [LANGUAGE C | CXX] +# [CYTHON_ARGS ...] +# [OUTPUT ] +# [OUTPUT_VARIABLE ]) +# +# Options: +# +# ``LANGUAGE [C | CXX]`` +# Force the generation of either a C or C++ file. Recommended; will attempt +# to be deduced if not specified, defaults to C unless only CXX is enabled. +# +# ``CYTHON_ARGS `` +# Specify additional arguments for the cythonization process. Will default to +# the ``CYTHON_ARGS`` variable if not specified. +# +# ``OUTPUT `` +# Specify a specific path for the output file as ````. By +# default, this will output into the current binary dir. A depfile will be +# created alongside this file as well. +# +# ``OUTPUT_VARIABLE `` +# Set the variable ```` in the parent scope to the path to the +# generated source file. +# +# Defined variables: +# +# ```` +# The path of the generated source file. +# +# Usage example: +# +# .. code-block:: cmake +# +# find_package(Cython) +# include(UseCython) +# +# Cython_transpile(_hello.pyx +# OUTPUT_VARIABLE _hello_source_files +# ) +# +# Python_add_library(_hello +# MODULE "${_hello_source_files}" +# WITH_SOABI +# ) +# +# +#============================================================================= +# Copyright 2011 Kitware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +# SPDX-License-Identifier: Apache-2.0 + +if(CMAKE_VERSION VERSION_LESS "3.8") + # CMake 3.7 required for DEPFILE + # CMake 3.8 required for COMMAND_EXPAND_LISTS + message(FATAL_ERROR "CMake 3.8 required for COMMAND_EXPAND_LISTS") +endif() + +function(Cython_transpile) + set(_options ) + set(_one_value LANGUAGE OUTPUT OUTPUT_VARIABLE) + set(_multi_value CYTHON_ARGS) + + cmake_parse_arguments(_args + "${_options}" + "${_one_value}" + "${_multi_value}" + ${ARGN} + ) + + if(DEFINED CYTHON_EXECUTABLE) + set(_cython_command "${CYTHON_EXECUTABLE}") + elseif(DEFINED Python_EXECUTABLE) + set(_cython_command "${Python_EXECUTABLE}" -m cython) + elseif(DEFINED Python3_EXECUTABLE) + set(_cython_command "${Python3_EXECUTABLE}" -m cython) + else() + message(FATAL_ERROR "Cython executable not found") + endif() + + # Default to CYTHON_ARGS if argument not specified + if(NOT _args_CYTHON_ARGS AND DEFINED CYTHON_ARGS) + set(_args_CYTHON_ARGS "${CYTHON_ARGS}") + endif() + + # Get input + set(_source_files ${_args_UNPARSED_ARGUMENTS}) + list(LENGTH _source_files input_length) + if(NOT input_length EQUAL 1) + message(FATAL_ERROR "One and only one input file must be specified, got '${_source_files}'") + endif() + + function(_transpile _source_file generated_file language) + + if(language STREQUAL "C") + set(_language_arg "") + elseif(language STREQUAL "CXX") + set(_language_arg "--cplus") + else() + message(FATAL_ERROR "_transpile language must be one of C or CXX") + endif() + + set_source_files_properties(${generated_file} PROPERTIES GENERATED TRUE) + + # Generated depfile is expected to have the ".dep" extension and be located along + # side the generated source file. + set(_depfile ${generated_file}.dep) + set(_depfile_arg "-M") + + # Normalize the input path + get_filename_component(_source_file "${_source_file}" ABSOLUTE) + + # Pretty-printed output names + file(RELATIVE_PATH generated_file_relative + "${CMAKE_BINARY_DIR}" "${generated_file}") + file(RELATIVE_PATH source_file_relative + "${CMAKE_SOURCE_DIR}" "${_source_file}") + set(comment "Generating ${_language} source '${generated_file_relative}' from '${source_file_relative}'") + + # Get output directory to ensure its exists + get_filename_component(output_directory "${generated_file}" DIRECTORY) + + get_source_file_property(pyx_location ${_source_file} LOCATION) + + # Add the command to run the compiler. + add_custom_command( + OUTPUT "${generated_file}" + COMMAND + "${CMAKE_COMMAND}" -E make_directory "${output_directory}" + COMMAND + ${_cython_command} + ${_language_arg} + "${_args_CYTHON_ARGS}" + ${_depfile_arg} + "${pyx_location}" + --output-file "${generated_file}" + COMMAND_EXPAND_LISTS + MAIN_DEPENDENCY + "${_source_file}" + DEPFILE + "${_depfile}" + VERBATIM + COMMENT "${comment}" + ) + endfunction() + + function(_set_output _input_file _language _output_var) + if(_language STREQUAL "C") + set(_language_extension "c") + elseif(_language STREQUAL "CXX") + set(_language_extension "cxx") + else() + message(FATAL_ERROR "_set_output language must be one of C or CXX") + endif() + + # Can use cmake_path for CMake 3.20+ + # cmake_path(GET _input_file STEM basename) + get_filename_component(_basename "${_input_file}" NAME_WE) + + if(IS_ABSOLUTE "${_input_file}") + file(RELATIVE_PATH _input_relative "${CMAKE_CURRENT_SOURCE_DIR}" "${_input_file}") + else() + set(_input_relative "${_input_file}") + endif() + + get_filename_component(_output_relative_dir "${_input_relative}" DIRECTORY) + string(REPLACE "." "_" _output_relative_dir "${_output_relative_dir}") + if(_output_relative_dir) + set(_output_relative_dir "${_output_relative_dir}/") + endif() + + set(${_output_var} "${CMAKE_CURRENT_BINARY_DIR}/${_output_relative_dir}${_basename}.${_language_extension}" PARENT_SCOPE) + endfunction() + + set(generated_files) + + list(GET _source_files 0 _source_file) + + # Set target language + set(_language ${_args_LANGUAGE}) + if(NOT _language) + get_property(_languages GLOBAL PROPERTY ENABLED_LANGUAGES) + if("C" IN_LIST _languages AND "CXX" IN_LIST _languages) + # Try to compute language. Returns falsy if not found. + _cython_compute_language(_language ${_source_file}) + elseif("C" IN_LIST _languages) + # If only C is enabled globally, assume C + set(_language "C") + elseif("CXX" IN_LIST _languages) + # Likewise for CXX + set(_language "CXX") + else() + message(FATAL_ERROR "LANGUAGE keyword required if neither C nor CXX enabled globally") + endif() + endif() + + if(NOT _language MATCHES "^(C|CXX)$") + message(FATAL_ERROR "Cython_transpile LANGUAGE must be one of C or CXX") + endif() + + # Place the cython files in the current binary dir if no path given + if(NOT _args_OUTPUT) + _set_output("${_source_file}" ${_language} _args_OUTPUT) + elseif(NOT IS_ABSOLUTE "${_args_OUTPUT}") + set(_args_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${_args_OUTPUT}") + endif() + + set(generated_file "${_args_OUTPUT}") + _transpile("${_source_file}" "${generated_file}" ${_language}) + list(APPEND generated_files "${generated_file}") + + # Output variable only if set + if(_args_OUTPUT_VARIABLE) + set(_output_variable ${_args_OUTPUT_VARIABLE}) + set(${_output_variable} "${generated_files}" PARENT_SCOPE) + endif() + +endfunction() + +function(_cython_compute_language OUTPUT_VARIABLE FILENAME) + file(READ "${FILENAME}" FILE_CONTENT) + # Check for compiler directive similar to "# distutils: language = c++" + # See https://cython.readthedocs.io/en/latest/src/userguide/wrapping_CPlusPlus.html#declare-a-var-with-the-wrapped-c-class + set(REGEX_PATTERN [=[^[[:space:]]*#[[:space:]]*distutils:.*language[[:space:]]*=[[:space:]]*(c\\+\\+|c)]=]) + string(REGEX MATCH "${REGEX_PATTERN}" MATCH_RESULT "${FILE_CONTENT}") + string(TOUPPER "${MATCH_RESULT}" LANGUAGE_NAME) + string(REPLACE "+" "X" LANGUAGE_NAME "${LANGUAGE_NAME}") + set(${OUTPUT_VARIABLE} ${LANGUAGE_NAME} PARENT_SCOPE) +endfunction() diff --git a/dev/build.sh b/dev/build.sh deleted file mode 100755 index 7f2b7a2..0000000 --- a/dev/build.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -CORES=`nproc --all` - -echo "Rebuilding CHERAB extension modules (in place)..." -python setup.py build_ext -j$CORES --inplace $1 $2 $3 $4 $5 diff --git a/dev/build_wheels.sh b/dev/build_wheels.sh index 377b4fd..7d78c84 100644 --- a/dev/build_wheels.sh +++ b/dev/build_wheels.sh @@ -1,30 +1,18 @@ #!/bin/sh -# Run in the PyPA manylinux1 docker container to produce wheels suitable for PyPI. +# Run in the PyPA manylinux2010 docker container to produce wheels suitable for PyPI. # Ensure that the path to the cherab-solps repository is mounted at /cherab-solps. +# Python 3.10 and later should use the manylinux2014 docker container instead. -# Due to the lack of manylinux1 wheels available for many of the dependencies, we -# install build-time dependencies manually using the last versions with wheels, -# then run build without build-time isolation. +# Set cmake.args=--fresh to ensure no cached configuration from previous builds is used. -/opt/python/cp37-cp37m/bin/python -m venv /tmp/venv --clear -. /tmp/venv/bin/activate -pip install --prefer-binary "numpy==1.14.6" "raysect==0.7.1" build wheel "cherab==1.3.0" "scipy<1.6" "matplotlib<3.3" "cython==3.0a5" -python -m build -n . - -/opt/python/cp38-cp38/bin/python -m venv /tmp/venv --clear -. /tmp/venv/bin/activate -pip install --prefer-binary "numpy==1.17.5" "raysect==0.7.1" build wheel "cherab==1.3.0" "scipy<1.8" "matplotlib<3.6" "cython==3.0a5" -python -m build -n . - -/opt/python/cp39-cp39/bin/python -m venv /tmp/venv --clear -. /tmp/venv/bin/activate -pip install --prefer-binary "numpy==1.19.5" "raysect==0.7.1" build wheel "cherab==1.3.0" "scipy<1.8" "matplotlib<3.6" "cython==3.0a5" -python -m build -n . +/opt/python/cp37-cp37m/bin/python -m build . -Ccmake.args=--fresh +/opt/python/cp38-cp38/bin/python -m build . -Ccmake.args=--fresh +/opt/python/cp39-cp39/bin/python -m build . -Ccmake.args=--fresh for wheel in dist/*.whl do auditwheel repair "$wheel" done -# Upload the manylinux1 wheels, along with the sdist in dist/, using twine. +# Upload the manylinux wheels, along with the sdist in dist/, using twine. diff --git a/dev/test.sh b/dev/test.sh index 6132427..547a1b2 100755 --- a/dev/test.sh +++ b/dev/test.sh @@ -1,3 +1,6 @@ #!/bin/bash -python -m unittest $1 $2 $3 $4 $5 +# Project must have been installed in editable mode for this to work. +# For namespace package discovery need to pass the path to the part of +# the namespace that has an __init__.py as the start directory. +python -m unittest discover -s cherab.solps -t . $1 $2 $3 $4 $5 diff --git a/pyproject.toml b/pyproject.toml index 3d17489..7b39891 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,60 @@ [build-system] -requires = ["setuptools", "oldest-supported-numpy", "cython~=3.0", "raysect==0.8.1.*", "cherab==1.5.*"] -build-backend="setuptools.build_meta" +requires = [ + "scikit-build-core", + # "cython-cmake", # Vendored to support Python 3.7 builds. + "cython~=3.0", + # "oldest-supported-numpy; python_version < '3.9'", + # "numpy~=2.0; python_version >= '3.9'", + # Cherab currently requires numpy < 2, so this package does too. + "oldest-supported-numpy", + "raysect==0.8.1.*", + "cherab==1.5.*", +] +build-backend="scikit_build_core.build" + +[project] +name = "cherab-solps" +version = "1.3.0.dev2" +dependencies = [ + "raysect == 0.8.1.*", + "cherab == 1.5.*", + "matplotlib", + "scipy", +] +description = "Cherab spectroscopy framework: SOLPS submodule" +readme = "README.md" +license = {file = "LICENCE.txt"} +requires-python = ">= 3.7" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "Intended Audience :: Education", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Programming Language :: Cython", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering :: Physics", +] + +[project.urls] +homepage = "https://github.com/cherab" +documentation = "https://www.cherab.info" +repository = "https://github.com/cherab/solps" +issues = "https://github.com/cherab/solps/issues" + +[tool.scikit-build] +cmake.version = ">=3.21" +minimum-version = "0.10" +sdist.exclude = [ + ".github", + ".gitignore", + "dev", +] +wheel.exclude = [ + "**.pyx", + "CMakeLists.txt", +] +wheel.packages = ["cherab"] +build-dir = "build/{state}" +build.verbose = true # For development, show the build commands. diff --git a/requirements.txt b/requirements.txt index 27e2c31..45f28f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ +scikit-build-core +oldest-supported-numpy cherab==1.5.* raysect==0.8.1.* cython~=3.0 diff --git a/setup.py b/setup.py deleted file mode 100644 index fa882c8..0000000 --- a/setup.py +++ /dev/null @@ -1,84 +0,0 @@ -from collections import defaultdict -from setuptools import setup, find_packages, Extension -from Cython.Build import cythonize -import sys -import numpy -import os -import os.path as path -from pathlib import Path - -force = False -profile = False - -if "--force" in sys.argv: - force = True - del sys.argv[sys.argv.index("--force")] - -if "--profile" in sys.argv: - profile = True - del sys.argv[sys.argv.index("--profile")] - -compilation_includes = [".", numpy.get_include()] - -setup_path = path.dirname(path.abspath(__file__)) - -# build extension list -extensions = [] -for root, dirs, files in os.walk(setup_path): - for file in files: - if path.splitext(file)[1] == ".pyx": - pyx_file = path.relpath(path.join(root, file), setup_path) - module = path.splitext(pyx_file)[0].replace("/", ".") - extensions.append(Extension(module, [pyx_file], include_dirs=compilation_includes),) - -cython_directives = {"language_level": 3} -if profile: - cython_directives["profile"] = True - - -with open("README.md") as f: - long_description = f.read() - -# Include demos in a separate directory in the distribution as data_files. -demo_parent_path = Path("share/cherab/demos/solps") -data_files = defaultdict(list) -demos_source = Path("demos") -for item in demos_source.rglob("*"): - if item.is_file(): - install_dir = demo_parent_path / item.parent.relative_to(demos_source) - data_files[str(install_dir)].append(str(item)) -data_files = list(data_files.items()) - -setup( - name="cherab-solps", - version="1.3.0.dev1", - license="EUPL 1.1", - namespace_packages=['cherab'], - description="Cherab spectroscopy framework: SOLPS submodule", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Science/Research", - "Intended Audience :: Education", - "Intended Audience :: Developers", - "Natural Language :: English", - "Operating System :: POSIX :: Linux", - "Programming Language :: Cython", - "Programming Language :: Python :: 3", - "Topic :: Scientific/Engineering :: Physics", - ], - url="https://github.com/cherab", - project_urls=dict( - Tracker="https://github.com/cherab/solps/issues", - Documentation="https://cherab.github.io/documentation/", - ), - long_description=long_description, - long_description_content_type="text/markdown", - packages=find_packages(), - package_data={"": [ - "**/*.pyx", "**/*.pxd", # Needed to build Cython extensions. - ], - }, - data_files=data_files, - install_requires=["raysect==0.8.1.*", "cherab==1.5.*"], - ext_modules=cythonize(extensions, force=force, compiler_directives=cython_directives), -)