fix(tools): re-raise ImportError without module name

The ImportError or ModuleNotFoundError might be raised without
specifying a module name. In this not so common situation, re-raise the
exception to print all the information that could assist in identifying
the problem.

Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
This commit is contained in:
Frantisek Hrbata 2024-11-08 12:32:54 +01:00
parent 7ac7939f52
commit 72a74a622b

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: Apache-2.0
#
@ -8,7 +8,6 @@
#
# You don't have to use idf.py, you can use cmake directly
# (or use cmake in an IDE)
# WARNING: we don't check for Python build-time dependencies until
# check_environment() function below. If possible, avoid importing
# any external libraries here - put in external script, or import in
@ -18,14 +17,20 @@ from __future__ import annotations
import codecs
import json
import locale
import os
import os.path
import subprocess
import sys
from collections import Counter, OrderedDict, _OrderedDictKeysView
from collections import Counter
from collections import OrderedDict
from collections.abc import KeysView
from importlib import import_module
from pkgutil import iter_modules
from typing import Any, Callable, Dict, List, Optional, Union
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import Optional
from typing import Union
# pyc files remain in the filesystem when switching between branches which might raise errors for incompatible
# idf.py extensions. Therefore, pyc file generation is turned off:
@ -40,13 +45,19 @@ try:
if os.getenv('IDF_COMPONENT_MANAGER') != '0':
from idf_component_manager import idf_extensions
except ImportError as e:
# For example, importing click could cause this.
print((f'Cannot import module "{e.name}". This usually means that "idf.py" was not '
print((f'{e}\n'
f'This usually means that "idf.py" was not '
f'spawned within an ESP-IDF shell environment or the python virtual '
f'environment used by "idf.py" is corrupted.\n'
f'Please use idf.py only in an ESP-IDF shell environment. If problem persists, '
f'please try to install ESP-IDF tools again as described in the Get Started guide.'),
file=sys.stderr)
if e.name is None:
# The ImportError or ModuleNotFoundError might be raised without
# specifying a module name. In this not so common situation, re-raise
# the exception to print all the information that could assist in
# identifying the problem.
raise
sys.exit(1)
@ -120,7 +131,7 @@ def _safe_relpath(path: str, start: Optional[str]=None) -> str:
return os.path.abspath(path)
def init_cli(verbose_output: List=None) -> Any:
def init_cli(verbose_output: Optional[List]=None) -> Any:
# Click is imported here to run it after check_environment()
import click
@ -186,7 +197,7 @@ def init_cli(verbose_output: List=None) -> Any:
self.action_args = action_args
self.aliases = aliases
def __call__(self, context: click.core.Context, global_args: PropertyDict, action_args: Dict=None) -> None:
def __call__(self, context: click.core.Context, global_args: PropertyDict, action_args: Optional[Dict]=None) -> None:
if action_args is None:
action_args = self.action_args
@ -287,7 +298,7 @@ def init_cli(verbose_output: List=None) -> Any:
SCOPES = ('default', 'global', 'shared')
def __init__(self, scope: Union['Scope', str]=None) -> None:
def __init__(self, scope: Optional[Union['Scope', str]]=None) -> None: # noqa: F821
if scope is None:
self._scope = 'default'
elif isinstance(scope, str) and scope in self.SCOPES:
@ -310,7 +321,7 @@ def init_cli(verbose_output: List=None) -> Any:
class Option(click.Option):
"""Option that knows whether it should be global"""
def __init__(self, scope: Union[Scope, str]=None, deprecated: Union[Dict, str, bool]=False, hidden: bool=False, **kwargs: str) -> None:
def __init__(self, scope: Optional[Union[Scope, str]]=None, deprecated: Union[Dict, str, bool]=False, hidden: bool=False, **kwargs: str) -> None:
"""
Keyword arguments additional to Click's Option class:
@ -348,7 +359,7 @@ def init_cli(verbose_output: List=None) -> Any:
class CLI(click.MultiCommand):
"""Action list contains all actions with options available for CLI"""
def __init__(self, all_actions: Dict=None, verbose_output: List=None, help: str=None) -> None:
def __init__(self, all_actions: Optional[Dict]=None, verbose_output: Optional[List]=None, help: Optional[str]=None) -> None:
super(CLI, self).__init__(
chain=True,
invoke_without_command=True,
@ -432,7 +443,7 @@ def init_cli(verbose_output: List=None) -> Any:
return Action(name=name, callback=callback.unwrapped_callback)
return None
def _print_closing_message(self, args: PropertyDict, actions: _OrderedDictKeysView) -> None:
def _print_closing_message(self, args: PropertyDict, actions: KeysView) -> None:
# print a closing message of some kind
#
if any(t in str(actions) for t in ('flash', 'dfu', 'uf2', 'uf2-app')):
@ -560,7 +571,7 @@ def init_cli(verbose_output: List=None) -> Any:
dependecies_processed = True
# If task have some dependecies they have to be executed before the task.
# If task have some dependencies they have to be executed before the task.
for dep in task.dependencies:
if dep not in tasks_to_run.keys():
# If dependent task is in the list of unprocessed tasks move to the front of the list
@ -697,7 +708,7 @@ def main() -> None:
# Check the environment only when idf.py is invoked regularly from command line.
checks_output = None if SHELL_COMPLETE_RUN else check_environment()
# Check existance of the current working directory to prevent exceptions from click cli.
# Check existence of the current working directory to prevent exceptions from click cli.
try:
os.getcwd()
except FileNotFoundError as e: