link cpython

This commit is contained in:
Barry-Thomas-Paul: Moss 2023-11-02 22:05:56 -04:00
parent c93c182953
commit 9f2ac70cc8
3 changed files with 233 additions and 13 deletions

View File

@ -31,6 +31,7 @@ import logging
import os
import platform
import re
import site
import shlex
import shutil
import socket
@ -168,6 +169,8 @@ OBJ_GRAPHIC = 'SwXTextGraphicObject'
OBJ_TEXTS = 'SwXTextRanges'
OBJ_TEXT = 'SwXTextRange'
IS_FLATPAK = bool(os.getenv("FLATPAK_ID", ""))
IS_APP_IMAGE = bool(os.getenv("APPIMAGE", ""))
# ~ from com.sun.star.sheet.FilterOperator import EMPTY, NO_EMPTY, EQUAL, NOT_EQUAL
@ -636,15 +639,60 @@ def sleep(seconds):
time.sleep(seconds)
return
def get_flatpak_site_packages_dir() -> str:
# should never be all users
sand_box = os.getenv("FLATPAK_SANDBOX_DIR", "") or str(
Path.home() / ".var/app/org.libreoffice.LibreOffice/sandbox"
)
def get_site_packages_dir() -> str:
"""Gets the site-packages folder for the current user."""
major_minor = f"{sys.version_info.major}.{sys.version_info.minor}"
site_packages = Path(sand_box) / f"lib/python{major_minor}/site-packages"
site_packages.mkdir(parents=True, exist_ok=True)
return str(site_packages)
def get_windows_site_packages_dir() -> str:
nonlocal major_minor
if site.USER_SITE:
site_packages = Path(site.USER_SITE).resolve()
else:
site_packages = (
Path.home() / f"'/AppData/Roaming/Python/Python{major_minor}/site-packages'"
)
site_packages.mkdir(parents=True, exist_ok=True)
return str(site_packages)
def get_flatpak_site_packages_dir() -> str:
# should never be all users
nonlocal major_minor
sand_box = os.getenv("FLATPAK_SANDBOX_DIR", "") or str(
Path.home() / ".var/app/org.libreoffice.LibreOffice/sandbox"
)
site_packages = Path(sand_box) / f"lib/python{major_minor}/site-packages"
site_packages.mkdir(parents=True, exist_ok=True)
return str(site_packages)
def get_mac_site_packages_dir() -> str:
nonlocal major_minor
if site.USER_SITE:
site_packages = Path(site.USER_SITE).resolve()
else:
site_packages = (
Path.home() / f"Library/LibreOfficePython/{major_minor}/lib/python/site-packages"
)
site_packages.mkdir(parents=True, exist_ok=True)
return str(site_packages)
def get_default_site_packages_dir() -> str:
nonlocal major_minor
if site.USER_SITE:
site_packages = Path(site.USER_SITE).resolve()
else:
site_packages = Path.home() / f".local/lib/python{major_minor}/site-packages"
site_packages.mkdir(parents=True, exist_ok=True)
return str(site_packages)
if IS_WIN:
return get_windows_site_packages_dir()
if IS_MAC:
return get_mac_site_packages_dir()
if IS_FLATPAK:
return get_flatpak_site_packages_dir()
return get_default_site_packages_dir()
class TimerThread(threading.Thread):
@ -6206,8 +6254,7 @@ class Paths(object):
elif IS_MAC:
path = self.join(self.config('Module'), '..', 'Resources', PYTHON)
else:
is_app_image = bool(os.getenv("APPIMAGE", ""))
if is_app_image:
if IS_APP_IMAGE:
path = self.join(self.config("Module"), PYTHON)
else:
path = sys.executable

View File

@ -0,0 +1,146 @@
"""
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

View File

@ -67,7 +67,6 @@ class Controllers(object):
def __init__(self, dialog):
self.d = dialog
self.path_python = app.paths.python
self.is_flatpak = app.IS_FLATPAK
def _set_state(self, state):
self._states = {
@ -260,8 +259,8 @@ class Controllers(object):
self.d.lst_log.visible = True
line = ''
if self.is_flatpak:
cmd = f' install --upgrade --target={app.get_flatpak_site_packages_dir()}'
if app.IS_FLATPAK:
cmd = f' install --upgrade --target={app.get_site_packages_dir()}'
else:
cmd = ' install --upgrade --user'
if value:
@ -275,6 +274,33 @@ class Controllers(object):
self.d.lst_log.insert(line, 'ok.png')
else:
self.d.lst_log.insert(line)
self._link_cpython()
return
def _link_cpython(self):
if not app.IS_MAC and not app.IS_APP_IMAGE:
app.debug('Not Mac or not AppImage')
return
try:
app.debug("Linking CPython")
from link_cpython import LinkCPython
cpy_link = LinkCPython(pth=app.get_site_packages_dir())
cpy_link.link()
except Exception as err:
app.error(err)
return
def _unlink_cpython(self):
if not app.IS_MAC and not app.IS_APP_IMAGE:
app.debug('Not Mac or not AppImage')
return
try:
app.debug("Unlinking CPython")
from link_cpython import LinkCPython
cpy_link = LinkCPython(pth=app.get_site_packages_dir())
cpy_link.unlink()
except Exception as err:
app.error(err)
return
def lst_package_double_click(self, event):
@ -305,6 +331,7 @@ class Controllers(object):
self.d.lst_log.insert(line, 'ok.png')
else:
self.d.lst_log.insert(line)
self._unlink_cpython()
return
def cmd_uninstall_action(self, event):