Merge branch 'fix/click_version_v5.5' into 'release/v5.5'

Fixed click deprecation warnings (v5.5)

See merge request espressif/esp-idf!40893
This commit is contained in:
Roland Dobai
2025-09-01 11:55:55 +02:00
4 changed files with 149 additions and 86 deletions

View File

@@ -9,6 +9,7 @@ import sys
import textwrap import textwrap
from datetime import datetime from datetime import datetime
from datetime import timedelta from datetime import timedelta
from importlib.metadata import version as importlib_version
from pathlib import Path from pathlib import Path
from subprocess import run from subprocess import run
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
@@ -19,7 +20,6 @@ from typing import List
from typing import TextIO from typing import TextIO
from typing import Union from typing import Union
import click
from console_output import debug from console_output import debug
from console_output import status_message from console_output import status_message
from console_output import warn from console_output import warn
@@ -122,7 +122,7 @@ class UnixShell(Shell):
print(f'. {self.script_file_path}') print(f'. {self.script_file_path}')
def click_ver(self) -> int: def click_ver(self) -> int:
return int(click.__version__.split('.')[0]) return int(importlib_version('click').split('.')[0])
class BashShell(UnixShell): class BashShell(UnixShell):

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
# SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
@@ -39,19 +39,30 @@ sys.dont_write_bytecode = True
import python_version_checker # noqa: E402 import python_version_checker # noqa: E402
try: try:
from idf_py_actions.errors import FatalError # noqa: E402 from idf_py_actions.errors import FatalError
from idf_py_actions.tools import (PROG, SHELL_COMPLETE_RUN, SHELL_COMPLETE_VAR, PropertyDict, # noqa: E402 from idf_py_actions.tools import PROG
debug_print_idf_version, get_target, merge_action_lists, print_warning) from idf_py_actions.tools import SHELL_COMPLETE_RUN
from idf_py_actions.tools import SHELL_COMPLETE_VAR
from idf_py_actions.tools import PropertyDict
from idf_py_actions.tools import debug_print_idf_version
from idf_py_actions.tools import get_target
from idf_py_actions.tools import merge_action_lists
from idf_py_actions.tools import print_warning
if os.getenv('IDF_COMPONENT_MANAGER') != '0': if os.getenv('IDF_COMPONENT_MANAGER') != '0':
from idf_component_manager import idf_extensions from idf_component_manager import idf_extensions
except ImportError as e: except ImportError as e:
print((f'{e}\n' print(
f'This usually means that "idf.py" was not ' (
f'spawned within an ESP-IDF shell environment or the python virtual ' f'{e}\n'
f'environment used by "idf.py" is corrupted.\n' f'This usually means that "idf.py" was not '
f'Please use idf.py only in an ESP-IDF shell environment. If problem persists, ' f'spawned within an ESP-IDF shell environment or the python virtual '
f'please try to install ESP-IDF tools again as described in the Get Started guide.'), f'environment used by "idf.py" is corrupted.\n'
file=sys.stderr) 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: if e.name is None:
# The ImportError or ModuleNotFoundError might be raised without # The ImportError or ModuleNotFoundError might be raised without
# specifying a module name. In this not so common situation, re-raise # specifying a module name. In this not so common situation, re-raise
@@ -85,8 +96,9 @@ def check_environment() -> List:
if set_idf_path != detected_idf_path: if set_idf_path != detected_idf_path:
print_warning( print_warning(
'WARNING: IDF_PATH environment variable is set to %s but %s path indicates IDF directory %s. ' 'WARNING: IDF_PATH environment variable is set to %s but %s path indicates IDF directory %s. '
'Using the environment variable directory, but results may be unexpected...' % 'Using the environment variable directory, but results may be unexpected...'
(set_idf_path, PROG, detected_idf_path)) % (set_idf_path, PROG, detected_idf_path)
)
else: else:
print_warning('Setting IDF_PATH environment variable: %s' % detected_idf_path) print_warning('Setting IDF_PATH environment variable: %s' % detected_idf_path)
os.environ['IDF_PATH'] = detected_idf_path os.environ['IDF_PATH'] = detected_idf_path
@@ -122,15 +134,18 @@ def check_environment() -> List:
try: try:
python_venv_path = os.environ['IDF_PYTHON_ENV_PATH'] python_venv_path = os.environ['IDF_PYTHON_ENV_PATH']
if python_venv_path and not sys.executable.startswith(python_venv_path): if python_venv_path and not sys.executable.startswith(python_venv_path):
print_warning(f'WARNING: Python interpreter "{sys.executable}" used to start idf.py is not from installed venv "{python_venv_path}"') print_warning(
f'WARNING: Python interpreter "{sys.executable}" used to start idf.py'
f' is not from installed venv "{python_venv_path}"'
)
except KeyError: except KeyError:
print_warning('WARNING: The IDF_PYTHON_ENV_PATH is missing in environmental variables!') print_warning('WARNING: The IDF_PYTHON_ENV_PATH is missing in environmental variables!')
return checks_output return checks_output
def _safe_relpath(path: str, start: Optional[str]=None) -> str: def _safe_relpath(path: str, start: Optional[str] = None) -> str:
""" Return a relative path, same as os.path.relpath, but only if this is possible. """Return a relative path, same as os.path.relpath, but only if this is possible.
It is not possible on Windows, if the start directory and the path are on different drives. It is not possible on Windows, if the start directory and the path are on different drives.
""" """
@@ -140,14 +155,15 @@ def _safe_relpath(path: str, start: Optional[str]=None) -> str:
return os.path.abspath(path) return os.path.abspath(path)
def init_cli(verbose_output: Optional[List]=None) -> Any: def init_cli(verbose_output: Optional[List] = None) -> Any:
# Click is imported here to run it after check_environment() # Click is imported here to run it after check_environment()
import click import click
from click.shell_completion import CompletionItem from click.shell_completion import CompletionItem
class Deprecation(object): class Deprecation(object):
"""Construct deprecation notice for help messages""" """Construct deprecation notice for help messages"""
def __init__(self, deprecated: Union[Dict, str, bool]=False) -> None:
def __init__(self, deprecated: Union[Dict, str, bool] = False) -> None:
self.deprecated = deprecated self.deprecated = deprecated
self.since = None self.since = None
self.removed = None self.removed = None
@@ -162,25 +178,25 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
elif isinstance(deprecated, str): elif isinstance(deprecated, str):
self.custom_message = deprecated self.custom_message = deprecated
def full_message(self, type: str='Option') -> str: def full_message(self, msg_type: str = 'Option') -> str:
if self.exit_with_error: if self.exit_with_error:
return '%s is deprecated %sand was removed%s.%s' % ( return '%s is deprecated %sand was removed%s.%s' % (
type, msg_type,
'since %s ' % self.since if self.since else '', 'since %s ' % self.since if self.since else '',
' in %s' % self.removed if self.removed else '', ' in %s' % self.removed if self.removed else '',
' %s' % self.custom_message if self.custom_message else '', ' %s' % self.custom_message if self.custom_message else '',
) )
else: else:
return '%s is deprecated %sand will be removed in%s.%s' % ( return '%s is deprecated %sand will be removed in%s.%s' % (
type, msg_type,
'since %s ' % self.since if self.since else '', 'since %s ' % self.since if self.since else '',
' %s' % self.removed if self.removed else ' future versions', ' %s' % self.removed if self.removed else ' future versions',
' %s' % self.custom_message if self.custom_message else '', ' %s' % self.custom_message if self.custom_message else '',
) )
def help(self, text: str, type: str='Option', separator: str=' ') -> str: def help(self, text: str, msg_type: str = 'Option', separator: str = ' ') -> str:
text = text or '' text = text or ''
return self.full_message(type) + separator + text if self.deprecated else text return self.full_message(msg_type) + separator + text if self.deprecated else text
def short_help(self, text: str) -> str: def short_help(self, text: str) -> str:
text = text or '' text = text or ''
@@ -198,8 +214,15 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
print_warning('Warning: %s' % deprecation.full_message('Option "%s"' % option.name)) print_warning('Warning: %s' % deprecation.full_message('Option "%s"' % option.name))
class Task(object): class Task(object):
def __init__(self, callback: Callable, name: str, aliases: List, dependencies: Optional[List], def __init__(
order_dependencies: Optional[List], action_args: Dict) -> None: self,
callback: Callable,
name: str,
aliases: List,
dependencies: Optional[List],
order_dependencies: Optional[List],
action_args: Dict,
) -> None:
self.callback = callback self.callback = callback
self.name = name self.name = name
self.dependencies = dependencies self.dependencies = dependencies
@@ -207,7 +230,9 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
self.action_args = action_args self.action_args = action_args
self.aliases = aliases self.aliases = aliases
def __call__(self, context: click.core.Context, global_args: PropertyDict, action_args: Optional[Dict]=None) -> None: def __call__(
self, context: click.core.Context, global_args: PropertyDict, action_args: Optional[Dict] = None
) -> None:
if action_args is None: if action_args is None:
action_args = self.action_args action_args = self.action_args
@@ -215,14 +240,15 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
class Action(click.Command): class Action(click.Command):
def __init__( def __init__(
self, self,
name: Optional[str]=None, name: Optional[str] = None,
aliases: Optional[List]=None, aliases: Optional[List] = None,
deprecated: Union[Dict, str, bool]=False, deprecated: Union[Dict, str, bool] = False,
dependencies: Optional[List]=None, dependencies: Optional[List] = None,
order_dependencies: Optional[List]=None, order_dependencies: Optional[List] = None,
hidden: bool=False, hidden: bool = False,
**kwargs: Any) -> None: **kwargs: Any,
) -> None:
super(Action, self).__init__(name, **kwargs) super(Action, self).__init__(name, **kwargs)
self.name: str = self.name or self.callback.__name__ self.name: str = self.name or self.callback.__name__
@@ -247,7 +273,7 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
if deprecated: if deprecated:
deprecation = Deprecation(deprecated) deprecation = Deprecation(deprecated)
self.short_help = deprecation.short_help(self.short_help) self.short_help = deprecation.short_help(self.short_help)
self.help = deprecation.help(self.help, type='Command', separator='\n') self.help = deprecation.help(self.help, msg_type='Command', separator='\n')
# Add aliases to help string # Add aliases to help string
if aliases: if aliases:
@@ -269,7 +295,7 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
aliases=self.aliases, aliases=self.aliases,
) )
self.callback = wrapped_callback self.callback: Callable = wrapped_callback
def invoke(self, ctx: click.core.Context) -> click.core.Context: def invoke(self, ctx: click.core.Context) -> click.core.Context:
if self.deprecated: if self.deprecated:
@@ -293,22 +319,23 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
names - alias of 'param_decls' names - alias of 'param_decls'
""" """
def __init__(self, **kwargs: str): def __init__(self, **kwargs: str):
names = kwargs.pop('names') names = kwargs.pop('names')
super(Argument, self).__init__(names, **kwargs) super(Argument, self).__init__(names, **kwargs)
class Scope(object): class Scope(object):
""" """
Scope for sub-command option. Scope for sub-command option.
possible values: possible values:
- default - only available on defined level (global/action) - default - only available on defined level (global/action)
- global - When defined for action, also available as global - global - When defined for action, also available as global
- shared - Opposite to 'global': when defined in global scope, also available for all actions - shared - Opposite to 'global': when defined in global scope, also available for all actions
""" """
SCOPES = ('default', 'global', 'shared') SCOPES = ('default', 'global', 'shared')
def __init__(self, scope: Optional[Union['Scope', str]]=None) -> None: # noqa: F821 def __init__(self, scope: Optional[Union['Scope', str]] = None) -> None: # noqa: F821
if scope is None: if scope is None:
self._scope = 'default' self._scope = 'default'
elif isinstance(scope, str) and scope in self.SCOPES: elif isinstance(scope, str) and scope in self.SCOPES:
@@ -331,7 +358,14 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
class Option(click.Option): class Option(click.Option):
"""Option that knows whether it should be global""" """Option that knows whether it should be global"""
def __init__(self, scope: Optional[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: Keyword arguments additional to Click's Option class:
@@ -367,16 +401,22 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
return super(Option, self).get_help_record(ctx) return super(Option, self).get_help_record(ctx)
class CLI(click.MultiCommand): class CLI(click.Group):
"""Action list contains all actions with options available for CLI""" """Action list contains all actions with options available for CLI"""
def __init__(self, all_actions: Optional[Dict]=None, verbose_output: Optional[List]=None, help: Optional[str]=None) -> None:
def __init__(
self,
all_actions: Optional[Dict] = None,
verbose_output: Optional[List] = None,
cli_help: Optional[str] = None,
) -> None:
super(CLI, self).__init__( super(CLI, self).__init__(
chain=True, chain=True,
invoke_without_command=True, invoke_without_command=True,
result_callback=self.execute_tasks, result_callback=self.execute_tasks,
no_args_is_help=True, no_args_is_help=True,
context_settings={'max_content_width': 140}, context_settings={'max_content_width': 140},
help=help, help=cli_help,
) )
self._actions = {} self._actions = {}
self.global_action_callbacks = [] self.global_action_callbacks = []
@@ -431,7 +471,8 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
if option.scope.is_shared: if option.scope.is_shared:
raise FatalError( raise FatalError(
'"%s" is defined for action "%s". ' '"%s" is defined for action "%s". '
' "shared" options can be declared only on global level' % (option.name, name)) ' "shared" options can be declared only on global level' % (option.name, name)
)
# Promote options to global if see for the first time # Promote options to global if see for the first time
if option.scope.is_global and option.name not in [o.name for o in self.params]: if option.scope.is_global and option.name not in [o.name for o in self.params]:
@@ -458,8 +499,9 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
# COMP_WORDBREAKS. When @ is included, the @-argument is not considered # COMP_WORDBREAKS. When @ is included, the @-argument is not considered
# part of the completion word, causing @-argument completion to function # part of the completion word, causing @-argument completion to function
# unreliably in bash. # unreliably in bash.
complete_file = ('bash' not in os.environ.get('_IDF.PY_COMPLETE', '') or complete_file = 'bash' not in os.environ.get('_IDF.PY_COMPLETE', '') or '@' not in os.environ.get(
'@' not in os.environ.get('IDF_PY_COMP_WORDBREAKS', '')) 'IDF_PY_COMP_WORDBREAKS', ''
)
if incomplete.startswith('@') and complete_file: if incomplete.startswith('@') and complete_file:
path_prefix = incomplete[1:] path_prefix = incomplete[1:]
candidates = glob.glob(path_prefix + '*') candidates = glob.glob(path_prefix + '*')
@@ -491,11 +533,12 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
if key != 'project': # flashing a single item if key != 'project': # flashing a single item
if key not in flasher_args: if key not in flasher_args:
# This is the case for 'idf.py bootloader' if Secure Boot is on, need to follow manual flashing steps # This is the case for 'idf.py bootloader'
# if Secure Boot is on, need to follow manual flashing steps
print('\n%s build complete.' % title) print('\n%s build complete.' % title)
return return
cmd = '' cmd = ''
if (key == 'bootloader'): # bootloader needs --flash-mode, etc to be passed in if key == 'bootloader': # bootloader needs --flash-mode, etc to be passed in
cmd = ' '.join(flasher_args['write_flash_args']) + ' ' cmd = ' '.join(flasher_args['write_flash_args']) + ' '
cmd += flasher_args[key]['offset'] + ' ' cmd += flasher_args[key]['offset'] + ' '
@@ -518,11 +561,13 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
print('or') print('or')
print(f' idf.py -p PORT {flash_target}') print(f' idf.py -p PORT {flash_target}')
esptool_cmd = ['python -m esptool', esptool_cmd = [
'--chip {}'.format(flasher_args['extra_esptool_args']['chip']), 'python -m esptool',
f'-b {args.baud}', '--chip {}'.format(flasher_args['extra_esptool_args']['chip']),
'--before {}'.format(flasher_args['extra_esptool_args']['before']), f'-b {args.baud}',
'--after {}'.format(flasher_args['extra_esptool_args']['after'])] '--before {}'.format(flasher_args['extra_esptool_args']['before']),
'--after {}'.format(flasher_args['extra_esptool_args']['after']),
]
if not flasher_args['extra_esptool_args']['stub']: if not flasher_args['extra_esptool_args']['stub']:
esptool_cmd += ['--no-stub'] esptool_cmd += ['--no-stub']
@@ -555,14 +600,16 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
# Show warning if some tasks are present several times in the list # Show warning if some tasks are present several times in the list
dupplicated_tasks = sorted( dupplicated_tasks = sorted(
[item for item, count in Counter(task.name for task in tasks).items() if count > 1]) [item for item, count in Counter(task.name for task in tasks).items() if count > 1]
)
if dupplicated_tasks: if dupplicated_tasks:
dupes = ', '.join('"%s"' % t for t in dupplicated_tasks) dupes = ', '.join('"%s"' % t for t in dupplicated_tasks)
print_warning( print_warning(
'WARNING: Command%s found in the list of commands more than once. ' % 'WARNING: Command%s found in the list of commands more than once. '
('s %s are' % dupes if len(dupplicated_tasks) > 1 else ' %s is' % dupes) + % ('s %s are' % dupes if len(dupplicated_tasks) > 1 else ' %s is' % dupes)
'Only first occurrence will be executed.') + 'Only first occurrence will be executed.'
)
for task in tasks: for task in tasks:
# Set propagated global options. # Set propagated global options.
@@ -577,13 +624,17 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
if global_value != default and local_value != default and global_value != local_value: if global_value != default and local_value != default and global_value != local_value:
if hasattr(option, 'envvar') and option.envvar and os.getenv(option.envvar) != default: if hasattr(option, 'envvar') and option.envvar and os.getenv(option.envvar) != default:
msg = (f'This option cannot be set in command line if the {option.envvar} ' msg = (
'environment variable is set to a different value.') f'This option cannot be set in command line if the {option.envvar} '
'environment variable is set to a different value.'
)
else: else:
msg = 'This option can appear at most once in the command line.' msg = 'This option can appear at most once in the command line.'
raise FatalError(f'Option "{key}" provided for "{task.name}" is already defined to ' raise FatalError(
f'a different value. {msg}') f'Option "{key}" provided for "{task.name}" is already defined to '
f'a different value. {msg}'
)
if local_value != default: if local_value != default:
global_args[key] = local_value global_args[key] = local_value
@@ -615,8 +666,9 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
# and put to the front of the list of unprocessed tasks # and put to the front of the list of unprocessed tasks
else: else:
print( print(
'Adding "%s"\'s dependency "%s" to list of commands with default set of options.' % 'Adding "%s"\'s dependency "%s" to list of commands with default set of options.'
(task.name, dep)) % (task.name, dep)
)
dep_task = ctx.invoke(ctx.command.get_command(ctx, dep)) dep_task = ctx.invoke(ctx.command.get_command(ctx, dep))
# Remove options with global scope from invoke tasks because they are already in global_args # Remove options with global scope from invoke tasks because they are already in global_args
@@ -650,7 +702,8 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
if task.aliases: if task.aliases:
name_with_aliases += ' (aliases: %s)' % ', '.join(task.aliases) name_with_aliases += ' (aliases: %s)' % ', '.join(task.aliases)
# When machine-readable json format for help is printed, don't show info about executing action so the output is deserializable # When machine-readable json format for help is printed,
# don't show info about executing action so the output is deserializable
if name_with_aliases != 'help' or not task.action_args.get('json_option', False): if name_with_aliases != 'help' or not task.action_args.get('json_option', False):
print('Executing action: %s' % name_with_aliases) print('Executing action: %s' % name_with_aliases)
task(ctx, global_args, task.action_args) task(ctx, global_args, task.action_args)
@@ -663,10 +716,7 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
# fully featured click parser to be sure that extensions are loaded from the right place # fully featured click parser to be sure that extensions are loaded from the right place
@click.command( @click.command(
add_help_option=False, add_help_option=False,
context_settings={ context_settings={'allow_extra_args': True, 'ignore_unknown_options': True},
'allow_extra_args': True,
'ignore_unknown_options': True
},
) )
@click.option('-C', '--project-dir', default=os.getcwd(), type=click.Path()) @click.option('-C', '--project-dir', default=os.getcwd(), type=click.Path())
def parse_project_dir(project_dir: str) -> Any: def parse_project_dir(project_dir: str) -> Any:
@@ -689,7 +739,7 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
extensions = [] extensions = []
for directory in extension_dirs: for directory in extension_dirs:
if directory and not os.path.exists(directory): if directory and not os.path.exists(directory):
print_warning('WARNING: Directory with idf.py extensions doesn\'t exist:\n %s' % directory) print_warning("WARNING: Directory with idf.py extensions doesn't exist:\n %s" % directory)
continue continue
sys.path.append(directory) sys.path.append(directory)
@@ -723,7 +773,8 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
except ImportError: except ImportError:
print_warning('Error importing extension file idf_ext.py. Skipping.') print_warning('Error importing extension file idf_ext.py. Skipping.')
print_warning( print_warning(
"Please make sure that it contains implementation (even if it's empty) of add_action_extensions") "Please make sure that it contains implementation (even if it's empty) of add_action_extensions"
)
try: try:
all_actions = merge_action_lists(all_actions, action_extensions(all_actions, project_dir)) all_actions = merge_action_lists(all_actions, action_extensions(all_actions, project_dir))
@@ -733,9 +784,10 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
cli_help = ( cli_help = (
'ESP-IDF CLI build management tool. ' 'ESP-IDF CLI build management tool. '
'For commands that are not known to idf.py an attempt to execute it as a build system target will be made. ' 'For commands that are not known to idf.py an attempt to execute it as a build system target will be made. '
'Selected target: {}'.format(get_target(project_dir))) 'Selected target: {}'.format(get_target(project_dir))
)
return CLI(help=cli_help, verbose_output=verbose_output, all_actions=all_actions) return CLI(cli_help=cli_help, verbose_output=verbose_output, all_actions=all_actions)
def main(argv: Optional[List[Any]] = None) -> None: def main(argv: Optional[List[Any]] = None) -> None:
@@ -791,11 +843,15 @@ def expand_file_arguments(argv: List[Any]) -> List[Any]:
try: try:
with open(rel_path, 'r', encoding='utf-8') as f: with open(rel_path, 'r', encoding='utf-8') as f:
for line in f: for line in f:
expanded_args.extend(expand_args(shlex.split(line), os.path.dirname(rel_path), file_stack + [file_name])) expanded_args.extend(
expand_args(shlex.split(line), os.path.dirname(rel_path), file_stack + [file_name])
)
except IOError: except IOError:
file_stack_str = ' -> '.join(['@' + f for f in file_stack + [file_name]]) file_stack_str = ' -> '.join(['@' + f for f in file_stack + [file_name]])
raise FatalError(f"File '{rel_path}' (expansion of {file_stack_str}) could not be opened. " raise FatalError(
'Please ensure the file exists and you have the necessary permissions to read it.') f"File '{rel_path}' (expansion of {file_stack_str}) could not be opened. "
'Please ensure the file exists and you have the necessary permissions to read it.'
)
return expanded_args return expanded_args
argv = expand_args(argv, os.getcwd(), []) argv = expand_args(argv, os.getcwd(), [])
@@ -820,7 +876,11 @@ def _valid_unicode_config() -> Union[codecs.CodecInfo, bool]:
def _find_usable_locale() -> str: def _find_usable_locale() -> str:
try: try:
locales = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0].decode('ascii', 'replace') locales = (
subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
.communicate()[0]
.decode('ascii', 'replace')
)
except OSError: except OSError:
locales = '' locales = ''
@@ -843,7 +903,8 @@ def _find_usable_locale() -> str:
if not usable_locales: if not usable_locales:
raise FatalError( raise FatalError(
'Support for Unicode filenames is required, but no suitable UTF-8 locale was found on your system.' 'Support for Unicode filenames is required, but no suitable UTF-8 locale was found on your system.'
' Please refer to the manual for your operating system for details on locale reconfiguration.') ' Please refer to the manual for your operating system for details on locale reconfiguration.'
)
return usable_locales[0] return usable_locales[0]
@@ -853,14 +914,16 @@ if __name__ == '__main__':
if 'MSYSTEM' in os.environ: if 'MSYSTEM' in os.environ:
print_warning( print_warning(
'MSys/Mingw is no longer supported. Please follow the getting started guide of the ' 'MSys/Mingw is no longer supported. Please follow the getting started guide of the '
'documentation in order to set up a suitiable environment, or continue at your own risk.') 'documentation in order to set up a suitiable environment, or continue at your own risk.'
)
elif os.name == 'posix' and not _valid_unicode_config(): elif os.name == 'posix' and not _valid_unicode_config():
# Trying to find best utf-8 locale available on the system and restart python with it # Trying to find best utf-8 locale available on the system and restart python with it
best_locale = _find_usable_locale() best_locale = _find_usable_locale()
print_warning( print_warning(
'Your environment is not configured to handle unicode filenames outside of ASCII range.' 'Your environment is not configured to handle unicode filenames outside of ASCII range.'
' Environment variable LC_ALL is temporary set to %s for unicode support.' % best_locale) ' Environment variable LC_ALL is temporary set to %s for unicode support.' % best_locale
)
os.environ['LC_ALL'] = best_locale os.environ['LC_ALL'] = best_locale
ret = subprocess.call([sys.executable] + sys.argv, env=os.environ) ret = subprocess.call([sys.executable] + sys.argv, env=os.environ)

View File

@@ -101,7 +101,7 @@ end
__main __main
set click_version (python -c 'import click; print(click.__version__.split(".")[0])') set click_version (python -c 'from importlib.metadata import version as importlib_version; print(importlib_version('click').split(".")[0])')
if test $click_version -lt 8 if test $click_version -lt 8
eval (env _IDF.PY_COMPLETE=source_fish idf.py) eval (env _IDF.PY_COMPLETE=source_fish idf.py)
else else

View File

@@ -209,7 +209,7 @@ __cleanup() {
__enable_autocomplete() { __enable_autocomplete() {
click_version="$(python -c 'import click; print(click.__version__.split(".")[0])')" click_version="$(python -c 'from importlib.metadata import version as importlib_version; print(importlib_version('click').split(".")[0])')"
if [ "${click_version}" -lt 8 ] if [ "${click_version}" -lt 8 ]
then then
SOURCE_ZSH=source_zsh SOURCE_ZSH=source_zsh