diff --git a/installers-conda/resources/spyder-menu.json b/installers-conda/resources/spyder-menu.json index 07097b68dc6..3719ab8f330 100644 --- a/installers-conda/resources/spyder-menu.json +++ b/installers-conda/resources/spyder-menu.json @@ -23,7 +23,6 @@ "StartupWMClass": "Spyder" }, "osx": { - "precommand": "eval \"$($SHELL -l -c \"declare -x\")\"", "command": [ "$(dirname $0)/python", "{{ PREFIX }}/bin/spyder", diff --git a/spyder/plugins/ipythonconsole/utils/kernelspec.py b/spyder/plugins/ipythonconsole/utils/kernelspec.py index 20bfaa7838b..475f2844e28 100644 --- a/spyder/plugins/ipythonconsole/utils/kernelspec.py +++ b/spyder/plugins/ipythonconsole/utils/kernelspec.py @@ -27,7 +27,7 @@ SpyderKernelError) from spyder.utils.conda import (add_quotes, get_conda_env_path, is_conda_env, find_conda) -from spyder.utils.environ import clean_env +from spyder.utils.environ import clean_env, get_user_environment_variables from spyder.utils.misc import get_python_executable from spyder.utils.programs import is_python_interpreter, is_module_installed @@ -171,7 +171,11 @@ def env(self): """Env vars for kernels""" default_interpreter = self.get_conf( 'default', section='main_interpreter') - env_vars = os.environ.copy() + + # Ensure that user environment variables are included, but don't + # override existing environ values + env_vars = get_user_environment_variables() + env_vars.update(os.environ) # Avoid IPython adding the virtualenv on which Spyder is running # to the kernel sys.path @@ -193,7 +197,7 @@ def env(self): # Environment variables that we need to pass to the kernel env_vars.update({ 'SPY_EXTERNAL_INTERPRETER': (not default_interpreter - or self.path_to_custom_interpreter), + or self.path_to_custom_interpreter), 'SPY_UMR_ENABLED': self.get_conf( 'umr/enabled', section='main_interpreter'), 'SPY_UMR_VERBOSE': self.get_conf( diff --git a/spyder/utils/environ.py b/spyder/utils/environ.py index d100570359c..051802ffef6 100644 --- a/spyder/utils/environ.py +++ b/spyder/utils/environ.py @@ -9,6 +9,8 @@ """ # Standard library imports +from functools import lru_cache +import logging import os from pathlib import Path import re @@ -22,11 +24,46 @@ from qtpy.QtWidgets import QMessageBox # Local imports -from spyder.config.base import _, running_in_ci +from spyder.config.base import _, running_in_ci, get_conf_path from spyder.widgets.collectionseditor import CollectionsEditor from spyder.utils.icon_manager import ima from spyder.utils.programs import run_shell_command +logger = logging.getLogger(__name__) + + +@lru_cache +def _get_user_env_script(): + """ + To get user environment variables from login and interactive startup files + on posix, both -l and -i flags must be used. Parsing the environment list + is problematic if the variable has a newline character, but Python's + os.environ can do this for us. However, executing Python in an interactive + shell in a subprocess causes Spyder to hang on Linux. Using a shell script + resolves the issue. Note that -i must be in the sha-bang line; if -i and + -l are swapped, Spyder will hang. + """ + script_text = None + shell = os.getenv('SHELL', '/bin/bash') + user_env_script = Path(get_conf_path()) / 'user-env.sh' + + if Path(shell).name in ('bash', 'zsh'): + script_text = ( + f"#!{shell} -i\n" + f"{shell} -l -c" + f" \"{sys.executable} -c 'import os; print(dict(os.environ))'\" " + f"\n" + ) + else: + logger.info("Getting user environment variables is not supported " + "for shell '%s'", shell) + + if script_text is not None: + user_env_script.write_text(script_text) + user_env_script.chmod(0o744) # Make it executable for the user + + return str(user_env_script) + def envdict2listdict(envdict): """Dict --> Dict of lists""" @@ -54,24 +91,24 @@ def get_user_environment_variables(): env_var : dict Key-value pairs of environment variables. """ - try: - if os.name == 'nt': - key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment") - num_values = winreg.QueryInfoKey(key)[1] - env_var = dict( - [winreg.EnumValue(key, k)[:2] for k in range(num_values)] - ) - else: - shell = os.environ.get("SHELL", "/bin/bash") - cmd = ( - f"{shell} -l -c" - f""" "{sys.executable} -c 'import os; print(dict(os.environ))'" """ - ) - proc = run_shell_command(cmd, env={}, text=True) + env_var = {} + if os.name == 'nt': + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Environment") + num_values = winreg.QueryInfoKey(key)[1] + env_var = dict( + [winreg.EnumValue(key, k)[:2] for k in range(num_values)] + ) + elif os.name == 'posix': + try: + user_env_script = _get_user_env_script() + proc = run_shell_command(user_env_script, env={}, text=True) stdout, stderr = proc.communicate() - env_var = eval(stdout, None) - except Exception: - return {} + if stderr: + logger.info(stderr.strip()) + if stdout: + env_var = eval(stdout, None) + except Exception as exc: + logger.info(exc) return env_var