From 06d236045b6a69c94c68f255dc683219ec833c83 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Fri, 7 Jun 2024 00:15:26 -0400 Subject: [PATCH 1/3] Delay imports of non-stdlib dependencies until time of use This is a bit of a hack, but we do this to ensure that when setuptools loads entrypoint based hooks it cannot (will not?) crash. The issue is that setuptools plugins are autoloaded, whether any given project uses them at all or not. So if setuptools-rust is installed, setuptools always tries to use it, and crashes if setuptools-rust is broken. Of course, setuptools-rust can't be broken, because it's a wonderful project. BUT. As it happens, third-party vendors providing setuptools-rust can get into a situation where multiple packages need to be installed, including setuptools-rust, and also build yet other packages from source. In the middle of this, setuptools-rust itself could be installed but in "half-configured" state, i.e. its dependencies were queued for afterwards due to complicated dependency graph magic. In such a scenario, it should be nominally all right to have an inert package installed, since if nothing actually uses setuptools-rust it doesn't need to *work* yet. And in fact, it is all right, as long as setuptools can import the autoloaded plugin hooks (and do nothing with them). Bug: https://bugs.gentoo.org/933553 --- setuptools_rust/extension.py | 7 ++++++- setuptools_rust/rustc_info.py | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/setuptools_rust/extension.py b/setuptools_rust/extension.py index 2d6e5aa7..9ba1cf5f 100644 --- a/setuptools_rust/extension.py +++ b/setuptools_rust/extension.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json import os import re @@ -14,11 +16,13 @@ NewType, Optional, Sequence, + TYPE_CHECKING, Union, cast, ) -from semantic_version import SimpleSpec +if TYPE_CHECKING: + from semantic_version import SimpleSpec from ._utils import format_called_process_error @@ -185,6 +189,7 @@ def get_rust_version(self) -> Optional[SimpleSpec]: # type: ignore[no-any-unimp if self.rust_version is None: return None try: + from semantic_version import SimpleSpec return SimpleSpec(self.rust_version) except ValueError: raise SetupError( diff --git a/setuptools_rust/rustc_info.py b/setuptools_rust/rustc_info.py index 070be0c1..d1d47484 100644 --- a/setuptools_rust/rustc_info.py +++ b/setuptools_rust/rustc_info.py @@ -1,15 +1,19 @@ +from __future__ import annotations + import subprocess from setuptools.errors import PlatformError from functools import lru_cache -from typing import Dict, List, NewType, Optional +from typing import Dict, List, NewType, Optional, TYPE_CHECKING -from semantic_version import Version +if TYPE_CHECKING: + from semantic_version import Version def get_rust_version() -> Optional[Version]: # type: ignore[no-any-unimported] try: # first line of rustc -Vv is something like # rustc 1.61.0 (fe5b13d68 2022-05-18) + from semantic_version import Version return Version(_rust_version().split(" ")[1]) except (subprocess.CalledProcessError, OSError): return None From a98e8a8d31d30c5d304e9ee7b48ba739946dea6f Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Fri, 7 Jun 2024 00:34:59 -0400 Subject: [PATCH 2/3] Try extra hard to pick up an existing tomli library from setuptools Since setuptools-rust already depends on setuptools, it is reasonable to assume that even if tomli isn't installed, setuptools is. And setuptools includes a vendored copy of tomli. If the copy in setuptools has been devendored, it will be available via "tomli". If it isn't devendored, it will be available via "setuptools.extern.tomli" unless setuptools changes their vendoring approach which has lasted many years so far. Either way, we are sure to have a fallback tomli without explicitly depending on one, which means one less dependency to install in the common case. --- pyproject.toml | 1 - setuptools_rust/setuptools_ext.py | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cae05366..148d85bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ classifiers = [ dependencies = [ "setuptools>=62.4", "semantic_version>=2.8.2,<3", - 'tomli>=1.2.1; python_version<"3.11"' ] [project.entry-points."distutils.commands"] diff --git a/setuptools_rust/setuptools_ext.py b/setuptools_rust/setuptools_ext.py index d4a02049..0ea6f588 100644 --- a/setuptools_rust/setuptools_ext.py +++ b/setuptools_rust/setuptools_ext.py @@ -26,7 +26,10 @@ if sys.version_info[:2] >= (3, 11): from tomllib import load as toml_load else: - from tomli import load as toml_load + try: + from tomli import load as toml_load + except ImportError: + from setuptools.extern.tomli import load as toml_load logger = logging.getLogger(__name__) From c46d3559f8eb7450ed9137ee37a6e5e4e85d1d57 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 11 Jun 2024 08:38:36 +0100 Subject: [PATCH 3/3] changelog entry --- CHANGELOG.md | 1 + setuptools_rust/extension.py | 1 + setuptools_rust/rustc_info.py | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2a82df7..8499ddc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### Changed - Use the base interpreter path when running inside a virtual environment to avoid recompilation when switching between virtual environments. [#429](https://github.com/PyO3/setuptools-rust/pull/429) + - Delay import of dependencies until use to avoid import errors during a partially complete install when multiple packages are installing at once. [#437](https://github.com/PyO3/setuptools-rust/pull/437) ## 1.9.0 (2024-02-24) ### Changed diff --git a/setuptools_rust/extension.py b/setuptools_rust/extension.py index 9ba1cf5f..e305960e 100644 --- a/setuptools_rust/extension.py +++ b/setuptools_rust/extension.py @@ -190,6 +190,7 @@ def get_rust_version(self) -> Optional[SimpleSpec]: # type: ignore[no-any-unimp return None try: from semantic_version import SimpleSpec + return SimpleSpec(self.rust_version) except ValueError: raise SetupError( diff --git a/setuptools_rust/rustc_info.py b/setuptools_rust/rustc_info.py index d1d47484..58dfd42a 100644 --- a/setuptools_rust/rustc_info.py +++ b/setuptools_rust/rustc_info.py @@ -14,6 +14,7 @@ def get_rust_version() -> Optional[Version]: # type: ignore[no-any-unimported] # first line of rustc -Vv is something like # rustc 1.61.0 (fe5b13d68 2022-05-18) from semantic_version import Version + return Version(_rust_version().split(" ")[1]) except (subprocess.CalledProcessError, OSError): return None