From 278c580c2db67603d0c8a5b14254edcc788b59f3 Mon Sep 17 00:00:00 2001 From: Ian Bell Date: Fri, 15 Nov 2024 11:14:43 -0500 Subject: [PATCH] Modernize the build system with scikit-build-core --- CMakeLists.txt | 19 +++++- README.md | 6 ++ interface/pybind11_wrapper.cpp | 1 - interface/teqpversion.hpp | 2 - pyproject.toml | 43 +++++++++++++ setup.py | 109 --------------------------------- 6 files changed, 65 insertions(+), 115 deletions(-) delete mode 100644 interface/teqpversion.hpp delete mode 100644 setup.py diff --git a/CMakeLists.txt b/CMakeLists.txt index f0014863..fb6c4439 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,9 +152,21 @@ add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/externals/autodiff") # Turn on more useful diagnostic messages in nlohmann::json, for instance if you are accessing a field that doesn't exist set(JSON_Diagnostics TRUE CACHE BOOL "Turn on more helpful diagnostics in nlohmann::json") - - - +# Single-source the version, either from scikit, or from parsing the pyproject.toml +if (SKBUILD) + add_definitions("-DTEQPVERSION=\"${SKBUILD_PROJECT_VERSION_FULL}\"") +else() + file(READ "pyproject.toml" TOML_CONTENT) + set(REG "version = \"([0-9]+\\.[0-9]+\\.[0-9]+)\"") + string(REGEX MATCH "${REG}" VERSION_MATCH "${TOML_CONTENT}") + if (NOT VERSION_MATCH) + message(FATAL_ERROR "Can't parse the version") + else() + string(REGEX REPLACE "${REG}" "\\1" PROJECT_VERSION_FULL "${VERSION_MATCH}") + message(STATUS "Version: ${PROJECT_VERSION_FULL}") + add_definitions("-DTEQPVERSION=\"${PROJECT_VERSION_FULL}\"") + endif() +endif() if (NOT TEQP_NO_TEQPCPP) # Add a static library with the C++ interface that uses only STL @@ -240,6 +252,7 @@ if (NOT TEQP_NO_PYTHON) if (MSVC) target_compile_options(teqp PRIVATE "/Zm1000") endif() + install(TARGETS teqp LIBRARY DESTINATION teqp) endif() if (NOT TEQP_NO_TESTS) diff --git a/README.md b/README.md index 1419ae4f..d0a2802c 100644 --- a/README.md +++ b/README.md @@ -420,3 +420,9 @@ cmake --build . --target multifluid_crit * When building in WSL via VS Code, you might need to enable metadata to avoid pages of configure errors in cmake: https://github.com/microsoft/WSL/issues/4257 * Debugging in WSL via VS Code (it really works!): https://code.visualstudio.com/docs/cpp/config-wsl +* Incremental rebuilds: + + * See https://nanobind.readthedocs.io/en/latest/packaging.html#step-5-incremental-rebuilds :: + + pip install scikit-build-core[pyproject] + pip install --no-build-isolation -ve . diff --git a/interface/pybind11_wrapper.cpp b/interface/pybind11_wrapper.cpp index f071bdb0..a52bf6f7 100644 --- a/interface/pybind11_wrapper.cpp +++ b/interface/pybind11_wrapper.cpp @@ -5,7 +5,6 @@ #include #include -#include "teqpversion.hpp" #include "teqp/ideal_eosterms.hpp" #include "teqp/cpp/derivs.hpp" #include "teqp/derivs.hpp" diff --git a/interface/teqpversion.hpp b/interface/teqpversion.hpp deleted file mode 100644 index 92b747d5..00000000 --- a/interface/teqpversion.hpp +++ /dev/null @@ -1,2 +0,0 @@ -#include -const std::string TEQPVERSION = "0.22.0"; diff --git a/pyproject.toml b/pyproject.toml index 414bcba7..67595a56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,3 +2,46 @@ select = "*-musllinux*" before-all = "apk add clang" environment = { CXX="clang++" } + +[build-system] +requires = ["scikit-build-core >=0.4.3", "pybind11 >=2.13", "typing_extensions"] +build-backend = "scikit_build_core.build" + +[project] +name = "teqp" +version = "0.23.0" +description = "teqp: Templated EQuation of state Package" +readme = "README.md" +requires-python = ">=3.8" +authors = [ + { name = "Ian Bell", email = "ian.bell@nist.gov" }, +] +#classifiers = [ +# "License :: BSD", +#] + +[project.urls] +Homepage = "https://github.com/usnistgov/teqp" + +[tool.scikit-build] +# Protect the configuration against future changes in scikit-build-core +minimum-version = "0.10" + +# Setuptools-style build caching in a local directory +build-dir = "build/{wheel_tag}" + +# The package to be built is that in the teqp folder +wheel.packages = ["teqp"] +build.targets = ["teqp"] + +# Build stable ABI wheels for CPython 3.12+ +# wheel.py-api = "cp312" + +#### Additional options for debugging +# cmake.verbose = true +# cmake.build-type = "Debug" +# cmake.args = ["-G Xcode", "-DXCODE_DEBUG_PYTHON=ON"] +# cmake.args = ["-DVERBOSE=ON"] + +[tool.cibuildwheel] +build-verbosity = 1 \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index c95cc5fe..00000000 --- a/setup.py +++ /dev/null @@ -1,109 +0,0 @@ -# Based on https://github.com/pybind/cmake_example - -import os -import re -import sys -import platform -import subprocess -import shutil -import re -import timeit - -from setuptools import setup, Extension -from setuptools.command.build_ext import build_ext -from distutils.version import LooseVersion - -# VERSION is now read from teqpversion.hpp header file -match = re.search(r'TEQPVERSION = \"([0-9a-z.]+)\"\;', open('interface/teqpversion.hpp').read()) -if match: - VERSION = match.group(1) -else: - raise ValueError("Unable to parse version string from interface/teqpversion.hpp") - -here = os.path.dirname(os.path.abspath(__file__)) - -tic = timeit.default_timer() - -class CMakeExtension(Extension): - def __init__(self, name, sourcedir=''): - Extension.__init__(self, name, sources=[]) - self.sourcedir = os.path.abspath(sourcedir) - -class CMakeBuild(build_ext): - def run(self): - try: - out = subprocess.check_output(['cmake', '--version']) - except OSError: - raise RuntimeError("CMake must be installed to build the following extensions: " + - ", ".join(e.name for e in self.extensions)) - - if platform.system() == "Windows": - cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) - if cmake_version < '3.1.0': - raise RuntimeError("CMake >= 3.1.0 is required on Windows") - - for ext in self.extensions: - self.build_extension(ext) - - def build_extension(self, ext): - extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) - cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable] - - cfg = 'Debug' if self.debug else 'Release' - build_args = ['--config', cfg] - - if platform.system() == "Windows": - cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] - # cmake_args += ['-T ClangCL'] - if sys.maxsize > 2**32: - cmake_args += ['-A', 'x64'] - build_args += ['--', '/m:4', '/p:CL_MPCount=4'] - else: - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] - build_args += ['--', '-j2'] - - if sys.platform.startswith("darwin"): - # Cross-compile support for macOS - respect ARCHFLAGS if set - archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) - if archs: - cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] - - env = os.environ.copy() - env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''), - self.distribution.get_version()) - if not os.path.exists(self.build_temp): - os.makedirs(self.build_temp) - - # Config - cmake_elements = ['cmake', ext.sourcedir] + cmake_args - print('cmake config command:', ' '.join(cmake_elements)) - print('running from:', self.build_temp) - subprocess.check_call(cmake_elements, cwd=self.build_temp, env=env) - - # Build - build_elements = ['cmake', '--build', '.', '--target', 'teqp'] + build_args - print('cmake build command:', ' '.join(build_elements)) - subprocess.check_call(build_elements, cwd=self.build_temp) - -from pathlib import Path -this_directory = Path(__file__).parent -long_description = (this_directory / "README.md").read_text() - -setup( - name='teqp', - version=VERSION, - author='Ian Bell and friends', - author_email='ian.bell@nist.gov', - description='Templated EQuation of state Package', - long_description=long_description, - long_description_content_type='text/markdown', - ext_modules=[CMakeExtension('teqp.teqp')], # teqp.teqp is the extension module that lives inside the teqp package - packages=['teqp','teqp.fluiddata.dev.fluids','teqp.fluiddata.dev.mixtures'], - include_package_data=True, - cmdclass=dict(build_ext=CMakeBuild), - zip_safe=False, -) -toc = timeit.default_timer() - -print('elapsed:', toc-tic, 'seconds')