Skip to content

Commit

Permalink
Try to fix base executable detection, thonny#3277
Browse files Browse the repository at this point in the history
  • Loading branch information
aivarannamaa committed Sep 7, 2024
1 parent 06f5334 commit 55a74c2
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 48 deletions.
65 changes: 44 additions & 21 deletions thonny/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,21 +391,6 @@ def get_site_dir(symbolic_name, executable=None):
return result if result else None


def get_base_executable():
if sys.exec_prefix == sys.base_exec_prefix:
return sys.executable

if sys.platform == "win32":
guess = sys.base_exec_prefix + "\\" + os.path.basename(sys.executable)
if os.path.isfile(guess):
return normpath_with_actual_case(guess)

if os.path.islink(sys.executable):
return os.path.realpath(sys.executable)

raise RuntimeError("Don't know how to locate base executable")


def get_augmented_system_path(extra_dirs):
path_items = os.environ.get("PATH", "").split(os.pathsep)

Expand Down Expand Up @@ -807,12 +792,7 @@ def is_private_python(executable):


def running_in_virtual_environment() -> bool:
return (
hasattr(sys, "base_prefix")
and sys.base_prefix != sys.prefix
or hasattr(sys, "real_prefix")
and getattr(sys, "real_prefix") != sys.prefix
)
return sys.base_prefix != sys.prefix


def is_remote_path(s: str) -> bool:
Expand Down Expand Up @@ -904,3 +884,46 @@ def infer_package_url(dist):
)
for dist in dists
]


def try_get_base_executable(executable: str) -> Optional[str]:
if os.path.islink(executable):
# a venv executable may link to another venv executable
return try_get_base_executable(os.path.realpath(executable))

may_be_venv_exe = False
for location in ["..", "."]:
cfg_path = os.path.join(os.path.dirname(executable), location, "pyvenv.cfg")

if not os.path.isfile(cfg_path):
continue

may_be_venv_exe = True

atts = {}
with open(cfg_path) as fp:
for line in fp:
if "=" not in line:
continue
key, value = line.split("=", maxsplit=1)
atts[key.strip()] = value.strip()

if "home" not in atts:
logger.warning("No home in %s", cfg_path)
continue

if "executable" in atts:
return atts["executable"]

# pyvenv.cfg may be present also in non-virtual envs.
# I can check for this in certain case
if may_be_venv_exe and os.path.samefile(sys.executable, executable) and sys.prefix == sys.base_prefix:
may_be_venv_exe = False

if may_be_venv_exe:
# should only happen with venv-s before Python 3.11
# as Python 3.11 started recording executable in pyvenv.cfg
logger.warning("Could not find base executable of %s", executable)
return None
else:
return executable
9 changes: 3 additions & 6 deletions thonny/plugins/cpython_backend/cp_back.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@
execute_with_frontend_sys_path,
export_installed_distributions_info,
get_augmented_system_path,
get_base_executable,
get_exe_dirs,
get_python_version_string,
get_single_dir_child_data,
path_startswith,
running_in_virtual_environment,
serialize_message,
try_get_base_executable,
update_system_path,
)

Expand Down Expand Up @@ -425,7 +425,7 @@ def _cmd_get_environment_info(self, cmd):
prefix=sys.prefix,
welcome_text=f"Python {get_python_version_string()} ({sys.executable})",
executable=sys.executable,
base_executable=get_base_executable(),
base_executable=try_get_base_executable(sys.executable),
exe_dirs=get_exe_dirs(),
in_venv=running_in_virtual_environment(),
python_version=get_python_version_string(),
Expand Down Expand Up @@ -1429,10 +1429,7 @@ def _is_library_file(filename):
return (
filename is None
or path_startswith(filename, sys.prefix)
or hasattr(sys, "base_prefix")
and path_startswith(filename, sys.base_prefix)
or hasattr(sys, "real_prefix")
and path_startswith(filename, getattr(sys, "real_prefix"))
or path_startswith(filename, sys.base_prefix)
or site.ENABLE_USER_SITE
and path_startswith(filename, site.getusersitepackages())
)
Expand Down
11 changes: 7 additions & 4 deletions thonny/plugins/cpython_frontend/cp_front.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import tkinter as tk
from logging import getLogger
from tkinter import ttk
from typing import Any, Dict, List, Tuple
from typing import Any, Dict, List, Optional, Tuple

import thonny
from thonny import get_runner, get_shell, get_workbench, ui_utils
Expand All @@ -18,10 +18,10 @@
InlineCommand,
InlineResponse,
ToplevelCommand,
get_base_executable,
is_private_python,
normpath_with_actual_case,
running_in_virtual_environment,
try_get_base_executable,
)
from thonny.languages import tr
from thonny.misc_utils import running_on_mac_os, running_on_windows
Expand Down Expand Up @@ -93,7 +93,7 @@ def get_target_executable(self):
def get_executable(self):
return self._reported_executable

def get_base_executable(self):
def get_base_executable(self) -> Optional[str]:
return self._reported_base_executable

def _update_gui_updating(self, msg):
Expand Down Expand Up @@ -363,7 +363,10 @@ def get_new_machine_id(self) -> str:
def get_default_cpython_executable_for_backend() -> str:
if is_private_python(sys.executable) and running_in_virtual_environment():
# Private venv. Make an exception and use base Python for default backend.
default_path = get_base_executable()
default_path = try_get_base_executable(sys.executable)
if default_path is None:
logger.warning("Could not find base executable of %s", sys.executable)
default_path = sys.executable
else:
default_path = sys.executable.replace("pythonw.exe", "python.exe")

Expand Down
18 changes: 2 additions & 16 deletions thonny/running.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
path_startswith,
read_one_incoming_message_str,
serialize_message,
try_get_base_executable,
universal_relpath,
update_system_path,
)
Expand Down Expand Up @@ -1601,22 +1602,7 @@ def __init__(self, returncode=None):


def is_venv_interpreter_of_current_interpreter(executable):
for location in ["..", "."]:
cfg_path = os.path.join(os.path.dirname(executable), location, "pyvenv.cfg")
if os.path.isfile(cfg_path):
with open(cfg_path) as fp:
content = fp.read()
for line in content.splitlines():
if line.replace(" ", "").startswith("home="):
_, home = line.split("=", maxsplit=1)
home = home.strip()
if os.path.isdir(home) and (
is_same_path(home, sys.prefix)
or is_same_path(home, os.path.join(sys.prefix, "bin"))
or is_same_path(home, os.path.join(sys.prefix, "Scripts"))
):
return True
return False
return try_get_base_executable(executable) == sys.executable


def get_environment_for_python_subprocess(target_executable) -> Dict[str, str]:
Expand Down
4 changes: 3 additions & 1 deletion thonny/venv_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ def populate_main_frame(self):

logger.info("Current base executable: %r", current_base_cpython)

if current_base_cpython is not None and current_base_cpython not in exes:
if current_base_cpython is not None:
if current_base_cpython in exes:
exes.remove(current_base_cpython)
exes.insert(0, current_base_cpython)

browse_button_width = 2 if get_workbench().is_using_aqua_based_theme() else 3
Expand Down

0 comments on commit 55a74c2

Please sign in to comment.