Skip to content

Commit

Permalink
ENH: add support for editable installs
Browse files Browse the repository at this point in the history
Signed-off-by: Filipe Laíns <[email protected]>
  • Loading branch information
FFY00 committed Nov 28, 2022
1 parent d098706 commit 64625c2
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 0 deletions.
21 changes: 21 additions & 0 deletions mesonpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,9 @@ def wheel(self, directory: Path) -> pathlib.Path: # noqa: F811
shutil.move(os.fspath(wheel), final_wheel)
return final_wheel

def editable(self, directory: Path) -> pathlib.Path:
raise NotImplementedError


@contextlib.contextmanager
def _project(config_settings: Optional[Dict[Any, Any]]) -> Iterator[Project]:
Expand Down Expand Up @@ -1054,3 +1057,21 @@ def build_wheel(
out = pathlib.Path(wheel_directory)
with _project(config_settings) as project:
return project.wheel(out).name


def build_editable(
wheel_directory: str,
config_settings: Optional[Dict[Any, Any]] = None,
metadata_directory: Optional[str] = None,
) -> str:
_setup_cli()

out = pathlib.Path(wheel_directory)
with _project(config_settings) as project:
return project.editable(out).name


def get_requires_for_build_editable(
config_settings: Optional[Dict[str, str]] = None,
) -> List[str]:
return get_requires_for_build_wheel()
70 changes: 70 additions & 0 deletions mesonpy/_editable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import functools
import importlib.abc
import os
import subprocess
import sys

from types import ModuleType
from typing import Optional, Union

from mesonpy._compat import Collection, Sequence


class MesonpyFinder(importlib.abc.MetaPathFinder):
"""Custom loader that whose purpose is to detect when the import system is
trying to load our modules, and trigger a rebuild. After triggering a
rebuild, we return None in find_spec, letting the normal finders pick up the
modules.
"""

def __init__(
self,
project_path: str,
top_level_modules: Collection[str],
rebuild_commands: Sequence[Sequence[str]],
) -> None:
if not os.path.isabs(project_path):
raise ImportError('Project path must be absolute')
self._project_path = project_path
self._top_level_modules = top_level_modules
self._rebuild_commands = rebuild_commands

def __repr__(self) -> str:
return f'{self.__class__}({self._project_path})'

def __hash__(self) -> int:
return hash((self._project_path, self._top_level_modules, self._rebuild_commands))

@functools.lru_cache(maxsize=1)
def rebuild(self) -> None:
for command in self._rebuild_commands:
subprocess.check_call(command)

def find_spec(
self,
fullname: str,
path: Optional[Sequence[Union[str, bytes]]],
target: Optional[ModuleType] = None,
) -> None:
# if it's one of our modules, trigger a rebuild
if fullname.split('.', maxsplit=1)[0] in self._top_level_modules:
self.rebuild()
# return none (meaning we "didn't find" the module) and let the normal
# finders find/import it
return None

@classmethod
def install(
cls,
project_path: str,
top_level_modules: Collection[str],
rebuild_commands: Sequence[Sequence[str]],
) -> None:
finder = cls(project_path, top_level_modules, rebuild_commands)
if finder not in sys.meta_path:
# prepend our finder to sys.meta_path, so that it is queried before
# the normal finders, and can trigger a project rebuild
sys.meta_path.insert(0, finder)
# add the project path to sys.path, so that the normal finder can
# find our modules
sys.path.append(project_path)

0 comments on commit 64625c2

Please sign in to comment.