Merge branch 'fix/specify_shell_in_exports' into 'master'

fix: explicitly set shell type in export.sh for bash and zsh

Closes IDF-11137, IDF-11138, and IDFGH-13713

See merge request espressif/esp-idf!33558
This commit is contained in:
Roland Dobai 2024-09-19 17:47:01 +08:00
commit fe29994924
5 changed files with 52 additions and 36 deletions

View File

@ -19,15 +19,19 @@ fi
# Attempt to identify the ESP-IDF directory # Attempt to identify the ESP-IDF directory
idf_path="." idf_path="."
shell_type="detect"
# shellcheck disable=SC2128,SC2169,SC2039,SC3054,SC3028 # ignore array expansion warning # shellcheck disable=SC2128,SC2169,SC2039,SC3054,SC3028 # ignore array expansion warning
if test -n "${BASH_SOURCE-}" if test -n "${BASH_SOURCE-}"
then then
# shellcheck disable=SC3028,SC3054 # unreachable with 'dash' # shellcheck disable=SC3028,SC3054 # unreachable with 'dash'
idf_path=$(dirname "${BASH_SOURCE[0]}") idf_path=$(dirname "${BASH_SOURCE[0]}")
shell_type="bash"
elif test -n "${ZSH_VERSION-}" elif test -n "${ZSH_VERSION-}"
then then
# shellcheck disable=SC2296 # ignore parameter starts with '{' because it's zsh # shellcheck disable=SC2296 # ignore parameter starts with '{' because it's zsh
idf_path=$(dirname "${(%):-%x}") idf_path=$(dirname "${(%):-%x}")
shell_type="zsh"
elif test -n "${IDF_PATH-}" elif test -n "${IDF_PATH-}"
then then
idf_path=$IDF_PATH idf_path=$IDF_PATH
@ -46,7 +50,7 @@ fi
. "${idf_path}/tools/detect_python.sh" . "${idf_path}/tools/detect_python.sh"
# Evaluate the ESP-IDF environment set up by the activate.py script. # Evaluate the ESP-IDF environment set up by the activate.py script.
idf_exports=$("$ESP_PYTHON" "${idf_path}/tools/activate.py" --export) idf_exports=$("$ESP_PYTHON" "${idf_path}/tools/activate.py" --export --shell $shell_type)
eval "${idf_exports}" eval "${idf_exports}"
unset idf_path unset idf_path
return 0 return 0

View File

