Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customize install #11

Merged
merged 1 commit into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading