-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ENH: add support for editable installs
Signed-off-by: Filipe Laíns <[email protected]>
- Loading branch information
Showing
2 changed files
with
91 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |