From 862e7c93dd155d681cce689a00e87ea8a02ed9fb Mon Sep 17 00:00:00 2001 From: jase231 Date: Wed, 17 Jun 2026 14:12:22 +0200 Subject: [PATCH 1/2] [build] Migrate build backend to scikit-build-core --- .../pythonizations/python/ROOT/__init__.py | 16 ++ pyproject.toml | 68 ++++++- setup.py | 184 ------------------ 3 files changed, 79 insertions(+), 189 deletions(-) delete mode 100644 setup.py diff --git a/bindings/pyroot/pythonizations/python/ROOT/__init__.py b/bindings/pyroot/pythonizations/python/ROOT/__init__.py index 65b8cbcbec98a..59b39544463e7 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/__init__.py +++ b/bindings/pyroot/pythonizations/python/ROOT/__init__.py @@ -18,6 +18,7 @@ import types from importlib.abc import Loader, MetaPathFinder from importlib.machinery import ModuleSpec +from importlib.metadata import PackageNotFoundError, version from . import _asan # noqa: F401 # imported for side effects for setup specific to AddressSanitizer environments from ._facade import ROOTFacade @@ -190,6 +191,21 @@ def find_spec(self, fullname: str, path, target=None) -> ModuleSpec: # from . import JsMVA +# Print a warning for users of the ROOT wheel alpha distribution +try: + if "a" in version("ROOT"): + import warnings + + warnings.warn( + "This distribution of ROOT is in alpha stage. Feedback is welcome and appreciated. " + "Feel free to reach out to the user forum for questions and general feedback at " + "https://github.com/root-project/root/issues. " + "Do not rely on this distribution for production purposes.", + stacklevel=2, # emit the warning in the caller + ) +except PackageNotFoundError: + pass + def _cleanup(): # Delete TBrowser instances while the GUI event loop is still alive, diff --git a/pyproject.toml b/pyproject.toml index f9d6ef5920606..52bcc6262e392 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,9 @@ [build-system] -requires = ["cmake", "setuptools<72", "wheel", "numpy"] +requires = ["scikit-build-core>=0.10"] +build-backend = "scikit_build_core.build" [project] -name = "ROOT" +name = "root" version = "0.1a12" requires-python = ">=3.10" maintainers = [ @@ -15,16 +16,73 @@ dependencies = [ "numpy", ] -# Demonstrate how to expose ROOT CLI tools the Python way. For now only include -# `root`, other tools can be added later using the same approach. +# Point backend to python packages and to explicitly use Ninja +[tool.scikit-build] +wheel.packages = [ + "bindings/pyroot/pythonizations/python/ROOT", + "bindings/pyroot/cppyy/cppyy/python/cppyy", +] +cmake.args = [ + "-GNinja", +] + +[tool.scikit-build.cmake.define] +# CMake install directories +CMAKE_INSTALL_BINDIR = "ROOT/bin" +CMAKE_INSTALL_CMAKEDIR = "ROOT/cmake" +CMAKE_INSTALL_FONTDIR = "ROOT/fonts" +CMAKE_INSTALL_ICONDIR = "ROOT/icons" +CMAKE_INSTALL_INCLUDEDIR = "ROOT/include" +CMAKE_INSTALL_LIBDIR = "ROOT/lib" +CMAKE_INSTALL_MACRODIR = "ROOT/macros" +CMAKE_INSTALL_MANDIR = "ROOT/man" +CMAKE_INSTALL_PYTHONDIR = "." +CMAKE_INSTALL_SYSCONFDIR = "ROOT/etc" +CMAKE_INSTALL_TUTDIR = "ROOT/tutorials" + +# Generic minimal build config +gminimal="ON" +asimage="ON" +opengl="OFF" +runtime_cxxmodules="ON" +fail-on-missing="ON" + +# Explicitly list components that gminimal implicitly turns off as documentation +# tmva-pymva and tpython are disabled for manylinux compatibility +# see https://peps.python.org/pep-0513/#libpythonx-y-so-1 +tmva-pymva="OFF" +tpython="OFF" +thisroot_scripts="OFF" # the thisroot.* scripts are broken if CMAKE_INSTALL_PYTHONDIR!=CMAKE_INSTALL_LIBDIR + +# Builtins +builtin_nlohmannjson="ON" +builtin_tbb="ON" +builtin_xrootd="ON" +builtin_tiff="ON" +builtin_lz4="ON" +builtin_fftw3="ON" +builtin_gsl="ON" +builtin_lzma="ON" +builtin_zstd="ON" +builtin_xxhash="ON" +pyroot="ON" +dataframe="ON" +xrootd="ON" +ssl="ON" +imt="ON" +roofit="ON" +mathmore="ON" + +# Expose the ROOT cli as an executable command via _rootcli wrapper [project.scripts] root = "ROOT._rootcli:main" [tool.cibuildwheel] -# Increase pip debugging output +build-frontend = { name = "build" } build-verbosity = 1 manylinux-x86_64-image = "manylinux_2_28" # Install system libraries [tool.cibuildwheel.linux] before-all = "dnf install -y epel-release && /usr/bin/crb enable && dnf install -y openssl-devel libX11-devel libXpm-devel libXft-devel libXext-devel libuuid-devel libjpeg-devel giflib-devel libtiff-devel" + diff --git a/setup.py b/setup.py deleted file mode 100644 index 1a4205ce07ffa..0000000000000 --- a/setup.py +++ /dev/null @@ -1,184 +0,0 @@ -""" -setuptools-based build of ROOT. - -This script uses setuptools API to steer a custom CMake build of ROOT. All build -configuration options are specified in the class responsible for building. A -custom extension module is injected in the setuptools setup to properly generate -the wheel with CPython extension metadata. Note that ROOT is first installed via -CMake into a temporary directory, then the ROOT installation artifacts are moved -to the final Python environment installation path, which often starts at -${ENV_PREFIX}/lib/pythonXX.YY/site-packages, before being packaged as a wheel. -""" - -import os -import pathlib -import shlex -import subprocess -import tempfile - -from setuptools import Extension, find_packages, setup -from setuptools.command.build import build as _build -from setuptools.command.install import install as _install - -# Get the long description from the README file -SOURCE_DIR = pathlib.Path(__file__).parent.resolve() -LONG_DESCRIPTION = (SOURCE_DIR / "README.md").read_text(encoding="utf-8") - -BUILD_DIR = tempfile.mkdtemp() -INSTALL_DIR = tempfile.mkdtemp() - -# Name given to an internal directory within the build directory -# used to mimick the structure of the target installation directory -# in the user Python environment, usually named "site-packages" -ROOT_BUILD_INTERNAL_DIRNAME = "mock_site_packages" - - -def _patch_root_init(): - warning_patch = ''' - -import warnings -import textwrap - -warnings.warn( -textwrap.dedent(""" -This distribution of ROOT is in alpha stage. Feedback is welcome and appreciated. Feel free to reach out to the user forum for questions and general feedback at https://root-forum.cern.ch or to submit an issue at https://github.com/root-project/root/issues. Do not rely on this distribution for production purposes. -""") -) -''' - root_init_path = pathlib.Path(SOURCE_DIR) / "bindings/pyroot/pythonizations/python/ROOT/__init__.py" - with open(root_init_path) as init_file: - full_init_text = init_file.read() - - new_init_text = full_init_text + warning_patch - with open(root_init_path, "w") as init_file: - init_file.write(new_init_text) - - -class ROOTBuild(_build): - def finalize_options(self): - # Normalize the distribution name before building - if self.distribution.metadata.name == "ROOT": - # Store original name for metadata - self.distribution.metadata._original_name = "ROOT" - # Use normalized name to comply with PEP625 and avoid errors - # caused by https://github.com/pypi/warehouse/pull/18924 - self.distribution.metadata.name = "root" - super().finalize_options() - - def run(self): - _build.run(self) - - # The PyPI distribution is in alpha stage, signal this to the user - # with a warning at import ROOT time. We inject the warning in the - # __init__.py file of the package before the start of the build - _patch_root_init() - - # Configure ROOT build - configure_command = shlex.split( - "cmake " - # gminimal=ON enables only a minimal set of components (cling+core+I/O+graphics) - "-Dgminimal=ON -Dasimage=ON -Dopengl=OFF " - "-Druntime_cxxmodules=ON -Dfail-on-missing=ON " # Generic build configuration - # Explicitly turned off components, even though they are already off because of gminimal, we want to keep - # them listed here for documentation purposes: - # - tmva-pymva, tpython: these components link against libPython, forbidden for manylinux compatibility, - # see https://peps.python.org/pep-0513/#libpythonx-y-so-1 - # - thisroot_scripts: the thisroot.* scripts are broken if CMAKE_INSTALL_PYTHONDIR!=CMAKE_INSTALL_LIBDIR - "-Dtmva-pymva=OFF -Dtpython=OFF -Dthisroot_scripts=OFF " - "-Dbuiltin_nlohmannjson=ON -Dbuiltin_tbb=ON -Dbuiltin_xrootd=ON " # builtins - "-Dbuiltin_lz4=ON -Dbuiltin_lzma=ON -Dbuiltin_zstd=ON -Dbuiltin_xxhash=ON " # builtins - "-Dpyroot=ON -Ddataframe=ON -Dxrootd=ON -Dssl=ON -Dimt=ON " - "-Droofit=ON -Dmathmore=ON -Dbuiltin_fftw3=ON -Dbuiltin_gsl=ON " - f"-DCMAKE_INSTALL_PREFIX={INSTALL_DIR} -B {BUILD_DIR} -S {SOURCE_DIR} " - # Next paths represent the structure of the target binaries/headers/libs - # as the target installation directory of the Python environment would expect - f"-DCMAKE_INSTALL_BINDIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/bin " - f"-DCMAKE_INSTALL_CMAKEDIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/cmake " - f"-DCMAKE_INSTALL_FONTDIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/fonts " - f"-DCMAKE_INSTALL_ICONDIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/icons " - f"-DCMAKE_INSTALL_INCLUDEDIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/include " - f"-DCMAKE_INSTALL_LIBDIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/lib " - f"-DCMAKE_INSTALL_MACRODIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/macros " - f"-DCMAKE_INSTALL_MANDIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/man " - f"-DCMAKE_INSTALL_PYTHONDIR={ROOT_BUILD_INTERNAL_DIRNAME} " - f"-DCMAKE_INSTALL_SYSCONFDIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/etc " - f"-DCMAKE_INSTALL_TUTDIR={ROOT_BUILD_INTERNAL_DIRNAME}/ROOT/tutorials " - ) - subprocess.run(configure_command, check=True) - - # Run build with CMake - build_command = f"cmake --build {BUILD_DIR} -j{os.cpu_count()}" - subprocess.run(shlex.split(build_command), check=True) - - -class ROOTInstall(_install): - def finalize_options(self): - # Normalize the distribution name before installing - if self.distribution.metadata.name == "ROOT": - # Store original name for metadata - self.distribution.metadata._original_name = "ROOT" - # Use normalized name to comply with PEP625 and avoid errors - # caused by https://github.com/pypi/warehouse/pull/18924 - self.distribution.metadata.name = "root" - super().finalize_options() - - def _get_install_path(self): - if hasattr(self, "bdist_dir") and self.bdist_dir: - install_path = self.bdist_dir - else: - install_path = self.install_lib - - return install_path - - def run(self): - _install.run(self) - - install_cmd = f"cmake --build {BUILD_DIR} --target install" - subprocess.run(shlex.split(install_cmd), check=True) - - install_path = self._get_install_path() - - # Copy ROOT installation tree to the ROOT package directory in the pip installation path - self.copy_tree(os.path.join(INSTALL_DIR, ROOT_BUILD_INTERNAL_DIRNAME), install_path) - - root_package_dir = os.path.join(install_path, "ROOT") - - # After the copy of the "mock" package structure from the ROOT installations, these are the - # leftover files that still need to be copied - self.copy_tree(os.path.join(INSTALL_DIR, "README"), os.path.join(root_package_dir, "README")) - self.copy_file(os.path.join(INSTALL_DIR, "LICENSE"), os.path.join(root_package_dir, "LICENSE")) - - def get_outputs(self): - outputs = _install.get_outputs(self) - return outputs - - -class DummyExtension(Extension): - """ - Dummy CPython extension for setuptools setup. - - In order to generate the wheel with CPython extension metadata (i.e. - producing one wheel per supported Python version), setuptools requires that - at least one CPython extension is declared in the `ext_modules` kwarg passed - to the `setup` function. Usually, declaring a CPython extension triggers - compilation of the corresponding sources, but in this case we already do - that in the CMake build step. This class defines a dummy extension that - can be declared to setuptools while avoiding any further compilation step. - """ - - def __init__(_): - super().__init__(name="Dummy", sources=[]) - - -pkgs = find_packages("bindings/pyroot/pythonizations/python") + find_packages( - "bindings/pyroot/cppyy/cppyy/python", include=["cppyy"] -) - -s = setup( - long_description=LONG_DESCRIPTION, - package_dir={"": "bindings/pyroot/pythonizations/python", "cppyy": "bindings/pyroot/cppyy/cppyy/python"}, - packages=pkgs, - # Crucial to signal this is not a pure Python package - ext_modules=[DummyExtension()], - cmdclass={"build": ROOTBuild, "install": ROOTInstall}, -) From 20e47430f782ec1d5fb6354642f3fd107a891384 Mon Sep 17 00:00:00 2001 From: jase231 Date: Wed, 17 Jun 2026 14:14:29 +0200 Subject: [PATCH 2/2] [build] Bump wheel distribution version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 52bcc6262e392..5f4f50d05bfe5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "root" -version = "0.1a12" +version = "0.1a14" requires-python = ">=3.10" maintainers = [ {name = "Vincenzo Eduardo Padulano", email = "vincenzo.eduardo.padulano@cern.ch"}