Skip to content
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

Allows to use Maturin with UV #290

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changelog/_unreleased.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[[entries]]
id = "cbae7d73-09aa-442f-b099-27a4ae23b3c3"
type = "improvement"
description = "Allows to use uv with maturin"
author = "thomas.pellissier-tanon@helsing.ai"
12 changes: 12 additions & 0 deletions examples/rust-uv-project-consumer/.kraken.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import os

from kraken.std import python

python.python_settings(always_use_managed_env=True).add_package_index(
alias="local",
index_url=os.environ["LOCAL_PACKAGE_INDEX"],
credentials=(os.environ["LOCAL_USER"], os.environ["LOCAL_PASSWORD"]),
)
python.install()
python.mypy(version_spec="==1.10.0")
python.update_pyproject_task()
11 changes: 11 additions & 0 deletions examples/rust-uv-project-consumer/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[project]
name = "rust-uv-project-consumer"
version = "0.1.0"
dependencies = [
"rust-uv-project"
]
requires-python = "~=3.7"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from rust_uv_project import sum_as_string

sum_as_string(1, 2)
12 changes: 12 additions & 0 deletions examples/rust-uv-project/.kraken.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import os

from kraken.std import python

python.python_settings(always_use_managed_env=True).add_package_index(
alias="local",
index_url=os.environ["LOCAL_PACKAGE_INDEX"],
credentials=(os.environ["LOCAL_USER"], os.environ["LOCAL_PASSWORD"]),
)
python.install()
python.mypy(version_spec="==1.10.0")
python.publish(package_index="local", distributions=python.build(as_version="0.1.0").output_files)
11 changes: 11 additions & 0 deletions examples/rust-uv-project/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "rust-uv-project"
version = "0.1.0"
edition = "2021"

[lib]
name = "rust_uv_project"
crate-type = ["cdylib"]

[dependencies]
pyo3 = { version = "0.19", features = ["extension-module"] }
10 changes: 10 additions & 0 deletions examples/rust-uv-project/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[project]
name = "rust-uv-project"
version = "0.1.0"
description = ""
authors = []
requires-python = ">=3.8"

[build-system]
requires = ["maturin~=1.0"]
build-backend = "maturin"
1 change: 1 addition & 0 deletions examples/rust-uv-project/rust_uv_project.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
def sum_as_string(a: int, b: int) -> str: ...
Empty file.
10 changes: 10 additions & 0 deletions examples/rust-uv-project/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use pyo3::prelude::*;

#[pymodule]
fn rust_uv_project(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
#[pyfn(m)]
fn sum_as_string(a: usize, b: usize) -> String {
(a + b).to_string()
}
Ok(())
}
8 changes: 6 additions & 2 deletions kraken-build/src/kraken/std/python/buildsystem/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,18 @@ def detect_build_system(project_directory: Path) -> PythonBuildSystem | None:
return PoetryPythonBuildSystem(project_directory)

if "maturin" in pyproject_content:
if "[tool.poetry]" in pyproject_content:
if "[tool.poetry" in pyproject_content:
from .maturin import MaturinPoetryPythonBuildSystem

return MaturinPoetryPythonBuildSystem(project_directory)
else:
elif "[tool.pdm" in pyproject_content:
from .maturin import MaturinPdmPythonBuildSystem

return MaturinPdmPythonBuildSystem(project_directory)
else:
from .maturin import MaturinUvPythonBuildSystem

return MaturinUvPythonBuildSystem(project_directory)

if "pdm" in pyproject_content:
from .pdm import PDMPythonBuildSystem
Expand Down
69 changes: 57 additions & 12 deletions kraken-build/src/kraken/std/python/buildsystem/maturin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import subprocess as sp
from collections.abc import Callable, Collection
from dataclasses import dataclass
from itertools import chain
from pathlib import Path

from kraken.common.path import is_relative_to
Expand All @@ -18,6 +19,7 @@
from ..settings import PythonSettings
from . import ManagedEnvironment
from .pdm import PDMManagedEnvironment, PDMPythonBuildSystem
from .uv import UvPythonBuildSystem
from .poetry import PoetryManagedEnvironment, PoetryPyprojectHandler, PoetryPythonBuildSystem

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -55,7 +57,10 @@ class MaturinZigTarget:

class _MaturinBuilder:
def __init__(
self, entry_point: str, get_pyproject_reader: Callable[[TomlFile], PyprojectHandler], project_directory: Path
self,
entry_point: Collection[str],
get_pyproject_reader: Callable[[TomlFile], PyprojectHandler],
project_directory: Path,
) -> None:
self._entry_point = entry_point
self._get_pyproject_reader = get_pyproject_reader
Expand Down Expand Up @@ -86,13 +91,12 @@ def build(self, output_directory: Path) -> list[Path]:
# We run the actual build
build_env = {**os.environ, **self._build_env}
if self._default_build:
command = [self._entry_point, "run", "maturin", "build", "--release"]
command = [*self._entry_point, "maturin", "build", "--release"]
logger.info("%s", command)
sp.check_call(command, cwd=self._project_directory, env=build_env)
for target in self._zig_targets:
command = [
self._entry_point,
"run",
*self._entry_point,
"maturin",
"build",
"--release",
Expand Down Expand Up @@ -171,7 +175,7 @@ class MaturinPoetryPythonBuildSystem(PoetryPythonBuildSystem):

def __init__(self, project_directory: Path) -> None:
super().__init__(project_directory)
self._builder = _MaturinBuilder("poetry", self.get_pyproject_reader, self.project_directory)
self._builder = _MaturinBuilder(["poetry", "run"], self.get_pyproject_reader, self.project_directory)

def disable_default_build(self) -> None:
self._builder.disable_default_build()
Expand Down Expand Up @@ -201,9 +205,6 @@ def update_pyproject(self, settings: PythonSettings, pyproject: TomlFile) -> Non
def build(self, output_directory: Path) -> list[Path]:
return self._builder.build(output_directory)

def get_lockfile(self) -> Path | None:
return self.project_directory / "poetry.lock"


class MaturinPoetryManagedEnvironment(PoetryManagedEnvironment):
def install(self, settings: PythonSettings) -> None:
Expand Down Expand Up @@ -233,7 +234,7 @@ class MaturinPdmPythonBuildSystem(PDMPythonBuildSystem):

def __init__(self, project_directory: Path) -> None:
super().__init__(project_directory)
self._builder = _MaturinBuilder("pdm", self.get_pyproject_reader, self.project_directory)
self._builder = _MaturinBuilder(["pdm", "run"], self.get_pyproject_reader, self.project_directory)

def disable_default_build(self) -> None:
self._builder.disable_default_build()
Expand All @@ -253,10 +254,54 @@ def get_managed_environment(self) -> ManagedEnvironment:
def build(self, output_directory: Path) -> list[Path]:
return self._builder.build(output_directory)

def get_lockfile(self) -> Path | None:
return self.project_directory / "pdm.lock"


class MaturinPdmManagedEnvironment(PDMManagedEnvironment):
def always_install(self) -> bool:
return True


class MaturinUvPythonBuildSystem(UvPythonBuildSystem):
"""A maturin-backed version of the UV build system, that invokes the maturin build-backend.
Can be enabled by adding the following to the local pyproject.yaml:
```toml
[build-system]
requires = ["maturin~=1.0"]
build-backend = "maturin"
```
"""

name = "Maturin UV"

def __init__(self, project_directory: Path, uv_bin: Path | None = None) -> None:
super().__init__(project_directory, uv_bin)
# We use the build requirement to do custom Maturin builds
self._builder = _MaturinBuilder(
[
self.uv_bin,
"tool",
"run",
*chain.from_iterable(("--with", r) for r in self._get_build_requirements()),
],
self.get_pyproject_reader,
self.project_directory,
)

def disable_default_build(self) -> None:
self._builder.disable_default_build()

def enable_zig_build(self, targets: Collection[MaturinZigTarget]) -> None:
"""
:param targets: Collection of MaturinTargets to cross-compile to using zig.
"""
self._builder.enable_zig_build(targets)

def add_build_environment_variable(self, key: str, value: str) -> None:
self._builder.add_build_environment_variable(key, value)

def build(self, output_directory: Path) -> list[Path]:
return self._builder.build(output_directory)

def _get_build_requirements(self) -> Collection[str]:
pyproject_toml = self.project_directory / "pyproject.toml"
toml = TomlFile.read(pyproject_toml)
return toml.get("build-system", {}).get("requires", []) # type: ignore[no-any-return]
10 changes: 9 additions & 1 deletion kraken-build/tests/kraken_std/integration/python/test_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,15 @@ def pypiserver(docker_service_manager: DockerServiceManager) -> str:

@pytest.mark.parametrize(
"project_dir",
["poetry-project", "slap-project", "pdm-project", "rust-poetry-project", "rust-pdm-project", "uv-project"],
[
"poetry-project",
"slap-project",
"pdm-project",
"uv-project",
"rust-poetry-project",
"rust-pdm-project",
"rust-uv-project",
],
)
@unittest.mock.patch.dict(os.environ, {})
def test__python_project_install_lint_and_publish(
Expand Down
Loading