Skip to content

Commit

Permalink
Build: Switch from setuptools to scikit-build-core
Browse files Browse the repository at this point in the history
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 <command>`, 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.
  • Loading branch information
jacklovell committed Oct 28, 2024
1 parent 0b58c2e commit ecd1e7d
Show file tree
Hide file tree
Showing 14 changed files with 553 additions and 140 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
70 changes: 70 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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(<name>) here along with
# -Ccmake.define.<name>=<value> (pypa-build)
# or --config-settings=cmake.define.<name>=<value> (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)
32 changes: 27 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <path-to-cherab-solps>
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 as long as they have been added to Git, but if you add any Cython files you will need to add (or modify) a CMakeLists.txt file in the same directory as the new files 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 <path-to-cherab-solps>
```

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 <path-to-cherab-solps>
```

**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`.
19 changes: 0 additions & 19 deletions cherab/__init__.py

This file was deleted.

9 changes: 9 additions & 0 deletions cherab/solps/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
7 changes: 7 additions & 0 deletions cherab/solps/models/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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})
113 changes: 113 additions & 0 deletions cmake/FindCython.cmake
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit ecd1e7d

Please sign in to comment.