mirror of
https://github.com/espressif/esp-idf.git
synced 2025-10-03 10:30:58 +02:00
Merge branch 'fix/click_version' into 'master'
Fixed click deprecation warnings Closes IDF-13075 and IDF-13088 See merge request espressif/esp-idf!40765
This commit is contained in:
@@ -9,7 +9,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [ "--fix" ]
|
args: [ "--fix", "--show-fixes" ]
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.5.0
|
rev: v4.5.0
|
||||||
hooks:
|
hooks:
|
||||||
|
@@ -9,17 +9,14 @@ 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
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
from typing import Dict
|
|
||||||
from typing import List
|
|
||||||
from typing import TextIO
|
from typing import TextIO
|
||||||
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
|
||||||
@@ -28,7 +25,7 @@ from utils import run_cmd
|
|||||||
|
|
||||||
|
|
||||||
class Shell:
|
class Shell:
|
||||||
def __init__(self, shell: str, deactivate_cmd: str, new_esp_idf_env: Dict[str, str]):
|
def __init__(self, shell: str, deactivate_cmd: str, new_esp_idf_env: dict[str, str]):
|
||||||
self.shell = shell
|
self.shell = shell
|
||||||
self.deactivate_cmd = deactivate_cmd
|
self.deactivate_cmd = deactivate_cmd
|
||||||
self.new_esp_idf_env = new_esp_idf_env
|
self.new_esp_idf_env = new_esp_idf_env
|
||||||
@@ -65,7 +62,7 @@ class Shell:
|
|||||||
def export(self) -> None:
|
def export(self) -> None:
|
||||||
raise NotImplementedError('Subclass must implement abstract method "export"')
|
raise NotImplementedError('Subclass must implement abstract method "export"')
|
||||||
|
|
||||||
def expanded_env(self) -> Dict[str, str]:
|
def expanded_env(self) -> dict[str, str]:
|
||||||
expanded_env = self.new_esp_idf_env.copy()
|
expanded_env = self.new_esp_idf_env.copy()
|
||||||
|
|
||||||
if 'PATH' not in expanded_env:
|
if 'PATH' not in expanded_env:
|
||||||
@@ -88,7 +85,7 @@ class Shell:
|
|||||||
|
|
||||||
|
|
||||||
class UnixShell(Shell):
|
class UnixShell(Shell):
|
||||||
def __init__(self, shell: str, deactivate_cmd: str, new_esp_idf_env: Dict[str, str]):
|
def __init__(self, shell: str, deactivate_cmd: str, new_esp_idf_env: dict[str, str]):
|
||||||
super().__init__(shell, deactivate_cmd, new_esp_idf_env)
|
super().__init__(shell, deactivate_cmd, new_esp_idf_env)
|
||||||
|
|
||||||
with NamedTemporaryFile(dir=self.tmp_dir_path, delete=False, prefix='activate_') as fd:
|
with NamedTemporaryFile(dir=self.tmp_dir_path, delete=False, prefix='activate_') as fd:
|
||||||
@@ -110,10 +107,8 @@ class UnixShell(Shell):
|
|||||||
if stdout is not None:
|
if stdout is not None:
|
||||||
fd.write(f'{stdout}\n')
|
fd.write(f'{stdout}\n')
|
||||||
fd.write(
|
fd.write(
|
||||||
(
|
'echo "\nDone! You can now compile ESP-IDF projects.\n'
|
||||||
'echo "\nDone! You can now compile ESP-IDF projects.\n'
|
'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:
|
||||||
@@ -122,7 +117,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):
|
||||||
@@ -208,7 +203,7 @@ class ZshShell(UnixShell):
|
|||||||
|
|
||||||
|
|
||||||
class FishShell(UnixShell):
|
class FishShell(UnixShell):
|
||||||
def __init__(self, shell: str, deactivate_cmd: str, new_esp_idf_env: Dict[str, str]):
|
def __init__(self, shell: str, deactivate_cmd: str, new_esp_idf_env: dict[str, str]):
|
||||||
super().__init__(shell, deactivate_cmd, new_esp_idf_env)
|
super().__init__(shell, deactivate_cmd, new_esp_idf_env)
|
||||||
self.new_esp_idf_env['IDF_TOOLS_INSTALL_CMD'] = os.path.join(conf.IDF_PATH, 'install.fish')
|
self.new_esp_idf_env['IDF_TOOLS_INSTALL_CMD'] = os.path.join(conf.IDF_PATH, 'install.fish')
|
||||||
self.new_esp_idf_env['IDF_TOOLS_EXPORT_CMD'] = os.path.join(conf.IDF_PATH, 'export.fish')
|
self.new_esp_idf_env['IDF_TOOLS_EXPORT_CMD'] = os.path.join(conf.IDF_PATH, 'export.fish')
|
||||||
@@ -232,7 +227,7 @@ class FishShell(UnixShell):
|
|||||||
|
|
||||||
|
|
||||||
class PowerShell(Shell):
|
class PowerShell(Shell):
|
||||||
def __init__(self, shell: str, deactivate_cmd: str, new_esp_idf_env: Dict[str, str]):
|
def __init__(self, shell: str, deactivate_cmd: str, new_esp_idf_env: dict[str, str]):
|
||||||
super().__init__(shell, deactivate_cmd, new_esp_idf_env)
|
super().__init__(shell, deactivate_cmd, new_esp_idf_env)
|
||||||
|
|
||||||
with NamedTemporaryFile(dir=self.tmp_dir_path, delete=False, prefix='activate_', suffix='.ps1') as fd:
|
with NamedTemporaryFile(dir=self.tmp_dir_path, delete=False, prefix='activate_', suffix='.ps1') as fd:
|
||||||
@@ -270,22 +265,20 @@ class PowerShell(Shell):
|
|||||||
functions = self.get_functions()
|
functions = self.get_functions()
|
||||||
fd.write(f'{functions}\n')
|
fd.write(f'{functions}\n')
|
||||||
fd.write(
|
fd.write(
|
||||||
(
|
'echo "\nDone! You can now compile ESP-IDF projects.\n'
|
||||||
'echo "\nDone! You can now compile ESP-IDF projects.\n'
|
'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 spawn(self) -> None:
|
def spawn(self) -> None:
|
||||||
self.init_file()
|
self.init_file()
|
||||||
new_env = os.environ.copy()
|
new_env = os.environ.copy()
|
||||||
arguments = ['-NoExit', '-Command', f'{self.script_file_path}']
|
arguments = ['-NoExit', '-Command', f'{self.script_file_path}']
|
||||||
cmd: Union[str, List[str]] = [self.shell] + arguments
|
cmd: str | list[str] = [self.shell] + arguments
|
||||||
run(cmd, env=new_env)
|
run(cmd, env=new_env)
|
||||||
|
|
||||||
|
|
||||||
class WinCmd(Shell):
|
class WinCmd(Shell):
|
||||||
def __init__(self, shell: str, deactivate_cmd: str, new_esp_idf_env: Dict[str, str]):
|
def __init__(self, shell: str, deactivate_cmd: str, new_esp_idf_env: dict[str, str]):
|
||||||
super().__init__(shell, deactivate_cmd, new_esp_idf_env)
|
super().__init__(shell, deactivate_cmd, new_esp_idf_env)
|
||||||
|
|
||||||
with NamedTemporaryFile(dir=self.tmp_dir_path, delete=False, prefix='activate_', suffix='.bat') as fd:
|
with NamedTemporaryFile(dir=self.tmp_dir_path, delete=False, prefix='activate_', suffix='.bat') as fd:
|
||||||
@@ -338,7 +331,7 @@ class WinCmd(Shell):
|
|||||||
self.init_file()
|
self.init_file()
|
||||||
new_env = os.environ.copy()
|
new_env = os.environ.copy()
|
||||||
arguments = ['/k', f'{self.script_file_path}']
|
arguments = ['/k', f'{self.script_file_path}']
|
||||||
cmd: Union[str, List[str]] = [self.shell] + arguments
|
cmd: str | list[str] = [self.shell] + arguments
|
||||||
cmd = ' '.join(cmd)
|
cmd = ' '.join(cmd)
|
||||||
run(cmd, env=new_env)
|
run(cmd, env=new_env)
|
||||||
|
|
||||||
|
350
tools/idf.py
350
tools/idf.py
@@ -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
|
||||||
#
|
#
|
||||||
@@ -22,14 +22,11 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from collections.abc import Callable
|
||||||
from collections.abc import KeysView
|
from collections.abc import KeysView
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from pkgutil import iter_modules
|
from pkgutil import iter_modules
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
|
||||||
from typing import Dict
|
|
||||||
from typing import List
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
# pyc files remain in the filesystem when switching between branches which might raise errors for incompatible
|
# pyc files remain in the filesystem when switching between branches which might raise errors for incompatible
|
||||||
@@ -39,19 +36,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'
|
'This usually means that "idf.py" was not '
|
||||||
f'Please use idf.py only in an ESP-IDF shell environment. If problem persists, '
|
'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.'),
|
'environment used by "idf.py" is corrupted.\n'
|
||||||
file=sys.stderr)
|
'Please use idf.py only in an ESP-IDF shell environment. If problem persists, '
|
||||||
|
'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
|
||||||
@@ -69,7 +77,7 @@ PYTHON = sys.executable
|
|||||||
os.environ['PYTHON'] = sys.executable
|
os.environ['PYTHON'] = sys.executable
|
||||||
|
|
||||||
|
|
||||||
def check_environment() -> List:
|
def check_environment() -> list:
|
||||||
"""
|
"""
|
||||||
Verify the environment contains the top-level tools we need to operate
|
Verify the environment contains the top-level tools we need to operate
|
||||||
|
|
||||||
@@ -84,11 +92,12 @@ def check_environment() -> List:
|
|||||||
set_idf_path = os.path.realpath(os.environ['IDF_PATH'])
|
set_idf_path = os.path.realpath(os.environ['IDF_PATH'])
|
||||||
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. '
|
f'WARNING: IDF_PATH environment variable is set to {set_idf_path}'
|
||||||
'Using the environment variable directory, but results may be unexpected...' %
|
f' but {PROG} path indicates IDF directory {detected_idf_path}. '
|
||||||
(set_idf_path, PROG, detected_idf_path))
|
'Using the environment variable directory, but results may be unexpected...'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print_warning('Setting IDF_PATH environment variable: %s' % detected_idf_path)
|
print_warning(f'Setting IDF_PATH environment variable: {detected_idf_path}')
|
||||||
os.environ['IDF_PATH'] = detected_idf_path
|
os.environ['IDF_PATH'] = detected_idf_path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -122,15 +131,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: str | None = 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 +152,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: list | None = 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:
|
||||||
"""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: 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 +175,33 @@ 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' % (
|
msg = f'{msg_type} is deprecated'
|
||||||
type,
|
if self.since:
|
||||||
'since %s ' % self.since if self.since else '',
|
msg += f' since {self.since}'
|
||||||
' in %s' % self.removed if self.removed else '',
|
msg += ' and was removed'
|
||||||
' %s' % self.custom_message if self.custom_message else '',
|
if self.removed:
|
||||||
)
|
msg += f' in {self.removed}. '
|
||||||
|
if self.custom_message:
|
||||||
|
msg += self.custom_message
|
||||||
|
return msg
|
||||||
else:
|
else:
|
||||||
return '%s is deprecated %sand will be removed in%s.%s' % (
|
msg = f'{msg_type} is deprecated'
|
||||||
type,
|
if self.since:
|
||||||
'since %s ' % self.since if self.since else '',
|
msg += f' since {self.since}'
|
||||||
' %s' % self.removed if self.removed else ' future versions',
|
msg += ' and will be removed'
|
||||||
' %s' % self.custom_message if self.custom_message else '',
|
if self.removed:
|
||||||
)
|
msg += f' in {self.removed}. '
|
||||||
|
else:
|
||||||
|
msg += ' in future versions. '
|
||||||
|
if self.custom_message:
|
||||||
|
msg += self.custom_message
|
||||||
|
return msg
|
||||||
|
|
||||||
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 ''
|
||||||
@@ -193,13 +214,22 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
if isinstance(option, Option) and option.deprecated and ctx.params[option.name] != default:
|
if isinstance(option, Option) and option.deprecated and ctx.params[option.name] != default:
|
||||||
deprecation = Deprecation(option.deprecated)
|
deprecation = Deprecation(option.deprecated)
|
||||||
if deprecation.exit_with_error:
|
if deprecation.exit_with_error:
|
||||||
raise FatalError('Error: %s' % deprecation.full_message('Option "%s"' % option.name))
|
error = deprecation.full_message(f'Option "{option.name}"')
|
||||||
|
raise FatalError(f'Error: {error}')
|
||||||
else:
|
else:
|
||||||
print_warning('Warning: %s' % deprecation.full_message('Option "%s"' % option.name))
|
error = deprecation.full_message(f'Option "{option.name}"')
|
||||||
|
print_warning(f'Warning: {error}')
|
||||||
|
|
||||||
class Task(object):
|
class Task:
|
||||||
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: list | None,
|
||||||
|
order_dependencies: list | None,
|
||||||
|
action_args: dict,
|
||||||
|
) -> None:
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.name = name
|
self.name = name
|
||||||
self.dependencies = dependencies
|
self.dependencies = dependencies
|
||||||
@@ -207,7 +237,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: dict | None = None
|
||||||
|
) -> None:
|
||||||
if action_args is None:
|
if action_args is None:
|
||||||
action_args = self.action_args
|
action_args = self.action_args
|
||||||
|
|
||||||
@@ -215,18 +247,19 @@ 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: str | None = None,
|
||||||
aliases: Optional[List]=None,
|
aliases: list | None = None,
|
||||||
deprecated: Union[Dict, str, bool]=False,
|
deprecated: dict | str | bool = False,
|
||||||
dependencies: Optional[List]=None,
|
dependencies: list | None = None,
|
||||||
order_dependencies: Optional[List]=None,
|
order_dependencies: list | None = None,
|
||||||
hidden: bool=False,
|
hidden: bool = False,
|
||||||
**kwargs: Any) -> None:
|
**kwargs: Any,
|
||||||
super(Action, self).__init__(name, **kwargs)
|
) -> None:
|
||||||
|
super().__init__(name, **kwargs)
|
||||||
|
|
||||||
self.name: str = self.name or self.callback.__name__
|
self.name: str = self.name or self.callback.__name__
|
||||||
self.deprecated: Union[Dict, str, bool] = deprecated
|
self.deprecated: dict | str | bool = deprecated
|
||||||
self.hidden: bool = hidden
|
self.hidden: bool = hidden
|
||||||
|
|
||||||
if aliases is None:
|
if aliases is None:
|
||||||
@@ -247,11 +280,11 @@ 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:
|
||||||
aliases_help = 'Aliases: %s.' % ', '.join(aliases)
|
aliases_help = f'Aliases: {", ".join(aliases)}.'
|
||||||
|
|
||||||
self.help = '\n'.join([self.help, aliases_help])
|
self.help = '\n'.join([self.help, aliases_help])
|
||||||
self.short_help = ' '.join([aliases_help, self.short_help])
|
self.short_help = ' '.join([aliases_help, self.short_help])
|
||||||
@@ -269,23 +302,23 @@ 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:
|
||||||
deprecation = Deprecation(self.deprecated)
|
deprecation = Deprecation(self.deprecated)
|
||||||
message = deprecation.full_message('Command "%s"' % self.name)
|
message = deprecation.full_message(f'Command "{self.name}"')
|
||||||
|
|
||||||
if deprecation.exit_with_error:
|
if deprecation.exit_with_error:
|
||||||
raise FatalError('Error: %s' % message)
|
raise FatalError(f'Error: {message}')
|
||||||
else:
|
else:
|
||||||
print_warning('Warning: %s' % message)
|
print_warning(f'Warning: {message}')
|
||||||
|
|
||||||
self.deprecated = False # disable Click's built-in deprecation handling
|
self.deprecated = False # disable Click's built-in deprecation handling
|
||||||
|
|
||||||
# Print warnings for options
|
# Print warnings for options
|
||||||
check_deprecation(ctx)
|
check_deprecation(ctx)
|
||||||
return super(Action, self).invoke(ctx)
|
return super().invoke(ctx)
|
||||||
|
|
||||||
class Argument(click.Argument):
|
class Argument(click.Argument):
|
||||||
"""
|
"""
|
||||||
@@ -293,22 +326,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().__init__(names, **kwargs)
|
||||||
|
|
||||||
class Scope(object):
|
class Scope:
|
||||||
"""
|
"""
|
||||||
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: Union['Scope', str] | None = 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:
|
||||||
@@ -316,7 +350,7 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
elif isinstance(scope, Scope):
|
elif isinstance(scope, Scope):
|
||||||
self._scope = str(scope)
|
self._scope = str(scope)
|
||||||
else:
|
else:
|
||||||
raise FatalError('Unknown scope for option: %s' % scope)
|
raise FatalError(f'Unknown scope for option: {scope}')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_global(self) -> bool:
|
def is_global(self) -> bool:
|
||||||
@@ -331,7 +365,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: Scope | str | None = None,
|
||||||
|
deprecated: 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:
|
||||||
|
|
||||||
@@ -344,7 +385,7 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs['param_decls'] = kwargs.pop('names')
|
kwargs['param_decls'] = kwargs.pop('names')
|
||||||
super(Option, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.deprecated = deprecated
|
self.deprecated = deprecated
|
||||||
self.scope = Scope(scope)
|
self.scope = Scope(scope)
|
||||||
@@ -355,7 +396,7 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
self.help: str = deprecation.help(self.help)
|
self.help: str = deprecation.help(self.help)
|
||||||
|
|
||||||
if self.envvar:
|
if self.envvar:
|
||||||
self.help += ' The default value can be set with the %s environment variable.' % self.envvar
|
self.help += f' The default value can be set with the {self.envvar} environment variable.'
|
||||||
|
|
||||||
if self.scope.is_global:
|
if self.scope.is_global:
|
||||||
self.help += ' This option can be used at most once either globally, or for one subcommand.'
|
self.help += ' This option can be used at most once either globally, or for one subcommand.'
|
||||||
@@ -365,18 +406,24 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
if self.hidden:
|
if self.hidden:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return super(Option, self).get_help_record(ctx)
|
return super().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:
|
|
||||||
super(CLI, self).__init__(
|
def __init__(
|
||||||
|
self,
|
||||||
|
all_actions: dict | None = None,
|
||||||
|
verbose_output: list | None = None,
|
||||||
|
cli_help: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
super().__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 = []
|
||||||
@@ -430,8 +477,9 @@ 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". '
|
f'"{option.name}" is defined for action "{name}". '
|
||||||
' "shared" options can be declared only on global level' % (option.name, name))
|
'"shared" options can be declared only on global level'
|
||||||
|
)
|
||||||
|
|
||||||
# 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]:
|
||||||
@@ -439,10 +487,10 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
|
|
||||||
self._actions[name].params.append(option)
|
self._actions[name].params.append(option)
|
||||||
|
|
||||||
def list_commands(self, ctx: click.core.Context) -> List:
|
def list_commands(self, ctx: click.core.Context) -> list:
|
||||||
return sorted(filter(lambda name: not self._actions[name].hidden, self._actions))
|
return sorted(filter(lambda name: not self._actions[name].hidden, self._actions))
|
||||||
|
|
||||||
def get_command(self, ctx: click.core.Context, name: str) -> Optional[Action]:
|
def get_command(self, ctx: click.core.Context, name: str) -> Action | None:
|
||||||
if name in self.commands_with_aliases:
|
if name in self.commands_with_aliases:
|
||||||
return self._actions.get(self.commands_with_aliases.get(name))
|
return self._actions.get(self.commands_with_aliases.get(name))
|
||||||
|
|
||||||
@@ -453,19 +501,20 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
return Action(name=name, callback=callback.unwrapped_callback)
|
return Action(name=name, callback=callback.unwrapped_callback)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def shell_complete(self, ctx: click.core.Context, incomplete: str) -> List[CompletionItem]:
|
def shell_complete(self, ctx: click.core.Context, incomplete: str) -> list[CompletionItem]:
|
||||||
# Enable @-argument completion in bash only if @ is not present in
|
# Enable @-argument completion in bash only if @ is not present in
|
||||||
# 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 + '*')
|
||||||
result = [CompletionItem(f'@{c}') for c in candidates]
|
result = [CompletionItem(f'@{c}') for c in candidates]
|
||||||
return result
|
return result
|
||||||
return super(CLI, self).shell_complete(ctx, incomplete) # type: ignore
|
return super().shell_complete(ctx, incomplete) # type: ignore
|
||||||
|
|
||||||
def _print_closing_message(self, args: PropertyDict, actions: KeysView) -> None:
|
def _print_closing_message(self, args: PropertyDict, actions: KeysView) -> None:
|
||||||
# print a closing message of some kind,
|
# print a closing message of some kind,
|
||||||
@@ -482,7 +531,7 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
# how to flash them
|
# how to flash them
|
||||||
def print_flashing_message(title: str, key: str) -> None:
|
def print_flashing_message(title: str, key: str) -> None:
|
||||||
with open(os.path.join(args.build_dir, 'flasher_args.json'), encoding='utf-8') as file:
|
with open(os.path.join(args.build_dir, 'flasher_args.json'), encoding='utf-8') as file:
|
||||||
flasher_args: Dict[str, Any] = json.load(file)
|
flasher_args: dict[str, Any] = json.load(file)
|
||||||
|
|
||||||
def flasher_path(f: Union[str, 'os.PathLike[str]']) -> str:
|
def flasher_path(f: Union[str, 'os.PathLike[str]']) -> str:
|
||||||
if type(args.build_dir) is bytes:
|
if type(args.build_dir) is bytes:
|
||||||
@@ -491,11 +540,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'
|
||||||
print('\n%s build complete.' % title)
|
# if Secure Boot is on, need to follow manual flashing steps
|
||||||
|
print(f'\n{title} build complete.')
|
||||||
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 +568,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']
|
||||||
@@ -549,20 +601,24 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
if 'bootloader' in actions:
|
if 'bootloader' in actions:
|
||||||
print_flashing_message('Bootloader', 'bootloader')
|
print_flashing_message('Bootloader', 'bootloader')
|
||||||
|
|
||||||
def execute_tasks(self, tasks: List, **kwargs: str) -> OrderedDict:
|
def execute_tasks(self, tasks: list, **kwargs: str) -> OrderedDict:
|
||||||
ctx = click.get_current_context()
|
ctx = click.get_current_context()
|
||||||
global_args = PropertyDict(kwargs)
|
global_args = PropertyDict(kwargs)
|
||||||
|
|
||||||
# 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)
|
print('----------------------------------------------------------------------------------------')
|
||||||
|
dupes = ', '.join(f'"{t}"' for t in dupplicated_tasks)
|
||||||
|
|
||||||
print_warning(
|
print_warning(
|
||||||
'WARNING: Command%s found in the list of commands more than once. ' %
|
f'WARNING: {"Commands" if len(dupplicated_tasks) > 1 else "Command"} {dupes} '
|
||||||
('s %s are' % dupes if len(dupplicated_tasks) > 1 else ' %s is' % dupes) +
|
f'{"are" if len(dupplicated_tasks) > 1 else "is"} '
|
||||||
'Only first occurrence will be executed.')
|
'found in the list of commands more than once. '
|
||||||
|
'Only first occurrence will be executed.'
|
||||||
|
)
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
# Set propagated global options.
|
# Set propagated global options.
|
||||||
@@ -577,13 +633,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 +675,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.' %
|
f'Adding "{task.name}"\'s dependency "{dep}" '
|
||||||
(task.name, dep))
|
'to list of commands with default set of options.'
|
||||||
|
)
|
||||||
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
|
||||||
@@ -648,11 +709,12 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
for task in tasks_to_run.values():
|
for task in tasks_to_run.values():
|
||||||
name_with_aliases = task.name
|
name_with_aliases = task.name
|
||||||
if task.aliases:
|
if task.aliases:
|
||||||
name_with_aliases += ' (aliases: %s)' % ', '.join(task.aliases)
|
name_with_aliases += f' (aliases: {", ".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(f'Executing action: {name_with_aliases}')
|
||||||
task(ctx, global_args, task.action_args)
|
task(ctx, global_args, task.action_args)
|
||||||
|
|
||||||
self._print_closing_message(global_args, tasks_to_run.keys())
|
self._print_closing_message(global_args, tasks_to_run.keys())
|
||||||
@@ -663,10 +725,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:
|
||||||
@@ -675,7 +734,7 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
# Set `complete_var` to not existing environment variable name to prevent early cmd completion
|
# Set `complete_var` to not existing environment variable name to prevent early cmd completion
|
||||||
project_dir = parse_project_dir(standalone_mode=False, complete_var='_IDF.PY_COMPLETE_NOT_EXISTING')
|
project_dir = parse_project_dir(standalone_mode=False, complete_var='_IDF.PY_COMPLETE_NOT_EXISTING')
|
||||||
|
|
||||||
all_actions: Dict = {}
|
all_actions: dict = {}
|
||||||
# Load extensions from components dir
|
# Load extensions from components dir
|
||||||
idf_py_extensions_path = os.path.join(os.environ['IDF_PATH'], 'tools', 'idf_py_actions')
|
idf_py_extensions_path = os.path.join(os.environ['IDF_PATH'], 'tools', 'idf_py_actions')
|
||||||
extension_dirs = [os.path.realpath(idf_py_extensions_path)]
|
extension_dirs = [os.path.realpath(idf_py_extensions_path)]
|
||||||
@@ -689,7 +748,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(f"WARNING: Directory with idf.py extensions doesn't exist:\n\t{directory}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sys.path.append(directory)
|
sys.path.append(directory)
|
||||||
@@ -713,7 +772,7 @@ def init_cli(verbose_output: Optional[List]=None) -> Any:
|
|||||||
try:
|
try:
|
||||||
all_actions = merge_action_lists(all_actions, extension.action_extensions(all_actions, project_dir))
|
all_actions = merge_action_lists(all_actions, extension.action_extensions(all_actions, project_dir))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
print_warning('WARNING: Cannot load idf.py extension "%s"' % name)
|
print_warning(f'WARNING: Cannot load idf.py extension "{name}"')
|
||||||
|
|
||||||
# Load extensions from project dir
|
# Load extensions from project dir
|
||||||
if os.path.exists(os.path.join(project_dir, 'idf_ext.py')):
|
if os.path.exists(os.path.join(project_dir, 'idf_ext.py')):
|
||||||
@@ -723,7 +782,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,12 +793,13 @@ 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)))
|
f'Selected target: {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: list[Any] | None = None) -> None:
|
||||||
# Check the environment only when idf.py is invoked regularly from command line.
|
# Check the environment only when idf.py is invoked regularly from command line.
|
||||||
checks_output = None if SHELL_COMPLETE_RUN else check_environment()
|
checks_output = None if SHELL_COMPLETE_RUN else check_environment()
|
||||||
|
|
||||||
@@ -761,7 +822,7 @@ def main(argv: Optional[List[Any]] = None) -> None:
|
|||||||
cli(argv, prog_name=PROG, complete_var=SHELL_COMPLETE_VAR)
|
cli(argv, prog_name=PROG, complete_var=SHELL_COMPLETE_VAR)
|
||||||
|
|
||||||
|
|
||||||
def expand_file_arguments(argv: List[Any]) -> List[Any]:
|
def expand_file_arguments(argv: list[Any]) -> list[Any]:
|
||||||
"""
|
"""
|
||||||
Any argument starting with "@" gets replaced with all values read from a text file.
|
Any argument starting with "@" gets replaced with all values read from a text file.
|
||||||
Text file arguments can be split by newline or by space.
|
Text file arguments can be split by newline or by space.
|
||||||
@@ -771,7 +832,7 @@ def expand_file_arguments(argv: List[Any]) -> List[Any]:
|
|||||||
visited = set()
|
visited = set()
|
||||||
expanded = False
|
expanded = False
|
||||||
|
|
||||||
def expand_args(args: List[Any], parent_path: str, file_stack: List[str]) -> List[str]:
|
def expand_args(args: list[Any], parent_path: str, file_stack: list[str]) -> list[str]:
|
||||||
expanded_args = []
|
expanded_args = []
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if not arg.startswith('@'):
|
if not arg.startswith('@'):
|
||||||
@@ -789,13 +850,17 @@ def expand_file_arguments(argv: List[Any]) -> List[Any]:
|
|||||||
visited.add(rel_path)
|
visited.add(rel_path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(rel_path, 'r', encoding='utf-8') as f:
|
with open(rel_path, 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(
|
||||||
except IOError:
|
expand_args(shlex.split(line), os.path.dirname(rel_path), file_stack + [file_name])
|
||||||
|
)
|
||||||
|
except OSError:
|
||||||
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(), [])
|
||||||
@@ -806,11 +871,7 @@ def expand_file_arguments(argv: List[Any]) -> List[Any]:
|
|||||||
return argv
|
return argv
|
||||||
|
|
||||||
|
|
||||||
def _valid_unicode_config() -> Union[codecs.CodecInfo, bool]:
|
def _valid_unicode_config() -> codecs.CodecInfo | bool:
|
||||||
# Python 2 is always good
|
|
||||||
if sys.version_info[0] == 2:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# With python 3 unicode environment is required
|
# With python 3 unicode environment is required
|
||||||
try:
|
try:
|
||||||
return codecs.lookup(locale.getpreferredencoding()).name != 'ascii'
|
return codecs.lookup(locale.getpreferredencoding()).name != 'ascii'
|
||||||
@@ -820,11 +881,15 @@ 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 = ''
|
||||||
|
|
||||||
usable_locales: List[str] = []
|
usable_locales: list[str] = []
|
||||||
for line in locales.splitlines():
|
for line in locales.splitlines():
|
||||||
locale = line.strip()
|
locale = line.strip()
|
||||||
locale_name = locale.lower().replace('-', '')
|
locale_name = locale.lower().replace('-', '')
|
||||||
@@ -843,7 +908,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 +919,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)
|
f' Environment variable LC_ALL is temporary set to {best_locale} for unicode support.'
|
||||||
|
)
|
||||||
|
|
||||||
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)
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@@ -9,28 +9,23 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from test_build_system_helpers import append_to_file
|
|
||||||
from test_build_system_helpers import EnvDict
|
from test_build_system_helpers import EnvDict
|
||||||
|
from test_build_system_helpers import IdfPyFunc
|
||||||
|
from test_build_system_helpers import append_to_file
|
||||||
from test_build_system_helpers import file_contains
|
from test_build_system_helpers import file_contains
|
||||||
from test_build_system_helpers import find_python
|
from test_build_system_helpers import find_python
|
||||||
from test_build_system_helpers import get_snapshot
|
from test_build_system_helpers import get_snapshot
|
||||||
from test_build_system_helpers import IdfPyFunc
|
|
||||||
from test_build_system_helpers import replace_in_file
|
from test_build_system_helpers import replace_in_file
|
||||||
from test_build_system_helpers import run_idf_py
|
from test_build_system_helpers import run_idf_py
|
||||||
|
|
||||||
|
|
||||||
def get_subdirs_absolute_paths(path: Path) -> List[str]:
|
def get_subdirs_absolute_paths(path: Path) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Get a list of files with absolute path in a given `path` folder
|
Get a list of files with absolute path in a given `path` folder
|
||||||
"""
|
"""
|
||||||
return [
|
return [f'{dir_path}/{file_name}' for dir_path, _, file_names in os.walk(path) for file_name in file_names]
|
||||||
'{}/{}'.format(dir_path, file_name)
|
|
||||||
for dir_path, _, file_names in os.walk(path)
|
|
||||||
for file_name in file_names
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('test_app_copy')
|
@pytest.mark.usefixtures('test_app_copy')
|
||||||
@@ -51,10 +46,11 @@ def test_hints_no_color_output_when_noninteractive(idf_py: IdfPyFunc) -> None:
|
|||||||
"""Check that idf.py hints don't include color escape codes in non-interactive builds"""
|
"""Check that idf.py hints don't include color escape codes in non-interactive builds"""
|
||||||
|
|
||||||
# make the build fail in such a way that idf.py shows a hint
|
# make the build fail in such a way that idf.py shows a hint
|
||||||
replace_in_file('main/build_test_app.c', '// placeholder_inside_main',
|
replace_in_file(
|
||||||
'esp_chip_info_t chip_info; esp_chip_info(&chip_info);')
|
'main/build_test_app.c', '// placeholder_inside_main', 'esp_chip_info_t chip_info; esp_chip_info(&chip_info);'
|
||||||
|
)
|
||||||
|
|
||||||
with (pytest.raises(subprocess.CalledProcessError)) as exc_info:
|
with pytest.raises(subprocess.CalledProcessError) as exc_info:
|
||||||
idf_py('build')
|
idf_py('build')
|
||||||
|
|
||||||
# Should not actually include a color escape sequence!
|
# Should not actually include a color escape sequence!
|
||||||
@@ -70,10 +66,7 @@ def test_idf_copy(idf_copy: Path, idf_py: IdfPyFunc) -> None:
|
|||||||
idf_py('build')
|
idf_py('build')
|
||||||
|
|
||||||
|
|
||||||
def test_idf_build_with_env_var_sdkconfig_defaults(
|
def test_idf_build_with_env_var_sdkconfig_defaults(test_app_copy: Path, default_idf_env: EnvDict) -> None:
|
||||||
test_app_copy: Path,
|
|
||||||
default_idf_env: EnvDict
|
|
||||||
) -> None:
|
|
||||||
with open(test_app_copy / 'sdkconfig.test', 'w') as fw:
|
with open(test_app_copy / 'sdkconfig.test', 'w') as fw:
|
||||||
fw.write('CONFIG_BT_ENABLED=y')
|
fw.write('CONFIG_BT_ENABLED=y')
|
||||||
|
|
||||||
@@ -86,9 +79,7 @@ def test_idf_build_with_env_var_sdkconfig_defaults(
|
|||||||
|
|
||||||
@pytest.mark.usefixtures('test_app_copy')
|
@pytest.mark.usefixtures('test_app_copy')
|
||||||
@pytest.mark.test_app_copy('examples/system/efuse')
|
@pytest.mark.test_app_copy('examples/system/efuse')
|
||||||
def test_efuse_summary_cmake_functions(
|
def test_efuse_summary_cmake_functions(default_idf_env: EnvDict) -> None:
|
||||||
default_idf_env: EnvDict
|
|
||||||
) -> None:
|
|
||||||
default_idf_env['IDF_CI_BUILD'] = '1'
|
default_idf_env['IDF_CI_BUILD'] = '1'
|
||||||
output = run_idf_py('efuse-filter', env=default_idf_env)
|
output = run_idf_py('efuse-filter', env=default_idf_env)
|
||||||
assert 'FROM_CMAKE: MAC: 00:00:00:00:00:00' in output.stdout
|
assert 'FROM_CMAKE: MAC: 00:00:00:00:00:00' in output.stdout
|
||||||
@@ -117,16 +108,22 @@ def test_python_interpreter_unix(test_app_copy: Path) -> None:
|
|||||||
logging.info("Make sure idf.py never runs '/usr/bin/env python' or similar")
|
logging.info("Make sure idf.py never runs '/usr/bin/env python' or similar")
|
||||||
env_dict = dict(**os.environ)
|
env_dict = dict(**os.environ)
|
||||||
python = find_python(env_dict['PATH'])
|
python = find_python(env_dict['PATH'])
|
||||||
(test_app_copy / 'python').write_text(textwrap.dedent("""#!/bin/sh
|
(test_app_copy / 'python').write_text(
|
||||||
echo "idf.py has executed '/usr/bin/env python' or similar"
|
textwrap.dedent(
|
||||||
exit 1
|
"""
|
||||||
"""))
|
#!/bin/sh
|
||||||
|
echo "idf.py has executed '/usr/bin/env python' or similar"
|
||||||
|
exit 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
st = os.stat(test_app_copy / 'python')
|
st = os.stat(test_app_copy / 'python')
|
||||||
# equivalent to 'chmod +x ./python'
|
# equivalent to 'chmod +x ./python'
|
||||||
os.chmod((test_app_copy / 'python'), st.st_mode | stat.S_IEXEC)
|
os.chmod((test_app_copy / 'python'), st.st_mode | stat.S_IEXEC)
|
||||||
|
|
||||||
env_dict['PATH'] = str(test_app_copy) + os.pathsep + env_dict['PATH']
|
env_dict['PATH'] = str(test_app_copy) + os.pathsep + env_dict['PATH']
|
||||||
# python is loaded from env:$PATH, but since false interpreter is provided there, python needs to be specified as argument
|
# python is loaded from env:$PATH
|
||||||
|
# but since false interpreter is provided there, python needs to be specified as argument
|
||||||
# if idf.py is reconfigured during it's execution, it would load a false interpreter
|
# if idf.py is reconfigured during it's execution, it would load a false interpreter
|
||||||
run_idf_py('reconfigure', env=env_dict, python=python)
|
run_idf_py('reconfigure', env=env_dict, python=python)
|
||||||
|
|
||||||
@@ -140,7 +137,8 @@ def test_python_interpreter_win(test_app_copy: Path) -> None:
|
|||||||
# on windows python interpreter has compiled code '.exe' format, so this false file can be empty
|
# on windows python interpreter has compiled code '.exe' format, so this false file can be empty
|
||||||
(test_app_copy / 'python.exe').write_text('')
|
(test_app_copy / 'python.exe').write_text('')
|
||||||
env_dict['PATH'] = str(test_app_copy) + os.pathsep + env_dict['PATH']
|
env_dict['PATH'] = str(test_app_copy) + os.pathsep + env_dict['PATH']
|
||||||
# python is loaded from env:$PATH, but since false interpreter is provided there, python needs to be specified as argument
|
# python is loaded from env:$PATH
|
||||||
|
# but since false interpreter is provided there, python needs to be specified as argument
|
||||||
# if idf.py is reconfigured during it's execution, it would load a false interpreter
|
# if idf.py is reconfigured during it's execution, it would load a false interpreter
|
||||||
run_idf_py('reconfigure', env=env_dict, python=python)
|
run_idf_py('reconfigure', env=env_dict, python=python)
|
||||||
|
|
||||||
@@ -172,7 +170,7 @@ def test_ccache_used_to_build(test_app_copy: Path) -> None:
|
|||||||
def test_toolchain_prefix_in_description_file(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
def test_toolchain_prefix_in_description_file(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
||||||
logging.info('Toolchain prefix is set in project description file')
|
logging.info('Toolchain prefix is set in project description file')
|
||||||
idf_py('reconfigure')
|
idf_py('reconfigure')
|
||||||
data = json.load(open(test_app_copy / 'build' / 'project_description.json', 'r'))
|
data = json.load(open(test_app_copy / 'build' / 'project_description.json'))
|
||||||
assert 'monitor_toolprefix' in data
|
assert 'monitor_toolprefix' in data
|
||||||
|
|
||||||
|
|
||||||
@@ -195,8 +193,10 @@ def test_subcommands_with_options(idf_py: IdfPyFunc, default_idf_env: EnvDict) -
|
|||||||
def test_fallback_to_build_system_target(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
def test_fallback_to_build_system_target(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
||||||
logging.info('idf.py fallback to build system target')
|
logging.info('idf.py fallback to build system target')
|
||||||
msg = 'Custom target is running'
|
msg = 'Custom target is running'
|
||||||
append_to_file(test_app_copy / 'CMakeLists.txt',
|
append_to_file(
|
||||||
'add_custom_target(custom_target COMMAND ${{CMAKE_COMMAND}} -E echo "{}")'.format(msg))
|
test_app_copy / 'CMakeLists.txt',
|
||||||
|
f'add_custom_target(custom_target COMMAND ${{CMAKE_COMMAND}} -E echo "{msg}")',
|
||||||
|
)
|
||||||
ret = idf_py('custom_target')
|
ret = idf_py('custom_target')
|
||||||
assert msg in ret.stdout, 'Custom target did not produce expected output'
|
assert msg in ret.stdout, 'Custom target did not produce expected output'
|
||||||
|
|
||||||
@@ -205,10 +205,16 @@ def test_create_component_project(idf_copy: Path) -> None:
|
|||||||
logging.info('Create project and component using idf.py and build it')
|
logging.info('Create project and component using idf.py and build it')
|
||||||
run_idf_py('-C', 'projects', 'create-project', 'temp_test_project', workdir=idf_copy)
|
run_idf_py('-C', 'projects', 'create-project', 'temp_test_project', workdir=idf_copy)
|
||||||
run_idf_py('-C', 'components', 'create-component', 'temp_test_component', workdir=idf_copy)
|
run_idf_py('-C', 'components', 'create-component', 'temp_test_component', workdir=idf_copy)
|
||||||
replace_in_file(idf_copy / 'projects' / 'temp_test_project' / 'main' / 'temp_test_project.c', '{\n\n}',
|
replace_in_file(
|
||||||
'\n'.join(['{', '\tfunc();', '}']))
|
idf_copy / 'projects' / 'temp_test_project' / 'main' / 'temp_test_project.c',
|
||||||
replace_in_file(idf_copy / 'projects' / 'temp_test_project' / 'main' / 'temp_test_project.c', '#include <stdio.h>',
|
'{\n\n}',
|
||||||
'\n'.join(['#include <stdio.h>', '#include "temp_test_component.h"']))
|
'\n'.join(['{', '\tfunc();', '}']),
|
||||||
|
)
|
||||||
|
replace_in_file(
|
||||||
|
idf_copy / 'projects' / 'temp_test_project' / 'main' / 'temp_test_project.c',
|
||||||
|
'#include <stdio.h>',
|
||||||
|
'\n'.join(['#include <stdio.h>', '#include "temp_test_component.h"']),
|
||||||
|
)
|
||||||
run_idf_py('build', workdir=(idf_copy / 'projects' / 'temp_test_project'))
|
run_idf_py('build', workdir=(idf_copy / 'projects' / 'temp_test_project'))
|
||||||
|
|
||||||
|
|
||||||
@@ -244,6 +250,7 @@ def test_create_project_with_idf_readonly(idf_copy: Path) -> None:
|
|||||||
if '/bin/' in path:
|
if '/bin/' in path:
|
||||||
continue # skip executables
|
continue # skip executables
|
||||||
os.chmod(os.path.join(root, name), file_permission)
|
os.chmod(os.path.join(root, name), file_permission)
|
||||||
|
|
||||||
logging.info('Check that command for creating new project will success if the IDF itself is readonly.')
|
logging.info('Check that command for creating new project will success if the IDF itself is readonly.')
|
||||||
change_file_permissions(idf_copy, write_permission=False)
|
change_file_permissions(idf_copy, write_permission=False)
|
||||||
try:
|
try:
|
||||||
@@ -267,7 +274,18 @@ def test_docs_command(idf_py: IdfPyFunc) -> None:
|
|||||||
assert 'https://docs.espressif.com/projects/esp-idf/en/v4.2.1' in ret.stdout
|
assert 'https://docs.espressif.com/projects/esp-idf/en/v4.2.1' in ret.stdout
|
||||||
ret = idf_py('docs', '--no-browser', '--language', 'en', '--version', 'v4.2.1', '--target', 'esp32')
|
ret = idf_py('docs', '--no-browser', '--language', 'en', '--version', 'v4.2.1', '--target', 'esp32')
|
||||||
assert 'https://docs.espressif.com/projects/esp-idf/en/v4.2.1/esp32' in ret.stdout
|
assert 'https://docs.espressif.com/projects/esp-idf/en/v4.2.1/esp32' in ret.stdout
|
||||||
ret = idf_py('docs', '--no-browser', '--language', 'en', '--version', 'v4.2.1', '--target', 'esp32', '--starting-page', 'get-started')
|
ret = idf_py(
|
||||||
|
'docs',
|
||||||
|
'--no-browser',
|
||||||
|
'--language',
|
||||||
|
'en',
|
||||||
|
'--version',
|
||||||
|
'v4.2.1',
|
||||||
|
'--target',
|
||||||
|
'esp32',
|
||||||
|
'--starting-page',
|
||||||
|
'get-started',
|
||||||
|
)
|
||||||
assert 'https://docs.espressif.com/projects/esp-idf/en/v4.2.1/esp32/get-started' in ret.stdout
|
assert 'https://docs.espressif.com/projects/esp-idf/en/v4.2.1/esp32/get-started' in ret.stdout
|
||||||
|
|
||||||
|
|
||||||
@@ -285,25 +303,31 @@ def test_deprecation_warning(idf_py: IdfPyFunc) -> None:
|
|||||||
|
|
||||||
def test_save_defconfig_check(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
def test_save_defconfig_check(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
||||||
logging.info('Save-defconfig checks')
|
logging.info('Save-defconfig checks')
|
||||||
(test_app_copy / 'sdkconfig').write_text('\n'.join(['CONFIG_COMPILER_OPTIMIZATION_SIZE=y',
|
(test_app_copy / 'sdkconfig').write_text(
|
||||||
'CONFIG_ESPTOOLPY_FLASHFREQ_80M=y']))
|
'\n'.join(['CONFIG_COMPILER_OPTIMIZATION_SIZE=y', 'CONFIG_ESPTOOLPY_FLASHFREQ_80M=y'])
|
||||||
|
)
|
||||||
idf_py('save-defconfig')
|
idf_py('save-defconfig')
|
||||||
assert not file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_IDF_TARGET'), \
|
assert not file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_IDF_TARGET'), (
|
||||||
'CONFIG_IDF_TARGET should not be in sdkconfig.defaults'
|
'CONFIG_IDF_TARGET should not be in sdkconfig.defaults'
|
||||||
assert file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_COMPILER_OPTIMIZATION_SIZE=y'), \
|
)
|
||||||
|
assert file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_COMPILER_OPTIMIZATION_SIZE=y'), (
|
||||||
'Missing CONFIG_COMPILER_OPTIMIZATION_SIZE=y in sdkconfig.defaults'
|
'Missing CONFIG_COMPILER_OPTIMIZATION_SIZE=y in sdkconfig.defaults'
|
||||||
assert file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_ESPTOOLPY_FLASHFREQ_80M=y'), \
|
)
|
||||||
|
assert file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_ESPTOOLPY_FLASHFREQ_80M=y'), (
|
||||||
'Missing CONFIG_ESPTOOLPY_FLASHFREQ_80M=y in sdkconfig.defaults'
|
'Missing CONFIG_ESPTOOLPY_FLASHFREQ_80M=y in sdkconfig.defaults'
|
||||||
|
)
|
||||||
idf_py('fullclean')
|
idf_py('fullclean')
|
||||||
(test_app_copy / 'sdkconfig').unlink()
|
(test_app_copy / 'sdkconfig').unlink()
|
||||||
(test_app_copy / 'sdkconfig.defaults').unlink()
|
(test_app_copy / 'sdkconfig.defaults').unlink()
|
||||||
idf_py('set-target', 'esp32c3')
|
idf_py('set-target', 'esp32c3')
|
||||||
(test_app_copy / 'sdkconfig').write_text('CONFIG_PARTITION_TABLE_OFFSET=0x8001')
|
(test_app_copy / 'sdkconfig').write_text('CONFIG_PARTITION_TABLE_OFFSET=0x8001')
|
||||||
idf_py('save-defconfig')
|
idf_py('save-defconfig')
|
||||||
assert file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_IDF_TARGET="esp32c3"'), \
|
assert file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_IDF_TARGET="esp32c3"'), (
|
||||||
'Missing CONFIG_IDF_TARGET="esp32c3" in sdkconfig.defaults'
|
'Missing CONFIG_IDF_TARGET="esp32c3" in sdkconfig.defaults'
|
||||||
assert file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_PARTITION_TABLE_OFFSET=0x8001'), \
|
)
|
||||||
|
assert file_contains(test_app_copy / 'sdkconfig.defaults', 'CONFIG_PARTITION_TABLE_OFFSET=0x8001'), (
|
||||||
'Missing CONFIG_PARTITION_TABLE_OFFSET=0x8001 in sdkconfig.defaults'
|
'Missing CONFIG_PARTITION_TABLE_OFFSET=0x8001 in sdkconfig.defaults'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_merge_bin_cmd(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
def test_merge_bin_cmd(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
||||||
|
@@ -204,7 +204,8 @@ class TestDeprecations(TestWithoutExtensions):
|
|||||||
[sys.executable, idf_py_path, '-C', current_dir, 'test-2'], env=os.environ, stderr=subprocess.STDOUT
|
[sys.executable, idf_py_path, '-C', current_dir, 'test-2'], env=os.environ, stderr=subprocess.STDOUT
|
||||||
)
|
)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
self.assertIn('Error: Command "test-2" is deprecated and was removed.', e.output.decode('utf-8', 'ignore'))
|
output = e.output.decode('utf-8', 'ignore').replace('\r\n', '\n')
|
||||||
|
self.assertIn('Error: Command "test-2" is deprecated and was removed\n', output)
|
||||||
|
|
||||||
def test_exit_with_error_for_option(self):
|
def test_exit_with_error_for_option(self):
|
||||||
try:
|
try:
|
||||||
@@ -239,7 +240,6 @@ class TestDeprecations(TestWithoutExtensions):
|
|||||||
env=os.environ,
|
env=os.environ,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
).decode('utf-8', 'ignore')
|
).decode('utf-8', 'ignore')
|
||||||
|
|
||||||
self.assertIn('Warning: Option "test_sub_1" is deprecated and will be removed in future versions.', output)
|
self.assertIn('Warning: Option "test_sub_1" is deprecated and will be removed in future versions.', output)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
'Warning: Command "test-1" is deprecated and will be removed in future versions. '
|
'Warning: Command "test-1" is deprecated and will be removed in future versions. '
|
||||||
@@ -374,8 +374,8 @@ class TestWrapperCommands(TestCase):
|
|||||||
class TestEFuseCommands(TestWrapperCommands):
|
class TestEFuseCommands(TestWrapperCommands):
|
||||||
"""
|
"""
|
||||||
Test if wrapper commands for espefuse.py are working as expected.
|
Test if wrapper commands for espefuse.py are working as expected.
|
||||||
The goal is NOT to test the functionality of espefuse.py, but to test if the wrapper commands
|
The goal is NOT to test the functionality of espefuse.py
|
||||||
are working as expected.
|
but to test if the wrapper commands are working as expected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_efuse_summary(self):
|
def test_efuse_summary(self):
|
||||||
@@ -438,8 +438,8 @@ class TestEFuseCommands(TestWrapperCommands):
|
|||||||
class TestSecureCommands(TestWrapperCommands):
|
class TestSecureCommands(TestWrapperCommands):
|
||||||
"""
|
"""
|
||||||
Test if wrapper commands for espsecure.py are working as expected.
|
Test if wrapper commands for espsecure.py are working as expected.
|
||||||
The goal is NOT to test the functionality of espsecure.py, but to test if the wrapper commands are
|
The goal is NOT to test the functionality of espsecure.py
|
||||||
working as expected.
|
but to test if the wrapper commands are working as expected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -577,8 +577,8 @@ class TestSecureCommands(TestWrapperCommands):
|
|||||||
class TestMergeBinCommands(TestWrapperCommands):
|
class TestMergeBinCommands(TestWrapperCommands):
|
||||||
"""
|
"""
|
||||||
Test if merge-bin command is invoked as expected.
|
Test if merge-bin command is invoked as expected.
|
||||||
This test is not testing the functionality of esptool.py merge_bin command, but the invocation of
|
This test is not testing the functionality of esptool.py merge_bin command
|
||||||
the command from idf.py.
|
but the invocation of the command from idf.py.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_merge_bin(self):
|
def test_merge_bin(self):
|
||||||
|
Reference in New Issue
Block a user