diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..b15a4760 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,13 @@ +repos: + - repo: local + hooks: + - id: local-prepare-commit-msg + name: 'local prepare-commit-msg' + entry: 'Utilities/Hooks/prepare-commit-msg' + language: system + stages: [prepare-commit-msg] + - id: kw-commit-msg + name: 'kw commit-msg' + entry: 'python3 Utilities/Hooks/kw-commit-msg.py' + language: system + stages: [commit-msg] diff --git a/CMakeLists.txt b/CMakeLists.txt index ea1fb577..6908545c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ -cmake_minimum_required(VERSION 3.16.3 FATAL_ERROR) +cmake_minimum_required(VERSION 3.26.6 FATAL_ERROR) +# NOTE: 3.26.6 is the first cmake vesion to support Development.SABIModule project(ITKPythonPackage CXX) @@ -16,19 +17,6 @@ message(STATUS "SuperBuild - ITKPythonPackage_WHEEL_NAME:${ITKPythonPackage_WHEE option(ITKPythonPackage_USE_TBB "Build and use oneTBB in the ITK python package" ON) -if(ITK_SOURCE_DIR) - set(TBB_DIR "${ITK_SOURCE_DIR}/../oneTBB-prefix/lib/cmake/TBB") -else() - set(TBB_DIR "${CMAKE_BINARY_DIR}/../oneTBB-prefix/lib/cmake/TBB") -endif() -set(tbb_args ) -if(ITKPythonPackage_USE_TBB) - set(tbb_args - -DModule_ITKTBB:BOOL=ON - -DTBB_DIR:PATH=${TBB_DIR} - ) -endif() - if(ITKPythonPackage_SUPERBUILD) #----------------------------------------------------------------------------- @@ -110,6 +98,18 @@ if(ITKPythonPackage_SUPERBUILD) endif() if(ITKPythonPackage_USE_TBB) + if(ITK_SOURCE_DIR) + set(TBB_DIR "${ITK_SOURCE_DIR}/../oneTBB-prefix/lib/cmake/TBB") + else() + set(TBB_DIR "${CMAKE_BINARY_DIR}/../oneTBB-prefix/lib/cmake/TBB") + endif() + set(tbb_args ) + if(ITKPythonPackage_USE_TBB) + set(tbb_args + -DModule_ITKTBB:BOOL=ON + -DTBB_DIR:PATH=${TBB_DIR} + ) + endif() set(tbb_cmake_cache_args) if(CMAKE_OSX_DEPLOYMENT_TARGET) diff --git a/README.md b/README.md index 9723888a..052a849a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # ITK Python Package -This project provides a `setup.py` script to build ITK Python binary -packages and infrastructure to build ITK external module Python -packages. +This project configures pyproject.toml files and manages environmental +variables needed to build ITK Python binary wheels on MacOS, Linux, and Windows platforms. +Scripts are available for both [ITK infrastructure](https://github.com/insightSoftwareConsortium/ITK) and +ITK external module Python packages. The Insight Toolkit (ITK) is an open-source, cross-platform system that provides developers with an extensive suite of software tools for image analysis. @@ -16,14 +17,14 @@ or at the [ITK GitHub homepage](https://github.com/insightSoftwareConsortium/ITK - [Frequently Asked Questions](#frequently-asked-questions) - [Additional Information](#additional-information) -## Using ITK Python Packages +## Using ITK Python Packages (pre-built, or locally built) ITKPythonPackage scripts can be used to produce [Python](https://www.python.org/) packages for ITK and ITK external modules. The resulting packages can be hosted on the [Python Package Index (PyPI)](https://pypi.org/) for easy distribution. -### Installation +### Installation of pre-built packages To install baseline ITK Python packages: diff --git a/Utilities/Hooks/kw-commit-msg.py b/Utilities/Hooks/kw-commit-msg.py new file mode 100755 index 00000000..0d6b0af8 --- /dev/null +++ b/Utilities/Hooks/kw-commit-msg.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# ========================================================================== +# +# Copyright NumFOCUS +# +# 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 +# +# https://www.apache.org/licenses/LICENSE-2.0.txt +# +# 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. +# +# ========================================================================== + +import os +import re +import subprocess +import sys + +from pathlib import Path + + +DEFAULT_LINE_LENGTH: int = 78 + + +def die(message, commit_msg_path): + print("commit-msg hook failure", file=sys.stderr) + print("-----------------------", file=sys.stderr) + print(message, file=sys.stderr) + print("-----------------------", file=sys.stderr) + print( + f""" +To continue editing, run the command + git commit -e -F "{commit_msg_path}" +(assuming your working directory is at the top).""", + file=sys.stderr, + ) + sys.exit(1) + + +def get_max_length(): + try: + result = subprocess.run( + ["git", "config", "--get", "hooks.commit-msg.ITKCommitSubjectMaxLength"], + capture_output=True, + text=True, + check=True, + ) + return int(result.stdout.strip()) + except (subprocess.CalledProcessError, ValueError): + return DEFAULT_LINE_LENGTH + + +def main(): + git_dir_path: Path = Path(os.environ.get("GIT_DIR", ".git")).resolve() + commit_msg_path: Path = git_dir_path / "COMMIT_MSG" + + if len(sys.argv) < 2: + die(f"Usage: {sys.argv[0]} ", commit_msg_path) + + input_file: Path = Path(sys.argv[1]) + if not input_file.exists(): + die( + f"Missing input_file {sys.argv[1]} for {sys.argv[0]} processing", + commit_msg_path, + ) + max_subjectline_length: int = get_max_length() + + original_input_file_lines: list[str] = [] + with open(input_file) as f_in: + original_input_file_lines = f_in.readlines() + + input_file_lines: list[str] = [] + for test_line in original_input_file_lines: + test_line = test_line.strip() + is_empty_line_before_subject: bool = ( + len(input_file_lines) == 0 and len(test_line) == 0 + ) + if test_line.startswith("#") or is_empty_line_before_subject: + continue + input_file_lines.append(f"{test_line}\n") + + with open(commit_msg_path, "w") as f_out: + f_out.writelines(input_file_lines) + + subject_line: str = input_file_lines[0] + + if len(subject_line) < 8: + die( + f"The first line must be at least 8 characters:\n--------\n{subject_line}\n--------", + commit_msg_path, + ) + if ( + len(subject_line) > max_subjectline_length + and not subject_line.startswith("Merge ") + and not subject_line.startswith("Revert ") + ): + die( + f"The first line may be at most {max_subjectline_length} characters:\n" + + "-" * max_subjectline_length + + f"\n{subject_line}\n" + + "-" * max_subjectline_length, + commit_msg_path, + ) + if re.match(r"^[ \t]|[ \t]$", subject_line): + die( + f"The first line may not have leading or trailing space:\n[{subject_line}]", + commit_msg_path, + ) + if not re.match( + r"^(Merge|Revert|BUG:|COMP:|DOC:|ENH:|PERF:|STYLE:|WIP:)\s", subject_line + ): + die( + f"""Start ITK commit messages with a standard prefix (and a space): + BUG: - fix for runtime crash or incorrect result + COMP: - compiler error or warning fix + DOC: - documentation change + ENH: - new functionality + PERF: - performance improvement + STYLE: - no logic impact (indentation, comments) + WIP: - Work In Progress not ready for merge +To reference GitHub issue XXXX, add "Issue #XXXX" to the commit message. +If the issue addresses an open issue, add "Closes #XXXX" to the message.""", + commit_msg_path, + ) + if re.match(r"^BUG: [0-9]+\.", subject_line): + die( + f'Do not put a "." after the bug number:\n\n {subject_line}', + commit_msg_path, + ) + del subject_line + + if len(input_file_lines) > 1: + second_line: str = input_file_lines[ + 1 + ].strip() # Remove whitespace at beginning and end + if len(second_line) == 0: + input_file_lines[1] = "\n" # Replace line with only newline + else: + die( + f'The second line of the commit message must be empty:\n"{second_line}" with length {len(second_line)}', + commit_msg_path, + ) + del second_line + + +if __name__ == "__main__": + main() diff --git a/Utilities/Hooks/prepare-commit-msg b/Utilities/Hooks/prepare-commit-msg new file mode 100755 index 00000000..276fd093 --- /dev/null +++ b/Utilities/Hooks/prepare-commit-msg @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +#========================================================================== +# +# Copyright NumFOCUS +# +# 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 +# +# https://www.apache.org/licenses/LICENSE-2.0.txt +# +# 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. +# +#========================================================================== + +egrep-q() { + egrep "$@" >/dev/null 2>/dev/null +} + +# First argument is file containing commit message. +commit_msg="$1" + +# Check for our extra instructions. +egrep-q "^# Start ITK commit messages" -- "$commit_msg" && return 0 + +# Insert our extra instructions. +commit_msg_tmp="$commit_msg.$$" +instructions='#\ +# Start ITK commit messages with a standard prefix (and a space):\ +# BUG: - fix for runtime crash or incorrect result\ +# COMP: - compiler error or warning fix\ +# DOC: - documentation change\ +# ENH: - new functionality\ +# PERF: - performance improvement\ +# STYLE: - no logic impact (indentation, comments)\ +# WIP: - Work In Progress not ready for merge\ +#\ +# The first line of the commit message should preferably be 72 characters\ +# or less; the maximum allowed is 78 characters.\ +#\ +# Follow the first line commit summary with an empty line, then a detailed\ +# description in one or more paragraphs.\ +#' && +sed '/^# On branch.*$/ a\ +'"$instructions"' +/^# Not currently on any branch.*$/ a\ +'"$instructions"' +' "$commit_msg" > "$commit_msg_tmp" && +mv "$commit_msg_tmp" "$commit_msg" diff --git a/docs/Build_ITK_Module_Python_packages.rst b/docs/Build_ITK_Module_Python_packages.rst index 6692b234..dd88d7b9 100644 --- a/docs/Build_ITK_Module_Python_packages.rst +++ b/docs/Build_ITK_Module_Python_packages.rst @@ -100,7 +100,7 @@ Congratulations! Your packages can be installed with the commands:: python -m pip install itk- where `itk-` is the short name for your module that is -specified in your setup.py file. +specified in the configured `pyproject.toml` file. Automate PyPI Package Uploads ----------------------------- @@ -118,7 +118,7 @@ and for the scope use:: itk- where `` is the short name for your module that is -specified in your setup.py file. That scope will be available if you have +specified in your configured `pyproject.toml` file. That scope will be available if you have already uploaded a first set of wheels via twine as described above; and that is the recommended approach. Otherwise, if you are creating the project at this time, choose an unlimited scope, but be careful with the created token. @@ -137,7 +137,7 @@ individual package as a best practice. :alt: GitHub PyPI token secret To push packages to PyPI, first, make sure to update the `version` for your -package in the *setup.py* file. The initial version might be `0.1.0` or +package in the *pyproject.toml* file. The initial version might be `0.1.0` or `1.0.0`. Subsequent versions should follow `semantic versioning `_. diff --git a/docs/Build_ITK_Python_packages.rst b/docs/Build_ITK_Python_packages.rst index 16157c9d..bb0d647c 100644 --- a/docs/Build_ITK_Python_packages.rst +++ b/docs/Build_ITK_Python_packages.rst @@ -21,7 +21,7 @@ automated. The following sections outline how to use the associated scripts. Linux ----- -On any linux distribution with docker and bash installed, running the script dockcross-manylinux-build-wheels.sh will create 64-bit wheels for both python 2.x and python 3.x in the dist directory. +On any linux distribution with docker and bash installed, running the script dockcross-manylinux-build-wheels.sh will create 64-bit wheels for python 3.9+ in the dist directory. For example:: @@ -94,38 +94,39 @@ files are created and deleted quickly, which can result in Access Denied errors. Windows 10 ships with an antivirus application, Windows Defender, that is enabled by default. -sdist ------ - -To create source distributions, sdist's, that will be used by pip to compile a wheel for installation if a binary wheel is not available for the current Python version or platform:: - - $ python setup.py sdist --formats=gztar,zip - [...] - - $ ls -1 dist/ - itk-4.11.0.dev20170216.tar.gz - itk-4.11.0.dev20170216.zip - -Manual builds -============= - -Building ITK Python wheels --------------------------- - -Build the ITK Python wheel with the following command:: - - python3 -m venv build-itk - ./build-itk/bin/pip install --upgrade pip - ./build-itk/bin/pip install -r requirements-dev.txt numpy - ./build-itk/bin/python setup.py bdist_wheel - -Build a wheel for a custom version of ITK ------------------------------------------ - -To build a wheel for a custom version of ITK, point to your ITK git repository -with the `ITK_SOURCE_DIR` CMake variable:: - - ./build-itk/bin/python setup.py bdist_wheel -- \ - -DITK_SOURCE_DIR:PATH=/path/to/ITKPythonPackage-core-build/ITK - -Other CMake variables can also be passed with `-D` after the double dash. +.. The below instructions are outdated and need to be re-written +.. sdist +.. ----- +.. +.. To create source distributions, sdist's, that will be used by pip to compile a wheel for installation if a binary wheel is not available for the current Python version or platform:: +.. +.. $ python setup.py sdist --formats=gztar,zip +.. [...] +.. +.. $ ls -1 dist/ +.. itk-4.11.0.dev20170216.tar.gz +.. itk-4.11.0.dev20170216.zip +.. +.. Manual builds +.. ============= +.. +.. Building ITK Python wheels +.. -------------------------- +.. +.. Build the ITK Python wheel with the following command:: +.. +.. python3 -m venv build-itk +.. ./build-itk/bin/pip install --upgrade pip +.. ./build-itk/bin/pip install -r requirements-dev.txt numpy +.. ./build-itk/bin/python setup.py bdist_wheel +.. +.. Build a wheel for a custom version of ITK +.. ----------------------------------------- +.. +.. To build a wheel for a custom version of ITK, point to your ITK git repository +.. with the `ITK_SOURCE_DIR` CMake variable:: +.. +.. ./build-itk/bin/python setup.py bdist_wheel -- \ +.. -DITK_SOURCE_DIR:PATH=/path/to/ITKPythonPackage-core-build/ITK +.. +.. Other CMake variables can also be passed with `-D` after the double dash. diff --git a/docs/code/CompareITKTypes.py b/docs/code/CompareITKTypes.py index 1e75187a..1af7468c 100644 --- a/docs/code/CompareITKTypes.py +++ b/docs/code/CompareITKTypes.py @@ -2,4 +2,4 @@ import itk -itk.F == itk.ctype('float') # True +itk.F == itk.ctype("float") # True diff --git a/docs/code/CreateBaseline.py b/docs/code/CreateBaseline.py index 47fdbb5e..0a63b189 100644 --- a/docs/code/CreateBaseline.py +++ b/docs/code/CreateBaseline.py @@ -3,10 +3,10 @@ import itk import sys -image = itk.Image[itk.UC,2].New() -image.SetRegions([10,10]) -image.SetOrigin([0,0]) -image.SetSpacing([0.5,0.5]) +image = itk.Image[itk.UC, 2].New() +image.SetRegions([10, 10]) +image.SetOrigin([0, 0]) +image.SetSpacing([0.5, 0.5]) image.Allocate() image.FillBuffer(1) itk.imwrite(image, sys.argv[1]) diff --git a/docs/code/ExplicitInstantiation.py b/docs/code/ExplicitInstantiation.py index 19da9a6f..a5bff6cb 100644 --- a/docs/code/ExplicitInstantiation.py +++ b/docs/code/ExplicitInstantiation.py @@ -7,7 +7,7 @@ # An apriori ImageType PixelType = itk.F -ImageType = itk.Image[PixelType,2] +ImageType = itk.Image[PixelType, 2] image = itk.imread(input_filename, PixelType) # An image type dynamically determined from the type on disk diff --git a/docs/code/InstantiateITKObjects.py b/docs/code/InstantiateITKObjects.py index 68689b70..b52bf266 100644 --- a/docs/code/InstantiateITKObjects.py +++ b/docs/code/InstantiateITKObjects.py @@ -3,8 +3,8 @@ import itk # Instantiate SmartPointer objects -InputType = itk.Image[itk.F,3] -OutputType = itk.Image[itk.F,3] +InputType = itk.Image[itk.F, 3] +OutputType = itk.Image[itk.F, 3] median = itk.MedianImageFilter[InputType, OutputType].New() # Instantiate non-SmartPointer objects diff --git a/docs/code/MixingITKAndNumPy.py b/docs/code/MixingITKAndNumPy.py index 4b21dfe5..f71e579b 100644 --- a/docs/code/MixingITKAndNumPy.py +++ b/docs/code/MixingITKAndNumPy.py @@ -3,15 +3,15 @@ import sys from pathlib import Path -data_dir = Path(__file__).parent.resolve() / '..' / 'data' +data_dir = Path(__file__).parent.resolve() / ".." / "data" input_image_filename = sys.argv[1] temp_dir = Path(input_image_filename).parent output_image_filename = sys.argv[2] -input_mesh_filename = data_dir / 'cow.vtk' -output_mesh_filename = temp_dir / 'cow.vtk' -input_transform_filename = data_dir / 'rigid.tfm' -output_transform_filename = temp_dir / 'rigid.tfm' +input_mesh_filename = data_dir / "cow.vtk" +output_mesh_filename = temp_dir / "cow.vtk" +input_transform_filename = data_dir / "rigid.tfm" +output_transform_filename = temp_dir / "rigid.tfm" import itk import numpy as np @@ -116,7 +116,7 @@ # VNL matrix from np.ndarray -arr = np.zeros([3,3], np.uint8) +arr = np.zeros([3, 3], np.uint8) matrix = itk.vnl_matrix_from_array(arr) # Array from VNL matrix diff --git a/docs/code/test.py b/docs/code/test.py index c3d692cd..d1e46d45 100644 --- a/docs/code/test.py +++ b/docs/code/test.py @@ -6,10 +6,12 @@ import tempfile import shutil + def add_test(cmd): cmd.insert(0, sys.executable) subprocess.check_call(cmd) + def cleanup(files): for f in files: if os.path.isdir(f): @@ -17,6 +19,7 @@ def cleanup(files): else: os.remove(f) + # Create temporary folder to save output images temp_folder = tempfile.mkdtemp() # Change current working directory to find scripts diff --git a/docs/conf.py b/docs/conf.py index 634b4c4d..b642ec55 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,5 @@ import os + # -*- coding: utf-8 -*- # # ITKPythonPackage documentation build configuration file, created by @@ -34,30 +35,30 @@ extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'ITKPythonPackage' -copyright = u'2017, Jean-Christophe Fillion-Robin and Matt McCormick' -author = u'Jean-Christophe Fillion-Robin and Matt McCormick' +project = "ITKPythonPackage" +copyright = "2017, Jean-Christophe Fillion-Robin and Matt McCormick" +author = "Jean-Christophe Fillion-Robin and Matt McCormick" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = u'' +version = "" # The full version, including alpha/beta/rc tags. -release = u'' +release = "" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -69,10 +70,10 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -83,7 +84,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -94,13 +95,13 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'ITKPythonPackagedoc' +htmlhelp_basename = "ITKPythonPackagedoc" # -- Options for LaTeX output --------------------------------------------- @@ -109,15 +110,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -127,8 +125,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'ITKPythonPackage.tex', u'ITKPythonPackage Documentation', - u'Jean-Christophe Fillion-Robin and Matt McCormick', 'manual'), + ( + master_doc, + "ITKPythonPackage.tex", + "ITKPythonPackage Documentation", + "Jean-Christophe Fillion-Robin and Matt McCormick", + "manual", + ), ] @@ -137,8 +140,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'itkpythonpackage', u'ITKPythonPackage Documentation', - [author], 1) + (master_doc, "itkpythonpackage", "ITKPythonPackage Documentation", [author], 1) ] @@ -148,19 +150,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'ITKPythonPackage', u'ITKPythonPackage Documentation', - author, 'ITKPythonPackage', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "ITKPythonPackage", + "ITKPythonPackage Documentation", + author, + "ITKPythonPackage", + "One line description of project.", + "Miscellaneous", + ), ] # -- Read The Docs ----------------------------------------------------- # on_rtd is whether we are on readthedocs.io -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + html_theme = "sphinx_rtd_theme" + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] diff --git a/docs/index.rst b/docs/index.rst index ab4fa0d4..32fc2fbc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,12 +1,15 @@ Welcome to ITKPythonPackage's documentation! ============================================ -This project provides a ``setup.py`` script to build ITK Python wheels and -infrastructure to build ITK external module Python wheels. +This project provides a script to generate `pyproject.toml` files used to build +ITK Python wheels and infrastructure to build ITK external module Python +wheels. -`ITK `_ is an open-source, cross-platform system that provides developers with an extensive suite of software tools for image analysis. +`ITK `_ is an open-source, cross-platform system that +provides developers with an extensive suite of software tools for image +analysis. -To install the stable ITK Python package:: +To install the pre-built stable ITK Python package:: $ pip install itk diff --git a/itkVersion.py b/itkVersion.py index fb5d6ba1..acf9bffb 100644 --- a/itkVersion.py +++ b/itkVersion.py @@ -1,4 +1,7 @@ -VERSION = '6.0b2' +from packaging.version import Version + +# Version needs to be python PEP 440 compliant (no leading v) +VERSION = '6.0b2'.removeprefix("v") def get_versions(): """Returns versions for the ITK Python package. @@ -19,6 +22,9 @@ def get_versions(): # '4.11.0.dev20170208+139.g922f2d9' get_versions()['package-version'] """ + + Version(VERSION) # Raise InvalidVersion exception if not PEP 440 compliant + versions = {} versions['version'] = VERSION versions['package-version'] = VERSION.split('+')[0] diff --git a/scripts/dockcross-manylinux-build-module-wheels.sh b/scripts/dockcross-manylinux-build-module-wheels.sh index 4775720f..392a2190 100755 --- a/scripts/dockcross-manylinux-build-module-wheels.sh +++ b/scripts/dockcross-manylinux-build-module-wheels.sh @@ -15,12 +15,12 @@ # =========================================== # ENVIRONMENT VARIABLES # -# These variables are set with the `export` bash command before calling the script.# +# These variables are set with the `export` bash command before calling the script.# # For example, # # export MANYLINUX_VERSION="_2_28" # scripts/dockcross-manylinux-build-module-wheels.sh cp310 -# +# # `LD_LIBRARY_PATH`: Shared libraries to be included in the resulting wheel. # For instance, `export LD_LIBRARY_PATH="/path/to/OpenCL.so:/path/to/OpenCL.so.1.2"` # diff --git a/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh b/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh index 83572267..37166edd 100755 --- a/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh +++ b/scripts/dockcross-manylinux-download-cache-and-build-module-wheels.sh @@ -18,7 +18,6 @@ # These variables are set with the `export` bash command before calling the script. # For example, # -# export ITK_PACKAGE_VERSION="v5.4.0" # scripts/dockcross-manylinux-build-module-wheels.sh cp310 # # `ITKPYTHONPACKAGE_ORG`: Github organization for fetching ITKPythonPackage build scripts. diff --git a/scripts/dockcross-manylinux-download-cache.sh b/scripts/dockcross-manylinux-download-cache.sh index 79a69451..10d17e2d 100755 --- a/scripts/dockcross-manylinux-download-cache.sh +++ b/scripts/dockcross-manylinux-download-cache.sh @@ -1,7 +1,7 @@ #!/bin/bash ######################################################################## -# Download ITK build cache and other requirements to prepare for +# Download ITK build cache and other requirements to prepare for # generating Linux Python wheels of the given ITK module. # # Most ITK modules will download and call `dockcross-manylinux-download-cache-and-build-module-wheels.sh` which will @@ -125,7 +125,7 @@ if [[ -n ${ITKPYTHONPACKAGE_TAG} ]]; then git checkout "${ITKPYTHONPACKAGE_TAG}" git status popd - + rm -rf ITKPythonPackage/scripts/ cp -r IPP-tmp/scripts ITKPythonPackage/ cp IPP-tmp/requirements-dev.txt ITKPythonPackage/ diff --git a/scripts/dockcross-manylinux-set-vars.sh b/scripts/dockcross-manylinux-set-vars.sh index 14081bca..f39ce7ab 100755 --- a/scripts/dockcross-manylinux-set-vars.sh +++ b/scripts/dockcross-manylinux-set-vars.sh @@ -1,7 +1,7 @@ #!/bin/bash ######################################################################## -# Run this script to set common enviroment variables used in building the +# Run this script to set common enviroment variables used in building the # ITK Python wheel packages for Linux. # # ENVIRONMENT VARIABLES diff --git a/scripts/internal/manylinux-build-module-wheels.sh b/scripts/internal/manylinux-build-module-wheels.sh index b6237fd8..37ab0382 100755 --- a/scripts/internal/manylinux-build-module-wheels.sh +++ b/scripts/internal/manylinux-build-module-wheels.sh @@ -7,9 +7,9 @@ # # /tmp/dockcross-manylinux-x64 manylinux-build-module-wheels.sh cp310 # -# Shared library dependencies can be included in the wheel by mounting them to /usr/lib64 or /usr/local/lib64 +# Shared library dependencies can be included in the wheel by mounting them to /usr/lib64 or /usr/local/lib64 # before running this script. -# +# # For example, # # DOCKER_ARGS="-v /path/to/lib.so:/usr/local/lib64/lib.so" @@ -73,12 +73,6 @@ source "${script_dir}/manylinux-build-common.sh" sudo ldconfig export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/work/oneTBB-prefix/lib:/usr/lib:/usr/lib64:/usr/local/lib:/usr/local/lib64 -if test -e setup.py; then - use_skbuild_classic=true -else - use_skbuild_classic=false -fi - # Compile wheels re-using standalone project and archive cache for PYBIN in "${PYBINARIES[@]}"; do Python3_EXECUTABLE=${PYBIN}/python @@ -88,11 +82,6 @@ for PYBIN in "${PYBINARIES[@]}"; do echo "Python3_EXECUTABLE:${Python3_EXECUTABLE}" echo "Python3_INCLUDE_DIR:${Python3_INCLUDE_DIR}" - if $use_skbuild_classic; then - # So older remote modules with setup.py continue to work - sudo ${Python3_EXECUTABLE} -m pip install --upgrade scikit-build - fi - if [[ -e /work/requirements-dev.txt ]]; then sudo ${PYBIN}/pip install --upgrade -r /work/requirements-dev.txt fi @@ -114,42 +103,28 @@ for PYBIN in "${PYBINARIES[@]}"; do echo 'ITK source tree not available!' 1>&2 exit 1 fi - if $use_skbuild_classic; then - ${PYBIN}/python setup.py clean - ${PYBIN}/python setup.py bdist_wheel --build-type Release -G Ninja -- \ - -DITK_DIR:PATH=${itk_build_dir} \ - -DWRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel \ - -DCMAKE_CXX_COMPILER_TARGET:STRING=$(uname -m)-linux-gnu \ - -DCMAKE_INSTALL_LIBDIR:STRING=lib \ - -DBUILD_TESTING:BOOL=OFF \ - -DPython3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - -DPython3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - ${CMAKE_OPTIONS} \ - || exit 1 - else - py_minor=$(echo $version | cut -d '-' -f 1 | cut -d '3' -f 2) - wheel_py_api="" - if test $py_minor -ge 11; then - wheel_py_api=cp3$py_minor - fi - ${PYBIN}/python -m build \ - --verbose \ - --wheel \ - --outdir dist \ - --no-isolation \ - --skip-dependency-check \ - --config-setting=cmake.define.ITK_DIR:PATH=${itk_build_dir} \ - --config-setting=cmake.define.WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel \ - --config-setting=cmake.define.CMAKE_CXX_COMPILER_TARGET:STRING=$(uname -m)-linux-gnu \ - --config-setting=cmake.define.CMAKE_INSTALL_LIBDIR:STRING=lib \ - --config-setting=cmake.define.PY_SITE_PACKAGES_PATH:PATH="." \ - --config-setting=wheel.py-api=$wheel_py_api \ - --config-setting=cmake.define.BUILD_TESTING:BOOL=OFF \ - --config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - --config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - ${CMAKE_OPTIONS//'-D'/'--config-setting=cmake.define.'} \ - || exit 1 + py_minor=$(echo $version | cut -d '-' -f 1 | cut -d '3' -f 2) + wheel_py_api="" + if test $py_minor -ge 11; then + wheel_py_api=cp3$py_minor fi + ${PYBIN}/python -m build \ + --verbose \ + --wheel \ + --outdir dist \ + --no-isolation \ + --skip-dependency-check \ + --config-setting=cmake.define.ITK_DIR:PATH=${itk_build_dir} \ + --config-setting=cmake.define.WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel \ + --config-setting=cmake.define.CMAKE_CXX_COMPILER_TARGET:STRING=$(uname -m)-linux-gnu \ + --config-setting=cmake.define.CMAKE_INSTALL_LIBDIR:STRING=lib \ + --config-setting=cmake.define.PY_SITE_PACKAGES_PATH:PATH="." \ + --config-setting=wheel.py-api=$wheel_py_api \ + --config-setting=cmake.define.BUILD_TESTING:BOOL=OFF \ + --config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ + --config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ + ${CMAKE_OPTIONS//'-D'/'--config-setting=cmake.define.'} \ + || exit 1 done # Convert list of excluded libs in --exclude_libs to auditwheel --exclude options @@ -159,10 +134,7 @@ fi sudo ${Python3_EXECUTABLE} -m pip install auditwheel for whl in dist/*linux*$(uname -m).whl; do - auditwheel repair ${whl} -w /work/dist/ ${AUDITWHEEL_EXCLUDE_ARGS} - if $use_skbuild_classic; then - rm ${whl} - fi + auditwheel repair ${whl} -w /work/dist/ ${AUDITWHEEL_EXCLUDE_ARGS} done if compgen -G "dist/itk*-linux*.whl" > /dev/null; then diff --git a/scripts/internal/manylinux-build-wheels.sh b/scripts/internal/manylinux-build-wheels.sh index c85dda6d..2ebacbec 100755 --- a/scripts/internal/manylinux-build-wheels.sh +++ b/scripts/internal/manylinux-build-wheels.sh @@ -7,9 +7,9 @@ # # /tmp/dockcross-manylinux-x64 manylinux-build-wheels.sh cp310 # -# Shared library dependencies can be included wheels by mounting them to /usr/lib64 or /usr/local/lib64 +# Shared library dependencies can be included wheels by mounting them to /usr/lib64 or /usr/local/lib64 # before running this script. -# +# # For example, # # DOCKER_ARGS="-v /path/to/lib.so:/usr/local/lib64/lib.so" diff --git a/scripts/internal/wheel_builder_utils.py b/scripts/internal/wheel_builder_utils.py index bbfac984..422d4f51 100644 --- a/scripts/internal/wheel_builder_utils.py +++ b/scripts/internal/wheel_builder_utils.py @@ -1,4 +1,3 @@ - """This module provides convenient function facilitating scripting. These functions have been copied from scikit-build project. @@ -29,8 +28,7 @@ def mkdir_p(path): @contextmanager def push_env(**kwargs): - """This context manager allow to set/unset environment variables. - """ + """This context manager allow to set/unset environment variables.""" saved_env = dict(os.environ) for var, value in kwargs.items(): if value is not None: @@ -39,7 +37,7 @@ def push_env(**kwargs): del os.environ[var] yield os.environ.clear() - for (saved_var, saved_value) in saved_env.items(): + for saved_var, saved_value in saved_env.items(): os.environ[saved_var] = saved_value @@ -62,12 +60,13 @@ def __call__(self, func): def inner(*args, **kwds): # pylint:disable=missing-docstring with self: return func(*args, **kwds) + return inner class push_dir(ContextDecorator): - """Context manager to change current directory. - """ + """Context manager to change current directory.""" + def __init__(self, directory=None, make_directory=False): """ :param directory: @@ -81,7 +80,8 @@ def __init__(self, directory=None, make_directory=False): self.make_directory = None self.old_cwd = None super(push_dir, self).__init__( - directory=directory, make_directory=make_directory) + directory=directory, make_directory=make_directory + ) def __enter__(self): self.old_cwd = os.getcwd() diff --git a/scripts/macpython-build-module-wheels.sh b/scripts/macpython-build-module-wheels.sh index dd51bf44..3c583d4d 100755 --- a/scripts/macpython-build-module-wheels.sh +++ b/scripts/macpython-build-module-wheels.sh @@ -62,13 +62,6 @@ VENVS=() source "${script_dir}/macpython-build-common.sh" # ----------------------------------------------------------------------- -if test -e setup.py; then - use_skbuild_classic=true -else - use_skbuild_classic=false -fi - - VENV="${VENVS[0]}" Python3_EXECUTABLE=${VENV}/bin/python3 dot_clean ${VENV} @@ -89,11 +82,6 @@ for VENV in "${VENVS[@]}"; do echo "Python3_EXECUTABLE:${Python3_EXECUTABLE}" echo "Python3_INCLUDE_DIR:${Python3_INCLUDE_DIR}" - if $use_skbuild_classic; then - # So older remote modules with setup.py continue to work - ${Python3_EXECUTABLE} -m pip install --upgrade scikit-build - fi - if [[ $(arch) == "arm64" ]]; then plat_name="macosx-15.0-arm64" osx_target="15.0" @@ -114,45 +102,30 @@ for VENV in "${VENVS[@]}"; do ${Python3_EXECUTABLE} -m pip install --upgrade -r $PWD/requirements-dev.txt fi itk_build_path="${build_path}" - if $use_skbuild_classic; then - ${Python3_EXECUTABLE} setup.py bdist_wheel --build-type Release --plat-name ${plat_name} -G Ninja -- \ - -DCMAKE_MAKE_PROGRAM:FILEPATH=${NINJA_EXECUTABLE} \ - -DITK_DIR:PATH=${itk_build_path} \ - -DCMAKE_INSTALL_LIBDIR:STRING=lib \ - -DWRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel \ - -DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=${osx_target} \ - -DCMAKE_OSX_ARCHITECTURES:STRING=${osx_arch} \ - -DBUILD_TESTING:BOOL=OFF \ - -DPython3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - -DPython3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - ${CMAKE_OPTIONS} \ - || exit 1 - else - py_minor=$(echo $py_mm | cut -d '.' -f 2) - wheel_py_api="" - if test $py_minor -ge 11; then - wheel_py_api=cp3$py_minor - fi - ${Python3_EXECUTABLE} -m build \ - --verbose \ - --wheel \ - --outdir dist \ - --no-isolation \ - --skip-dependency-check \ - --config-setting=cmake.define.CMAKE_MAKE_PROGRAM:FILEPATH=${NINJA_EXECUTABLE} \ - --config-setting=cmake.define.ITK_DIR:PATH=${itk_build_path} \ - --config-setting=cmake.define.CMAKE_INSTALL_LIBDIR:STRING=lib \ - --config-setting=cmake.define.WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel \ - --config-setting=cmake.define.CMAKE_OSX_DEPLOYMENT_TARGET:STRING=${osx_target} \ - --config-setting=cmake.define.CMAKE_OSX_ARCHITECTURES:STRING=${osx_arch} \ - --config-setting=cmake.define.PY_SITE_PACKAGES_PATH:PATH="." \ - --config-setting=wheel.py-api=$wheel_py_api \ - --config-setting=cmake.define.BUILD_TESTING:BOOL=OFF \ - --config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ - --config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ - ${CMAKE_OPTIONS//'-D'/'--config-setting=cmake.define.'} \ - || exit 1 + py_minor=$(echo $py_mm | cut -d '.' -f 2) + wheel_py_api="" + if test $py_minor -ge 11; then + wheel_py_api=cp3$py_minor fi + ${Python3_EXECUTABLE} -m build \ + --verbose \ + --wheel \ + --outdir dist \ + --no-isolation \ + --skip-dependency-check \ + --config-setting=cmake.define.CMAKE_MAKE_PROGRAM:FILEPATH=${NINJA_EXECUTABLE} \ + --config-setting=cmake.define.ITK_DIR:PATH=${itk_build_path} \ + --config-setting=cmake.define.CMAKE_INSTALL_LIBDIR:STRING=lib \ + --config-setting=cmake.define.WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel \ + --config-setting=cmake.define.CMAKE_OSX_DEPLOYMENT_TARGET:STRING=${osx_target} \ + --config-setting=cmake.define.CMAKE_OSX_ARCHITECTURES:STRING=${osx_arch} \ + --config-setting=cmake.define.PY_SITE_PACKAGES_PATH:PATH="." \ + --config-setting=wheel.py-api=$wheel_py_api \ + --config-setting=cmake.define.BUILD_TESTING:BOOL=OFF \ + --config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} \ + --config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} \ + ${CMAKE_OPTIONS//'-D'/'--config-setting=cmake.define.'} \ + || exit 1 done for wheel in $PWD/dist/*.whl; do diff --git a/scripts/macpython-delocate-wheels.sh b/scripts/macpython-delocate-wheels.sh deleted file mode 100755 index 3a16c217..00000000 --- a/scripts/macpython-delocate-wheels.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env bash - -# Run this script to build the ITK Python wheel packages for macOS. -# -# Versions can be restricted by passing them in as arguments to the script -# For example, -# -# scripts/macpython-build-wheels.sh 3.10 -# -# Shared libraries can be included in the wheel by exporting them to DYLD_LIBRARY_PATH before -# running this script. -# -# For example, -# -# export DYLD_LIBRARY_PATH="/path/to/libs" -# scripts/macpython-build-module-wheels.sh 3.10 -# - -# ----------------------------------------------------------------------- -# These variables are set in common script: -# -MACPYTHON_PY_PREFIX="" -PYBINARIES="" -SCRIPT_DIR="" - -script_dir=$(cd $(dirname $0) || exit 1; pwd) -source "${script_dir}/macpython-build-common.sh" - -# ----------------------------------------------------------------------- -# Remove previous virtualenv's -rm -rf ${SCRIPT_DIR}/../venvs -# Create virtualenv's -VENVS=() -mkdir -p ${SCRIPT_DIR}/../venvs -for PYBIN in "${PYBINARIES[@]}"; do - if [[ $(basename $PYBIN) = "Current" ]]; then - continue - fi - py_mm=$(basename ${PYBIN}) - VENV=${SCRIPT_DIR}/../venvs/${py_mm} - VIRTUALENV_EXECUTABLE="${PYBIN}/bin/python3 -m venv" - ${VIRTUALENV_EXECUTABLE} ${VENV} - VENVS+=(${VENV}) -done - -VENV="${VENVS[0]}" -Python3_EXECUTABLE=${VENV}/bin/python3 -${Python3_EXECUTABLE} -m pip install --no-cache delocate -DELOCATE_LISTDEPS=${VENV}/bin/delocate-listdeps -DELOCATE_WHEEL=${VENV}/bin/delocate-wheel -DELOCATE_PATCH=${VENV}/bin/delocate-patch - -build_type="Release" - -if [[ $(arch) == "arm64" ]]; then - osx_target="15.0" - osx_arch="arm64" - use_tbb="OFF" -else - osx_target="15.0" - osx_arch="x86_64" - use_tbb="OFF" -fi - -for wheel in dist/*.whl; do - echo "Delocating $wheel" - #if [[ $wheel = *itk_core* ]]; then - ${DELOCATE_LISTDEPS} $wheel # lists library dependencies - ${DELOCATE_WHEEL} $wheel # copies library dependencies into wheel - #else - #${DELOCATE_PATCH} $wheel ${SCRIPT_DIR}/delocate.package.apply.patch # workaround for delocate's need for a package - #${DELOCATE_LISTDEPS} $wheel # lists library dependencies - #${DELOCATE_WHEEL} $wheel # copies library dependencies into wheel - #${DELOCATE_PATCH} $wheel ${SCRIPT_DIR}/delocate.package.revert.patch # workaround for delocate's need for a package - #fi -done - -# Install packages and test -# numpy wheel not currently available for the M1 -# https://github.com/numpy/numpy/issues/17807 -if [[ $(arch) != "arm64" ]]; then - for VENV in "${VENVS[@]}"; do - ${VENV}/bin/pip install numpy - ${VENV}/bin/pip install itk --no-cache-dir --no-index -f ${SCRIPT_DIR}/../dist - (cd $HOME && ${VENV}/bin/python -c 'import itk;') - (cd $HOME && ${VENV}/bin/python -c 'import itk; image = itk.Image[itk.UC, 2].New()') - (cd $HOME && ${VENV}/bin/python -c 'import itkConfig; itkConfig.LazyLoading = False; import itk;') - (cd $HOME && ${VENV}/bin/python ${SCRIPT_DIR}/../docs/code/test.py ) - done -fi diff --git a/scripts/macpython-download-cache-and-build-module-wheels.sh b/scripts/macpython-download-cache-and-build-module-wheels.sh index 788918a7..297e827d 100755 --- a/scripts/macpython-download-cache-and-build-module-wheels.sh +++ b/scripts/macpython-download-cache-and-build-module-wheels.sh @@ -10,8 +10,9 @@ # Versions can be restricted by passing them in as arguments to the script. # For example, # -# scripts/macpython-build-module-wheels.sh 3.10 3.11 -# Shared libraries can be included in the wheel by exporting them to DYLD_LIBRARY_PATH before +# scripts/macpython-download-cache-and-build-module-wheels.sh 3.9 3.11 +# +# Shared libraries can be included in the wheel by setting DYLD_LIBRARY_PATH before # running this script. # # =========================================== @@ -21,7 +22,6 @@ # For example, # # export DYLD_LIBRARY_PATH="/path/to/libs" -# scripts/macpython-build-module-wheels.sh 3.10 3.11 # # `ITK_PACKAGE_VERSION`: ITKPythonBuilds archive tag to use for ITK build artifacts. # See https://github.com/InsightSoftwareConsortium/ITKPythonBuilds for available tags. @@ -79,7 +79,7 @@ if [[ -n ${ITKPYTHONPACKAGE_TAG} ]]; then git checkout "${ITKPYTHONPACKAGE_TAG}" git status popd - + rm -rf ITKPythonPackage/scripts/ cp -r IPP-tmp/scripts ITKPythonPackage/ rm -rf IPP-tmp/ diff --git a/scripts/pyproject.toml.in b/scripts/pyproject.toml.in index 9dbab849..1ed3b17b 100644 --- a/scripts/pyproject.toml.in +++ b/scripts/pyproject.toml.in @@ -39,7 +39,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Medical Science Apps.", "Topic :: Software Development :: Libraries", ] -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ @PYPROJECT_DEPENDENCIES@ ] diff --git a/scripts/pyproject_configure.py b/scripts/pyproject_configure.py index 65cd37b1..55ba9c2c 100755 --- a/scripts/pyproject_configure.py +++ b/scripts/pyproject_configure.py @@ -28,27 +28,19 @@ import os import re import sys -import textwrap sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) PARAMETER_OPTION_DEFAULTS = { - 'indent': 0, - 'newline_if_set': False, - 'newline_indent': 0, - 'remove_line_if_empty': False + "indent": 0, + "newline_if_set": False, + "newline_indent": 0, + "remove_line_if_empty": False, } PARAMETER_OPTIONS = { - 'PYPROJECT_PY_MODULES': { - 'indent': 8, - 'newline_if_set': True, - 'newline_indent': 4 - }, - 'PYPROJECT_DEPENDENCIES': { - 'indent': 8, - 'remove_line_if_empty': True - }, + "PYPROJECT_PY_MODULES": {"indent": 8, "newline_if_set": True, "newline_indent": 4}, + "PYPROJECT_DEPENDENCIES": {"indent": 8, "remove_line_if_empty": True}, } @@ -75,6 +67,7 @@ def indent(text, prefix, predicate=None): Copied from textwrap.py available in python 3 (cpython/cpython@a2d2bef) """ if predicate is None: + def predicate(line): return line.strip() @@ -82,7 +75,7 @@ def prefixed_lines(): for line in text.splitlines(True): yield (prefix + line if predicate(line) else line) - return ''.join(prefixed_lines()) + return "".join(prefixed_lines()) def list_to_str(list_, newline=True): @@ -97,145 +90,166 @@ def configure(template_file, parameters, output_file): `parameters`. """ updated_lines = [] - with open(template_file, 'r') as file_: + with open(template_file, "r") as file_: lines = file_.readlines() for line in lines: append = True for key in parameters.keys(): value = parameters[key].strip() - if (key in line - and not value - and parameter_option(key, 'remove_line_if_empty')): + if ( + key in line + and not value + and parameter_option(key, "remove_line_if_empty") + ): append = False break - block_indent = " " * parameter_option(key, 'indent') + block_indent = " " * parameter_option(key, "indent") value = indent(value, block_indent) - newline_indent = " " * parameter_option(key, 'newline_indent') - if value.strip() and parameter_option(key, 'newline_if_set'): + newline_indent = " " * parameter_option(key, "newline_indent") + if value.strip() and parameter_option(key, "newline_if_set"): value = "\n%s\n%s" % (value, newline_indent) line = line.replace("@%s@" % key, value) if append: updated_lines.append(line) - with open(output_file, 'w') as file_: + with open(output_file, "w") as file_: file_.writelines(updated_lines) def from_group_to_wheel(group): return "itk-%s" % group.lower() -def update_wheel_setup_py_parameters(): + +def update_wheel_pyproject_toml_parameters(): global PYPROJECT_PY_PARAMETERS for wheel_name in get_wheel_names(): params = dict(ITK_PYPROJECT_PY_PARAMETERS) # generator - params['PYPROJECT_GENERATOR'] = "python %s '%s'" % (SCRIPT_NAME, wheel_name) + params["PYPROJECT_GENERATOR"] = "python %s '%s'" % (SCRIPT_NAME, wheel_name) # name - if wheel_name == 'itk-meta': - params['PYPROJECT_NAME'] = 'itk' - params['PYPROJECT_PLATLIB'] = r'false' + if wheel_name == "itk-meta": + params["PYPROJECT_NAME"] = "itk" + params["PYPROJECT_PLATLIB"] = r"false" else: - params['PYPROJECT_NAME'] = wheel_name - + params["PYPROJECT_NAME"] = wheel_name # long description - if wheel_name == 'itk-core': - params['PYPROJECT_LONG_DESCRIPTION'] += (r'\n\n' - 'This package contain the toolkit framework used' - ' by other modules. There are common base classes for data objects and process' - ' objects, basic data structures such as Image, Mesh, QuadEdgeMesh, and' - ' SpatialObjects, and common functionality for operations such as finite' - ' differences, image adaptors, or image transforms.') - elif wheel_name == 'itk-filtering': - params['PYPROJECT_LONG_DESCRIPTION'] += (r'\n\n' - 'These packages contains filters that modify data' - ' in the ITK pipeline framework. These filters take an input object, such as an' - ' Image, and modify it to create an output. Filters can be chained together to' - ' create a processing pipeline.') - elif wheel_name == 'itk-io': - params['PYPROJECT_LONG_DESCRIPTION'] += (r'\n\n' - 'This package contains classes for reading and writing images and other data objects.') - elif wheel_name == 'itk-numerics': - params['PYPROJECT_LONG_DESCRIPTION'] += (r'\n\n' - 'This package contains basic numerical tools and algorithms that' - ' have general applications outside of imaging.') - elif wheel_name == 'itk-registration': - params['PYPROJECT_LONG_DESCRIPTION'] += (r'\n\n' - 'This package addresses the registration problem: ' - ' find the spatial transformation between two images. This is a high' - ' level package that makes use of many lower level packages.') - elif wheel_name == 'itk-segmentation': - params['PYPROJECT_LONG_DESCRIPTION'] += (r'\n\n' - 'This package addresses the segmentation problem: ' - ' partition the image into classified regions (labels). This is a high' - ' level package that makes use of many lower level packages.') + if wheel_name == "itk-core": + params["PYPROJECT_LONG_DESCRIPTION"] += ( + r"\n\n" + "This package contain the toolkit framework used" + " by other modules. There are common base classes for data objects and process" + " objects, basic data structures such as Image, Mesh, QuadEdgeMesh, and" + " SpatialObjects, and common functionality for operations such as finite" + " differences, image adaptors, or image transforms." + ) + elif wheel_name == "itk-filtering": + params["PYPROJECT_LONG_DESCRIPTION"] += ( + r"\n\n" + "These packages contains filters that modify data" + " in the ITK pipeline framework. These filters take an input object, such as an" + " Image, and modify it to create an output. Filters can be chained together to" + " create a processing pipeline." + ) + elif wheel_name == "itk-io": + params["PYPROJECT_LONG_DESCRIPTION"] += ( + r"\n\n" + "This package contains classes for reading and writing images and other data objects." + ) + elif wheel_name == "itk-numerics": + params["PYPROJECT_LONG_DESCRIPTION"] += ( + r"\n\n" + "This package contains basic numerical tools and algorithms that" + " have general applications outside of imaging." + ) + elif wheel_name == "itk-registration": + params["PYPROJECT_LONG_DESCRIPTION"] += ( + r"\n\n" + "This package addresses the registration problem: " + " find the spatial transformation between two images. This is a high" + " level package that makes use of many lower level packages." + ) + elif wheel_name == "itk-segmentation": + params["PYPROJECT_LONG_DESCRIPTION"] += ( + r"\n\n" + "This package addresses the segmentation problem: " + " partition the image into classified regions (labels). This is a high" + " level package that makes use of many lower level packages." + ) # cmake_args - params['PYPROJECT_CMAKE_ARGS'] = list_to_str([ - '-DITK_WRAP_unsigned_short:BOOL=ON', - '-DITK_WRAP_double:BOOL=ON', - '-DITK_WRAP_complex_double:BOOL=ON', - '-DITK_WRAP_IMAGE_DIMS:STRING=2;3;4', - '-DITK_WRAP_DOC:BOOL=ON', - '-DITKPythonPackage_WHEEL_NAME:STRING=%s' % wheel_name - ], True) + params["PYPROJECT_CMAKE_ARGS"] = list_to_str( + [ + "-DITK_WRAP_unsigned_short:BOOL=ON", + "-DITK_WRAP_double:BOOL=ON", + "-DITK_WRAP_complex_double:BOOL=ON", + "-DITK_WRAP_IMAGE_DIMS:STRING=2;3;4", + "-DITK_WRAP_DOC:BOOL=ON", + "-DITKPythonPackage_WHEEL_NAME:STRING=%s" % wheel_name, + ], + True, + ) # install_requires wheel_depends = get_wheel_dependencies()[wheel_name] # py_modules - if wheel_name != 'itk-core': - params['PYPROJECT_PY_MODULES'] = r'' + if wheel_name != "itk-core": + params["PYPROJECT_PY_MODULES"] = r"" else: - wheel_depends.append('numpy') + wheel_depends.append("numpy") - params['PYPROJECT_DEPENDENCIES'] = list_to_str(wheel_depends) + params["PYPROJECT_DEPENDENCIES"] = list_to_str(wheel_depends) PYPROJECT_PY_PARAMETERS[wheel_name] = params def get_wheel_names(): - with open(os.path.join(SCRIPT_DIR, 'WHEEL_NAMES.txt'), 'r') as _file: + with open(os.path.join(SCRIPT_DIR, "WHEEL_NAMES.txt"), "r") as _file: return [wheel_name.strip() for wheel_name in _file.readlines()] + def get_version(): from itkVersion import get_versions - version = get_versions()['package-version'] + + version = get_versions()["package-version"] return version + def get_py_api(): import sys + if sys.version_info < (3, 11): return "" else: return "cp" + str(sys.version_info.major) + str(sys.version_info.minor) + def get_wheel_dependencies(): - """Return a dictionary of ITK wheel dependencies. - """ + """Return a dictionary of ITK wheel dependencies.""" all_depends = {} - regex_group_depends = \ - r'set\s*\(\s*ITK\_GROUP\_([a-zA-Z0-9\_\-]+)\_DEPENDS\s*([a-zA-Z0-9\_\-\s]*)\s*' # noqa: E501 + regex_group_depends = r"set\s*\(\s*ITK\_GROUP\_([a-zA-Z0-9\_\-]+)\_DEPENDS\s*([a-zA-Z0-9\_\-\s]*)\s*" # noqa: E501 pattern = re.compile(regex_group_depends) version = get_version() - with open(os.path.join(SCRIPT_DIR, "..", "CMakeLists.txt"), 'r') as file_: + with open(os.path.join(SCRIPT_DIR, "..", "CMakeLists.txt"), "r") as file_: for line in file_.readlines(): match = re.search(pattern, line) if not match: continue wheel = from_group_to_wheel(match.group(1)) _wheel_depends = [ - from_group_to_wheel(group) + '==' + version + from_group_to_wheel(group) + "==" + version for group in match.group(2).split() - ] + ] all_depends[wheel] = _wheel_depends - all_depends['itk-meta'] = [ - wheel_name + '==' + version for wheel_name in get_wheel_names() - if wheel_name != 'itk-meta' - ] - all_depends['itk-meta'].append('numpy') + all_depends["itk-meta"] = [ + wheel_name + "==" + version + for wheel_name in get_wheel_names() + if wheel_name != "itk-meta" + ] + all_depends["itk-meta"].append("numpy") return all_depends @@ -243,40 +257,40 @@ def get_wheel_dependencies(): SCRIPT_NAME = os.path.basename(__file__) ITK_PYPROJECT_PY_PARAMETERS = { - 'PYPROJECT_GENERATOR': "python %s '%s'" % (SCRIPT_NAME, 'itk'), - 'PYPROJECT_NAME': r'itk', - 'PYPROJECT_VERSION': get_version(), - 'PYPROJECT_CMAKE_ARGS': r'', - 'PYPROJECT_PY_API': get_py_api(), - 'PYPROJECT_PLATLIB': r'true', - 'PYPROJECT_PY_MODULES': list_to_str([ - 'itkBase', - 'itkConfig', - 'itkExtras', - 'itkHelpers', - 'itkLazy', - 'itkTemplate', - 'itkTypes', - 'itkVersion', - 'itkBuildOptions' - ]), - 'PYPROJECT_DOWNLOAD_URL': r'https://github.com/InsightSoftwareConsortium/ITK/releases', - 'PYPROJECT_DESCRIPTION': r'ITK is an open-source toolkit for multidimensional image analysis', # noqa: E501 - 'PYPROJECT_LONG_DESCRIPTION': r'ITK is an open-source, cross-platform library that ' - 'provides developers with an extensive suite of software ' - 'tools for image analysis. Developed through extreme ' - 'programming methodologies, ITK employs leading-edge ' - 'algorithms for registering and segmenting ' - 'multidimensional scientific images.', - 'PYPROJECT_EXTRA_KEYWORDS': r'"scientific", "medical", "image", "imaging"', - 'PYPROJECT_DEPENDENCIES': r'', + "PYPROJECT_GENERATOR": "python %s '%s'" % (SCRIPT_NAME, "itk"), + "PYPROJECT_NAME": r"itk", + "PYPROJECT_VERSION": get_version(), + "PYPROJECT_CMAKE_ARGS": r"", + "PYPROJECT_PY_API": get_py_api(), + "PYPROJECT_PLATLIB": r"true", + "PYPROJECT_PY_MODULES": list_to_str( + [ + "itkBase", + "itkConfig", + "itkExtras", + "itkHelpers", + "itkLazy", + "itkTemplate", + "itkTypes", + "itkVersion", + "itkBuildOptions", + ] + ), + "PYPROJECT_DOWNLOAD_URL": r"https://github.com/InsightSoftwareConsortium/ITK/releases", + "PYPROJECT_DESCRIPTION": r"ITK is an open-source toolkit for multidimensional image analysis", # noqa: E501 + "PYPROJECT_LONG_DESCRIPTION": r"ITK is an open-source, cross-platform library that " + "provides developers with an extensive suite of software " + "tools for image analysis. Developed through extreme " + "programming methodologies, ITK employs leading-edge " + "algorithms for registering and segmenting " + "multidimensional scientific images.", + "PYPROJECT_EXTRA_KEYWORDS": r'"scientific", "medical", "image", "imaging"', + "PYPROJECT_DEPENDENCIES": r"", } -PYPROJECT_PY_PARAMETERS = { - 'itk': ITK_PYPROJECT_PY_PARAMETERS -} +PYPROJECT_PY_PARAMETERS = {"itk": ITK_PYPROJECT_PY_PARAMETERS} -update_wheel_setup_py_parameters() +update_wheel_pyproject_toml_parameters() def main(): @@ -286,13 +300,14 @@ def main(): # Parse arguments parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter - ) + ) parser.add_argument("wheel_name") parser.add_argument( - "--output-dir", type=str, + "--output-dir", + type=str, help="Output directory for configured 'pyproject.toml'", - default=default_output_dir - ) + default=default_output_dir, + ) args = parser.parse_args() template = os.path.join(SCRIPT_DIR, "pyproject.toml.in") if args.wheel_name not in PYPROJECT_PY_PARAMETERS.keys(): @@ -300,17 +315,18 @@ def main(): sys.exit(1) # Configure 'pyproject.toml' - output_file = os.path.join(args.output_dir, 'pyproject.toml') + output_file = os.path.join(args.output_dir, "pyproject.toml") configure(template, PYPROJECT_PY_PARAMETERS[args.wheel_name], output_file) # Configure or remove 'itk/__init__.py' # init_py = os.path.join(args.output_dir, "itk", "__init__.py") # if args.wheel_name in ["itk", "itk-core"]: - # with open(init_py, 'w') as file_: - # file_.write("# Stub required for package\n") + # with open(init_py, 'w') as file_: + # file_.write("# Stub required for package\n") # else: - # if os.path.exists(init_py): - # os.remove(init_py) + # if os.path.exists(init_py): + # os.remove(init_py) + if __name__ == "__main__": main() diff --git a/scripts/update_python_version.py b/scripts/update_python_version.py index c9123060..f69207d3 100755 --- a/scripts/update_python_version.py +++ b/scripts/update_python_version.py @@ -8,14 +8,15 @@ import os import subprocess from datetime import datetime +from packaging.version import Version argparser = argparse.ArgumentParser(description=__doc__) -argparser.add_argument('itkSourceDir') +argparser.add_argument("itkSourceDir") args = argparser.parse_args() itkSourceDir = args.itkSourceDir -if not os.path.exists(os.path.join(itkSourceDir, '.git')): - print('itkSourceDir does not appear to be a git repository!') +if not os.path.exists(os.path.join(itkSourceDir, ".git")): + print("itkSourceDir does not appear to be a git repository!") sys.exit(1) itkPythonPackageDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -23,49 +24,55 @@ os.chdir(itkSourceDir) # "Wed Feb 8 15:21:09 2017"\n -commitDate = subprocess.check_output(['git', - 'show', '-s', '--date=local', '--format="%cd"']) +commitDate = subprocess.check_output( + ["git", "show", "-s", "--date=local", '--format="%cd"'] +) # Wed Feb 8 15:21:09 2017 commitDate = commitDate.strip()[1:-1] # Wed Feb 08 15:21:09 2017 -commitDate = commitDate.split(' ') -commitDate[2] = '{:02d}'.format(int(commitDate[2])) -commitDate = ' '.join(commitDate) +commitDate = commitDate.split(" ") +commitDate[2] = "{:02d}".format(int(commitDate[2])) +commitDate = " ".join(commitDate) # 2017-02-08 -commitDateDashes = datetime.strptime(commitDate, "%a %b %d %H:%M:%S %Y").strftime("%Y-%m-%d") +commitDateDashes = datetime.strptime(commitDate, "%a %b %d %H:%M:%S %Y").strftime( + "%Y-%m-%d" +) # 20170208 -commitDate = commitDateDashes.replace('-', '') +commitDate = commitDateDashes.replace("-", "") # v4.11.0-139-g922f2d9 # -revision = subprocess.check_output(['git', 'describe', '--tags', '--long']) +revision = subprocess.check_output(["git", "describe", "--tags", "--long"]) revision.strip() # 4.11.0-139-g922f2d9 revision = revision[1:] -version, numberOfCommits, gHash = revision.split('-') +version, numberOfCommits, gHash = revision.split("-") version = version.strip() numberOfCommits = numberOfCommits.strip() gHash = gHash.strip() pythonRevision = version if int(numberOfCommits) > 0: - pythonRevision += '.dev' + pythonRevision += ".dev" pythonRevision += commitDate - pythonRevision += '+' + pythonRevision += "+" pythonRevision += numberOfCommits - pythonRevision += '.' + pythonRevision += "." pythonRevision += gHash os.chdir(itkPythonPackageDir) -itkVersionPath = os.path.join(itkPythonPackageDir, 'itkVersion.py') +itkVersionPath = os.path.join(itkPythonPackageDir, "itkVersion.py") + +Version(VERSION) # Raise InvalidVersion exception if not PEP 440 compliant + if not os.path.exists(itkVersionPath): - print('Expected file ' + itkVersionPath + ' not found!') + print("Expected file " + itkVersionPath + " not found!") sys.exit(1) -with open(itkVersionPath, 'r') as fp: +with open(itkVersionPath, "r") as fp: lines = fp.readlines() -with open(itkVersionPath, 'w') as fp: +with open(itkVersionPath, "w") as fp: for line in lines: - if line.startswith('VERSION = '): + if line.startswith("VERSION = "): fp.write("VERSION = '") fp.write(pythonRevision) fp.write("'\n") @@ -73,15 +80,15 @@ fp.write(line) -with open('CMakeLists.txt', 'r') as fp: +with open("CMakeLists.txt", "r") as fp: lines = fp.readlines() -with open('CMakeLists.txt', 'w') as fp: +with open("CMakeLists.txt", "w") as fp: for line in lines: - if line.startswith(' # ITK nightly-master'): - fp.write(' # ITK nightly-master ') + if line.startswith(" # ITK nightly-master"): + fp.write(" # ITK nightly-master ") fp.write(commitDateDashes) - fp.write('\n') - elif line.startswith(' set(ITK_GIT_TAG'): + fp.write("\n") + elif line.startswith(" set(ITK_GIT_TAG"): fp.write(' set(ITK_GIT_TAG "') fp.write(gHash[1:]) fp.write('")\n') diff --git a/scripts/windows-download-cache-and-build-module-wheels.ps1 b/scripts/windows-download-cache-and-build-module-wheels.ps1 index ad8a1220..0d7b8fe2 100644 --- a/scripts/windows-download-cache-and-build-module-wheels.ps1 +++ b/scripts/windows-download-cache-and-build-module-wheels.ps1 @@ -12,13 +12,13 @@ # or equivalently: # > windows-download-cache-and-build-module-wheels.ps1 -python_version_minor 11 # -# - 1st parameter or -setup_options: setup.py options. +# - 1st parameter or -setup_options: pyproject.toml options. # For instance, for Python 3.11, excluding nvcuda.dll during packaging: # > windows-download-cache-and-build-module-wheels.ps1 11 "--exclude-libs nvcuda.dll" # or equivalently: # > windows-download-cache-and-build-module-wheels.ps1 -python_version_minor 11 -setup_options "--exclude-libs nvcuda.dll" # -# - 2nd parameter or -cmake_options: CMake options passed to setup.py for project configuration. +# - 2nd parameter or -cmake_options: CMake options passed to pyproject.tom for project configuration. # For instance, for Python 3.11, excluding nvcuda.dll during packaging # and setting RTK_USE_CUDA ON during configuration: # > windows-download-cache-and-build-module-wheels.ps1 11 "--exclude-libs nvcuda.dll" "-DRTK_USE_CUDA:BOOL=ON" diff --git a/scripts/windows_build_module_wheels.py b/scripts/windows_build_module_wheels.py index 4f2e33e0..858f1bac 100755 --- a/scripts/windows_build_module_wheels.py +++ b/scripts/windows_build_module_wheels.py @@ -51,20 +51,29 @@ def build_wheels(py_envs=DEFAULT_PY_ENVS, cleanup=True, cmake_options=[]): ) = venv_paths(py_env) with push_env(PATH="%s%s%s" % (path, os.pathsep, os.environ["PATH"])): - use_scikit_build_core = True - if Path(os.getcwd()).joinpath("setup.py").exists(): - use_scikit_build_core = False - # Install dependencies + # + # Bootstrap pip from CPython's bundled ensurepip wheel BEFORE + # invoking the system pip, to sidestep a chicken-and-egg failure + # on Python 3.10+ runners. scikit-ci-addons' install-python.ps1 + # downloads get-pip.py from a pinned GitHub gist (jcfr commit + # 8478d43e), which installs a pip old enough to vendor an + # html5lib that still does `from collections import Mapping` + # (removed in Python 3.10). Loading that pip to upgrade itself + # raises ImportError before any package operation can run. + # CPython's ensurepip module installs the pip wheel that ships + # with the interpreter (>= 21.x for Python 3.10), which has a + # working html5lib >= 1.1; the subsequent `pip install pip + # --upgrade` then succeeds normally. + check_call( + [python_executable, "-m", "ensurepip", "--upgrade", "--default-pip"] + ) check_call([python_executable, "-m", "pip", "install", "pip", "--upgrade"]) requirements_file = os.path.join(ROOT_DIR, "requirements-dev.txt") if os.path.exists(requirements_file): check_call([pip, "install", "--upgrade", "-r", requirements_file]) check_call([pip, "install", "cmake"]) - if use_scikit_build_core: - check_call([pip, "install", "scikit-build-core", "--upgrade"]) - else: - check_call([pip, "install", "scikit_build", "--upgrade"]) + check_call([pip, "install", "scikit-build-core", "--upgrade"]) check_call([pip, "install", "ninja", "--upgrade"]) check_call([pip, "install", "delvewheel"]) @@ -75,90 +84,57 @@ def build_wheels(py_envs=DEFAULT_PY_ENVS, cleanup=True, cmake_options=[]): ) print("ITKDIR: %s" % itk_build_path) - if use_scikit_build_core: - minor_version = py_env.split("-")[0][1:] - if int(minor_version) >= 11: - # Stable ABI - wheel_py_api = "cp3%s" % minor_version - else: - wheel_py_api = "" - # Generate wheel - check_call( - [ - python_executable, - "-m", - "build", - "--verbose", - "--wheel", - "--outdir", - "dist", - "--no-isolation", - "--skip-dependency-check", - "--config-setting=wheel.py-api=%s" % wheel_py_api, - "--config-setting=cmake.define.SKBUILD:BOOL=ON", - "--config-setting=cmake.define.PY_SITE_PACKAGES_PATH:PATH=.", - "--config-setting=cmake.args=-G Ninja", - "--config-setting=cmake.define.CMAKE_BUILD_TYPE:STRING=Release", - "--config-setting=cmake.define.CMAKE_MAKE_PROGRAM:FILEPATH=%s" - % ninja_executable, - "--config-setting=cmake.define.ITK_DIR:PATH=%s" - % itk_build_path, - "--config-setting=cmake.define.WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel", - "--config-setting=cmake.define.SWIG_EXECUTABLE:FILEPATH=%s/Wrapping/Generators/SwigInterface/swig/bin/swig.exe" - % itk_build_path, - "--config-setting=cmake.define.BUILD_TESTING:BOOL=OFF", - "--config-setting=cmake.define.CMAKE_INSTALL_LIBDIR:STRING=lib", - "--config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=%s" - % python_executable, - "--config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=%s" - % python_include_dir, - "--config-setting=cmake.define.Python3_INCLUDE_DIRS:PATH=%s" - % python_include_dir, - "--config-setting=cmake.define.Python3_LIBRARY:FILEPATH=%s" - % python_library, - "--config-setting=cmake.define.Python3_SABI_LIBRARY:FILEPATH=%s" - % python_sabi_library, - ] - + [ - o.replace("-D", "--config-setting=cmake.define.") - for o in cmake_options - ] - + [ - ".", - ] - ) + minor_version = py_env.split("-")[0][1:] + if int(minor_version) >= 11: + # Stable ABI + wheel_py_api = "cp3%s" % minor_version else: - # scikit-build classic - build_type = "Release" - - # Generate wheel - check_call( - [ - python_executable, - "setup.py", - "bdist_wheel", - "--build-type", - build_type, - "-G", - "Ninja", - "--", - "-DCMAKE_MAKE_PROGRAM:FILEPATH=%s" % ninja_executable, - "-DITK_DIR:PATH=%s" % itk_build_path, - "-DWRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel", - "-DSWIG_EXECUTABLE:FILEPATH=%s/Wrapping/Generators/SwigInterface/swig/bin/swig.exe" - % itk_build_path, - "-DBUILD_TESTING:BOOL=OFF", - "-DCMAKE_INSTALL_LIBDIR:STRING=lib", - "-DPython3_EXECUTABLE:FILEPATH=%s" % python_executable, - "-DPython3_INCLUDE_DIR:PATH=%s" % python_include_dir, - "-DPython3_INCLUDE_DIRS:PATH=%s" % python_include_dir, - "-DPython3_LIBRARY:FILEPATH=%s" % python_library, - ] - + cmake_options - ) - # Cleanup - if cleanup: - check_call([python_executable, "setup.py", "clean"]) + wheel_py_api = "" + # Generate wheel + check_call( + [ + python_executable, + "-m", + "build", + "--verbose", + "--wheel", + "--outdir", + "dist", + "--no-isolation", + "--skip-dependency-check", + "--config-setting=wheel.py-api=%s" % wheel_py_api, + "--config-setting=cmake.define.SKBUILD:BOOL=ON", + "--config-setting=cmake.define.PY_SITE_PACKAGES_PATH:PATH=.", + "--config-setting=cmake.args=-G Ninja", + "--config-setting=cmake.define.CMAKE_BUILD_TYPE:STRING=Release", + "--config-setting=cmake.define.CMAKE_MAKE_PROGRAM:FILEPATH=%s" + % ninja_executable, + "--config-setting=cmake.define.ITK_DIR:PATH=%s" + % itk_build_path, + "--config-setting=cmake.define.WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER:STRING=PythonWheel", + "--config-setting=cmake.define.SWIG_EXECUTABLE:FILEPATH=%s/Wrapping/Generators/SwigInterface/swig/bin/swig.exe" + % itk_build_path, + "--config-setting=cmake.define.BUILD_TESTING:BOOL=OFF", + "--config-setting=cmake.define.CMAKE_INSTALL_LIBDIR:STRING=lib", + "--config-setting=cmake.define.Python3_EXECUTABLE:FILEPATH=%s" + % python_executable, + "--config-setting=cmake.define.Python3_INCLUDE_DIR:PATH=%s" + % python_include_dir, + "--config-setting=cmake.define.Python3_INCLUDE_DIRS:PATH=%s" + % python_include_dir, + "--config-setting=cmake.define.Python3_LIBRARY:FILEPATH=%s" + % python_library, + "--config-setting=cmake.define.Python3_SABI_LIBRARY:FILEPATH=%s" + % python_sabi_library, + ] + + [ + o.replace("-D", "--config-setting=cmake.define.") + for o in cmake_options + ] + + [ + ".", + ] + ) def rename_wheel_init(py_env, filepath, add_module_name=True):