Skip to content

Commit

Permalink
make site install more configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
mbway committed Dec 7, 2024
1 parent 298c70c commit 598f536
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 139 deletions.
46 changes: 39 additions & 7 deletions src/maturin_import_hook/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import site
import subprocess
from pathlib import Path
from typing import Optional

from maturin_import_hook._building import get_default_build_dir
from maturin_import_hook._site import (
Expand Down Expand Up @@ -98,9 +99,17 @@ def _action_site_info(format_name: str) -> None:
)


def _action_site_install(*, user: bool, preset_name: str, force: bool) -> None:
def _action_site_install(
*,
user: bool,
force: bool,
args: Optional[str],
enable_project_importer: bool,
enable_rs_file_importer: bool,
detect_uv: bool,
) -> None:
module_path = get_usercustomize_path() if user else get_sitecustomize_path()
insert_automatic_installation(module_path, preset_name, force)
insert_automatic_installation(module_path, force, args, enable_project_importer, enable_rs_file_importer, detect_uv)


def _action_site_uninstall(*, user: bool) -> None:
Expand Down Expand Up @@ -183,10 +192,26 @@ def _main() -> None:
help="whether to overwrite any existing managed import hook installation",
)
install.add_argument(
"--preset",
default="debug",
choices=["debug", "release"],
help="the settings preset for the import hook to use when building packages. Defaults to 'debug'.",
"--project-importer",
default=True,
help="Whether to enable the project importer",
action=argparse.BooleanOptionalAction,
)
install.add_argument(
"--rs-file-importer",
default=True,
help="Whether to enable the rs file importer",
action=argparse.BooleanOptionalAction,
)
install.add_argument(
"--detect-uv",
default=True,
help="Whether to automatically detect and use the --uv flag",
action=argparse.BooleanOptionalAction,
)
install.add_argument(
"--args",
help="The arguments to pass to `maturin`. See `maturin develop --help` or `maturin build --help`",
)
install.add_argument(
"--user",
Expand Down Expand Up @@ -226,7 +251,14 @@ def _main() -> None:
if args.sub_action == "info":
_action_site_info(args.format)
elif args.sub_action == "install":
_action_site_install(user=args.user, preset_name=args.preset, force=args.force)
_action_site_install(
user=args.user,
force=args.force,
args=args.args,
enable_project_importer=args.project_importer,
enable_rs_file_importer=args.rs_file_importer,
detect_uv=args.detect_uv,
)
elif args.sub_action == "uninstall":
_action_site_uninstall(user=args.user)
else:
Expand Down
12 changes: 4 additions & 8 deletions src/maturin_import_hook/_building.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,6 @@ 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 ImportHookError(msg)
success, output = run_maturin(
maturin_path,
[
Expand All @@ -162,7 +159,7 @@ def build_wheel(
sys.executable,
"--out",
str(output_dir),
*settings.to_args(),
*settings.to_args("build"),
],
)
if not success:
Expand All @@ -176,10 +173,9 @@ def develop_build_project(
manifest_path: Path,
settings: MaturinSettings,
) -> str:
if "develop" not in settings.supported_commands():
msg = f'provided {type(settings).__name__} does not support the "develop" command'
raise ImportHookError(msg)
success, output = run_maturin(maturin_path, ["develop", "--manifest-path", str(manifest_path), *settings.to_args()])
success, output = run_maturin(
maturin_path, ["develop", "--manifest-path", str(manifest_path), *settings.to_args("develop")]
)
if not success:
msg = "Failed to build package with maturin"
raise MaturinError(msg)
Expand Down
97 changes: 70 additions & 27 deletions src/maturin_import_hook/_site.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
import dataclasses
import importlib
import shlex
import shutil
import site
from pathlib import Path
from textwrap import dedent
from typing import Optional

from maturin_import_hook._logging import logger
from maturin_import_hook.settings import MaturinSettings

MANAGED_INSTALL_START = "# <maturin_import_hook>"
MANAGED_INSTALL_END = "# </maturin_import_hook>\n"
MANAGED_INSTALL_COMMENT = """
# the following commands install the maturin import hook during startup.
# the following installs the maturin import hook during startup.
# see: `python -m maturin_import_hook site`
"""

MANAGED_INSTALLATION_PRESETS = {
"debug": dedent("""\
try:
import maturin_import_hook
except ImportError:
pass
else:
maturin_import_hook.install()
"""),
"release": dedent("""\
try:
import maturin_import_hook
from maturin_import_hook.settings import MaturinSettings
except ImportError:
pass
else:
maturin_import_hook.install(MaturinSettings(release=True))
"""),
}
INSTALL_TEMPLATE = """\
try:
import maturin_import_hook
from maturin_import_hook.settings import MaturinSettings
except ImportError:
pass
else:
maturin_import_hook.install(
settings=MaturinSettings(
{settings}
),
enable_project_importer={enable_project_importer},
enable_rs_file_importer={enable_rs_file_importer},
)
"""


def get_sitecustomize_path() -> Path:
Expand Down Expand Up @@ -83,10 +84,44 @@ def remove_automatic_installation(module_path: Path) -> None:
module_path.unlink(missing_ok=True)


def insert_automatic_installation(module_path: Path, preset_name: str, force: bool) -> None:
if preset_name not in MANAGED_INSTALLATION_PRESETS:
msg = f"Unknown managed installation preset name: '{preset_name}'"
raise ValueError(msg)
def _should_use_uv() -> bool:
"""Whether the `--uv` flag should be used when installing into this environment.
virtual environments managed with `uv` do not have `pip` installed so the `--uv` flag is required.
"""
try:
importlib.import_module("pip")
except ModuleNotFoundError:
if shutil.which("uv") is not None:
return True
else:
logger.warning("neither `pip` nor `uv` were found. `maturin develop` may not work...")
return False
else:
# since pip is a more established program, use it even if uv may be installed
return False


def insert_automatic_installation(
module_path: Path,
force: bool,
args: Optional[str],
enable_project_importer: bool,
enable_rs_file_importer: bool,
detect_uv: bool,
) -> None:
if args is None:
parsed_args = MaturinSettings.default()
else:
parsed_args = MaturinSettings.from_args(shlex.split(args))
if parsed_args.color is None:
parsed_args.color = True
if detect_uv and not parsed_args.uv and _should_use_uv():
parsed_args.uv = True
logger.info(
"using `--uv` flag as it was detected to be necessary for this environment. "
"Use `site install --no-detect-uv` to set manually."
)

logger.info(f"installing automatic activation into '{module_path}'")
if has_automatic_installation(module_path):
Expand All @@ -97,14 +132,22 @@ def insert_automatic_installation(module_path: Path, preset_name: str, force: bo
logger.info("already installed. Aborting install.")
return

parts = []
parts: list[str] = []
if module_path.exists():
parts.append(module_path.read_text())
parts.append("\n")

defaults = MaturinSettings()
non_default_settings = {k: v for k, v in dataclasses.asdict(parsed_args).items() if getattr(defaults, k) != v}

parts.extend([
MANAGED_INSTALL_START,
MANAGED_INSTALL_COMMENT,
MANAGED_INSTALLATION_PRESETS[preset_name],
INSTALL_TEMPLATE.format(
settings=",\n ".join(f"{k}={v!r}" for k, v in non_default_settings.items()),
enable_project_importer=repr(enable_project_importer),
enable_rs_file_importer=repr(enable_rs_file_importer),
),
MANAGED_INSTALL_END,
])
code = "".join(parts)
Expand Down
4 changes: 2 additions & 2 deletions src/maturin_import_hook/project_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def _rebuild_project(
if mtime is None:
logger.error("could not get installed package mtime")
else:
build_status = BuildStatus(mtime, project_dir, settings.to_args(), maturin_output)
build_status = BuildStatus(mtime, project_dir, settings.to_args("develop"), maturin_output)
build_cache.store_build_status(build_status)

return spec, True
Expand Down Expand Up @@ -315,7 +315,7 @@ def _get_spec_for_up_to_date_package(
return None, "no build status found"
if build_status.source_path != project_dir:
return None, "source path in build status does not match the project dir"
if build_status.maturin_args != settings.to_args():
if build_status.maturin_args != settings.to_args("develop"):
return None, "current maturin args do not match the previous build"

installed_paths = self._file_searcher.get_installation_paths(installed_package_root)
Expand Down
4 changes: 2 additions & 2 deletions src/maturin_import_hook/rust_file_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def _import_rust_file(
build_status = BuildStatus(
extension_module_path.stat().st_mtime,
file_path,
settings.to_args(),
settings.to_args("build"),
maturin_output,
)
build_cache.store_build_status(build_status)
Expand Down Expand Up @@ -270,7 +270,7 @@ def _get_spec_for_up_to_date_extension_module(
return None, "no build status found"
if build_status.source_path != source_path:
return None, "source path in build status does not match the project dir"
if build_status.maturin_args != settings.to_args():
if build_status.maturin_args != settings.to_args("build"):
return None, "current maturin args do not match the previous build"

freshness = get_installation_freshness(
Expand Down
Loading

0 comments on commit 598f536

Please sign in to comment.