mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
change(tools): Unified outputs of various standalone scripts (esp-pylib)
This commit is contained in:
@@ -1,53 +1,22 @@
|
||||
# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
|
||||
from console_output import CONSOLE_STDERR
|
||||
from console_output import CONSOLE_STDOUT
|
||||
from console_output import debug
|
||||
from console_output import die
|
||||
from console_output import eprint
|
||||
from console_output import oprint
|
||||
import rich_click as click
|
||||
from console_output import configure_output
|
||||
from console_output import status_message
|
||||
from console_output import warn
|
||||
from esp_pylib.excepthook import install_exception_reporting
|
||||
from esp_pylib.logger import log
|
||||
from rich.markup import escape
|
||||
from shell_types import SHELL_CLASSES
|
||||
from shell_types import SUPPORTED_SHELLS
|
||||
from utils import conf
|
||||
from utils import run_cmd
|
||||
|
||||
|
||||
def parse_arguments() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='activate',
|
||||
description='Activate ESP-IDF environment',
|
||||
epilog='On Windows, run `python activate.py` to execute this script in the current terminal window.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-s',
|
||||
'--shell',
|
||||
metavar='SHELL',
|
||||
default=os.environ.get('ESP_IDF_SHELL', 'detect'),
|
||||
help='Explicitly specify shell to start. For example bash, zsh, powershell.exe, cmd.exe',
|
||||
)
|
||||
parser.add_argument('-l', '--list', action='store_true', help=('List supported shells.'))
|
||||
parser.add_argument('-e', '--export', action='store_true', help=('Generate commands to run in the terminal.'))
|
||||
parser.add_argument('-n', '--no-color', action='store_true', help=('Disable ANSI color escape sequences.'))
|
||||
parser.add_argument(
|
||||
'-d',
|
||||
'--debug',
|
||||
action='store_true',
|
||||
default=bool(os.environ.get('ESP_IDF_EXPORT_DEBUG')),
|
||||
help=('Enable debug information.'),
|
||||
)
|
||||
parser.add_argument('-q', '--quiet', action='store_true', help=('Suppress all output.'))
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@status_message('Checking python version', rv_on_ok=True)
|
||||
def check_python_version() -> str:
|
||||
# Check the Python version within a virtual environment
|
||||
@@ -72,7 +41,7 @@ def get_deactivate_cmd() -> str:
|
||||
|
||||
|
||||
@status_message('Establishing a new ESP-IDF environment')
|
||||
def get_idf_env() -> Dict[str, str]:
|
||||
def get_idf_env() -> dict[str, str]:
|
||||
# Get ESP-IDF system environment variables
|
||||
extra_paths_list = [
|
||||
os.path.join('components', 'espcoredump'),
|
||||
@@ -84,7 +53,7 @@ def get_idf_env() -> Dict[str, str]:
|
||||
stdout = run_cmd(cmd)
|
||||
|
||||
# idf_tools.py might not export certain environment variables if they are already set
|
||||
idf_env: Dict[str, Any] = {
|
||||
idf_env: dict[str, Any] = {
|
||||
'IDF_PATH': os.environ['IDF_PATH'],
|
||||
'ESP_IDF_VERSION': os.environ['ESP_IDF_VERSION'],
|
||||
'IDF_PYTHON_ENV_PATH': os.environ['IDF_PYTHON_ENV_PATH'],
|
||||
@@ -95,7 +64,7 @@ def get_idf_env() -> Dict[str, str]:
|
||||
var, val = line.split('=')
|
||||
idf_env[var] = val
|
||||
except ValueError as e:
|
||||
debug('\n'.join(['Output from `./tools/idf_tools.py export --format key-value`:', f'{stdout}']))
|
||||
log.debug('\n'.join(['Output from `./tools/idf_tools.py export --format key-value`:', f'{stdout}']))
|
||||
raise ValueError(
|
||||
'\n'.join(
|
||||
[
|
||||
@@ -117,7 +86,7 @@ def detect_shell(args: Any) -> str:
|
||||
import psutil
|
||||
|
||||
if args.shell != 'detect':
|
||||
debug(f'Shell explicitly stated: "{args.shell}"')
|
||||
log.debug(f'Shell explicitly stated: "{args.shell}"')
|
||||
return str(args.shell)
|
||||
|
||||
current_pid = os.getpid()
|
||||
@@ -128,9 +97,9 @@ def detect_shell(args: Any) -> str:
|
||||
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}')
|
||||
log.debug(f'Parent: pid: {parent_pid}, cmdline: {parent_cmdline}, exe: {parent_exe}, name: {parent_name}')
|
||||
if not parent_name.lower().startswith('python'):
|
||||
detected_shell_name = parent_name
|
||||
detected_shell_name = str(parent_name)
|
||||
break
|
||||
current_pid = parent_pid
|
||||
|
||||
@@ -155,29 +124,56 @@ def print_uninstall_msg() -> Any:
|
||||
return msg
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_arguments()
|
||||
@click.command(
|
||||
context_settings={'help_option_names': ['-h', '--help']},
|
||||
epilog='On Windows, run `python activate.py` to execute this script in the current terminal window.',
|
||||
)
|
||||
@click.option(
|
||||
'-s',
|
||||
'--shell',
|
||||
metavar='SHELL',
|
||||
default=os.environ.get('ESP_IDF_SHELL', 'detect'),
|
||||
show_default=True,
|
||||
help='Explicitly specify shell to start. For example bash, zsh, powershell.exe, cmd.exe',
|
||||
)
|
||||
@click.option('-l', '--list', 'list_shells', is_flag=True, help='List supported shells.')
|
||||
@click.option('-e', '--export', is_flag=True, help='Generate commands to run in the terminal.')
|
||||
@click.option('-n', '--no-color', is_flag=True, help='Disable ANSI color escape sequences.')
|
||||
@click.option(
|
||||
'-d',
|
||||
'--debug',
|
||||
'debug_flag',
|
||||
is_flag=True,
|
||||
default=bool(os.environ.get('ESP_IDF_EXPORT_DEBUG')),
|
||||
help='Enable debug information.',
|
||||
)
|
||||
@click.option('-q', '--quiet', is_flag=True, help='Suppress all output.')
|
||||
def main(shell: str, list_shells: bool, export: bool, no_color: bool, debug_flag: bool, quiet: bool) -> None:
|
||||
install_exception_reporting()
|
||||
|
||||
# Setup parsed arguments
|
||||
CONSOLE_STDERR.no_color = args.no_color
|
||||
CONSOLE_STDOUT.no_color = args.no_color
|
||||
CONSOLE_STDERR.quiet = args.quiet
|
||||
CONSOLE_STDOUT.quiet = args.quiet
|
||||
# Fill config global holder
|
||||
conf.ARGS = args
|
||||
# Fill config global holder before configure_output
|
||||
conf.ARGS = SimpleNamespace(
|
||||
shell=shell,
|
||||
list=list_shells,
|
||||
export=export,
|
||||
no_color=no_color,
|
||||
debug=debug_flag,
|
||||
quiet=quiet,
|
||||
)
|
||||
configure_output(no_color=no_color, quiet=quiet, debug=debug_flag)
|
||||
|
||||
debug(f'command line: {sys.argv}')
|
||||
log.debug(f'command line: {sys.argv}')
|
||||
if conf.ARGS.list:
|
||||
oprint(SUPPORTED_SHELLS)
|
||||
log.print(SUPPORTED_SHELLS)
|
||||
sys.exit()
|
||||
|
||||
eprint(f'[dark_orange]Activating ESP-IDF {conf.IDF_VERSION}')
|
||||
log.print(f'[dark_orange]Activating ESP-IDF {conf.IDF_VERSION}', file=sys.stderr)
|
||||
if conf.IDF_PATH_OLD and conf.IDF_PATH != conf.IDF_PATH_OLD:
|
||||
warn(f"IDF_PATH is changed from '{conf.IDF_PATH_OLD}' to '{conf.IDF_PATH}'.")
|
||||
log.warn(f"IDF_PATH is changed from '{escape(conf.IDF_PATH_OLD)}' to '{escape(conf.IDF_PATH)}'.")
|
||||
else:
|
||||
eprint(f"Setting IDF_PATH to '{conf.IDF_PATH}'.")
|
||||
log.print(f"Setting IDF_PATH to '{escape(conf.IDF_PATH)}'.", file=sys.stderr)
|
||||
|
||||
debug(f'IDF_PYTHON_ENV_PATH {conf.IDF_PYTHON_ENV_PATH}')
|
||||
log.debug(f'IDF_PYTHON_ENV_PATH {conf.IDF_PYTHON_ENV_PATH}')
|
||||
|
||||
check_python_version()
|
||||
check_python_dependencies()
|
||||
@@ -188,19 +184,21 @@ def main() -> None:
|
||||
print_uninstall_msg()
|
||||
|
||||
if detected_shell not in SHELL_CLASSES:
|
||||
die(f'"{detected_shell}" shell is not among the supported options: "{SUPPORTED_SHELLS}"')
|
||||
log.die(f'"{escape(str(detected_shell))}" shell is not among the supported options: "{SUPPORTED_SHELLS}"')
|
||||
|
||||
shell = SHELL_CLASSES[detected_shell](detected_shell, deactivate_cmd, new_esp_idf_env)
|
||||
shell_obj = SHELL_CLASSES[detected_shell](detected_shell, deactivate_cmd, new_esp_idf_env)
|
||||
|
||||
if conf.ARGS.export:
|
||||
shell.export()
|
||||
shell_obj.export()
|
||||
sys.exit()
|
||||
|
||||
eprint(
|
||||
f'[dark_orange]Starting new \'{shell.shell}\' shell with ESP-IDF environment... (use "exit" command to quit)'
|
||||
log.print(
|
||||
f"[dark_orange]Starting new '{escape(str(shell_obj.shell))}' shell with ESP-IDF environment..."
|
||||
' (use "exit" command to quit)',
|
||||
file=sys.stderr,
|
||||
)
|
||||
shell.spawn()
|
||||
eprint('[dark_orange]ESP-IDF environment exited.')
|
||||
shell_obj.spawn()
|
||||
log.print('[dark_orange]ESP-IDF environment exited.', file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,71 +1,62 @@
|
||||
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
|
||||
from rich.markup import escape
|
||||
from utils import conf
|
||||
|
||||
try:
|
||||
# The ESP-IDF virtual environment hasn't been verified yet, so see if the rich library
|
||||
# The ESP-IDF virtual environment hasn't been verified yet, so see if esp-pylib
|
||||
# can be imported to display error and status messages nicely.
|
||||
from rich.console import Console
|
||||
from esp_pylib.logger import EspLog
|
||||
from esp_pylib.logger import Verbosity
|
||||
from esp_pylib.logger import log
|
||||
except ImportError as e:
|
||||
sys.exit(f'error: Unable to import the rich module: {e}. Please execute the install script.')
|
||||
|
||||
CONSOLE_STDERR = Console(stderr=True, width=255)
|
||||
CONSOLE_STDOUT = Console(width=255)
|
||||
sys.exit(f'error: Unable to import esp-pylib: {e}. Please execute the install script.')
|
||||
|
||||
|
||||
def status_message(msg: str, msg_result: str='', rv_on_ok: bool=False, die_on_err: bool=True) -> Callable:
|
||||
def configure_output(*, no_color: bool = False, quiet: bool = False, debug: bool = False) -> None:
|
||||
# log is typed as EspLogBase; console options live on the default EspLog singleton.
|
||||
# Only pass no_color=True when requested. False would override Rich's NO_COLOR detection.
|
||||
options: dict[str, Any] = {'quiet': quiet}
|
||||
if no_color:
|
||||
options['no_color'] = True
|
||||
cast(EspLog, log).set_console_options(**options)
|
||||
if quiet:
|
||||
log.set_verbosity(Verbosity.SILENT)
|
||||
elif debug:
|
||||
log.set_verbosity(Verbosity.VERBOSE)
|
||||
else:
|
||||
log.set_verbosity(Verbosity.NORMAL)
|
||||
|
||||
|
||||
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 wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
eprint(f'[dark_orange]*[/dark_orange] {msg} ... ', end='')
|
||||
log.print(f'[dark_orange]*[/dark_orange] {escape(msg)} ... ', file=sys.stderr, end='')
|
||||
|
||||
try:
|
||||
rv = func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
eprint('[red]FAILED[/red]')
|
||||
if conf.ARGS.debug:
|
||||
log.print('[red]FAILED[/red]', file=sys.stderr)
|
||||
if conf.ARGS and conf.ARGS.debug:
|
||||
raise
|
||||
if not die_on_err:
|
||||
return None
|
||||
die(str(e))
|
||||
log.die(escape(str(e)))
|
||||
|
||||
if rv_on_ok:
|
||||
eprint(f'[green]{rv}[/green]')
|
||||
log.print(f'[green]{escape(str(rv))}[/green]', file=sys.stderr)
|
||||
elif msg_result:
|
||||
eprint(f'[green]{msg_result}[/green]')
|
||||
log.print(f'[green]{escape(msg_result)}[/green]', file=sys.stderr)
|
||||
else:
|
||||
eprint('[green]OK[/green]')
|
||||
log.print('[green]OK[/green]', file=sys.stderr)
|
||||
|
||||
return rv
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
def err(*args: Any, **kwargs: Any) -> None:
|
||||
CONSOLE_STDERR.print('[red]error[/red]: ', *args, **kwargs) # type: ignore
|
||||
|
||||
|
||||
def warn(*args: Any, **kwargs: Any) -> None:
|
||||
CONSOLE_STDERR.print('[yellow]warning[/yellow]: ', *args, **kwargs) # type: ignore
|
||||
|
||||
|
||||
def debug(*args: Any, **kwargs: Any) -> None:
|
||||
if not conf.ARGS.debug:
|
||||
return
|
||||
CONSOLE_STDERR.print('[green_yellow]debug[/green_yellow]: ', *args, **kwargs) # type: ignore
|
||||
|
||||
|
||||
def die(*args: Any, **kwargs: Any) -> None:
|
||||
err(*args, **kwargs)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def eprint(*args: Any, **kwargs: Any) -> None:
|
||||
CONSOLE_STDERR.print(*args, **kwargs) # type: ignore
|
||||
|
||||
|
||||
def oprint(*args: Any, **kwargs: Any) -> None:
|
||||
CONSOLE_STDOUT.print(*args, **kwargs) # type: ignore
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import base64
|
||||
import getpass
|
||||
@@ -17,9 +17,8 @@ from tempfile import TemporaryDirectory
|
||||
from tempfile import gettempdir
|
||||
from typing import TextIO
|
||||
|
||||
from console_output import debug
|
||||
from console_output import status_message
|
||||
from console_output import warn
|
||||
from esp_pylib.logger import log
|
||||
from utils import conf
|
||||
from utils import run_cmd
|
||||
|
||||
@@ -42,7 +41,7 @@ class Shell:
|
||||
self.tmp_dir_path = Path(gettempdir()) / f'esp_idf_activate_{username_safe}'
|
||||
except Exception as e:
|
||||
self.tmp_dir_path = Path(gettempdir()) / 'esp_idf_activate'
|
||||
warn(f'Failed to get username with error: {e}. Using default temporary directory {self.tmp_dir_path}.')
|
||||
log.warn(f'Failed to get username with error: {e}. Using default temporary directory {self.tmp_dir_path}.')
|
||||
|
||||
if not conf.ARGS.debug and os.path.exists(self.tmp_dir_path):
|
||||
# Do not cleanup temporary directory when debugging
|
||||
@@ -55,7 +54,7 @@ class Shell:
|
||||
if current_time - file_creation_time > timedelta(hours=1):
|
||||
item.unlink()
|
||||
except Exception as e:
|
||||
warn(f'Failed to clean temp activation directory with file {item}: {e}')
|
||||
log.warn(f'Failed to clean temp activation directory with file {item}: {e}')
|
||||
|
||||
self.tmp_dir_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -90,7 +89,7 @@ class UnixShell(Shell):
|
||||
|
||||
with NamedTemporaryFile(dir=self.tmp_dir_path, delete=False, prefix='activate_') as fd:
|
||||
self.script_file_path = Path(fd.name)
|
||||
debug(f'Temporary script file path: {self.script_file_path}')
|
||||
log.debug(f'Temporary script file path: {self.script_file_path}')
|
||||
|
||||
self.new_esp_idf_env['IDF_TOOLS_INSTALL_CMD'] = os.path.join(conf.IDF_PATH, 'install.sh')
|
||||
self.new_esp_idf_env['IDF_TOOLS_EXPORT_CMD'] = os.path.join(conf.IDF_PATH, 'export.sh')
|
||||
@@ -189,7 +188,7 @@ class ZshShell(UnixShell):
|
||||
# Create a temporary directory to use as ZDOTDIR
|
||||
tmpdir = TemporaryDirectory()
|
||||
tmpdir_path = Path(tmpdir.name)
|
||||
debug(f'Temporary ZDOTDIR {tmpdir_path} with .zshrc file')
|
||||
log.debug(f'Temporary ZDOTDIR {tmpdir_path} with .zshrc file')
|
||||
|
||||
# Copy init script to the custom ZDOTDIR
|
||||
zshrc_path = tmpdir_path / '.zshrc'
|
||||
@@ -232,7 +231,7 @@ class PowerShell(Shell):
|
||||
|
||||
with NamedTemporaryFile(dir=self.tmp_dir_path, delete=False, prefix='activate_', suffix='.ps1') as fd:
|
||||
self.script_file_path = Path(fd.name)
|
||||
debug(f'Temporary script file path: {self.script_file_path}')
|
||||
log.debug(f'Temporary script file path: {self.script_file_path}')
|
||||
|
||||
self.new_esp_idf_env['IDF_TOOLS_INSTALL_CMD'] = os.path.join(conf.IDF_PATH, 'install.ps1')
|
||||
self.new_esp_idf_env['IDF_TOOLS_EXPORT_CMD'] = os.path.join(conf.IDF_PATH, 'export.ps1')
|
||||
@@ -284,7 +283,7 @@ class WinCmd(Shell):
|
||||
|
||||
with NamedTemporaryFile(dir=self.tmp_dir_path, delete=False, prefix='activate_', suffix='.bat') as fd:
|
||||
self.script_file_path = Path(fd.name)
|
||||
debug(f'Temporary script file path: {self.script_file_path}')
|
||||
log.debug(f'Temporary script file path: {self.script_file_path}')
|
||||
|
||||
self.new_esp_idf_env['IDF_TOOLS_INSTALL_CMD'] = os.path.join(conf.IDF_PATH, 'install.bat')
|
||||
self.new_esp_idf_env['IDF_TOOLS_EXPORT_CMD'] = os.path.join(conf.IDF_PATH, 'export.bat')
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import argparse
|
||||
import os
|
||||
from subprocess import run
|
||||
from subprocess import SubprocessError
|
||||
from subprocess import run
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Config:
|
||||
@@ -15,6 +11,7 @@ class Config:
|
||||
Config serves as global hodler for variables used across modules
|
||||
It holds also arguments from command line
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.IDF_PATH = os.environ['IDF_PATH']
|
||||
self.IDF_PATH_OLD = os.environ['IDF_PATH_OLD']
|
||||
@@ -22,14 +19,14 @@ class Config:
|
||||
self.IDF_PYTHON_ENV_PATH = os.environ['IDF_PYTHON_ENV_PATH']
|
||||
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.ARGS: Optional[argparse.Namespace] = None
|
||||
self.ARGS: Any | None = None
|
||||
|
||||
|
||||
# Global variable instance
|
||||
conf = Config()
|
||||
|
||||
|
||||
def run_cmd(cmd: List[str], env: Optional[Dict[str, Any]]=None) -> str:
|
||||
def run_cmd(cmd: list[str], env: dict[str, Any] | None = None) -> str:
|
||||
new_env = os.environ.copy()
|
||||
if env is not None:
|
||||
new_env.update(env)
|
||||
|
||||
Reference in New Issue
Block a user