Skip to content

Commit 6f3241a

Browse files
fhrbataMarek Fiala
authored andcommitted
fix(tools): Upgrade shell detection & simplify autocompletion
Explicitly set shell type in export.sh for bash and zsh -Most of the issues reported with the updated export scripts are related to shell detection. Since we already know the shell type for commonly used ones like bash or zsh, we can pass the shell type directly to the activate script. This should hopefully resolve the shell detection problems for most users. This update also modifies the shell type detection to rely solely on psutil.Process().cmdline() rather than psutil.Process().exe(). This change aims to resolve permission issues that may occur if, for example, the Python binary has certain capabilities assigned. Move shell completion to the init script - Currently we are expanding the autocompletion in the activate script and adding the expanded commands into the init script. To avoid concerns about shell versions, move the entire expansion to the init script.
1 parent 2109f81 commit 6f3241a

File tree

5 files changed

+47
-32
lines changed

5 files changed

+47
-32
lines changed

export.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,19 @@ fi
1919
# Attempt to identify the ESP-IDF directory
2020
idf_path="."
2121

22+
shell_type="detect"
23+
2224
# shellcheck disable=SC2128,SC2169,SC2039,SC3054,SC3028 # ignore array expansion warning
2325
if test -n "${BASH_SOURCE-}"
2426
then
2527
# shellcheck disable=SC3028,SC3054 # unreachable with 'dash'
2628
idf_path=$(dirname "${BASH_SOURCE[0]}")
29+
shell_type="bash"
2730
elif test -n "${ZSH_VERSION-}"
2831
then
2932
# shellcheck disable=SC2296 # ignore parameter starts with '{' because it's zsh
3033
idf_path=$(dirname "${(%):-%x}")
34+
shell_type="zsh"
3135
elif test -n "${IDF_PATH-}"
3236
then
3337
idf_path=$IDF_PATH
@@ -46,7 +50,7 @@ fi
4650
. "${idf_path}/tools/detect_python.sh"
4751

4852
# Evaluate the ESP-IDF environment set up by the activate.py script.
49-
idf_exports=$("$ESP_PYTHON" "${idf_path}/tools/activate.py" --export)
53+
idf_exports=$("$ESP_PYTHON" "${idf_path}/tools/activate.py" --export --shell $shell_type)
5054
eval "${idf_exports}"
5155
unset idf_path
5256
return 0

