From 438e207e4577b3ae3d00660d98d2bd9df7b0e675 Mon Sep 17 00:00:00 2001 From: Jan Beran Date: Fri, 3 Oct 2025 16:18:23 +0200 Subject: [PATCH] change: fix formatting issues before editing tools.py and core_ext.py --- tools/idf_py_actions/core_ext.py | 202 ++++++++++++++++---------- tools/idf_py_actions/tools.py | 236 ++++++++++++++++++++----------- 2 files changed, 284 insertions(+), 154 deletions(-) diff --git a/tools/idf_py_actions/core_ext.py b/tools/idf_py_actions/core_ext.py index 93d34461a5..e8046354f1 100644 --- a/tools/idf_py_actions/core_ext.py +++ b/tools/idf_py_actions/core_ext.py @@ -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 import fnmatch import glob @@ -20,21 +20,22 @@ from webbrowser import open_new_tab import click from click.core import Context + from idf_py_actions.constants import GENERATORS from idf_py_actions.constants import PREVIEW_TARGETS from idf_py_actions.constants import SUPPORTED_TARGETS from idf_py_actions.constants import URL_TO_DOC from idf_py_actions.errors import FatalError from idf_py_actions.global_options import global_options +from idf_py_actions.tools import PropertyDict +from idf_py_actions.tools import TargetChoice from idf_py_actions.tools import ensure_build_directory from idf_py_actions.tools import generate_hints from idf_py_actions.tools import get_target from idf_py_actions.tools import idf_version from idf_py_actions.tools import merge_action_lists from idf_py_actions.tools import print_warning -from idf_py_actions.tools import PropertyDict from idf_py_actions.tools import run_target -from idf_py_actions.tools import TargetChoice from idf_py_actions.tools import yellow_print @@ -49,8 +50,15 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: ensure_build_directory(args, ctx.info_name) run_target(target_name, args, force_progression=GENERATORS[args.generator].get('force_progression', False)) - def size_target(target_name: str, ctx: Context, args: PropertyDict, output_format: str, - output_file: str, diff_map_file: str, legacy: bool) -> None: + def size_target( + target_name: str, + ctx: Context, + args: PropertyDict, + output_format: str, + output_file: str, + diff_map_file: str, + legacy: bool, + ) -> None: """ Builds the app and then executes a size-related target passed in 'target_name'. `tool_error_handler` handler is used to suppress errors during the build, @@ -93,7 +101,9 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: # The diff_map_file argument is a directory. Try to look for the map # file directly in it, in case it's a build directory or in one level below # if it's a project directory. - files = glob.glob(os.path.join(diff_map_file, '*.map')) or glob.glob(os.path.join(diff_map_file, '*/*.map')) + files = glob.glob(os.path.join(diff_map_file, '*.map')) or glob.glob( + os.path.join(diff_map_file, '*/*.map') + ) if not files: raise FatalError(f'No diff map file found in {diff_map_file} directory') if len(files) > 1: @@ -104,8 +114,12 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: env['SIZE_DIFF_FILE'] = diff_map_file ensure_build_directory(args, ctx.info_name) - run_target('all', args, force_progression=GENERATORS[args.generator].get('force_progression', False), - custom_error_handler=tool_error_handler) + run_target( + 'all', + args, + force_progression=GENERATORS[args.generator].get('force_progression', False), + custom_error_handler=tool_error_handler, + ) run_target(target_name, args, env=env) def list_build_system_targets(target_name: str, ctx: Context, args: PropertyDict) -> None: @@ -121,9 +135,15 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: try: import curses # noqa: F401 except ImportError: - raise FatalError('\n'.join( - ['', "menuconfig failed to import the standard Python 'curses' library.", - 'Please re-run the install script which might be able to fix the issue.'])) + raise FatalError( + '\n'.join( + [ + '', + "menuconfig failed to import the standard Python 'curses' library.", + 'Please re-run the install script which might be able to fix the issue.', + ] + ) + ) if sys.version_info[0] < 3: # The subprocess lib cannot accept environment variables as "unicode". # This encoding step is required only in Python 2. @@ -151,11 +171,14 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: except Exception: if target_name in ['clang-check', 'clang-html-report']: - raise FatalError('command "{}" requires an additional plugin "pyclang". ' - 'Please install it via "pip install --upgrade pyclang"'.format(target_name)) + raise FatalError( + 'command "{}" requires an additional plugin "pyclang". ' + 'Please install it via "pip install --upgrade pyclang"'.format(target_name) + ) raise FatalError( - 'command "%s" is not known to idf.py and is not a %s target' % (target_name, args.generator)) + 'command "%s" is not known to idf.py and is not a %s target' % (target_name, args.generator) + ) run_target(target_name, args) @@ -186,14 +209,16 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: if not os.path.exists(os.path.join(build_dir, 'CMakeCache.txt')): raise FatalError( "Directory '%s' doesn't seem to be a CMake build directory. Refusing to automatically " - "delete files in this directory. Delete the directory manually to 'clean' it." % build_dir) + "delete files in this directory. Delete the directory manually to 'clean' it." % build_dir + ) red_flags = ['CMakeLists.txt', '.git', '.svn'] for red in red_flags: red = os.path.join(build_dir, red) if os.path.exists(red): raise FatalError( - "Refusing to automatically delete files in directory containing '%s'. Delete files manually if you're sure." - % red) + f"Refusing to automatically delete files in directory containing '{red}'. " + "Delete files manually if you're sure." + ) if args.verbose and len(build_dir) > 1: print('The following symlinks were identified and removed:\n%s' % '\n'.join(build_dir)) for f in os.listdir(build_dir): # TODO: once we are Python 3 only, this can be os.scandir() @@ -220,10 +245,11 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: os.remove(file_to_delete) def set_target(action: str, ctx: Context, args: PropertyDict, idf_target: str) -> None: - if (not args['preview'] and idf_target in PREVIEW_TARGETS): + if not args['preview'] and idf_target in PREVIEW_TARGETS: raise FatalError( "%s is still in preview. You have to append '--preview' option after idf.py to use any preview feature." - % idf_target) + % idf_target + ) args.define_cache_entry.append('IDF_TARGET=' + idf_target) print(f'Set Target to: {idf_target}, new sdkconfig will be created.') env = {'_IDF_PY_SET_TARGET_ACTION': '1'} @@ -237,7 +263,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: if args.build_dir is not None and args.project_dir == os.path.realpath(args.build_dir): raise FatalError( 'Setting the build directory to the project directory is not supported. Suggest dropping ' - "--build-dir option, the default is a 'build' subdirectory inside the project directory.") + "--build-dir option, the default is a 'build' subdirectory inside the project directory." + ) if args.build_dir is None: args.build_dir = os.path.join(args.project_dir, 'build') args.build_dir = os.path.realpath(args.build_dir) @@ -267,7 +294,16 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: sys.exit(0) - def show_docs(action: str, ctx: Context, args: PropertyDict, no_browser: bool, language: str, starting_page: str, version: str, target: str) -> None: + def show_docs( + action: str, + ctx: Context, + args: PropertyDict, + no_browser: bool, + language: str, + starting_page: str, + version: str, + target: str, + ) -> None: if language == 'cn': language = 'zh_CN' if not version: @@ -288,7 +324,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: except URLError: print("We can't check the link's functionality because you don't have an internet connection") if redirect_link: - print('Target', target, 'doesn\'t exist for version', version) + print('Target', target, "doesn't exist for version", version) link = '/'.join([URL_TO_DOC, language, version, starting_page or '']) if not no_browser: print('Opening documentation in the default browser:') @@ -355,8 +391,10 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: }, { 'names': ['-w/-n', '--cmake-warn-uninitialized/--no-warnings'], - 'help': ('Enable CMake uninitialized variable warnings for CMake files inside the project directory. ' - "(--no-warnings is now the default, and doesn't need to be specified.)"), + 'help': ( + 'Enable CMake uninitialized variable warnings for CMake files inside the project directory. ' + "(--no-warnings is now the default, and doesn't need to be specified.)" + ), 'envvar': 'IDF_CMAKE_WARN_UNINITIALIZED', 'is_flag': True, 'default': False, @@ -399,8 +437,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: 'names': ['--no-hints'], 'help': 'Disable hints on how to resolve errors and logging.', 'is_flag': True, - 'default': False - } + 'default': False, + }, ], 'global_action_callbacks': [validate_root_options], } @@ -408,19 +446,31 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: # 'default' is introduced instead of simply setting 'text' as the default so that we know # if the user explicitly specified the format or not. If the format is not specified, then # the legacy OUTPUT_JSON CMake variable will be taken into account. - size_options = [{'names': ['--format', 'output_format'], - 'type': click.Choice(['default', 'text', 'csv', 'json', 'json2', 'tree', 'raw']), - 'help': 'Specify output format: text (same as "default"), csv, json, json2, tree or raw.', - 'default': 'default'}, - {'names': ['--legacy', '-l'], - 'is_flag': True, - 'default': os.environ.get('ESP_IDF_SIZE_LEGACY', '0') == '1', - 'help': 'Use legacy esp-idf-size version'}, - {'names': ['--diff', 'diff_map_file'], - 'help': ('Show the differences in comparison with another project. ' - 'Argument can be map file or project directory.')}, - {'names': ['--output-file', 'output_file'], - 'help': 'Print output to the specified file instead of to the standard output'}] + size_options = [ + { + 'names': ['--format', 'output_format'], + 'type': click.Choice(['default', 'text', 'csv', 'json', 'json2', 'tree', 'raw']), + 'help': 'Specify output format: text (same as "default"), csv, json, json2, tree or raw.', + 'default': 'default', + }, + { + 'names': ['--legacy', '-l'], + 'is_flag': True, + 'default': os.environ.get('ESP_IDF_SIZE_LEGACY', '0') == '1', + 'help': 'Use legacy esp-idf-size version', + }, + { + 'names': ['--diff', 'diff_map_file'], + 'help': ( + 'Show the differences in comparison with another project. ' + 'Argument can be map file or project directory.' + ), + }, + { + 'names': ['--output-file', 'output_file'], + 'help': 'Print output to the specified file instead of to the standard output', + }, + ] build_actions = { 'actions': { @@ -437,7 +487,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: 'and generate build files for the main build tool.\n\n' '3. Run the main build tool (Ninja or GNU Make). ' 'By default, the build tool is automatically detected ' - 'but it can be explicitly set by passing the -G option to idf.py.\n\n'), + 'but it can be explicitly set by passing the -G option to idf.py.\n\n' + ), 'options': global_options, 'order_dependencies': [ 'reconfigure', @@ -449,7 +500,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: 'menuconfig': { 'callback': menuconfig, 'help': 'Run "menuconfig" project configuration tool.', - 'options': global_options + [ + 'options': global_options + + [ { 'names': ['--style', '--color-scheme', 'style'], 'help': ( @@ -460,7 +512,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: '- aquatic - a blue theme.\n\n' 'It is possible to customize these themes further' ' as it is described in the Color schemes section of the kconfiglib documentation.\n' - 'The default value is \"aquatic\".'), + 'The default value is "aquatic".' + ), 'envvar': 'MENUCONFIG_STYLE', 'default': 'aquatic', } @@ -499,13 +552,13 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: }, 'efuse-common-table': { 'callback': build_target, - 'help': 'Generate C-source for IDF\'s eFuse fields.', + 'help': "Generate C-source for IDF's eFuse fields.", 'order_dependencies': ['reconfigure'], 'options': global_options, }, 'efuse-custom-table': { 'callback': build_target, - 'help': 'Generate C-source for user\'s eFuse fields.', + 'help': "Generate C-source for user's eFuse fields.", 'order_dependencies': ['reconfigure'], 'options': global_options, }, @@ -534,41 +587,37 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: 'callback': show_docs, 'help': 'Open web browser with documentation for ESP-IDF', 'options': [ - { - 'names': ['--no-browser', '-nb'], - 'is_flag': True, - 'help': 'Don\'t open browser.' - }, + {'names': ['--no-browser', '-nb'], 'is_flag': True, 'help': "Don't open browser."}, { 'names': ['--language', '-l'], 'default': get_default_language(), 'type': click.Choice(['en', 'zh_CN', 'cn']), - 'help': 'Documentation language. Your system language by default (en or cn)' + 'help': 'Documentation language. Your system language by default (en or cn)', }, { 'names': ['--starting-page', '-sp'], - 'help': 'Documentation page (get-started, api-reference etc).' - }, - { - 'names': ['--version', '-v'], - 'help': 'Version of ESP-IDF.' + 'help': 'Documentation page (get-started, api-reference etc).', }, + {'names': ['--version', '-v'], 'help': 'Version of ESP-IDF.'}, { 'names': ['--target', '-t'], 'type': TargetChoice(SUPPORTED_TARGETS + PREVIEW_TARGETS + ['']), - 'help': 'Chip target.' - } - ] + 'help': 'Chip target.', + }, + ], }, 'save-defconfig': { 'callback': save_defconfig, 'help': 'Generate a sdkconfig.defaults with options different from the default ones', - 'options': global_options + [{ - 'names': ['--add-menu-labels'], - 'is_flag': True, - 'help': 'Add menu labels to minimal config.', - }] - } + 'options': global_options + + [ + { + 'names': ['--add-menu-labels'], + 'is_flag': True, + 'help': 'Add menu labels to minimal config.', + } + ], + }, } } @@ -582,8 +631,9 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: "This isn't necessary during normal usage, " 'but can be useful after adding/removing files from the source tree, ' 'or when modifying CMake cache variables. ' - "For example, \"idf.py -DNAME='VALUE' reconfigure\" " - 'can be used to set variable "NAME" in CMake cache to value "VALUE".'), + 'For example, "idf.py -DNAME=\'VALUE\' reconfigure" ' + 'can be used to set variable "NAME" in CMake cache to value "VALUE".' + ), 'options': global_options, 'order_dependencies': ['menuconfig', 'fullclean'], }, @@ -594,8 +644,9 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: 'Set the chip target to build. This will remove the ' 'existing sdkconfig file and corresponding CMakeCache and ' 'create new ones according to the new target.\nFor example, ' - "\"idf.py set-target esp32\" will select esp32 as the new chip " - 'target.'), + '"idf.py set-target esp32" will select esp32 as the new chip ' + 'target.' + ), 'arguments': [ { 'names': ['idf-target'], @@ -612,7 +663,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: 'Delete build output files from the build directory, ' "forcing a 'full rebuild' the next time " "the project is built. Cleaning doesn't delete " - 'CMake configuration output and some other files'), + 'CMake configuration output and some other files' + ), 'order_dependencies': ['fullclean'], }, 'fullclean': { @@ -625,7 +677,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: 'CMake will configure it from scratch. ' 'Note that this option recursively deletes all files ' 'in the build directory, so use with care.' - 'Project configuration is not deleted.') + 'Project configuration is not deleted.' + ), }, 'python-clean': { 'callback': python_clean, @@ -633,7 +686,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: 'help': ( 'Delete generated Python byte code from the IDF directory ' 'which may cause issues when switching between IDF and Python versions. ' - 'It is advised to run this target after switching versions.') + 'It is advised to run this target after switching versions.' + ), }, } } @@ -648,13 +702,13 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any: { 'names': ['--json', 'json_option'], 'is_flag': True, - 'help': 'Print out actions in machine-readable format for selected target.' + 'help': 'Print out actions in machine-readable format for selected target.', }, { 'names': ['--add-options'], 'is_flag': True, - 'help': 'Add options about actions to machine-readable format.' - } + 'help': 'Add options about actions to machine-readable format.', + }, ], } } diff --git a/tools/idf_py_actions/tools.py b/tools/idf_py_actions/tools.py index 2717e15d60..6fb9ce16ce 100644 --- a/tools/idf_py_actions/tools.py +++ b/tools/idf_py_actions/tools.py @@ -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 import asyncio import importlib @@ -23,6 +23,7 @@ from typing import Union import click import yaml from esp_idf_monitor import get_ansi_converter + from idf_py_actions.errors import NoSerialPortFoundError from .constants import GENERATORS @@ -43,7 +44,7 @@ SHELL_COMPLETE_RUN = SHELL_COMPLETE_VAR in os.environ # https://docs.python.org/3/reference/compound_stmts.html#function-definitions # Default parameter values are evaluated from left to right # when the function definition is executed -def get_build_context(ctx: Dict={}) -> Dict: +def get_build_context(ctx: Dict = {}) -> Dict: """ The build context is set in the ensure_build_directory function. It can be used in modules or other code, which don't have direct access to such information. @@ -98,7 +99,7 @@ def _idf_version_from_cmake() -> Optional[str]: return None -def get_target(path: str, sdkconfig_filename: str='sdkconfig') -> Optional[str]: +def get_target(path: str, sdkconfig_filename: str = 'sdkconfig') -> Optional[str]: path = os.path.join(path, sdkconfig_filename) return get_sdkconfig_value(path, 'CONFIG_IDF_TARGET') @@ -108,12 +109,22 @@ def idf_version() -> Optional[str]: # Try to get version from git: try: - version: Optional[str] = subprocess.check_output([ - 'git', - '--git-dir=%s' % os.path.join(os.environ['IDF_PATH'], '.git'), - '--work-tree=%s' % os.environ['IDF_PATH'], - 'describe', '--tags', '--dirty', '--match', 'v*.*', - ]).decode('utf-8', 'ignore').strip() + version: Optional[str] = ( + subprocess.check_output( + [ + 'git', + '--git-dir=%s' % os.path.join(os.environ['IDF_PATH'], '.git'), + '--work-tree=%s' % os.environ['IDF_PATH'], + 'describe', + '--tags', + '--dirty', + '--match', + 'v*.*', + ] + ) + .decode('utf-8', 'ignore') + .strip() + ) except Exception: # if failed, then try to parse cmake.version file sys.stderr.write('WARNING: Git version unavailable, reading from source\n') @@ -128,19 +139,18 @@ def get_default_serial_port() -> Any: try: import esptool import serial.tools.list_ports + ports = list(sorted(p.device for p in serial.tools.list_ports.comports())) if sys.platform == 'darwin': - ports = [ - port - for port in ports - if not port.endswith(('Bluetooth-Incoming-Port', 'wlan-debug')) - ] + ports = [port for port in ports if not port.endswith(('Bluetooth-Incoming-Port', 'wlan-debug'))] # high baud rate could cause the failure of creation of the connection - esp = esptool.get_default_connected_device(serial_list=ports, port=None, connect_attempts=4, - initial_baud=115200) + esp = esptool.get_default_connected_device( + serial_list=ports, port=None, connect_attempts=4, initial_baud=115200 + ) if esp is None: raise NoSerialPortFoundError( - "No serial ports found. Connect a device, or use '-p PORT' option to set a specific port.") + "No serial ports found. Connect a device, or use '-p PORT' option to set a specific port." + ) serial_port = esp.serial_port esp._port.close() @@ -155,24 +165,24 @@ def get_default_serial_port() -> Any: # function prints warning when autocompletion is not being performed # set argument stream to sys.stderr for errors and exceptions -def print_warning(message: str, stream: Optional[TextIO]=None) -> None: +def print_warning(message: str, stream: Optional[TextIO] = None) -> None: if not SHELL_COMPLETE_RUN: print(message, file=stream or sys.stderr) -def color_print(message: str, color: str, newline: Optional[str]='\n') -> None: - """ Print a message to stderr with colored highlighting """ +def color_print(message: str, color: str, newline: Optional[str] = '\n') -> None: + """Print a message to stderr with colored highlighting""" ansi_normal = '\033[0m' sys.stderr.write('%s%s%s%s' % (color, message, ansi_normal, newline)) sys.stderr.flush() -def yellow_print(message: str, newline: Optional[str]='\n') -> None: +def yellow_print(message: str, newline: Optional[str] = '\n') -> None: ansi_yellow = '\033[0;33m' color_print(message, ansi_yellow, newline) -def red_print(message: str, newline: Optional[str]='\n') -> None: +def red_print(message: str, newline: Optional[str] = '\n') -> None: ansi_red = '\033[1;31m' color_print(message, ansi_red, newline) @@ -183,10 +193,7 @@ def debug_print_idf_version() -> None: def load_hints() -> Dict: """Helper function to load hints yml file""" - hints: Dict = { - 'yml': [], - 'modules': [] - } + hints: Dict = {'yml': [], 'modules': []} current_module_dir = os.path.dirname(__file__) with open(os.path.join(current_module_dir, 'hints.yml'), 'r', encoding='utf-8') as file: @@ -279,14 +286,24 @@ def fit_text_in_terminal(out: str) -> str: if len(out) >= terminal_width: elide_size = (terminal_width - space_for_dots) // 2 # cut out the middle part of the output if it does not fit in the terminal - return '...'.join([out[:elide_size], out[len(out) - elide_size:]]) + return '...'.join([out[:elide_size], out[len(out) - elide_size :]]) return out class RunTool: - def __init__(self, tool_name: str, args: List, cwd: str, env: Optional[Dict]=None, custom_error_handler: Optional[FunctionType]=None, - build_dir: Optional[str]=None, hints: bool=True, force_progression: bool=False, interactive: bool=False, convert_output: bool=False - ) -> None: + def __init__( + self, + tool_name: str, + args: List, + cwd: str, + env: Optional[Dict] = None, + custom_error_handler: Optional[FunctionType] = None, + build_dir: Optional[str] = None, + hints: bool = True, + force_progression: bool = False, + interactive: bool = False, + convert_output: bool = False, + ) -> None: self.tool_name = tool_name self.args = args self.cwd = cwd @@ -301,8 +318,10 @@ class RunTool: def __call__(self) -> None: def quote_arg(arg: str) -> str: - """ Quote the `arg` with whitespace in them because it can cause problems when we call it from a subprocess.""" - if re.match(r"^(?![\'\"]).*\s.*", arg): + """ + Quote the `arg` with whitespace in them because it can cause problems when we call it from a subprocess. + """ + if re.match(r'^(?![\'\"]).*\s.*', arg): return ''.join(["'", arg, "'"]) return arg @@ -332,14 +351,17 @@ class RunTool: if not self.interactive: for hint in generate_hints(stderr_output_file, stdout_output_file): yellow_print(hint) - raise FatalError('{} failed with exit code {}, output of the command is in the {} and {}'.format(self.tool_name, process.returncode, - stderr_output_file, stdout_output_file)) + raise FatalError( + '{} failed with exit code {}, output of the command is in the {} and {}'.format( + self.tool_name, process.returncode, stderr_output_file, stdout_output_file + ) + ) raise FatalError('{} failed with exit code {}'.format(self.tool_name, process.returncode)) async def run_command(self, cmd: List, env_copy: Dict) -> Tuple[Process, Optional[str], Optional[str]]: - """ Run the `cmd` command with capturing stderr and stdout from that function and return returncode - and of the command, the id of the process, paths to captured output """ + """Run the `cmd` command with capturing stderr and stdout from that function and return returncode + and of the command, the id of the process, paths to captured output""" log_dir_name = 'log' try: os.mkdir(os.path.join(self.build_dir, log_dir_name)) @@ -348,13 +370,24 @@ class RunTool: # Note: we explicitly pass in os.environ here, as we may have set IDF_PATH there during startup # limit was added for avoiding error in idf.py confserver try: - p = await asyncio.create_subprocess_exec(*cmd, env=env_copy, limit=1024 * 256, cwd=self.cwd, stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE) + p = await asyncio.create_subprocess_exec( + *cmd, + env=env_copy, + limit=1024 * 256, + cwd=self.cwd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) except NotImplementedError: - message = f'ERROR: {sys.executable} doesn\'t support asyncio. The issue can be worked around by re-running idf.py with the "--no-hints" argument.' + message = ( + f"ERROR: {sys.executable} doesn't support asyncio. " + 'The issue can be worked around by re-running idf.py with the "--no-hints" argument.' + ) if sys.platform == 'win32': - message += ' To fix the issue use the Windows Installer for setting up your python environment, ' \ + message += ( + ' To fix the issue use the Windows Installer for setting up your python environment, ' 'available from: https://dl.espressif.com/dl/esp-idf/' + ) sys.exit(message) stderr_output_file = os.path.join(self.build_dir, log_dir_name, f'idf_py_stderr_output_{p.pid}') @@ -363,7 +396,8 @@ class RunTool: try: await asyncio.gather( self.read_and_write_stream(p.stderr, stderr_output_file, sys.stderr), - self.read_and_write_stream(p.stdout, stdout_output_file, sys.stdout)) + self.read_and_write_stream(p.stdout, stdout_output_file, sys.stdout), + ) except asyncio.CancelledError: # The process we are trying to read from was terminated. Print the # message here and let the asyncio to finish, because @@ -376,9 +410,11 @@ class RunTool: await p.wait() # added for avoiding None returncode return p, stderr_output_file, stdout_output_file - async def read_and_write_stream(self, input_stream: asyncio.StreamReader, output_filename: str, - output_stream: TextIO) -> None: + async def read_and_write_stream( + self, input_stream: asyncio.StreamReader, output_filename: str, output_stream: TextIO + ) -> None: """read the output of the `input_stream` and then write it into `output_filename` and `output_stream`""" + def delete_ansi_escape(text: str) -> str: ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') return ansi_escape.sub('', text) @@ -471,8 +507,10 @@ class RunTool: yellow_print(hint) last_line = '' except (RuntimeError, EnvironmentError) as e: - yellow_print('WARNING: The exception {} was raised and we can\'t capture all your {} and ' - 'hints on how to resolve errors can be not accurate.'.format(e, output_stream.name.strip('<>'))) + yellow_print( + "WARNING: The exception {} was raised and we can't capture all your {} and " + 'hints on how to resolve errors can be not accurate.'.format(e, output_stream.name.strip('<>')) + ) def run_tool(*args: Any, **kwargs: Any) -> None: @@ -480,8 +518,14 @@ def run_tool(*args: Any, **kwargs: Any) -> None: return RunTool(*args, **kwargs)() -def run_target(target_name: str, args: 'PropertyDict', env: Optional[Dict]=None, - custom_error_handler: Optional[FunctionType]=None, force_progression: bool=False, interactive: bool=False) -> None: +def run_target( + target_name: str, + args: 'PropertyDict', + env: Optional[Dict] = None, + custom_error_handler: Optional[FunctionType] = None, + force_progression: bool = False, + interactive: bool = False, +) -> None: """Run target in build directory.""" if env is None: env = {} @@ -498,11 +542,19 @@ def run_target(target_name: str, args: 'PropertyDict', env: Optional[Dict]=None, if 'CLICOLOR_FORCE' not in env: env['CLICOLOR_FORCE'] = '1' - RunTool(generator_cmd[0], generator_cmd + [target_name], args.build_dir, env, custom_error_handler, hints=not args.no_hints, - force_progression=force_progression, interactive=interactive)() + RunTool( + generator_cmd[0], + generator_cmd + [target_name], + args.build_dir, + env, + custom_error_handler, + hints=not args.no_hints, + force_progression=force_progression, + interactive=interactive, + )() -def _strip_quotes(value: str, regexp: re.Pattern=re.compile(r"^\"(.*)\"$|^'(.*)'$|^(.*)$")) -> Optional[str]: +def _strip_quotes(value: str, regexp: re.Pattern = re.compile(r"^\"(.*)\"$|^'(.*)'$|^(.*)$")) -> Optional[str]: """ Strip quotes like CMake does during parsing cache entries """ @@ -557,14 +609,15 @@ def _detect_cmake_generator(prog_name: str) -> Any: """ Find the default cmake generator, if none was specified. Raises an exception if no valid generator is found. """ - for (generator_name, generator) in GENERATORS.items(): + for generator_name, generator in GENERATORS.items(): if executable_exists(generator['version']): return generator_name raise FatalError("To use %s, either the 'ninja' or 'GNU make' build tool must be available in the PATH" % prog_name) -def ensure_build_directory(args: 'PropertyDict', prog_name: str, always_run_cmake: bool=False, - env: Optional[Dict]=None) -> None: +def ensure_build_directory( + args: 'PropertyDict', prog_name: str, always_run_cmake: bool = False, env: Optional[Dict] = None +) -> None: """Check the build directory exists and that cmake has been run there. If this isn't the case, create the build directory (if necessary) and @@ -641,17 +694,20 @@ def ensure_build_directory(args: 'PropertyDict', prog_name: str, always_run_cmak except KeyError: generator = _detect_cmake_generator(prog_name) if args.generator is None: - args.generator = (generator) # reuse the previously configured generator, if none was given + args.generator = generator # reuse the previously configured generator, if none was given if generator != args.generator: - raise FatalError("Build is configured for generator '%s' not '%s'. Run '%s fullclean' to start again." % - (generator, args.generator, prog_name)) + raise FatalError( + "Build is configured for generator '%s' not '%s'. Run '%s fullclean' to start again." + % (generator, args.generator, prog_name) + ) try: home_dir = cache['CMAKE_HOME_DIRECTORY'] if os.path.realpath(home_dir) != os.path.realpath(project_dir): raise FatalError( - "Build directory '%s' configured for project '%s' not '%s'. Run '%s fullclean' to start again." % - (build_dir, os.path.realpath(home_dir), os.path.realpath(project_dir), prog_name)) + "Build directory '%s' configured for project '%s' not '%s'. Run '%s fullclean' to start again." + % (build_dir, os.path.realpath(home_dir), os.path.realpath(project_dir), prog_name) + ) except KeyError: pass # if cmake failed part way, CMAKE_HOME_DIRECTORY may not be set yet @@ -660,7 +716,8 @@ def ensure_build_directory(args: 'PropertyDict', prog_name: str, always_run_cmak if os.path.normcase(python) != os.path.normcase(sys.executable): raise FatalError( "'{}' is currently active in the environment while the project was configured with '{}'. " - "Run '{} fullclean' to start again.".format(sys.executable, python, prog_name)) + "Run '{} fullclean' to start again.".format(sys.executable, python, prog_name) + ) except KeyError: pass @@ -681,7 +738,7 @@ def merge_action_lists(*action_lists: Dict) -> Dict: return merged_actions -def get_sdkconfig_filename(args: 'PropertyDict', cache_cmdl: Optional[Dict]=None) -> str: +def get_sdkconfig_filename(args: 'PropertyDict', cache_cmdl: Optional[Dict] = None) -> str: """ Get project's sdkconfig file name. """ @@ -713,7 +770,7 @@ def get_sdkconfig_value(sdkconfig_file: str, key: str) -> Optional[str]: # keep track of the last seen value for the given key value = None # if the value is quoted, this excludes the quotes from the value - pattern = re.compile(r"^{}=\"?([^\"]*)\"?$".format(key)) + pattern = re.compile(r'^{}=\"?([^\"]*)\"?$'.format(key)) with open(sdkconfig_file, 'r', encoding='utf-8') as f: for line in f: match = re.match(pattern, line) @@ -729,8 +786,9 @@ def is_target_supported(project_path: str, supported_targets: List) -> bool: return get_target(project_path) in supported_targets -def _check_idf_target(args: 'PropertyDict', prog_name: str, cache: Dict, - cache_cmdl: Dict, env: Optional[Dict]=None) -> None: +def _check_idf_target( + args: 'PropertyDict', prog_name: str, cache: Dict, cache_cmdl: Dict, env: Optional[Dict] = None +) -> None: """ Cross-check the three settings (sdkconfig, CMakeCache, environment) and if there is mismatch, fail with instructions on how to fix this. @@ -750,34 +808,51 @@ def _check_idf_target(args: 'PropertyDict', prog_name: str, cache: Dict, if idf_target_from_env: # Let's check that IDF_TARGET values are consistent if idf_target_from_sdkconfig and idf_target_from_sdkconfig != idf_target_from_env: - raise FatalError("Project sdkconfig '{cfg}' was generated for target '{t_conf}', but environment variable IDF_TARGET " - "is set to '{t_env}'. Run '{prog} set-target {t_env}' to generate new sdkconfig file for target {t_env}." - .format(cfg=sdkconfig, t_conf=idf_target_from_sdkconfig, t_env=idf_target_from_env, prog=prog_name)) + raise FatalError( + "Project sdkconfig '{cfg}' was generated for target '{t_conf}', but environment variable " + "IDF_TARGET is set to '{t_env}'. Run '{prog} set-target {t_env}' to generate " + 'new sdkconfig file for target {t_env}.'.format( + cfg=sdkconfig, t_conf=idf_target_from_sdkconfig, t_env=idf_target_from_env, prog=prog_name + ) + ) if idf_target_from_cache and idf_target_from_cache != idf_target_from_env: - raise FatalError("Target settings are not consistent: '{t_env}' in the environment, '{t_cache}' in CMakeCache.txt. " - "Run '{prog} fullclean' to start again." - .format(t_env=idf_target_from_env, t_cache=idf_target_from_cache, prog=prog_name)) + raise FatalError( + "Target settings are not consistent: '{t_env}' in the environment, '{t_cache}' in CMakeCache.txt. " + "Run '{prog} fullclean' to start again.".format( + t_env=idf_target_from_env, t_cache=idf_target_from_cache, prog=prog_name + ) + ) if idf_target_from_cache_cmdl and idf_target_from_cache_cmdl != idf_target_from_env: - raise FatalError("Target '{t_cmdl}' specified on command line is not consistent with " - "target '{t_env}' in the environment." - .format(t_cmdl=idf_target_from_cache_cmdl, t_env=idf_target_from_env)) + raise FatalError( + "Target '{t_cmdl}' specified on command line is not consistent with " + "target '{t_env}' in the environment.".format( + t_cmdl=idf_target_from_cache_cmdl, t_env=idf_target_from_env + ) + ) elif idf_target_from_cache_cmdl: # Check if -DIDF_TARGET is consistent with target in CMakeCache.txt if idf_target_from_cache and idf_target_from_cache != idf_target_from_cache_cmdl: - raise FatalError("Target '{t_cmdl}' specified on command line is not consistent with " - "target '{t_cache}' in CMakeCache.txt. Run '{prog} set-target {t_cmdl}' to re-generate " - 'CMakeCache.txt.' - .format(t_cache=idf_target_from_cache, t_cmdl=idf_target_from_cache_cmdl, prog=prog_name)) + raise FatalError( + "Target '{t_cmdl}' specified on command line is not consistent with " + "target '{t_cache}' in CMakeCache.txt. Run '{prog} set-target {t_cmdl}' to re-generate " + 'CMakeCache.txt.'.format( + t_cache=idf_target_from_cache, t_cmdl=idf_target_from_cache_cmdl, prog=prog_name + ) + ) elif idf_target_from_cache: # This shouldn't happen, unless the user manually edits CMakeCache.txt or sdkconfig, but let's check anyway. if idf_target_from_sdkconfig and idf_target_from_cache != idf_target_from_sdkconfig: - raise FatalError("Project sdkconfig '{cfg}' was generated for target '{t_conf}', but CMakeCache.txt contains '{t_cache}'. " - "To keep the setting in sdkconfig ({t_conf}) and re-generate CMakeCache.txt, run '{prog} fullclean'. " - "To re-generate sdkconfig for '{t_cache}' target, run '{prog} set-target {t_cache}'." - .format(cfg=sdkconfig, t_conf=idf_target_from_sdkconfig, t_cache=idf_target_from_cache, prog=prog_name)) + raise FatalError( + "Project sdkconfig '{cfg}' was generated for target '{t_conf}', " + "but CMakeCache.txt contains '{t_cache}'. To keep the setting in sdkconfig ({t_conf}) and re-generate " + "CMakeCache.txt, run '{prog} fullclean'. To re-generate sdkconfig for '{t_cache}' target, " + "run '{prog} set-target {t_cache}'.".format( + cfg=sdkconfig, t_conf=idf_target_from_sdkconfig, t_cache=idf_target_from_cache, prog=prog_name + ) + ) class TargetChoice(click.Choice): @@ -786,6 +861,7 @@ class TargetChoice(click.Choice): - ignores hyphens - not case sensitive """ + def __init__(self, choices: List) -> None: super(TargetChoice, self).__init__(choices, case_sensitive=False)