zaz-pip/source/pythonpath/link_cpython.py

147 lines
5.2 KiB
Python

"""
On some systems such as Mac and AppImage (Linux) the python extension suffix does not match the
cpython suffix used by the embedded python interpreter.
This class creates symlinks for all .so files in dest folder that match the current python embedded suffix.
For example a file named ``indexers.cpython-38-x86_64-linux-gnu.so`` would be symlinked to ``indexers.cpython-3.8.so``.
This renaming allows the python interpreter to find the import.
"""
from __future__ import annotations
from typing import List
from pathlib import Path
from importlib import machinery
import easymacro as app
class LinkCPython:
def __init__(self, pth: str) -> None:
"""
Constructor
Args:
pth (str): Path to site-packages folder.
"""
self._current_suffix = self._get_current_suffix()
app.debug("CPythonLink.__init__")
self._link_root = Path(pth)
if not self._link_root.exists():
raise FileNotFoundError(f"Path does not exist {self._link_root}")
app.debug("CPythonLink.__init__ done")
def _get_current_suffix(self) -> str:
"""Gets suffix currently used by the embedded python interpreter such as ``cpython-3.8``"""
for suffix in machinery.EXTENSION_SUFFIXES:
if suffix.startswith(".cpython-") and suffix.endswith(".so"):
# remove leading . and trailing .so
return suffix[1:][:-3]
return ""
def _get_all_files(self, path: Path) -> List[Path]:
return [p for p in path.glob(f"**/*{self.file_suffix}.so") if p.is_file()]
def _get_all_links(self, path: Path) -> List[Path]:
return [p for p in path.glob(f"**/*{self.current_suffix}.so") if p.is_symlink()]
def _create_symlink(self, src: Path, dst: Path, overwrite: bool) -> None:
if dst.is_symlink():
if overwrite:
app.debug(f"Removing existing symlink {dst}")
dst.unlink()
else:
app.debug(f"Symlink already exists {dst}")
return
dst.symlink_to(src)
app.debug(f"Created symlink {dst} -> {src}")
def _find_current_installed_suffix(self, path: Path) -> str:
"""
Finds the current suffix from the current installed python so files such as ``cpython-38-x86_64-linux-gnu``.
Args:
path (Path): Path to search in. Usually site-packages.
Returns:
str: suffix if found, otherwise empty string.
"""
return next(
(str(p).rsplit(".", 2)[1] for p in path.glob("**/*.cpython-*.so") if not p.is_symlink()),
"",
)
def link(self, overwrite:bool = False) -> None:
"""
Creates symlinks for all .so files in site-packages that match the current suffix.
Args:
overwrite (bool, optional): Override any existing sys links. Defaults to False.
"""
app.debug("CPythonLink.link starting")
if not self._link_root:
app.debug("No site-packages found")
return
if not self.file_suffix:
app.debug("No current file suffix found")
return
if not self._link_root.exists():
app.debug(f"Site-packages does not exist {self._link_root}")
return
app.debug(f"Python current suffix: {self._current_suffix}")
app.debug(f"Found file suffix: {self.file_suffix}")
files = self._get_all_files(self._link_root)
if not files:
app.debug(f"No files found in {self._link_root}")
return
cp_old = self.file_suffix
cp_new = self._current_suffix
if cp_old == cp_new:
app.debug(f"Suffixes match, no need to link: {cp_old} == {cp_new}")
return
for file in files:
ln_name = file.name.replace(cp_old, cp_new)
src = file
if not src.is_absolute():
src = file.resolve()
dst = src.parent / ln_name
self._create_symlink(src, dst, overwrite)
app.debug("CPythonLink.link done")
def unlink(self) -> None:
"""Unlinks all broken sys links"""
links = self._get_all_links(self._link_root)
if not links:
app.debug(f"No links found in {self._link_root}")
return
for link in links:
if not link.exists():
app.debug(f"Removing broken symlink {link}")
link.unlink()
# region Properties
@property
def cpy_name(self) -> str:
"""Gets/Sets CPython name, e.g. cpython-3.8"""
return self._current_suffix
@cpy_name.setter
def cpy_name(self, value: str) -> None:
self._current_suffix = value
@property
def current_suffix(self) -> str:
"""Current Suffix such as ``cpython-3.8``"""
return self._current_suffix
@property
def file_suffix(self) -> str:
"""Current Suffix such as ``cpython-38-x86_64-linux-gnu``"""
try:
return self._file_suffix
except AttributeError:
self._file_suffix = self._find_current_installed_suffix(self._link_root)
return self._file_suffix
# endregion Properties