Skip to content

Commit

Permalink
Draft: allows to use Maturin with UV
Browse files Browse the repository at this point in the history
  • Loading branch information
Tpt committed Oct 4, 2024
1 parent e5c0be7 commit cc7f961
Show file tree
Hide file tree
Showing 15 changed files with 148 additions and 15 deletions.
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)
13 changes: 13 additions & 0 deletions examples/rust-uv-project/.kraken.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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()
mypy_task = python.mypy(version_spec="==1.10.0")
python.mypy_stubtest(package="rust_uv_project", ignore_missing_stubs=True, mypy_pex_bin=mypy_task.mypy_pex_bin.get())
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

0 comments on commit cc7f961

Please sign in to comment.