From 7cd9289ef2f89c678e83b30fa182be4b0af179a8 Mon Sep 17 00:00:00 2001 From: konstin Date: Wed, 1 Jan 2025 21:53:50 +0100 Subject: [PATCH 1/3] Self contained rust boostrapping --- maturin/__init__.py | 31 ++++++++++++++++++++++++------- maturin/boostrap.py | 31 +++++++++++++++++++++++++++++++ pyproject.toml | 7 ++++--- setup.py | 12 +++++++++++- 4 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 maturin/boostrap.py diff --git a/maturin/__init__.py b/maturin/__init__.py index 3b69ba647..9a16c1476 100644 --- a/maturin/__init__.py +++ b/maturin/__init__.py @@ -65,6 +65,17 @@ def _additional_pep517_args() -> List[str]: return [] +def _get_env() -> Optional[Dict[str, str]]: + if not os.environ.get("MATURIN_NO_INSTALL_RUST") and not shutil.which("cargo"): + from puccinialin import setup_rust + + print("Rust not found, installing into a temporary directory") + extra_env = setup_rust() + return {**os.environ, **extra_env} + else: + return None + + # noinspection PyUnusedLocal def _build_wheel( wheel_directory: str, @@ -97,7 +108,7 @@ def _build_wheel( print("Running `{}`".format(" ".join(command))) sys.stdout.flush() - result = subprocess.run(command, stdout=subprocess.PIPE) + result = subprocess.run(command, stdout=subprocess.PIPE, env=_get_env()) sys.stdout.buffer.write(result.stdout) sys.stdout.flush() if result.returncode != 0: @@ -125,7 +136,7 @@ def build_sdist(sdist_directory: str, config_settings: Optional[Mapping[str, Any print("Running `{}`".format(" ".join(command))) sys.stdout.flush() - result = subprocess.run(command, stdout=subprocess.PIPE) + result = subprocess.run(command, stdout=subprocess.PIPE, env=_get_env()) sys.stdout.buffer.write(result.stdout) sys.stdout.flush() if result.returncode != 0: @@ -138,9 +149,12 @@ def build_sdist(sdist_directory: str, config_settings: Optional[Mapping[str, Any # noinspection PyUnusedLocal def get_requires_for_build_wheel(config_settings: Optional[Mapping[str, Any]] = None) -> List[str]: if get_config().get("bindings") == "cffi": - return ["cffi"] + requirements = ["cffi"] else: - return [] + requirements = [] + if not os.environ.get("MATURIN_NO_INSTALL_RUST") and not shutil.which("cargo"): + requirements += ["puccinialin"] + return requirements # noinspection PyUnusedLocal @@ -158,7 +172,10 @@ def build_editable( # noinspection PyUnusedLocal def get_requires_for_build_sdist(config_settings: Optional[Mapping[str, Any]] = None) -> List[str]: - return [] + requirements = [] + if not os.environ.get("MATURIN_NO_INSTALL_RUST") and not shutil.which("cargo"): + requirements += ["puccinialin"] + return requirements # noinspection PyUnusedLocal @@ -168,7 +185,7 @@ def prepare_metadata_for_build_wheel( print("Checking for Rust toolchain....") is_cargo_installed = False try: - output = subprocess.check_output(["cargo", "--version"]).decode("utf-8", "ignore") + output = subprocess.check_output(["cargo", "--version"], env=_get_env()).decode("utf-8", "ignore") if "cargo" in output: is_cargo_installed = True except (FileNotFoundError, SubprocessError): @@ -200,7 +217,7 @@ def prepare_metadata_for_build_wheel( print("Running `{}`".format(" ".join(command))) try: - _output = subprocess.check_output(command) + _output = subprocess.check_output(command, env=_get_env()) except subprocess.CalledProcessError as e: sys.stderr.write(f"Error running maturin: {e}\n") sys.exit(1) diff --git a/maturin/boostrap.py b/maturin/boostrap.py new file mode 100644 index 000000000..a87f2dc19 --- /dev/null +++ b/maturin/boostrap.py @@ -0,0 +1,31 @@ +"""Support installing rust before compiling (bootstrapping) maturin. + +Installing a package that uses maturin as build backend on a platform without maturin +binaries, we install rust in a cache directory if the user doesn't have a rust +installation already. Since this bootstrapping requires more dependencies but is only +required if rust is missing, we check if cargo is present before requesting those +dependencies. + +https://setuptools.pypa.io/en/stable/build_meta.html#dynamic-build-dependencies-and-other-build-meta-tweaks +""" + +from __future__ import annotations + +import os +import shutil +from typing import Any + +# noinspection PyUnresolvedReferences +from setuptools.build_meta import * # noqa:F403 + + +def get_requires_for_build_wheel(_config_settings: dict[str, Any] = None) -> list[str]: + if not os.environ.get("MATURIN_NO_INSTALL_RUST") and not shutil.which("cargo"): + return ["puccinialin>=0.1,<0.2"] + return [] + + +def get_requires_for_build_sdist(_config_settings: dict[str, Any] = None) -> list[str]: + if not os.environ.get("MATURIN_NO_INSTALL_RUST") and not shutil.which("cargo"): + return ["puccinialin>=0.1,<0.2"] + return [] diff --git a/pyproject.toml b/pyproject.toml index c08f06178..19ac1d89d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,8 @@ # Workaround to bootstrap maturin on non-manylinux platforms [build-system] -requires = ["setuptools", "wheel>=0.36.2", "tomli>=1.1.0 ; python_version<'3.11'", "setuptools-rust>=1.4.0"] -build-backend = "setuptools.build_meta" +requires = ["setuptools", "tomli>=1.1.0 ; python_version<'3.11'", "setuptools-rust @ git+https://github.com/PyO3/setuptools-rust#konsti/add-custom-env-vars"] +backend-path = ["maturin"] +build-backend = "boostrap" [project] name = "maturin" @@ -9,7 +10,7 @@ description = "Build and publish crates with pyo3, cffi and uniffi bindings as w authors = [{ name = "konstin", email = "konstin@mailbox.org" }] readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.7" -license = {text = "MIT OR Apache-2.0"} +license = { text = "MIT OR Apache-2.0" } classifiers = [ "Topic :: Software Development :: Build Tools", "Programming Language :: Rust", diff --git a/setup.py b/setup.py index 1ac2cef25..5d9fd2854 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ import os import shlex +import shutil try: import tomllib @@ -47,9 +48,18 @@ def finalize_options(self): if os.getenv("MATURIN_SETUP_ARGS"): cargo_args = shlex.split(os.getenv("MATURIN_SETUP_ARGS", "")) +if not os.environ.get("MATURIN_NO_INSTALL_RUST") and not shutil.which("cargo"): + from puccinialin import setup_rust + + print("Rust not found, installing into a temporary directory") + extra_env = setup_rust() + env = {**os.environ, **extra_env} +else: + env = None + setup( version=version, cmdclass={"bdist_wheel": bdist_wheel}, - rust_extensions=[RustBin("maturin", args=cargo_args, cargo_manifest_args=["--locked"])], + rust_extensions=[RustBin("maturin", args=cargo_args, cargo_manifest_args=["--locked"], env=env)], zip_safe=False, ) From 9f924a6997a528e6f44f720e93b65ae9bb446ad3 Mon Sep 17 00:00:00 2001 From: konstin Date: Wed, 1 Jan 2025 23:14:29 +0100 Subject: [PATCH 2/3] . --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 19ac1d89d..fa512d832 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ # Workaround to bootstrap maturin on non-manylinux platforms [build-system] -requires = ["setuptools", "tomli>=1.1.0 ; python_version<'3.11'", "setuptools-rust @ git+https://github.com/PyO3/setuptools-rust#konsti/add-custom-env-vars"] +requires = ["setuptools", "tomli>=1.1.0 ; python_version<'3.11'", "setuptools-rust @ git+https://github.com/PyO3/setuptools-rust@konsti/add-custom-env-vars"] backend-path = ["maturin"] build-backend = "boostrap" From e6a32a3ae1fa2af758ada24ea59a5ecee033465a Mon Sep 17 00:00:00 2001 From: konstin Date: Fri, 3 Jan 2025 09:35:50 +0100 Subject: [PATCH 3/3] mypy --- maturin/boostrap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maturin/boostrap.py b/maturin/boostrap.py index a87f2dc19..0ab10b65e 100644 --- a/maturin/boostrap.py +++ b/maturin/boostrap.py @@ -19,13 +19,13 @@ from setuptools.build_meta import * # noqa:F403 -def get_requires_for_build_wheel(_config_settings: dict[str, Any] = None) -> list[str]: +def get_requires_for_build_wheel(_config_settings: dict[str, Any] | None = None) -> list[str]: if not os.environ.get("MATURIN_NO_INSTALL_RUST") and not shutil.which("cargo"): return ["puccinialin>=0.1,<0.2"] return [] -def get_requires_for_build_sdist(_config_settings: dict[str, Any] = None) -> list[str]: +def get_requires_for_build_sdist(_config_settings: dict[str, Any] | None = None) -> list[str]: if not os.environ.get("MATURIN_NO_INSTALL_RUST") and not shutil.which("cargo"): return ["puccinialin>=0.1,<0.2"] return []