1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
|
from abc import ABC, abstractmethod
import logging
from setuptools import Command, Distribution
from setuptools.errors import PlatformError
from typing import List, Optional
from .extension import RustExtension
from .rustc_info import get_rust_version
logger = logging.getLogger(__name__)
class RustCommand(Command, ABC):
"""Abstract base class for commands which interact with Rust Extensions."""
# Types for distutils variables which exist on all commands but seem to be
# missing from https://github.com/python/typeshed/blob/master/stdlib/distutils/cmd.pyi
distribution: Distribution
verbose: int
def initialize_options(self) -> None:
self.extensions: List[RustExtension] = []
def finalize_options(self) -> None:
extensions: Optional[List[RustExtension]] = getattr(
self.distribution, "rust_extensions", None
)
if extensions is None:
# extensions is None if the setup.py file did not contain
# rust_extensions keyword; just no-op if this is the case.
return
if not isinstance(extensions, list):
ty = type(extensions)
raise ValueError(
"expected list of RustExtension objects for rust_extensions "
f"argument to setup(), got `{ty}`"
)
for i, extension in enumerate(extensions):
if not isinstance(extension, RustExtension):
ty = type(extension)
raise ValueError(
"expected RustExtension object for rust_extensions "
f"argument to setup(), got `{ty}` at position {i}"
)
# Extensions have been verified to be at the correct type
self.extensions = extensions
def run(self) -> None:
if not self.extensions:
logger.info("%s: no rust_extensions defined", self.get_command_name())
return
all_optional = all(ext.optional for ext in self.extensions)
try:
version = get_rust_version()
if version is None:
min_version = max( # type: ignore[type-var]
filter(
lambda version: version is not None,
(ext.get_rust_version() for ext in self.extensions),
),
default=None,
)
raise PlatformError(
"can't find Rust compiler\n\n"
"If you are using an outdated pip version, it is possible a "
"prebuilt wheel is available for this package but pip is not able "
"to install from it. Installing from the wheel would avoid the "
"need for a Rust compiler.\n\n"
"To update pip, run:\n\n"
" pip install --upgrade pip\n\n"
"and then retry package installation.\n\n"
"If you did intend to build this package from source, try "
"installing a Rust compiler from your system package manager and "
"ensure it is on the PATH during installation. Alternatively, "
"rustup (available at https://rustup.rs) is the recommended way "
"to download and update the Rust compiler toolchain."
+ (
f"\n\nThis package requires Rust {min_version}."
if min_version is not None
else ""
)
)
except PlatformError as e:
if not all_optional:
raise
else:
print(str(e))
return
for ext in self.extensions:
try:
rust_version = ext.get_rust_version()
if rust_version is not None and version not in rust_version:
raise PlatformError(
f"Rust {version} does not match extension requirement {rust_version}"
)
self.run_for_extension(ext)
except Exception as e:
if not ext.optional:
raise
else:
command_name = self.get_command_name()
print(f"{command_name}: optional Rust extension {ext.name} failed")
print(str(e))
@abstractmethod
def run_for_extension(self, extension: RustExtension) -> None:
...
|