tools/export_utils/activate_venv.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def parse_arguments() -> argparse.Namespace:
2525
epilog='On Windows, run `python activate.py` to execute this script in the current terminal window.')
2626
parser.add_argument('-s', '--shell',
2727
metavar='SHELL',
28-
default=os.environ.get('ESP_IDF_SHELL', None),
28+
default=os.environ.get('ESP_IDF_SHELL', 'detect'),
2929
help='Explicitly specify shell to start. For example bash, zsh, powershell.exe, cmd.exe')
3030
parser.add_argument('-l', '--list',
3131
action='store_true',
@@ -38,6 +38,7 @@ def parse_arguments() -> argparse.Namespace:
3838
help=('Disable ANSI color escape sequences.'))
3939
parser.add_argument('-d', '--debug',
4040
action='store_true',
41+
default=bool(os.environ.get('ESP_IDF_EXPORT_DEBUG')),
4142
help=('Enable debug information.'))
4243
parser.add_argument('-q', '--quiet',
4344
action='store_true',
@@ -100,17 +101,21 @@ def get_idf_env() -> Dict[str,str]:
100101
def detect_shell(args: Any) -> str:
101102
import psutil
102103

103-
if args.shell is not None:
104+
if args.shell != 'detect':
105+
debug(f'Shell explicitly stated: "{args.shell}"')
104106
return str(args.shell)
105107

106108
current_pid = os.getpid()
107109
detected_shell_name = ''
108110
while True:
109111
parent_pid = psutil.Process(current_pid).ppid()
110-
parent_name = os.path.basename(psutil.Process(parent_pid).exe())
112+
parent = psutil.Process(parent_pid)
113+
parent_cmdline = parent.cmdline()
114+
parent_exe = parent_cmdline[0].lstrip('-')
115+
parent_name = os.path.basename(parent_exe)
116+
debug(f'Parent: pid: {parent_pid}, cmdline: {parent_cmdline}, exe: {parent_exe}, name: {parent_name}')
111117
if not parent_name.lower().startswith('python'):
112118
detected_shell_name = parent_name
113-
conf.DETECTED_SHELL_PATH = psutil.Process(parent_pid).exe()
114119
break
115120
current_pid = parent_pid
116121

@@ -143,6 +148,7 @@ def main() -> None:
143148
# Fill config global holder
144149
conf.ARGS = args
145150

151+
debug(f'command line: {sys.argv}')
146152
if conf.ARGS.list:
147153
oprint(SUPPORTED_SHELLS)
148154
sys.exit()

tools/export_utils/console_output.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
CONSOLE_STDOUT = Console(width=255)
1818

1919

20-
def status_message(msg: str, rv_on_ok: bool=False, die_on_err: bool=True) -> Callable:
20+
def status_message(msg: str, msg_result: str='', rv_on_ok: bool=False, die_on_err: bool=True) -> Callable:
2121
def inner(func: Callable) -> Callable:
2222
def wrapper(*args: Any, **kwargs: Any) -> Any:
2323
eprint(f'[dark_orange]*[/dark_orange] {msg} ... ', end='')
@@ -34,6 +34,8 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
3434

3535
if rv_on_ok:
3636
eprint(f'[green]{rv}[/green]')
37+
elif msg_result:
38+
eprint(f'[green]{msg_result}[/green]')
3739
else:
3840
eprint('[green]OK[/green]')
3941

tools/export_utils/shell_types.py

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import re
55
import shutil
66
import sys
7+
import textwrap
78
from pathlib import Path
89
from subprocess import run
910
from tempfile import gettempdir
@@ -92,26 +93,23 @@ def click_ver(self) -> int:
9293

9394

9495
class BashShell(UnixShell):
95-
def get_bash_major_minor(self) -> float:
96-
env = self.expanded_env()
97-
bash_interpreter = conf.DETECTED_SHELL_PATH if conf.DETECTED_SHELL_PATH else 'bash'
98-
stdout = run_cmd([bash_interpreter, '-c', 'echo ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}'], env=env)
99-
bash_maj_min = float(stdout)
100-
return bash_maj_min
101-
102-
@status_message('Shell completion', die_on_err=False)
96+
@status_message('Shell completion', msg_result='Autocompletion code generated')
10397
def autocompletion(self) -> str:
104-
bash_maj_min = self.get_bash_major_minor()
105-
# Click supports bash version >= 4.4
106-
# https://click.palletsprojects.com/en/8.1.x/changes/#version-8-0-0
107-
if bash_maj_min < 4.4:
108-
raise RuntimeError('Autocompletion not supported')
109-
110-
env = self.expanded_env()
111-
env['LANG'] = 'en'
112-
env['_IDF.PY_COMPLETE'] = 'bash_source' if self.click_ver() >= 8 else 'source_bash'
113-
stdout: str = run_cmd([sys.executable, conf.IDF_PY], env=env)
114-
return stdout
98+
bash_source = 'bash_source' if self.click_ver() >= 8 else 'source_bash'
99+
autocom = textwrap.dedent(f"""
100+
WARNING_MSG="WARNING: Failed to load shell autocompletion for bash version: $BASH_VERSION!"
101+
if test ${{BASH_VERSINFO[0]}} -lt 4
102+
then
103+
echo "$WARNING_MSG"
104+
else
105+
if ! eval "$(env LANG=en _IDF.PY_COMPLETE={bash_source} idf.py)"
106+
then
107+
echo "$WARNING_MSG"
108+
fi
109+
fi
110+
""")
111+
112+
return autocom
115113

116114
def init_file(self) -> None:
117115
with open(self.script_file_path, 'w') as fd:
@@ -130,13 +128,19 @@ def spawn(self) -> None:
130128

131129

132130
class ZshShell(UnixShell):
133-
@status_message('Shell completion', die_on_err=False)
131+
@status_message('Shell completion', msg_result='Autocompletion code generated')
134132
def autocompletion(self) -> str:
135-
env = self.expanded_env()
136-
env['LANG'] = 'en'
137-
env['_IDF.PY_COMPLETE'] = 'zsh_source' if self.click_ver() >= 8 else 'source_zsh'
138-
stdout = run_cmd([sys.executable, conf.IDF_PY], env=env)
139-
return f'autoload -Uz compinit && compinit -u\n{stdout}'
133+
zsh_source = 'zsh_source' if self.click_ver() >= 8 else 'source_zsh'
134+
autocom = textwrap.dedent(f"""
135+
WARNING_MSG="WARNING: Failed to load shell autocompletion for zsh version: $ZSH_VERSION!"
136+
autoload -Uz compinit && compinit -u
137+
if ! eval "$(env _IDF.PY_COMPLETE={zsh_source} idf.py)"
138+
then
139+
echo "$WARNING_MSG"
140+
fi
141+
""")
142+
143+
return autocom
140144

141145
def init_file(self) -> None:
142146
# If ZDOTDIR is unset, HOME is used instead.

tools/export_utils/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ def __init__(self) -> None:
2222
self.IDF_TOOLS_PY = os.path.join(self.IDF_PATH, 'tools', 'idf_tools.py')
2323
self.IDF_PY = os.path.join(self.IDF_PATH, 'tools', 'idf.py')
2424
self.ARGS: Optional[argparse.Namespace] = None
25-
self.DETECTED_SHELL_PATH: str = ''
2625

2726

2827
# Global variable instance

0 commit comments

Comments
 (0)