Skip to content

Commit

Permalink
support more maturin flags and properly support parsing output with c…
Browse files Browse the repository at this point in the history
…olor
  • Loading branch information
mbway committed Aug 28, 2023
1 parent 6b0eccf commit 1be2370
Show file tree
Hide file tree
Showing 9 changed files with 218 additions and 14 deletions.
17 changes: 14 additions & 3 deletions maturin/import_hook/_building.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ def build_wheel(
output_dir: Path,
settings: MaturinSettings,
) -> str:
if "build" not in settings.supported_commands():
msg = f'provided {type(settings).__name__} does not support the "build" command'
raise ImportError(msg)
success, output = _run_maturin(
[
"build",
Expand All @@ -186,6 +189,11 @@ def develop_build_project(
args = ["develop", "--manifest-path", str(manifest_path)]
if skip_install:
args.append("--skip-install")
if "develop" not in settings.supported_commands():
msg = (
f'provided {type(settings).__name__} does not support the "develop" command'
)
raise ImportError(msg)
args.extend(settings.to_args())
success, output = _run_maturin(args)
if not success:
Expand All @@ -211,7 +219,11 @@ def _run_maturin(args: list[str]) -> Tuple[bool, str]:
logger.error("maturin output:\n%s", output)
return False, output
if logger.isEnabledFor(logging.DEBUG):
logger.debug("maturin output:\n%s", output)
logger.debug(
"maturin output (has warnings: %r):\n%s",
maturin_output_has_warnings(output),
output,
)
return True, output


Expand Down Expand Up @@ -242,6 +254,5 @@ def _find_single_file(dir_path: Path, extension: Optional[str]) -> Optional[Path

def maturin_output_has_warnings(output: str) -> bool:
return (
re.search(r"warning: `.*` \((lib|bin)\) generated [0-9]+ warnings?", output)
is not None
re.search(r"`.*` \((lib|bin)\) generated [0-9]+ warnings?", output) is not None
)
2 changes: 1 addition & 1 deletion maturin/import_hook/project_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def _get_settings(self, module_path: str, source_path: Path) -> MaturinSettings:
elif isinstance(self._settings, MaturinSettingsProvider):
return self._settings.get_settings(module_path, source_path)
else:
return MaturinSettings()
return MaturinSettings.default()

def find_spec(
self,
Expand Down
2 changes: 1 addition & 1 deletion maturin/import_hook/rust_file_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def _get_settings(self, module_path: str, source_path: Path) -> MaturinSettings:
elif isinstance(self._settings, MaturinSettingsProvider):
return self._settings.get_settings(module_path, source_path)
else:
return MaturinSettings()
return MaturinSettings.default()

def find_spec(
self,
Expand Down
99 changes: 93 additions & 6 deletions maturin/import_hook/settings.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional
from typing import List, Optional, Dict, Set

__all__ = ["MaturinSettings", "MaturinSettingsProvider"]
__all__ = [
"MaturinSettings",
"MaturinBuildSettings",
"MaturinDevelopSettings",
"MaturinSettingsProvider",
]


@dataclass
class MaturinSettings:
"""Settings common to `maturin build` and `maturin develop`"""

release: bool = False
strip: bool = False
quiet: bool = False
jobs: Optional[int] = None
profile: Optional[str] = None
features: Optional[List[str]] = None
all_features: bool = False
no_default_features: bool = False
target: Optional[str] = None
ignore_rust_version: bool = False
color: Optional[bool] = None
frozen: bool = False
locked: bool = False
offline: bool = False
config: Optional[Dict[str, str]] = None
unstable_flags: Optional[List[str]] = None
verbose: int = 0
rustc_flags: Optional[List[str]] = None

@staticmethod
def supported_commands() -> Set[str]:
return {"build", "develop"}

def __post_init__(self) -> None:
if self.verbose not in (0, 1, 2):
msg = f"invalid verbose value: {self.verbose}"
raise ValueError(msg)
@staticmethod
def default() -> "MaturinSettings":
"""MaturinSettings() sets no flags but default() corresponds to some sensible defaults"""
return MaturinSettings(
color=True,
)

def to_args(self) -> List[str]:
args = []
Expand All @@ -36,21 +56,88 @@ def to_args(self) -> List[str]:
if self.jobs is not None:
args.append("--jobs")
args.append(str(self.jobs))
if self.profile is not None:
args.append("--profile")
args.append(self.profile)
if self.features:
args.append("--features")
args.append(",".join(self.features))
if self.all_features:
args.append("--all-features")
if self.no_default_features:
args.append("--no-default-features")
if self.target is not None:
args.append("--target")
args.append(self.target)
if self.ignore_rust_version:
args.append("--ignore-rust-version")
if self.color is not None:
args.append("--color")
if self.color:
args.append("always")
else:
args.append("never")
if self.frozen:
args.append("--frozen")
if self.locked:
args.append("--locked")
if self.offline:
args.append("--offline")
if self.config is not None:
for key, value in self.config.items():
args.append("--config")
args.append(f"{key}={value}")
if self.unstable_flags is not None:
for flag in self.unstable_flags:
args.append("-Z")
args.append(flag)
if self.verbose > 0:
args.append("-{}".format("v" * self.verbose))
if self.rustc_flags is not None:
args.extend(self.rustc_flags)
return args


@dataclass
class MaturinBuildSettings(MaturinSettings):
"""settings for `maturin build`"""

skip_auditwheel: bool = False
zig: bool = False

@staticmethod
def supported_commands() -> Set[str]:
return {"build"}

def to_args(self) -> List[str]:
args = []
if self.skip_auditwheel:
args.append("--skip-auditwheel")
if self.zig:
args.append("--zig")
args.extend(super().to_args())
return args


@dataclass
class MaturinDevelopSettings(MaturinSettings):
"""settings for `maturin develop`"""

extras: Optional[List[str]] = None
skip_install: bool = False

@staticmethod
def supported_commands() -> Set[str]:
return {"develop"}

def to_args(self) -> List[str]:
args = []
if self.extras is not None:
args.append("--extras")
args.append(",".join(self.extras))
if self.skip_install:
args.append("--skip-install")
args.extend(super().to_args())
return args


Expand Down
9 changes: 9 additions & 0 deletions tests/import_hook/common.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import re
import shutil
import site
import subprocess
Expand Down Expand Up @@ -238,3 +239,11 @@ def create_project_from_blank_template(
f"from .{package_name} import *"
)
return project_dir


def remove_ansii_escape_characters(text: str) -> str:
"""Remove escape characters (eg used to color terminal output) from the given string
based on: https://stackoverflow.com/a/14693789
"""
return re.sub(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])", "", text)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def get_settings(self, module_path: str, source_path: Path) -> MaturinSettings:
return MaturinSettings(features=["pyo3/extension-module", "large_number"])
else:
print(f"building {module_path} with default settings")
return MaturinSettings()
return MaturinSettings.default()


import_hook.install(settings=CustomSettingsProvider())
Expand Down
3 changes: 3 additions & 0 deletions tests/import_hook/test_project_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
test_crates,
uninstall,
with_underscores,
remove_ansii_escape_characters,
)

"""
Expand Down Expand Up @@ -679,6 +680,7 @@ def test_default_compile_warning(self, workspace: Path, is_mixed: bool) -> None:
)

output1, _ = run_python_code(self.loader_script)
output1 = remove_ansii_escape_characters(output1)
pattern = (
'building "test_project"\n'
'maturin.import_hook \\[WARNING\\] build of "test_project" succeeded with warnings:\n'
Expand All @@ -694,6 +696,7 @@ def test_default_compile_warning(self, workspace: Path, is_mixed: bool) -> None:
)

output2, _ = run_python_code(self.loader_script)
output2 = remove_ansii_escape_characters(output2)
pattern = (
'maturin.import_hook \\[WARNING\\] the last build of "test_project" succeeded with warnings:\n'
".*"
Expand Down
10 changes: 9 additions & 1 deletion tests/import_hook/test_rust_file_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@

import pytest

from .common import log, run_python, script_dir, test_crates
from .common import (
log,
run_python,
script_dir,
test_crates,
remove_ansii_escape_characters,
)

"""
These tests ensure the correct functioning of the rust file importer import hook.
Expand Down Expand Up @@ -309,6 +315,7 @@ def test_default_compile_warning(self, workspace: Path) -> None:
)

output1, _ = run_python([str(py_path)], workspace)
output1 = remove_ansii_escape_characters(output1)
pattern = (
'building "my_script"\n'
'maturin.import_hook \\[WARNING\\] build of "my_script" succeeded with warnings:\n'
Expand All @@ -324,6 +331,7 @@ def test_default_compile_warning(self, workspace: Path) -> None:
)

output2, _ = run_python([str(py_path)], workspace)
output2 = remove_ansii_escape_characters(output2)
pattern = (
'maturin.import_hook \\[WARNING\\] the last build of "my_script" succeeded with warnings:\n'
".*"
Expand Down
88 changes: 87 additions & 1 deletion tests/import_hook/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@

import pytest

from maturin.import_hook._building import BuildCache, BuildStatus, LockNotHeldError
from maturin.import_hook import MaturinSettings
from maturin.import_hook._building import (
BuildCache,
BuildStatus,
LockNotHeldError,
)
from maturin.import_hook._file_lock import AtomicOpenLock, FileLock
from maturin.import_hook._resolve_project import ProjectResolveError, _resolve_project
from maturin.import_hook.project_importer import (
_get_installed_package_mtime,
_get_project_mtime,
)
from maturin.import_hook.settings import MaturinDevelopSettings, MaturinBuildSettings

from .common import log, test_crates

Expand All @@ -31,6 +37,86 @@
os.environ["RESOLVED_PACKAGES_PATH"] = str(SAVED_RESOLVED_PACKAGES_PATH)


def test_settings() -> None:
assert MaturinSettings().to_args() == []
assert MaturinBuildSettings().to_args() == []
assert MaturinDevelopSettings().to_args() == []

settings = MaturinSettings(
release=True,
strip=True,
quiet=True,
jobs=1,
profile="profile1",
features=["feature1", "feature2"],
all_features=True,
no_default_features=True,
target="target1",
ignore_rust_version=True,
color=True,
frozen=True,
locked=True,
offline=True,
config={"key1": "value1", "key2": "value2"},
unstable_flags=["unstable1", "unstable2"],
verbose=2,
rustc_flags=["flag1", "flag2"],
)
# fmt: off
assert settings.to_args() == [
'--release',
'--strip',
'--quiet',
'--jobs', '1',
'--profile', 'profile1',
'--features', 'feature1,feature2',
'--all-features',
'--no-default-features',
'--target', 'target1',
'--ignore-rust-version',
'--color', 'always',
'--frozen',
'--locked',
'--offline',
'--config', 'key1=value1',
'--config', 'key2=value2',
'-Z', 'unstable1',
'-Z', 'unstable2',
'-vv',
'flag1',
'flag2',
]
# fmt: on

build_settings = MaturinBuildSettings(
skip_auditwheel=True, zig=True, color=False, rustc_flags=["flag1", "flag2"]
)
assert build_settings.to_args() == [
"--skip-auditwheel",
"--zig",
"--color",
"never",
"flag1",
"flag2",
]

develop_settings = MaturinDevelopSettings(
extras=["extra1", "extra2"],
skip_install=True,
color=False,
rustc_flags=["flag1", "flag2"],
)
assert develop_settings.to_args() == [
"--extras",
"extra1,extra2",
"--skip-install",
"--color",
"never",
"flag1",
"flag2",
]


class TestFileLock:
@staticmethod
def _create_lock(path: Path, timeout_seconds: float, fallback: bool) -> FileLock:
Expand Down

0 comments on commit 1be2370

Please sign in to comment.