-
Notifications
You must be signed in to change notification settings - Fork 752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support building C/cython extensions #7222
Comments
uv should work just fine to build an extension module as long as your build backend is configured to do so. |
cc @henryiii — not sure if you have any good ideas for how to improve this in our documentation. |
IMHO this should be something to consider if #3957 happens. |
See https://learn.scientific-python.org/development/guides/packaging-compiled/. This should all work just fine with uv, AFAICT any valid PEP 621 backend should work, which is all of them except Poetry (and that's currently a WIP for the next release). Setuptools in the most recent release got experimental support for pyproject.toml extensions too, but it's a lot more rudimentary (just like the setup.py support) compared to scikit-build-core's CMake or meson-python's Meson usage.
IMO, no. Saving less than 1 second when compiling an extension would not be noticeable like it would be for making a wheel from an SDist. And requiring a Rust compiler is suddenly a much bigger ask when you must have it to install a project that can't ship pure-Python wheels. Having a way to call maturin without a Python call in the middle, perhaps, but a custom build backend that can compile extensions is a massive project (and that's why CMake, Meson, Bazel, etc. exist). As for docs, mentioning that there are compiled backends and linking to scikit-build-core, meson-python, and maturin might help? Hatching also supports binary-builds via plugins, including a scikit-build-core one. |
Thanks @henryiii I knew about I guess this would work fine as long as Also not sure how this works at the development level. Setutools or poetry allow building the C extension in editable mode and copy the shared object inside the package. Every time one does |
Setuptools also doesn't support multithreaded builds, IDEs, limited cross-compile support, no common things like C++ standard, no support for other libraries like Boost, etc. You basically get a bare-bones compile that you could probably do manually with hatching's local plugins, which are also in Python. But, regardless, it would also work just fine with uv. And uv does support editable installs, and I think it's the default for this. |
The biggest issue is I don't think output is shown by default except for |
Can you please provide some examples in the documentation or on GitHub for building c extensions or cython extensions using uv. I recently tried a hello world level case where I could build using setup.py but where |
Can you open a new issue with details? |
You can do |
Side comment: |
Thanks for your reply. I tried the following: uv init --lib --build-backend setuptools hello Inside the the created [project]
name = "hello"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "me", email = "[email protected]" }
]
requires-python = ">=3.13"
dependencies = []
[build-system]
requires = ["setuptools>=61", "cython"]
build-backend = "setuptools.build_meta" and add a basic from setuptools import setup
from Cython.Build import cythonize
setup(
name="hello",
ext_modules=cythonize("src/hello/*.pyx", include_path=["/usr/local/include"]),
) ... and add # hello.pyx
def add(int x, int y) -> int:
return x + y
def world() -> str:
return "HELLO WORLD!" I can build the cython module using but when I try to build using % uv build
Building source distribution...
running egg_info
writing src/hello.egg-info/PKG-INFO
writing dependency_links to src/hello.egg-info/dependency_links.txt
writing top-level names to src/hello.egg-info/top_level.txt
reading manifest file 'src/hello.egg-info/SOURCES.txt'
writing manifest file 'src/hello.egg-info/SOURCES.txt'
running sdist
running egg_info
writing src/hello.egg-info/PKG-INFO
writing dependency_links to src/hello.egg-info/dependency_links.txt
writing top-level names to src/hello.egg-info/top_level.txt
reading manifest file 'src/hello.egg-info/SOURCES.txt'
writing manifest file 'src/hello.egg-info/SOURCES.txt'
running check
creating hello-0.1.0
creating hello-0.1.0/src/hello
creating hello-0.1.0/src/hello.egg-info
copying files to hello-0.1.0...
copying README.md -> hello-0.1.0
copying pyproject.toml -> hello-0.1.0
copying setup.py -> hello-0.1.0
copying src/hello/__init__.py -> hello-0.1.0/src/hello
copying src/hello/hello.c -> hello-0.1.0/src/hello
copying src/hello/py.typed -> hello-0.1.0/src/hello
copying src/hello.egg-info/PKG-INFO -> hello-0.1.0/src/hello.egg-info
copying src/hello.egg-info/SOURCES.txt -> hello-0.1.0/src/hello.egg-info
copying src/hello.egg-info/dependency_links.txt -> hello-0.1.0/src/hello.egg-info
copying src/hello.egg-info/top_level.txt -> hello-0.1.0/src/hello.egg-info
copying src/hello.egg-info/SOURCES.txt -> hello-0.1.0/src/hello.egg-info
Writing hello-0.1.0/setup.cfg
Creating tar archive
removing 'hello-0.1.0' (and everything under it)
Building wheel from source distribution...
Traceback (most recent call last):
File "<string>", line 14, in <module>
requires = get_requires_for_build({})
File "~/.cache/uv/builds-v0/.tmpMscKeN/lib/python3.13/site-packages/setuptools/build_meta.py", line 332, in get_requires_for_build_wheel
return self._get_build_requires(config_settings, requirements=[])
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~/.cache/uv/builds-v0/.tmpMscKeN/lib/python3.13/site-packages/setuptools/build_meta.py", line 302, in _get_build_requires
self.run_setup()
~~~~~~~~~~~~~~^^
File "~/.cache/uv/builds-v0/.tmpMscKeN/lib/python3.13/site-packages/setuptools/build_meta.py", line 318, in run_setup
exec(code, locals())
~~~~^^^^^^^^^^^^^^^^
File "<string>", line 6, in <module>
sys.path = [] + sys.path
^^^^^^^^
File "~/.cache/uv/builds-v0/.tmpMscKeN/lib/python3.13/site-packages/Cython/Build/Dependencies.py", line 1010, in cythonize
module_list, module_metadata = create_extension_list(
~~~~~~~~~~~~~~~~~~~~~^
module_list,
^^^^^^^^^^^^
...<4 lines>...
language=language,
^^^^^^^^^^^^^^^^^^
aliases=aliases)
^^^^^^^^^^^^^^^^
File "~/.cache/uv/builds-v0/.tmpMscKeN/lib/python3.13/site-packages/Cython/Build/Dependencies.py", line 845, in create_extension_list
for file in nonempty(sorted(extended_iglob(filepattern)), "'%s' doesn't match any files" % filepattern):
~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~/.cache/uv/builds-v0/.tmpMscKeN/lib/python3.13/site-packages/Cython/Build/Dependencies.py", line 117, in nonempty
raise ValueError(error_msg)
ValueError: 'src/hello/*.pyx' doesn't match any files
error: Build backend failed to determine requirements with `build_wheel()` (exit status: 1) I do
then % uv build
Building source distribution...
running egg_info
writing src/hello.egg-info/PKG-INFO
writing dependency_links to src/hello.egg-info/dependency_links.txt
writing requirements to src/hello.egg-info/requires.txt
writing top-level names to src/hello.egg-info/top_level.txt
reading manifest file 'src/hello.egg-info/SOURCES.txt'
writing manifest file 'src/hello.egg-info/SOURCES.txt'
running sdist
running egg_info
writing src/hello.egg-info/PKG-INFO
writing dependency_links to src/hello.egg-info/dependency_links.txt
writing requirements to src/hello.egg-info/requires.txt
writing top-level names to src/hello.egg-info/top_level.txt
reading manifest file 'src/hello.egg-info/SOURCES.txt'
writing manifest file 'src/hello.egg-info/SOURCES.txt'
running check
creating hello-0.1.0
creating hello-0.1.0/src/hello
creating hello-0.1.0/src/hello.egg-info
copying files to hello-0.1.0...
copying README.md -> hello-0.1.0
copying pyproject.toml -> hello-0.1.0
copying setup.py -> hello-0.1.0
copying src/hello/__init__.py -> hello-0.1.0/src/hello
copying src/hello/hello.c -> hello-0.1.0/src/hello
copying src/hello/py.typed -> hello-0.1.0/src/hello
copying src/hello.egg-info/PKG-INFO -> hello-0.1.0/src/hello.egg-info
copying src/hello.egg-info/SOURCES.txt -> hello-0.1.0/src/hello.egg-info
copying src/hello.egg-info/dependency_links.txt -> hello-0.1.0/src/hello.egg-info
copying src/hello.egg-info/requires.txt -> hello-0.1.0/src/hello.egg-info
copying src/hello.egg-info/top_level.txt -> hello-0.1.0/src/hello.egg-info
copying src/hello.egg-info/SOURCES.txt -> hello-0.1.0/src/hello.egg-info
Writing hello-0.1.0/setup.cfg
Creating tar archive
removing 'hello-0.1.0' (and everything under it)
Building wheel from source distribution...
Traceback (most recent call last):
File "<string>", line 14, in <module>
requires = get_requires_for_build({})
File "~/.cache/uv/builds-v0/.tmpL0cVDL/lib/python3.13/site-packages/setuptools/build_meta.py", line 332, in get_requires_for_build_wheel
return self._get_build_requires(config_settings, requirements=[])
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~/.cache/uv/builds-v0/.tmpL0cVDL/lib/python3.13/site-packages/setuptools/build_meta.py", line 302, in _get_build_requires
self.run_setup()
~~~~~~~~~~~~~~^^
File "~/.cache/uv/builds-v0/.tmpL0cVDL/lib/python3.13/site-packages/setuptools/build_meta.py", line 318, in run_setup
exec(code, locals())
~~~~^^^^^^^^^^^^^^^^
File "<string>", line 6, in <module>
sys.path = [] + sys.path
^^^^^^^^
File "~/.cache/uv/builds-v0/.tmpL0cVDL/lib/python3.13/site-packages/Cython/Build/Dependencies.py", line 1010, in cythonize
module_list, module_metadata = create_extension_list(
~~~~~~~~~~~~~~~~~~~~~^
module_list,
^^^^^^^^^^^^
...<4 lines>...
language=language,
^^^^^^^^^^^^^^^^^^
aliases=aliases)
^^^^^^^^^^^^^^^^
File "~/.cache/uv/builds-v0/.tmpL0cVDL/lib/python3.13/site-packages/Cython/Build/Dependencies.py", line 845, in create_extension_list
for file in nonempty(sorted(extended_iglob(filepattern)), "'%s' doesn't match any files" % filepattern):
~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~/.cache/uv/builds-v0/.tmpL0cVDL/lib/python3.13/site-packages/Cython/Build/Dependencies.py", line 117, in nonempty
raise ValueError(error_msg)
ValueError: 'src/hello/*.pyx' doesn't match any files
error: Build backend failed to determine requirements with `build_wheel()` (exit status: 1) |
Do you have a MANIFEST.in? .pyx isn't included by setuptools by default, IIRC. |
@henryiii Thanks, that worked! I added a
and then That's great. I think including such recipes in the |
You don't have to do this if you use scikit-build-core. :) I'm not sure if uv needs/should document every quirk of every build backend and binding tool? Also, you don't need Cython as a dependency, just placing it in the |
henryiii wrote:
I disagree... the more you document the quirks, the easier it is to get people to use |
We can't possibly maintain the quirks of all the build backends — we don't maintain them and can't always update the documentation when things change. |
FYI, here's the procedure for scikit-build-core: uv init --lib --build-backend scikit hello pyproject.toml: [project]
name = "hello"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "Henry Schreiner", email = "[email protected]" }
]
requires-python = ">=3.7"
dependencies = []
[tool.scikit-build]
minimum-version = "build-system.requires"
build-dir = "build/{wheel_tag}"
[build-system]
requires = ["scikit-build-core>=0.10", "cython", "cython-cmake"]
build-backend = "scikit_build_core.build" CMakeLists.txt: cmake_minimum_required(VERSION 3.21)
project(${SKBUILD_PROJECT_NAME} LANGUAGES C)
find_package(
Python
COMPONENTS Interpreter Development.Module
REQUIRED)
include(UseCython)
cython_transpile(src/hello/hello.pyx LANGUAGE C OUTPUT_VARIABLE hello_c)
python_add_library(hello MODULE WITH_SOABI "${hello_c}")
install(TARGETS hello DESTINATION ${SKBUILD_PROJECT_NAME}) (PS: I really don't like the build-system at the bottom. Would it be acceptable to move it to the top, more like how |
@zanieb wrote
I understand, but building c-extensions, cython, cffi, and even pybind11 and nanobind extensions are now pretty common. Having a place where recipes can be added and updated is better than not having it, especially if you are open to community PRs. |
That's inclusion in the SDist, not wheel. Wheel inclusion is controlled by package data in setuptools. |
Thanks very much. You are right again 😄 As per this page in the setuptools docs, If one adds the following to [tool.setuptools]
include-package-data = false |
Does
uv
support building C/cython extensions, and if so is it documented somewhere? This would be similar to what's described herehttps://setuptools.pypa.io/en/latest/userguide/ext_modules.html
Or, alternatively, would
uv
work if thebuild-backend
is set to"setuptools.build_meta"
?In my opinion the
setuptools
pyproject.toml
approach is somewhat lacking. In my experience compiling a C extension usually requires more than just defining the extension parameters and files with static text. A.py
file that allows to configure things at runtime (for example depending on the OS) is much more useful. This would be similar topoetry
's workable but never documentedThe text was updated successfully, but these errors were encountered: