Scikit-build-core 0.10 is out, and it is one of the largest releases we’ve produced in terms of new configuration options. It automatically reads your minimum CMake version, you can tell it to read your scikit-build-core minimum-version, and there are settings for many of the advanced things users used to do with scikit-build (classic), like rebuilds or pure Python fallbacks on build failures.
Auto CMake version
Scikit-build-core 0.10 has a change to the default behavior; if you don’t
specify the minimum version of CMake you require, we will now read it from your
CMakeLists.txt
file. This provides better behaviour out of the box, and you
can still manually specify it if you want.
To use it, just write your CMakeLists.txt as you would normally, with something like this at the top:
cmake_minimum_requires(VERSION 3.15...3.30)
Scikit-build-core will warn if your minimum is less than our supported minimum (3.15); set it manually if you support building your package without scikit-build-core and want a lower version.
If we can’t find a minimum, then the default (3.15) will be used; you can force the new auto-search with the special string:
[tool.scikit-build]
cmake.version = "CMakeLists.txt"
Implementing the feature reliably was interesting; we run a custom streaming tokenizer and AST generator for CMake, and this process will stop when it finds this expression. We’ve tested this on every CMakeLists.txt on PyPI (32,000 files) to ensure it is reliable. Because of this, we are not tripped up by multiline comments, if blocks, etc, and will only detect the minimum version if it is at the top level.
Auto scikit-build minimum-version
One of scikit-build-core’s most powerful features is the minimum-version
setting. Much like cmake_minimum_requires
for CMake, this allows us to change
things without breaking existing packages. The above feature, for example, will
only apply if your minimum-version
is either unspecified or set to 0.10 or
greater.
However, there’s an issue: you often specify exactly the same information in
your build-system.requires
table, and these to values should be updated
together. Now you can opt-into single sourcing this! You can do it like this:
[build-system]
requires = ["scikit-build-core>=0.10"]
build-backend = "scikit_build_core.build"
[tool.scikit-build]
minimum-version = "build-system.requires"
This is opt-in only, since it could be surprising if changing the value in this table affects the build. If you specify this, then there must be a lower bound on the scikit-build-core requirement.
Environment markers are supported, so if you have different minimum versions for different versions of Python, for example, that is also supported.
Many new overrides
There are over half a dozen new override options, including several that are much more powerful than anything we’ve had here before. You can look over each one in the changelog, but I want to instead highlight what you can do with them here.
Control what happens on build failure
You can now use a new override, if.failed
, to specify a new configuration if
the build fails. This can be used to make a pure-Python “fallback” version, for
example:
[tool.scikit-build]
wheel.packages = ["compiled/mypkg"]
[[tool.scikit-build.overrides]]
if.failed = true
wheel.cmake = false
wheel.packages = ["pure-python/mypkg"]
If the build fails, then the configuration will be re-evaluated once; if there’s
a matching override with if.failed = true
, it will return once with the new
settings. Above, cmake is no longer used and a different Python package is
copied in as “mypkg”.
You could also use this to rebuild with different CMake defines, etc.
Customize what happens if cmake isn’t available
The cmake
wheel is great on systems that provide wheels, but has a tendency to
struggle to build on systems that don’t have a modern CMake already available
and aren’t supported by PyPI wheels. Two new if selectors, if.system-cmake
and
if.cmake-wheel
, have been added to allow you to fully control what happens in
every possible case. For example, if you want to have a pure Python fallback if
there’s no system CMake and no pre-built wheel available, you could do this:
[tool.scikit-build]
wheel.cmake = false
[[tool.scikit-build.overrides]]
if.any.system-cmake = ">=3.15"
if.any.cmake-wheel = true
wheel.cmake = true
If there’s a system CMake (3.15+) or a known wheel for the platform, this will
build binary wheels. Other systems will get pure Python wheels. You can combine
this with the build failure above, or with environment variables
(if.any.env.<NAME>
) as well.
Support a range of scikit-build-core versions
In preparation for removing Python 3.7 support, we are making sure packages can
support a range of scikit-build-core versions. You’ve already seen that the auto
minimum-version
feature supports environment markers; another aspect of this
is the new if.scikit-build-version
selector. This is special selector: if it
is present, unknown selectors are ignored. Combined with the fact that overrides
are not processed if the selectors don’t match, you can specify future features
in protected overrides!
# WARNING: imaginary example, 0.11 doesn't exist yet!
[build-system]
requires = [
"scikit-build-core>=0.10; python_version<3.8",
"scikit-build-core>=0.11; python_version>=3.8",
]
[tool.scikit-build]
minimum-version = "build-system.requires"
[[tool.scikit-build.overrides]]
if.scikit-build-version = ">=0.11"
if.new-selector = true
not-in-010 = "I'm okay"
The above example will work on Python 3.7, because it is going to get a
minimum-version
of 0.10, and the extra selector will be ignored.
A few other overrides
The other new overrides not mentioned above are if.abi-flags
, for detecting
free-threaded Python (3.13+) andif.from-sdist
, which lets you distinguish
source -> wheel vs. sdist -> wheel.
Custom output messages for users
You can now add custom messages for your users with messages.after-failure
and
messages.after-success
. These support .format
style keyword arguments; we
provide several like sys
, platform
, and several style options; more can be
added by request.
[tool.scikit-build]
messages.after-sucesss = "{green}Wheel successfully built"
messages.after-failure = """
{bold.red}Sorry{normal}, build failed. Your platform is {platform.platform}.
"""
You can provide debugging information for failed builds, for example. These are very useful when mixed with overrides to tell users if a pure or compiled wheel was built, etc.
Color system
The messages.*
options expose a redesigned color system that scikit-build-core
uses to colorize its messages. These include the main eight terminal colors and
the most common terminal styles like bold
, italic
, and underline
. You can
chain these together, like bold.red
to produce optimized escape codes, as
well. default
is the default color, normal
is the default style, and reset
brings everything back to normal.
On Windows or when using pip
, you need FORCE_COLOR
to get colors.
Fail a build based on Python version
If you know you don’t support a platform or Python version, you can set the new
fail
option to immediately fail the build. Combined with the
messages.after-failure
, you can provide helpful information as to why it is
failing. For example:
[[tool.scikit-build.overrides]]
if.python-version = ">=3.13"
fail = true
messages.after-failure = """
This package doesn't support Python 3.13 yet.
"""
This is much better than setting project.requires-python
with an upper cap,
which doesn’t work as you probably would expect, either causing an older version
to be selected (its purpose is to allow back-solving for older versions, after
all), or producing a “no package found” message.
As a reminder, only use this if you know for certain the next version of Python won’t be supported! This is only true for a very small number of packages.
You can use fail = true
with any other selector - or even in the main table
(though if you don’t then override it to be false, you will have made a very
boring package!)
Regex plugin improvements
The regex plugin has been expanded with two new features to enable a much wider
range of possible versions read in: result=
, which gives you complete control
over how the regex is processed, and remove=
, which can filter out unused bits
of the generated version.
Here’s how nanobind could use this, for example:
[tool.scikit-build.metadata.version]
provider = "scikit_build_core.metadata.regex"
input = "src/mypackage/version.hpp"
regex = '''(?sx)
\#define \s+ NANOBIND_VERSION_MAJOR \s+ (?P<major>\d+) .*?
\#define \s+ NANOBIND_VERSION_MINOR \s+ (?P<minor>\d+) .*?
\#define \s+ NANOBIND_VERSION_PATCH \s+ (?P<patch>\d+) .*?
\#define \s+ NANOVIND_VERSION_DEV \s+ (?P<dev>\d+) .*?
'''
result = "{major}.{minor}.{patch}dev{dev}"
remove = "dev0"
There are four parts which are then combined into a result, and if the result
ends in dev0
, that is stripped from the version number.
Other changes
cmake.targets
and cmake.verbose
have been moved to build.targets
and
build.verbose
. As usual, warnings and errors are handled by your
minimum-version
setting.
We now warn if you put "cmake"
or "ninja"
in your build-system.requires
.
You should not do this; we add the dependencies for you only if they are needed.
We now support nested gitignores, as well as local git ignores.
Be sure to check out our docs again if you’ve not looked at them in a while, they’ve been improved a bit with more pages (including a dedicated overrides page and a projects page).
There aren’t a huge number of fixes in this release, as we’ve been backporting most of them to the 0.9.x series. We do now detect manual generator selections in CMake args and respect that when producing the dynamic build requirements.
In the future
We’ve completed most of the core set of features we expect to be present for scikit-build-core 1.0. There are just a few things left. We plan to work on the plugins (setuptools and hatching), and pull them out into separate packages. The setuptools one will be used as the backend for scikit-build (classic), removing the need to maintain two build systems and is planned to be the “1.0” point for both packages.
We also will be removing Python 3.7 support in the near future, probably just before 1.0.
We are working on cython-cmake and f2py-cmake, modern replacements for the
helpers in scikit-build (classic). Feel free to try them out, and you can embed
them into your cmake
folder to allow direct CMake runs as well as protect you
from changes as we finalize the interfaces to these packages.
And we are working on standardizing dynamic-metadata; feel free to chime in on that package if you have design considerations!
Conclusion
We are excited to release the highly-capable scikit-build-core 0.10. Several packages have been waiting on this release to transition from older build systems. We’ve got new documentation pages, including on highlighting some of our main users. If you have questions, feel free to visit our monthly community meeting; it takes place at noon EST/EDT every third Friday on Google Meet.