Merge branch 'feat/remove_legacy_esp_size' into 'master'

Tools/esp-idf-size: Remove --legacy argument and replace JSON format with JSON2

Closes IDF-8772 and DOC-12437

See merge request espressif/esp-idf!41743
This commit is contained in:
Roland Dobai
2025-09-30 09:47:44 +02:00
7 changed files with 218 additions and 167 deletions

View File

@@ -216,7 +216,7 @@ This command prints size information per source file in the project.
Options Options
^^^^^^^ ^^^^^^^
- ``--format`` specifies the output format with available options: ``text``, ``csv``, ``json``, default being ``text``. - ``--format`` specifies the output format with available options: ``text``, ``csv``, ``json2``, ``tree``, ``raw``, default being ``text``.
- ``--output-file`` optionally specifies the name of the file to print the command output to instead of the standard output. - ``--output-file`` optionally specifies the name of the file to print the command output to instead of the standard output.
Reconfigure the Project: ``reconfigure`` Reconfigure the Project: ``reconfigure``

View File

@@ -14,9 +14,9 @@ For example, `Python 3.13 is not supported <https://github.com/cs01/gdbgui/issue
The Windows operating system is not supported since gdbgui version 0.14. Because of the existence of other issues, you will need Python 3.10 with some specific versions of the dependencies. The last known working gdbgui and dependency versions can be installed with the following command: The Windows operating system is not supported since gdbgui version 0.14. Because of the existence of other issues, you will need Python 3.10 with some specific versions of the dependencies. The last known working gdbgui and dependency versions can be installed with the following command:
```bash .. code-block:: bash
pipx install "gdbgui==0.13.2.0" "pygdbmi<=0.9.0.2" "python-socketio<5" "jinja2<3.1" "itsdangerous<2.1" pipx install "gdbgui==0.13.2.0" "pygdbmi<=0.9.0.2" "python-socketio<5" "jinja2<3.1" "itsdangerous<2.1"
```
On Linux or macOS, you can use Python 3.11 or 3.12 and gdbgui version 0.15.2.0. On Linux or macOS, you can use Python 3.11 or 3.12 and gdbgui version 0.15.2.0.
@@ -67,3 +67,35 @@ Catch
----- -----
The header-only copy of Catch2 unit testing library previously located in tools/catch directory has been removed. To continue using Catch2 in your project, migrate to Catch2 3.x, available from the `ESP component registry <https://components.espressif.com/components/espressif/catch2>`_. For an example of migrating from Catch2 2.x to Catch2 3.x, see commit 79a2c15477dc327550ff46a64ee0f8b4679cc417. The header-only copy of Catch2 unit testing library previously located in tools/catch directory has been removed. To continue using Catch2 in your project, migrate to Catch2 3.x, available from the `ESP component registry <https://components.espressif.com/components/espressif/catch2>`_. For an example of migrating from Catch2 2.x to Catch2 3.x, see commit 79a2c15477dc327550ff46a64ee0f8b4679cc417.
Dropped ``idf.py size --legacy`` option
---------------------------------------
The ``--legacy`` argument for ``idf.py size`` has been removed, as the legacy implementation is no longer supported. The ``ESP_IDF_SIZE_LEGACY`` environment variable also no longer has any effect. To continue using the legacy option, use ESP-IDF version 5.5 or lower. For ESP-IDF v6.0 and later, simply replace ``idf.py size --legacy`` with ``idf.py size``. If you encounter esp-idf-size version problems, please run the install script to update to the correct version.
Changed ``idf.py size --format json`` to ``--format json2``
-----------------------------------------------------------
The ``--format json`` option has been replaced with ``--format json2``. The ``json2`` format provides better structure with explicit ``total``, ``used``, and ``free`` fields for each memory region, and detailed breakdown in the ``parts`` section. To migrate, replace ``idf.py size --format json`` with ``idf.py size --format json2``.
- **Old format (json)**: Flat structure with direct memory type fields like ``"dram_data": 9192, "iram_text": 43295``.
- **New format (json2)**: Hierarchical structure with a ``layout`` array containing memory regions:
.. code-block:: json
{
"version": "1.1",
"layout": [
{
"name": "DRAM",
"total": 180736,
"used": 11344,
"free": 169392,
"parts": {
".data": {"size": 9192},
".bss": {"size": 2152}
}
}
]
}

View File

@@ -216,7 +216,7 @@ uf2 二进制文件也可以通过 :ref:`idf.py uf2 <generate-uf2-binary>` 生
选项 选项
^^^^^^^ ^^^^^^^
- ``--format`` 指定输出格式,可输出 ``text````csv`` ``json`` 格式,默认格式为 ``text`` - ``--format`` 指定输出格式,可输出 ``text````csv````json2````tree````raw`` 格式,默认格式为 ``text``
- ``--output-file`` 可选参数,可以指定命令输出文件的文件名,而非标准输出。 - ``--output-file`` 可选参数,可以指定命令输出文件的文件名,而非标准输出。
重新配置工程:``reconfigure`` 重新配置工程:``reconfigure``

View File

@@ -14,9 +14,9 @@
gdbgui 自 v0.14 起不再支持 Windows 操作系统。由于其他兼容性问题Windows 下需使用 Python 3.10,并配合特定依赖版本。可通过以下命令安装已知可用的 gdbgui 及其所需版本的依赖: gdbgui 自 v0.14 起不再支持 Windows 操作系统。由于其他兼容性问题Windows 下需使用 Python 3.10,并配合特定依赖版本。可通过以下命令安装已知可用的 gdbgui 及其所需版本的依赖:
```bash .. code-block:: bash
pipx install "gdbgui==0.13.2.0" "pygdbmi<=0.9.0.2" "python-socketio<5" "jinja2<3.1" "itsdangerous<2.1" pipx install "gdbgui==0.13.2.0" "pygdbmi<=0.9.0.2" "python-socketio<5" "jinja2<3.1" "itsdangerous<2.1"
```
如果操作系统为 Linux 或 macOS可以使用 Python 3.11 或 3.12 以及 gdbgui v0.15.2.0。 如果操作系统为 Linux 或 macOS可以使用 Python 3.11 或 3.12 以及 gdbgui v0.15.2.0。
@@ -53,3 +53,35 @@ CMake 版本升级
如果无法升级操作系统,可以使用以下命令安装推荐的 CMake 版本:``./tools/idf_tools.py install cmake`` 如果无法升级操作系统,可以使用以下命令安装推荐的 CMake 版本:``./tools/idf_tools.py install cmake``
此变更影响所有使用系统自带 CMake 或自定义 CMake 的 ESP-IDF 用户。 此变更影响所有使用系统自带 CMake 或自定义 CMake 的 ESP-IDF 用户。
移除 ``idf.py size --legacy`` 选项
-------------------------------------
``idf.py size`` 命令的 ``--legacy`` 参数已被移除,因为旧版实现已不再受支持。``ESP_IDF_SIZE_LEGACY`` 环境变量也随之失效。如需继续使用旧版选项,请使用 ESP-IDF 5.5 或更早版本。对于 ESP-IDF v6.0 及更高版本,请直接将 ``idf.py size --legacy`` 替换为 ``idf.py size``。若遇到 esp-idf-size 版本问题,请运行安装脚本更新至正确版本。
更改 ``idf.py size --format json````--format json2``
------------------------------------------------------------
``--format json`` 选项已替换为 ``--format json2````json2`` 格式提供了更完善的结构,为每个内存区域提供明确的 ``total````used````free`` 字段,并在 ``parts`` 部分提供详细分类。迁移时,请将 ``idf.py size --format json`` 替换为 ``idf.py size --format json2``
- **旧格式 (json)**:扁平结构,内存类型字段直接列出,如 ``"dram_data": 9192, "iram_text": 43295``
- **新格式 (json2)**:分层结构,包含一个 ``layout`` 数组来表示内存区域:
.. code-block:: json
{
"version": "1.1",
"layout": [
{
"name": "DRAM",
"total": 180736,
"used": 11344,
"free": 169392,
"parts": {
".data": {"size": 9192},
".bss": {"size": 2152}
}
}
]
}

View File

@@ -16,8 +16,8 @@ if(NOT DEFINED ENV{SIZE_OUTPUT_FORMAT} OR "$ENV{SIZE_OUTPUT_FORMAT}" STREQUAL "d
# Format not passed to "idf.py size" explicitly, or this target was invoked # Format not passed to "idf.py size" explicitly, or this target was invoked
# from make/ninja directly (without idf.py) # from make/ninja directly (without idf.py)
if(DEFINED OUTPUT_JSON AND OUTPUT_JSON) if(DEFINED OUTPUT_JSON AND OUTPUT_JSON)
# honor the legacy OUTPUT_JSON variable, if set # honor the legacy OUTPUT_JSON variable, if set (use json2 format as json is no longer supported)
list(APPEND IDF_SIZE_CMD "--format=json") list(APPEND IDF_SIZE_CMD "--format=json2")
endif() endif()
elseif(DEFINED ENV{SIZE_OUTPUT_FORMAT}) elseif(DEFINED ENV{SIZE_OUTPUT_FORMAT})
# specific format was requested # specific format was requested

View File

@@ -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 fnmatch import fnmatch
import glob import glob
@@ -10,9 +10,6 @@ import shutil
import subprocess import subprocess
import sys import sys
from typing import Any from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from urllib.error import URLError from urllib.error import URLError
from urllib.request import Request from urllib.request import Request
from urllib.request import urlopen from urllib.request import urlopen
@@ -20,25 +17,25 @@ from webbrowser import open_new_tab
import click import click
from click.core import Context from click.core import Context
from idf_py_actions.constants import GENERATORS from idf_py_actions.constants import GENERATORS
from idf_py_actions.constants import PREVIEW_TARGETS from idf_py_actions.constants import PREVIEW_TARGETS
from idf_py_actions.constants import SUPPORTED_TARGETS from idf_py_actions.constants import SUPPORTED_TARGETS
from idf_py_actions.constants import URL_TO_DOC from idf_py_actions.constants import URL_TO_DOC
from idf_py_actions.errors import FatalError from idf_py_actions.errors import FatalError
from idf_py_actions.global_options import global_options 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 ensure_build_directory
from idf_py_actions.tools import generate_hints from idf_py_actions.tools import generate_hints
from idf_py_actions.tools import get_target from idf_py_actions.tools import get_target
from idf_py_actions.tools import idf_version from idf_py_actions.tools import idf_version
from idf_py_actions.tools import merge_action_lists 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 run_target
from idf_py_actions.tools import TargetChoice
from idf_py_actions.tools import yellow_print from idf_py_actions.tools import yellow_print
def action_extensions(base_actions: Dict, project_path: str) -> Any: def action_extensions(base_actions: dict, project_path: str) -> Any:
def build_target(target_name: str, ctx: Context, args: PropertyDict) -> None: def build_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
""" """
Execute the target build system to build target 'target_name' Execute the target build system to build target 'target_name'
@@ -49,8 +46,9 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
ensure_build_directory(args, ctx.info_name) ensure_build_directory(args, ctx.info_name)
run_target(target_name, args, force_progression=GENERATORS[args.generator].get('force_progression', False)) 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, def size_target(
output_file: str, diff_map_file: str, legacy: bool) -> None: target_name: str, ctx: Context, args: PropertyDict, output_format: str, output_file: str, diff_map_file: str
) -> None:
""" """
Builds the app and then executes a size-related target passed in 'target_name'. 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, `tool_error_handler` handler is used to suppress errors during the build,
@@ -61,29 +59,11 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
for hint in generate_hints(stdout, stderr): for hint in generate_hints(stdout, stderr):
yellow_print(hint) yellow_print(hint)
env: Dict[str, Any] = {} env: dict[str, Any] = {}
if not legacy and output_format != 'json': # Enforce NG mode for esp-idf-size v 1.x. After v 2.x is fully incorporated, 'ESP_IDF_SIZE_NG' can be removed.
try:
import esp_idf_size.ng # noqa: F401
except ImportError:
print_warning('WARNING: refactored esp-idf-size not installed, using legacy mode')
legacy = True
else:
# Legacy mode is used only when explicitly requested with --legacy option
# or when "--format json" option is specified. Here we enable the
# esp-idf-size refactored version with ESP_IDF_SIZE_NG env. variable.
env['ESP_IDF_SIZE_NG'] = '1' env['ESP_IDF_SIZE_NG'] = '1'
# ESP_IDF_SIZE_FORCE_TERMINAL is set to force terminal control codes even
# if stdout is not attached to terminal. This is set to pass color codes
# from esp-idf-size to idf.py.
env['ESP_IDF_SIZE_FORCE_TERMINAL'] = '1' env['ESP_IDF_SIZE_FORCE_TERMINAL'] = '1'
if legacy and output_format in ['json2', 'raw', 'tree']:
# These formats are supported in new version only.
# We would get error from the esp-idf-size anyway, so print error early.
raise FatalError(f'Legacy esp-idf-size does not support {output_format} format')
env['SIZE_OUTPUT_FORMAT'] = output_format env['SIZE_OUTPUT_FORMAT'] = output_format
if output_file: if output_file:
env['SIZE_OUTPUT_FILE'] = os.path.abspath(output_file) env['SIZE_OUTPUT_FILE'] = os.path.abspath(output_file)
@@ -93,7 +73,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 # 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 # file directly in it, in case it's a build directory or in one level below
# if it's a project directory. # 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: if not files:
raise FatalError(f'No diff map file found in {diff_map_file} directory') raise FatalError(f'No diff map file found in {diff_map_file} directory')
if len(files) > 1: if len(files) > 1:
@@ -104,8 +86,12 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
env['SIZE_DIFF_FILE'] = diff_map_file env['SIZE_DIFF_FILE'] = diff_map_file
ensure_build_directory(args, ctx.info_name) ensure_build_directory(args, ctx.info_name)
run_target('all', args, force_progression=GENERATORS[args.generator].get('force_progression', False), run_target(
custom_error_handler=tool_error_handler) 'all',
args,
force_progression=GENERATORS[args.generator].get('force_progression', False),
custom_error_handler=tool_error_handler,
)
run_target(target_name, args, env=env) run_target(target_name, args, env=env)
def list_build_system_targets(target_name: str, ctx: Context, args: PropertyDict) -> None: def list_build_system_targets(target_name: str, ctx: Context, args: PropertyDict) -> None:
@@ -121,13 +107,15 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
try: try:
import curses # noqa: F401 import curses # noqa: F401
except ImportError: except ImportError:
raise FatalError('\n'.join( raise FatalError(
['', "menuconfig failed to import the standard Python 'curses' library.", '\n'.join(
'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". "menuconfig failed to import the standard Python 'curses' library.",
# This encoding step is required only in Python 2. 'Please re-run the install script which might be able to fix the issue.',
style = style.encode(sys.getfilesystemencoding() or 'utf-8') ]
)
)
os.environ['MENUCONFIG_STYLE'] = style os.environ['MENUCONFIG_STYLE'] = style
args.no_hints = True args.no_hints = True
build_target(target_name, ctx, args) build_target(target_name, ctx, args)
@@ -151,15 +139,16 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
except Exception: except Exception:
if target_name in ['clang-check', 'clang-html-report']: 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( raise FatalError(
'command "%s" is not known to idf.py and is not a %s target' % (target_name, args.generator)) f'command "{target_name}" requires an additional plugin "pyclang". '
'Please install it via "pip install --upgrade pyclang"'
)
raise FatalError(f'command "{target_name}" is not known to idf.py and is not a {args.generator} target')
run_target(target_name, args) run_target(target_name, args)
def verbose_callback(ctx: Context, param: List, value: str) -> Optional[str]: def verbose_callback(ctx: Context, param: list, value: str) -> str | None:
if not value or ctx.resilient_parsing: if not value or ctx.resilient_parsing:
return None return None
@@ -170,36 +159,39 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
def clean(action: str, ctx: Context, args: PropertyDict) -> None: def clean(action: str, ctx: Context, args: PropertyDict) -> None:
if not os.path.isdir(args.build_dir): if not os.path.isdir(args.build_dir):
print("Build directory '%s' not found. Nothing to clean." % args.build_dir) print(f"Build directory '{args.build_dir}' not found. Nothing to clean.")
return return
build_target('clean', ctx, args) build_target('clean', ctx, args)
def fullclean(action: str, ctx: Context, args: PropertyDict) -> None: def fullclean(action: str, ctx: Context, args: PropertyDict) -> None:
build_dir = args.build_dir build_dir = args.build_dir
if not os.path.isdir(build_dir): if not os.path.isdir(build_dir):
print("Build directory '%s' not found. Nothing to clean." % build_dir) print(f"Build directory '{build_dir}' not found. Nothing to clean.")
return return
if len(os.listdir(build_dir)) == 0: if len(os.listdir(build_dir)) == 0:
print("Build directory '%s' is empty. Nothing to clean." % build_dir) print(f"Build directory '{build_dir}' is empty. Nothing to clean.")
return return
if not os.path.exists(os.path.join(build_dir, 'CMakeCache.txt')): if not os.path.exists(os.path.join(build_dir, 'CMakeCache.txt')):
raise FatalError( raise FatalError(
"Directory '%s' doesn't seem to be a CMake build directory. Refusing to automatically " f"Directory '{build_dir}' 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."
)
red_flags = ['CMakeLists.txt', '.git', '.svn'] red_flags = ['CMakeLists.txt', '.git', '.svn']
for red in red_flags: for red in red_flags:
red = os.path.join(build_dir, red) red = os.path.join(build_dir, red)
if os.path.exists(red): if os.path.exists(red):
raise FatalError( raise FatalError(
"Refusing to automatically delete files in directory containing '%s'. Delete files manually if you're sure." f"Refusing to automatically delete files in directory containing '{red}'. "
% red) "Delete files manually if you're sure."
)
if args.verbose and len(build_dir) > 1: if args.verbose and len(build_dir) > 1:
print('The following symlinks were identified and removed:\n%s' % '\n'.join(build_dir)) symlinks_list = '\n'.join(build_dir)
print(f'The following symlinks were identified and removed:\n{symlinks_list}')
for f in os.listdir(build_dir): # TODO: once we are Python 3 only, this can be os.scandir() for f in os.listdir(build_dir): # TODO: once we are Python 3 only, this can be os.scandir()
f = os.path.join(build_dir, f) f = os.path.join(build_dir, f)
if args.verbose: if args.verbose:
print('Removing: %s' % f) print(f'Removing: {f}')
if os.path.isdir(f): if os.path.isdir(f):
shutil.rmtree(f) shutil.rmtree(f)
else: else:
@@ -211,19 +203,20 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
if d == '__pycache__': if d == '__pycache__':
dir_to_delete = os.path.join(root, d) dir_to_delete = os.path.join(root, d)
if args.verbose: if args.verbose:
print('Removing: %s' % dir_to_delete) print(f'Removing: {dir_to_delete}')
shutil.rmtree(dir_to_delete) shutil.rmtree(dir_to_delete)
for filename in fnmatch.filter(filenames, '*.py[co]'): for filename in fnmatch.filter(filenames, '*.py[co]'):
file_to_delete = os.path.join(root, filename) file_to_delete = os.path.join(root, filename)
if args.verbose: if args.verbose:
print('Removing: %s' % file_to_delete) print(f'Removing: {file_to_delete}')
os.remove(file_to_delete) os.remove(file_to_delete)
def set_target(action: str, ctx: Context, args: PropertyDict, idf_target: str) -> None: 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( raise FatalError(
"%s is still in preview. You have to append '--preview' option after idf.py to use any preview feature." f"{idf_target} is still in preview. You have to append '--preview' option after "
% idf_target) 'idf.py to use any preview feature.'
)
args.define_cache_entry.append('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.') print(f'Set Target to: {idf_target}, new sdkconfig will be created.')
env = {'_IDF_PY_SET_TARGET_ACTION': '1'} env = {'_IDF_PY_SET_TARGET_ACTION': '1'}
@@ -232,12 +225,13 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
def reconfigure(action: str, ctx: Context, args: PropertyDict) -> None: def reconfigure(action: str, ctx: Context, args: PropertyDict) -> None:
ensure_build_directory(args, ctx.info_name, True) ensure_build_directory(args, ctx.info_name, True)
def validate_root_options(ctx: Context, args: PropertyDict, tasks: List) -> None: def validate_root_options(ctx: Context, args: PropertyDict, tasks: list) -> None:
args.project_dir = os.path.realpath(args.project_dir) args.project_dir = os.path.realpath(args.project_dir)
if args.build_dir is not None and args.project_dir == os.path.realpath(args.build_dir): if args.build_dir is not None and args.project_dir == os.path.realpath(args.build_dir):
raise FatalError( raise FatalError(
'Setting the build directory to the project directory is not supported. Suggest dropping ' '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: if args.build_dir is None:
args.build_dir = os.path.join(args.project_dir, 'build') args.build_dir = os.path.join(args.project_dir, 'build')
args.build_dir = os.path.realpath(args.build_dir) args.build_dir = os.path.realpath(args.build_dir)
@@ -251,10 +245,10 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
if not version: if not version:
raise FatalError('ESP-IDF version cannot be determined') raise FatalError('ESP-IDF version cannot be determined')
print('ESP-IDF %s' % version) print(f'ESP-IDF {version}')
sys.exit(0) sys.exit(0)
def list_targets_callback(ctx: Context, param: List, value: int) -> None: def list_targets_callback(ctx: Context, param: list, value: int) -> None:
if not value or ctx.resilient_parsing: if not value or ctx.resilient_parsing:
return return
@@ -267,7 +261,16 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
sys.exit(0) 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': if language == 'cn':
language = 'zh_CN' language = 'zh_CN'
if not version: if not version:
@@ -288,7 +291,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
except URLError: except URLError:
print("We can't check the link's functionality because you don't have an internet connection") print("We can't check the link's functionality because you don't have an internet connection")
if redirect_link: 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 '']) link = '/'.join([URL_TO_DOC, language, version, starting_page or ''])
if not no_browser: if not no_browser:
print('Opening documentation in the default browser:') print('Opening documentation in the default browser:')
@@ -306,7 +309,7 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
language = 'en' language = 'en'
return language return language
def help_and_exit(action: str, ctx: Context, param: List, json_option: bool, add_options: bool) -> None: def help_and_exit(action: str, ctx: Context, param: list, json_option: bool, add_options: bool) -> None:
if json_option: if json_option:
output_dict = {} output_dict = {}
output_dict['target'] = get_target(param.project_dir) # type: ignore output_dict['target'] = get_target(param.project_dir) # type: ignore
@@ -355,8 +358,10 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
}, },
{ {
'names': ['-w/-n', '--cmake-warn-uninitialized/--no-warnings'], 'names': ['-w/-n', '--cmake-warn-uninitialized/--no-warnings'],
'help': ('Enable CMake uninitialized variable warnings for CMake files inside the project directory. ' 'help': (
"(--no-warnings is now the default, and doesn't need to be specified.)"), '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', 'envvar': 'IDF_CMAKE_WARN_UNINITIALIZED',
'is_flag': True, 'is_flag': True,
'default': False, 'default': False,
@@ -399,8 +404,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
'names': ['--no-hints'], 'names': ['--no-hints'],
'help': 'Disable hints on how to resolve errors and logging.', 'help': 'Disable hints on how to resolve errors and logging.',
'is_flag': True, 'is_flag': True,
'default': False 'default': False,
} },
], ],
'global_action_callbacks': [validate_root_options], 'global_action_callbacks': [validate_root_options],
} }
@@ -408,19 +413,25 @@ 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 # '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 # 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. # the legacy OUTPUT_JSON CMake variable will be taken into account.
size_options = [{'names': ['--format', 'output_format'], size_options = [
'type': click.Choice(['default', 'text', 'csv', 'json', 'json2', 'tree', 'raw']), {
'help': 'Specify output format: text (same as "default"), csv, json, json2, tree or raw.', 'names': ['--format', 'output_format'],
'default': 'default'}, 'type': click.Choice(['default', 'text', 'csv', 'json2', 'tree', 'raw']),
{'names': ['--legacy', '-l'], 'help': 'Specify output format: text (same as "default"), csv, json2, tree or raw.',
'is_flag': True, 'default': 'default',
'default': os.environ.get('ESP_IDF_SIZE_LEGACY', '0') == '1', },
'help': 'Use legacy esp-idf-size version'}, {
{'names': ['--diff', 'diff_map_file'], 'names': ['--diff', 'diff_map_file'],
'help': ('Show the differences in comparison with another project. ' 'help': (
'Argument can be map file or project directory.')}, 'Show the differences in comparison with another project. '
{'names': ['--output-file', 'output_file'], 'Argument can be map file or project directory.'
'help': 'Print output to the specified file instead of to the standard output'}] ),
},
{
'names': ['--output-file', 'output_file'],
'help': 'Print output to the specified file instead of to the standard output',
},
]
build_actions = { build_actions = {
'actions': { 'actions': {
@@ -437,7 +448,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
'and generate build files for the main build tool.\n\n' 'and generate build files for the main build tool.\n\n'
'3. Run the main build tool (Ninja or GNU Make). ' '3. Run the main build tool (Ninja or GNU Make). '
'By default, the build tool is automatically detected ' '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, 'options': global_options,
'order_dependencies': [ 'order_dependencies': [
'reconfigure', 'reconfigure',
@@ -449,7 +461,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
'menuconfig': { 'menuconfig': {
'callback': menuconfig, 'callback': menuconfig,
'help': 'Run "menuconfig" project configuration tool.', 'help': 'Run "menuconfig" project configuration tool.',
'options': global_options + [ 'options': global_options
+ [
{ {
'names': ['--style', '--color-scheme', 'style'], 'names': ['--style', '--color-scheme', 'style'],
'help': ( 'help': (
@@ -460,7 +473,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
'- aquatic - a blue theme.\n\n' '- aquatic - a blue theme.\n\n'
'It is possible to customize these themes further' 'It is possible to customize these themes further'
' as it is described in the Color schemes section of the kconfiglib documentation.\n' ' 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', 'envvar': 'MENUCONFIG_STYLE',
'default': 'aquatic', 'default': 'aquatic',
} }
@@ -499,13 +513,13 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
}, },
'efuse-common-table': { 'efuse-common-table': {
'callback': build_target, 'callback': build_target,
'help': 'Generate C-source for IDF\'s eFuse fields.', 'help': "Generate C-source for IDF's eFuse fields.",
'order_dependencies': ['reconfigure'], 'order_dependencies': ['reconfigure'],
'options': global_options, 'options': global_options,
}, },
'efuse-custom-table': { 'efuse-custom-table': {
'callback': build_target, 'callback': build_target,
'help': 'Generate C-source for user\'s eFuse fields.', 'help': "Generate C-source for user's eFuse fields.",
'order_dependencies': ['reconfigure'], 'order_dependencies': ['reconfigure'],
'options': global_options, 'options': global_options,
}, },
@@ -534,41 +548,37 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
'callback': show_docs, 'callback': show_docs,
'help': 'Open web browser with documentation for ESP-IDF', 'help': 'Open web browser with documentation for ESP-IDF',
'options': [ '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'], 'names': ['--language', '-l'],
'default': get_default_language(), 'default': get_default_language(),
'type': click.Choice(['en', 'zh_CN', 'cn']), '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'], 'names': ['--starting-page', '-sp'],
'help': 'Documentation page (get-started, api-reference etc).' 'help': 'Documentation page (get-started, api-reference etc).',
},
{
'names': ['--version', '-v'],
'help': 'Version of ESP-IDF.'
}, },
{'names': ['--version', '-v'], 'help': 'Version of ESP-IDF.'},
{ {
'names': ['--target', '-t'], 'names': ['--target', '-t'],
'type': TargetChoice(SUPPORTED_TARGETS + PREVIEW_TARGETS + ['']), 'type': TargetChoice(SUPPORTED_TARGETS + PREVIEW_TARGETS + ['']),
'help': 'Chip target.' 'help': 'Chip target.',
} },
] ],
}, },
'save-defconfig': { 'save-defconfig': {
'callback': save_defconfig, 'callback': save_defconfig,
'help': 'Generate a sdkconfig.defaults with options different from the default ones', 'help': 'Generate a sdkconfig.defaults with options different from the default ones',
'options': global_options + [{ 'options': global_options
+ [
{
'names': ['--add-menu-labels'], 'names': ['--add-menu-labels'],
'is_flag': True, 'is_flag': True,
'help': 'Add menu labels to minimal config.', 'help': 'Add menu labels to minimal config.',
}]
} }
],
},
} }
} }
@@ -582,8 +592,9 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
"This isn't necessary during normal usage, " "This isn't necessary during normal usage, "
'but can be useful after adding/removing files from the source tree, ' 'but can be useful after adding/removing files from the source tree, '
'or when modifying CMake cache variables. ' 'or when modifying CMake cache variables. '
"For example, \"idf.py -DNAME='VALUE' reconfigure\" " 'For example, "idf.py -DNAME=\'VALUE\' reconfigure" '
'can be used to set variable "NAME" in CMake cache to value "VALUE".'), 'can be used to set variable "NAME" in CMake cache to value "VALUE".'
),
'options': global_options, 'options': global_options,
'order_dependencies': ['menuconfig', 'fullclean'], 'order_dependencies': ['menuconfig', 'fullclean'],
}, },
@@ -594,8 +605,9 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
'Set the chip target to build. This will remove the ' 'Set the chip target to build. This will remove the '
'existing sdkconfig file and corresponding CMakeCache and ' 'existing sdkconfig file and corresponding CMakeCache and '
'create new ones according to the new target.\nFor example, ' 'create new ones according to the new target.\nFor example, '
"\"idf.py set-target esp32\" will select esp32 as the new chip " '"idf.py set-target esp32" will select esp32 as the new chip '
'target.'), 'target.'
),
'arguments': [ 'arguments': [
{ {
'names': ['idf-target'], 'names': ['idf-target'],
@@ -612,7 +624,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
'Delete build output files from the build directory, ' 'Delete build output files from the build directory, '
"forcing a 'full rebuild' the next time " "forcing a 'full rebuild' the next time "
"the project is built. Cleaning doesn't delete " "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'], 'order_dependencies': ['fullclean'],
}, },
'fullclean': { 'fullclean': {
@@ -625,7 +638,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
'CMake will configure it from scratch. ' 'CMake will configure it from scratch. '
'Note that this option recursively deletes all files ' 'Note that this option recursively deletes all files '
'in the build directory, so use with care.' 'in the build directory, so use with care.'
'Project configuration is not deleted.') 'Project configuration is not deleted.'
),
}, },
'python-clean': { 'python-clean': {
'callback': python_clean, 'callback': python_clean,
@@ -633,7 +647,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
'help': ( 'help': (
'Delete generated Python byte code from the IDF directory ' 'Delete generated Python byte code from the IDF directory '
'which may cause issues when switching between IDF and Python versions. ' '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 +663,13 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
{ {
'names': ['--json', 'json_option'], 'names': ['--json', 'json_option'],
'is_flag': True, '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'], 'names': ['--add-options'],
'is_flag': True, 'is_flag': True,
'help': 'Add options about actions to machine-readable format.' 'help': 'Add options about actions to machine-readable format.',
} },
], ],
} }
} }