@ -25,7 +25,7 @@ def parse_arguments() -> argparse.Namespace:
epilog='On Windows, run `python activate.py` to execute this script in the current terminal window.') epilog='On Windows, run `python activate.py` to execute this script in the current terminal window.')
parser.add_argument('-s', '--shell', parser.add_argument('-s', '--shell',
metavar='SHELL', metavar='SHELL',
default=os.environ.get('ESP_IDF_SHELL', None), default=os.environ.get('ESP_IDF_SHELL', 'detect'),
help='Explicitly specify shell to start. For example bash, zsh, powershell.exe, cmd.exe') help='Explicitly specify shell to start. For example bash, zsh, powershell.exe, cmd.exe')
parser.add_argument('-l', '--list', parser.add_argument('-l', '--list',
action='store_true', action='store_true',
@ -38,6 +38,7 @@ def parse_arguments() -> argparse.Namespace:
help=('Disable ANSI color escape sequences.')) help=('Disable ANSI color escape sequences.'))
parser.add_argument('-d', '--debug', parser.add_argument('-d', '--debug',
action='store_true', action='store_true',
default=bool(os.environ.get('ESP_IDF_EXPORT_DEBUG')),
help=('Enable debug information.')) help=('Enable debug information.'))
parser.add_argument('-q', '--quiet', parser.add_argument('-q', '--quiet',
action='store_true', action='store_true',
@ -100,17 +101,21 @@ def get_idf_env() -> Dict[str,str]:
def detect_shell(args: Any) -> str: def detect_shell(args: Any) -> str:
import psutil import psutil
if args.shell is not None: if args.shell != 'detect':
debug(f'Shell explicitly stated: "{args.shell}"')
return str(args.shell) return str(args.shell)
current_pid = os.getpid() current_pid = os.getpid()
detected_shell_name = '' detected_shell_name = ''
while True: while True:
parent_pid = psutil.Process(current_pid).ppid() parent_pid = psutil.Process(current_pid).ppid()
parent_name = os.path.basename(psutil.Process(parent_pid).exe()) parent = psutil.Process(parent_pid)
parent_cmdline = parent.cmdline()
parent_exe = parent_cmdline[0].lstrip('-')
parent_name = os.path.basename(parent_exe)
debug(f'Parent: pid: {parent_pid}, cmdline: {parent_cmdline}, exe: {parent_exe}, name: {parent_name}')
if not parent_name.lower().startswith('python'): if not parent_name.lower().startswith('python'):
detected_shell_name = parent_name detected_shell_name = parent_name
conf.DETECTED_SHELL_PATH = psutil.Process(parent_pid).exe()
break break
current_pid = parent_pid current_pid = parent_pid
@ -143,6 +148,7 @@ def main() -> None:
# Fill config global holder # Fill config global holder
conf.ARGS = args conf.ARGS = args
debug(f'command line: {sys.argv}')
if conf.ARGS.list: if conf.ARGS.list:
oprint(SUPPORTED_SHELLS) oprint(SUPPORTED_SHELLS)
sys.exit() sys.exit()

View File

@ -17,7 +17,7 @@ CONSOLE_STDERR = Console(stderr=True, width=255)
CONSOLE_STDOUT = Console(width=255) CONSOLE_STDOUT = Console(width=255)
def status_message(msg: str, rv_on_ok: bool=False, die_on_err: bool=True) -> Callable: def status_message(msg: str, msg_result: str='', rv_on_ok: bool=False, die_on_err: bool=True) -> Callable:
def inner(func: Callable) -> Callable: def inner(func: Callable) -> Callable:
def wrapper(*args: Any, **kwargs: Any) -> Any: def wrapper(*args: Any, **kwargs: Any) -> Any:
eprint(f'[dark_orange]*[/dark_orange] {msg} ... ', end='') eprint(f'[dark_orange]*[/dark_orange] {msg} ... ', end='')
@ -34,6 +34,8 @@ def status_message(msg: str, rv_on_ok: bool=False, die_on_err: bool=True) -> Cal
if rv_on_ok: if rv_on_ok:
eprint(f'[green]{rv}[/green]') eprint(f'[green]{rv}[/green]')
elif msg_result:
eprint(f'[green]{msg_result}[/green]')
else: else:
eprint('[green]OK[/green]') eprint('[green]OK[/green]')

View File

@ -4,6 +4,7 @@ import os
import re import re
import shutil import shutil
import sys import sys
import textwrap
from pathlib import Path from pathlib import Path
from subprocess import run from subprocess import run
from tempfile import gettempdir from tempfile import gettempdir
@ -72,10 +73,6 @@ class UnixShell(Shell):
# Basic POSIX shells does not support autocompletion # Basic POSIX shells does not support autocompletion
return None return None
def init_file(self) -> None:
with open(self.script_file_path, 'w') as fd:
self.export_file(fd)
def export_file(self, fd: TextIO) -> None: def export_file(self, fd: TextIO) -> None:
fd.write(f'{self.deactivate_cmd}\n') fd.write(f'{self.deactivate_cmd}\n')
for var, value in self.new_esp_idf_env.items(): for var, value in self.new_esp_idf_env.items():
@ -87,7 +84,8 @@ class UnixShell(Shell):
'Go to the project directory and run:\n\n idf.py build"\n')) 'Go to the project directory and run:\n\n idf.py build"\n'))
def export(self) -> None: def export(self) -> None:
self.init_file() with open(self.script_file_path, 'w') as fd:
self.export_file(fd)
print(f'. {self.script_file_path}') print(f'. {self.script_file_path}')
def click_ver(self) -> int: def click_ver(self) -> int:
@ -95,26 +93,23 @@ class UnixShell(Shell):
class BashShell(UnixShell): class BashShell(UnixShell):
def get_bash_major_minor(self) -> float: @status_message('Shell completion', msg_result='Autocompletion code generated')
env = self.expanded_env()
bash_interpreter = conf.DETECTED_SHELL_PATH if conf.DETECTED_SHELL_PATH else 'bash'
stdout = run_cmd([bash_interpreter, '-c', 'echo ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}'], env=env)
bash_maj_min = float(stdout)
return bash_maj_min
@status_message('Shell completion', die_on_err=False)
def autocompletion(self) -> str: def autocompletion(self) -> str:
bash_maj_min = self.get_bash_major_minor() bash_source = 'bash_source' if self.click_ver() >= 8 else 'source_bash'
# Click supports bash version >= 4.4 autocom = textwrap.dedent(f"""
# https://click.palletsprojects.com/en/8.1.x/changes/#version-8-0-0 WARNING_MSG="WARNING: Failed to load shell autocompletion for bash version: $BASH_VERSION!"
if bash_maj_min < 4.4: if test ${{BASH_VERSINFO[0]}} -lt 4
raise RuntimeError('Autocompletion not supported') then
echo "$WARNING_MSG"
else
if ! eval "$(env LANG=en _IDF.PY_COMPLETE={bash_source} idf.py)"
then
echo "$WARNING_MSG"
fi
fi
""")
env = self.expanded_env() return autocom
env['LANG'] = 'en'
env['_IDF.PY_COMPLETE'] = 'bash_source' if self.click_ver() >= 8 else 'source_bash'
stdout: str = run_cmd([sys.executable, conf.IDF_PY], env=env)
return stdout
def init_file(self) -> None: def init_file(self) -> None:
with open(self.script_file_path, 'w') as fd: with open(self.script_file_path, 'w') as fd:
@ -133,13 +128,19 @@ class BashShell(UnixShell):
class ZshShell(UnixShell): class ZshShell(UnixShell):
@status_message('Shell completion', die_on_err=False) @status_message('Shell completion', msg_result='Autocompletion code generated')
def autocompletion(self) -> str: def autocompletion(self) -> str:
env = self.expanded_env() zsh_source = 'zsh_source' if self.click_ver() >= 8 else 'source_zsh'
env['LANG'] = 'en' autocom = textwrap.dedent(f"""
env['_IDF.PY_COMPLETE'] = 'zsh_source' if self.click_ver() >= 8 else 'source_zsh' WARNING_MSG="WARNING: Failed to load shell autocompletion for zsh version: $ZSH_VERSION!"
stdout = run_cmd([sys.executable, conf.IDF_PY], env=env) autoload -Uz compinit && compinit -u
return f'autoload -Uz compinit && compinit -u\n{stdout}' if ! eval "$(env _IDF.PY_COMPLETE={zsh_source} idf.py)"
then
echo "$WARNING_MSG"
fi
""")
return autocom
def init_file(self) -> None: def init_file(self) -> None:
# If ZDOTDIR is unset, HOME is used instead. # If ZDOTDIR is unset, HOME is used instead.
@ -188,6 +189,10 @@ class FishShell(UnixShell):
stdout: str = run_cmd([sys.executable, conf.IDF_PY], env=env) stdout: str = run_cmd([sys.executable, conf.IDF_PY], env=env)
return stdout return stdout
def init_file(self) -> None:
with open(self.script_file_path, 'w') as fd:
self.export_file(fd)
def spawn(self) -> None: def spawn(self) -> None:
self.init_file() self.init_file()
new_env = os.environ.copy() new_env = os.environ.copy()

View File

@ -22,7 +22,6 @@ class Config:
self.IDF_TOOLS_PY = os.path.join(self.IDF_PATH, 'tools', 'idf_tools.py') self.IDF_TOOLS_PY = os.path.join(self.IDF_PATH, 'tools', 'idf_tools.py')
self.IDF_PY = os.path.join(self.IDF_PATH, 'tools', 'idf.py') self.IDF_PY = os.path.join(self.IDF_PATH, 'tools', 'idf.py')
self.ARGS: Optional[argparse.Namespace] = None self.ARGS: Optional[argparse.Namespace] = None
self.DETECTED_SHELL_PATH: str = ''
# Global variable instance # Global variable instance