maturin
maturin's implementation of the PEP 517 interface. Calls maturin through subprocess
Currently, the "return value" of the rust implementation is the last line of stdout
On windows, apparently pip's subprocess handling sets stdout to some windows encoding (e.g. cp1252 on my machine), even though the terminal supports utf8. Writing directly to the binary stdout buffer avoids encoding errors due to maturin's emojis.
1#!/usr/bin/env python3 2""" 3maturin's implementation of the PEP 517 interface. Calls maturin through subprocess 4 5Currently, the "return value" of the rust implementation is the last line of stdout 6 7On windows, apparently pip's subprocess handling sets stdout to some windows encoding (e.g. cp1252 on my machine), 8even though the terminal supports utf8. Writing directly to the binary stdout buffer avoids encoding errors due to 9maturin's emojis. 10""" 11 12from __future__ import annotations 13 14import os 15import platform 16import shlex 17import shutil 18import struct 19import subprocess 20import sys 21from subprocess import SubprocessError 22from typing import Any, Dict, Mapping, List, Optional 23 24try: 25 import tomllib 26except ModuleNotFoundError: 27 import tomli as tomllib # type: ignore 28 29 30def get_config() -> Dict[str, str]: 31 with open("pyproject.toml", "rb") as fp: 32 pyproject_toml = tomllib.load(fp) 33 return pyproject_toml.get("tool", {}).get("maturin", {}) 34 35 36def get_maturin_pep517_args(config_settings: Optional[Mapping[str, Any]] = None) -> List[str]: 37 build_args = config_settings.get("build-args") if config_settings else None 38 if build_args is None: 39 env_args = os.getenv("MATURIN_PEP517_ARGS", "") 40 args = shlex.split(env_args) 41 elif isinstance(build_args, str): 42 args = shlex.split(build_args) 43 else: 44 args = build_args 45 return args 46 47 48def _get_sys_executable() -> str: 49 executable = sys.executable 50 if os.getenv("MATURIN_PEP517_USE_BASE_PYTHON") in {"1", "true"}: 51 # Use the base interpreter path when running inside a venv to avoid recompilation 52 # when switching between venvs 53 base_executable = getattr(sys, "_base_executable") 54 if base_executable and os.path.exists(base_executable): 55 executable = os.path.realpath(base_executable) 56 return executable 57 58 59def _additional_pep517_args() -> List[str]: 60 # Support building for 32-bit Python on x64 Windows 61 if platform.system().lower() == "windows" and platform.machine().lower() == "amd64": 62 pointer_width = struct.calcsize("P") * 8 63 if pointer_width == 32: 64 return ["--target", "i686-pc-windows-msvc"] 65 return [] 66 67 68# noinspection PyUnusedLocal 69def _build_wheel( 70 wheel_directory: str, 71 config_settings: Optional[Mapping[str, Any]] = None, 72 metadata_directory: Optional[str] = None, 73 editable: bool = False, 74) -> str: 75 # PEP 517 specifies that only `sys.executable` points to the correct 76 # python interpreter 77 base_command = [ 78 "maturin", 79 "pep517", 80 "build-wheel", 81 "-i", 82 _get_sys_executable(), 83 ] 84 options = _additional_pep517_args() 85 if editable: 86 options.append("--editable") 87 88 pep517_args = get_maturin_pep517_args(config_settings) 89 if pep517_args: 90 options.extend(pep517_args) 91 92 if "--compatibility" not in options and "--manylinux" not in options: 93 # default to off if not otherwise specified 94 options = ["--compatibility", "off", *options] 95 96 command = [*base_command, *options] 97 98 print("Running `{}`".format(" ".join(command))) 99 sys.stdout.flush() 100 result = subprocess.run(command, stdout=subprocess.PIPE) 101 sys.stdout.buffer.write(result.stdout) 102 sys.stdout.flush() 103 if result.returncode != 0: 104 sys.stderr.write(f"Error: command {command} returned non-zero exit status {result.returncode}\n") 105 sys.exit(1) 106 output = result.stdout.decode(errors="replace") 107 wheel_path = output.strip().splitlines()[-1] 108 filename = os.path.basename(wheel_path) 109 shutil.copy2(wheel_path, os.path.join(wheel_directory, filename)) 110 return filename 111 112 113# noinspection PyUnusedLocal 114def build_wheel( 115 wheel_directory: str, 116 config_settings: Optional[Mapping[str, Any]] = None, 117 metadata_directory: Optional[str] = None, 118) -> str: 119 return _build_wheel(wheel_directory, config_settings, metadata_directory) 120 121 122# noinspection PyUnusedLocal 123def build_sdist(sdist_directory: str, config_settings: Optional[Mapping[str, Any]] = None) -> str: 124 command = ["maturin", "pep517", "write-sdist", "--sdist-directory", sdist_directory] 125 126 print("Running `{}`".format(" ".join(command))) 127 sys.stdout.flush() 128 result = subprocess.run(command, stdout=subprocess.PIPE) 129 sys.stdout.buffer.write(result.stdout) 130 sys.stdout.flush() 131 if result.returncode != 0: 132 sys.stderr.write(f"Error: command {command} returned non-zero exit status {result.returncode}\n") 133 sys.exit(1) 134 output = result.stdout.decode(errors="replace") 135 return output.strip().splitlines()[-1] 136 137 138# noinspection PyUnusedLocal 139def get_requires_for_build_wheel(config_settings: Optional[Mapping[str, Any]] = None) -> List[str]: 140 if get_config().get("bindings") == "cffi": 141 return ["cffi"] 142 else: 143 return [] 144 145 146# noinspection PyUnusedLocal 147def build_editable( 148 wheel_directory: str, 149 config_settings: Optional[Mapping[str, Any]] = None, 150 metadata_directory: Optional[str] = None, 151) -> str: 152 return _build_wheel(wheel_directory, config_settings, metadata_directory, editable=True) 153 154 155# Requirements to build an editable are the same as for a wheel 156get_requires_for_build_editable = get_requires_for_build_wheel 157 158 159# noinspection PyUnusedLocal 160def get_requires_for_build_sdist(config_settings: Optional[Mapping[str, Any]] = None) -> List[str]: 161 return [] 162 163 164# noinspection PyUnusedLocal 165def prepare_metadata_for_build_wheel( 166 metadata_directory: str, config_settings: Optional[Mapping[str, Any]] = None 167) -> str: 168 print("Checking for Rust toolchain....") 169 is_cargo_installed = False 170 try: 171 output = subprocess.check_output(["cargo", "--version"]).decode("utf-8", "ignore") 172 if "cargo" in output: 173 is_cargo_installed = True 174 except (FileNotFoundError, SubprocessError): 175 pass 176 177 if not is_cargo_installed: 178 sys.stderr.write( 179 "\nCargo, the Rust package manager, is not installed or is not on PATH.\n" 180 "This package requires Rust and Cargo to compile extensions. Install it through\n" 181 "the system's package manager or via https://rustup.rs/\n\n" 182 ) 183 sys.exit(1) 184 185 command = [ 186 "maturin", 187 "pep517", 188 "write-dist-info", 189 "--metadata-directory", 190 metadata_directory, 191 # PEP 517 specifies that only `sys.executable` points to the correct 192 # python interpreter 193 "--interpreter", 194 _get_sys_executable(), 195 ] 196 command.extend(_additional_pep517_args()) 197 pep517_args = get_maturin_pep517_args(config_settings) 198 if pep517_args: 199 command.extend(pep517_args) 200 201 print("Running `{}`".format(" ".join(command))) 202 try: 203 _output = subprocess.check_output(command) 204 except subprocess.CalledProcessError as e: 205 sys.stderr.write(f"Error running maturin: {e}\n") 206 sys.exit(1) 207 sys.stdout.buffer.write(_output) 208 sys.stdout.flush() 209 output = _output.decode(errors="replace") 210 return output.strip().splitlines()[-1] 211 212 213# Metadata for editable are the same as for a wheel 214prepare_metadata_for_build_editable = prepare_metadata_for_build_wheel
def
get_config() -> Dict[str, str]:
def
get_maturin_pep517_args(config_settings: Optional[Mapping[str, Any]] = None) -> List[str]:
37def get_maturin_pep517_args(config_settings: Optional[Mapping[str, Any]] = None) -> List[str]: 38 build_args = config_settings.get("build-args") if config_settings else None 39 if build_args is None: 40 env_args = os.getenv("MATURIN_PEP517_ARGS", "") 41 args = shlex.split(env_args) 42 elif isinstance(build_args, str): 43 args = shlex.split(build_args) 44 else: 45 args = build_args 46 return args
def
build_wheel( wheel_directory: str, config_settings: Optional[Mapping[str, Any]] = None, metadata_directory: Optional[str] = None) -> str:
def
build_sdist( sdist_directory: str, config_settings: Optional[Mapping[str, Any]] = None) -> str:
124def build_sdist(sdist_directory: str, config_settings: Optional[Mapping[str, Any]] = None) -> str: 125 command = ["maturin", "pep517", "write-sdist", "--sdist-directory", sdist_directory] 126 127 print("Running `{}`".format(" ".join(command))) 128 sys.stdout.flush() 129 result = subprocess.run(command, stdout=subprocess.PIPE) 130 sys.stdout.buffer.write(result.stdout) 131 sys.stdout.flush() 132 if result.returncode != 0: 133 sys.stderr.write(f"Error: command {command} returned non-zero exit status {result.returncode}\n") 134 sys.exit(1) 135 output = result.stdout.decode(errors="replace") 136 return output.strip().splitlines()[-1]
def
get_requires_for_build_wheel(config_settings: Optional[Mapping[str, Any]] = None) -> List[str]:
def
build_editable( wheel_directory: str, config_settings: Optional[Mapping[str, Any]] = None, metadata_directory: Optional[str] = None) -> str:
def
get_requires_for_build_editable(config_settings: Optional[Mapping[str, Any]] = None) -> List[str]:
def
get_requires_for_build_sdist(config_settings: Optional[Mapping[str, Any]] = None) -> List[str]:
def
prepare_metadata_for_build_wheel( metadata_directory: str, config_settings: Optional[Mapping[str, Any]] = None) -> str:
166def prepare_metadata_for_build_wheel( 167 metadata_directory: str, config_settings: Optional[Mapping[str, Any]] = None 168) -> str: 169 print("Checking for Rust toolchain....") 170 is_cargo_installed = False 171 try: 172 output = subprocess.check_output(["cargo", "--version"]).decode("utf-8", "ignore") 173 if "cargo" in output: 174 is_cargo_installed = True 175 except (FileNotFoundError, SubprocessError): 176 pass 177 178 if not is_cargo_installed: 179 sys.stderr.write( 180 "\nCargo, the Rust package manager, is not installed or is not on PATH.\n" 181 "This package requires Rust and Cargo to compile extensions. Install it through\n" 182 "the system's package manager or via https://rustup.rs/\n\n" 183 ) 184 sys.exit(1) 185 186 command = [ 187 "maturin", 188 "pep517", 189 "write-dist-info", 190 "--metadata-directory", 191 metadata_directory, 192 # PEP 517 specifies that only `sys.executable` points to the correct 193 # python interpreter 194 "--interpreter", 195 _get_sys_executable(), 196 ] 197 command.extend(_additional_pep517_args()) 198 pep517_args = get_maturin_pep517_args(config_settings) 199 if pep517_args: 200 command.extend(pep517_args) 201 202 print("Running `{}`".format(" ".join(command))) 203 try: 204 _output = subprocess.check_output(command) 205 except subprocess.CalledProcessError as e: 206 sys.stderr.write(f"Error running maturin: {e}\n") 207 sys.exit(1) 208 sys.stdout.buffer.write(_output) 209 sys.stdout.flush() 210 output = _output.decode(errors="replace") 211 return output.strip().splitlines()[-1]
def
prepare_metadata_for_build_editable( metadata_directory: str, config_settings: Optional[Mapping[str, Any]] = None) -> str:
166def prepare_metadata_for_build_wheel( 167 metadata_directory: str, config_settings: Optional[Mapping[str, Any]] = None 168) -> str: 169 print("Checking for Rust toolchain....") 170 is_cargo_installed = False 171 try: 172 output = subprocess.check_output(["cargo", "--version"]).decode("utf-8", "ignore") 173 if "cargo" in output: 174 is_cargo_installed = True 175 except (FileNotFoundError, SubprocessError): 176 pass 177 178 if not is_cargo_installed: 179 sys.stderr.write( 180 "\nCargo, the Rust package manager, is not installed or is not on PATH.\n" 181 "This package requires Rust and Cargo to compile extensions. Install it through\n" 182 "the system's package manager or via https://rustup.rs/\n\n" 183 ) 184 sys.exit(1) 185 186 command = [ 187 "maturin", 188 "pep517", 189 "write-dist-info", 190 "--metadata-directory", 191 metadata_directory, 192 # PEP 517 specifies that only `sys.executable` points to the correct 193 # python interpreter 194 "--interpreter", 195 _get_sys_executable(), 196 ] 197 command.extend(_additional_pep517_args()) 198 pep517_args = get_maturin_pep517_args(config_settings) 199 if pep517_args: 200 command.extend(pep517_args) 201 202 print("Running `{}`".format(" ".join(command))) 203 try: 204 _output = subprocess.check_output(command) 205 except subprocess.CalledProcessError as e: 206 sys.stderr.write(f"Error running maturin: {e}\n") 207 sys.exit(1) 208 sys.stdout.buffer.write(_output) 209 sys.stdout.flush() 210 output = _output.decode(errors="replace") 211 return output.strip().splitlines()[-1]