View File

@@ -1,49 +1,21 @@
#!/usr/bin/env python #!/usr/bin/env python
# #
# SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
import argparse
import os import os
import subprocess import subprocess
import sys import sys
if __name__ == '__main__': if __name__ == '__main__':
# Here the argparse is used only to "peek" into arguments if
# legacy version is requested or if old json format is specified.
# In these two cases the esp_idf_size legacy version is spawned.
parser = argparse.ArgumentParser(add_help=False)
# Make the --format arg optional, so this argparse instance does not
# fail with an error and the proper underlying help is displayed.
# Note that exit_on_error is supported from python3.9, so this is
# a workaround how to make sure that the args parsing doesn't fail here if idf_size.py
# is invoked e.g. without specifying the format, like "idf_size.py --format".
parser.add_argument('--format', nargs='?')
parser.add_argument('-l', '--legacy', action='store_true', default=os.environ.get('ESP_IDF_SIZE_LEGACY', '0') == '1')
# The sys.argv is parsed with "exit_on_error", but the argparse.ArgumentError
# exception should never occur, because unknown args should be put into
# the rest variable, since the parse_known_args() method is used.
args, rest = parser.parse_known_args()
if not args.legacy and args.format != 'json':
# By default start the refactored version, unless legacy version is explicitly requested with
# -l/--legacy option or if old json format is specified.
try: try:
import esp_idf_size.ng # noqa: F401 import esp_idf_size # noqa: F401
except ImportError:
print('warning: refactored esp-idf-size not installed, using legacy mode', file=sys.stderr) # Enforce NG mode for esp-idf-size v 1.x. After v 2.x is fully incorporated, 'ESP_IDF_SIZE_NG' can be removed.
args.legacy = True
else:
os.environ['ESP_IDF_SIZE_NG'] = '1' os.environ['ESP_IDF_SIZE_NG'] = '1'
if not rest or '-h' in rest or '--help' in rest: except ImportError:
print(('Note: legacy esp_idf_size version can be invoked by specifying the -l/--legacy ' print('WARNING: esp-idf-size not installed, please run the install script to install it', file=sys.stderr)
'option or by setting the ESP_IDF_SIZE_LEGACY environment variable. Additionally, the ' raise SystemExit(1)
'legacy version is automatically employed when the JSON format is specified for '
'compatibility with previous versions.'))
if args.format is not None: sys.exit(subprocess.run([sys.executable, '-m', 'esp_idf_size'] + sys.argv[1:]).returncode)
rest = ['--format', args.format] + rest
sys.exit(subprocess.run([sys.executable, '-m', 'esp_idf_size'] + rest).returncode)