change: fix formatting issues before editing tools.py and core_ext.py
This commit is contained in:
@@ -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.',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
+156
-80
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user