diff --git a/docs/en/api-guides/tools/idf-tools.rst b/docs/en/api-guides/tools/idf-tools.rst index 291314ce34..c058304b2e 100644 --- a/docs/en/api-guides/tools/idf-tools.rst +++ b/docs/en/api-guides/tools/idf-tools.rst @@ -107,6 +107,10 @@ Any mirror server can be used provided the URL matches the ``github.com`` downlo * ``list``: Lists the known versions of the tools, and indicates which ones are installed. + Following options are available to customize the output. + + - ``--outdated``: List only outdated versions of tools installed in ``IDF_TOOLS_PATH``. + * ``check``: For each tool, checks whether the tool is available in the system path and in ``IDF_TOOLS_PATH``. * ``install-python-env``: Create a Python virtual environment in the ``${IDF_TOOLS_PATH}/python_env`` directory and install there the required Python packages. An optional ``--features`` argument allows one to specify a comma-separated list of features to be added or removed. Feature that begins with ``-`` will be removed and features with ``+`` or without any sign will be added. Example syntax for removing feature ``XY`` is ``--features=-XY`` and for adding ``--features=+XY`` or ``--features=XY``. If both removing and adding options are provided with the same feature, no operation is performed. For each feature a requirements file must exist. For example, feature ``XY`` is a valid feature if ``${IDF_PATH}/tools/requirements/requirements.XY.txt`` is an existing file with a list of Python packages to be installed. There is one mandatory ``core`` feature ensuring core functionality of ESP-IDF (build, flash, monitor, debug in console). There can be an arbitrary number of optional features. The selected list of features is stored in ``idf-env.json``. The requirement files contain a list of the desired Python packages to be installed and ``espidf.constraints.*.txt`` downloaded from https://dl.espressif.com and stored in ``${IDF_TOOLS_PATH}`` the package version requirements for a given ESP-IDF version. Althought it is not recommended, the download and use of constraint files can be disabled with the ``--no-constraints`` argument or setting the ``IDF_PYTHON_CHECK_CONSTRAINTS`` environment variable to ``no``. diff --git a/tools/idf_tools.py b/tools/idf_tools.py index 28b9f34a5e..a68eb4fa02 100755 --- a/tools/idf_tools.py +++ b/tools/idf_tools.py @@ -759,6 +759,31 @@ class IDFTool(object): else: self.versions_installed.append(version) + def latest_installed_version(self): # type: () -> Optional[str] + """ + Get the latest installed tool version by directly checking the + tool's version directories. + """ + tool_path = self.get_path() + if not os.path.exists(tool_path): + return None + dentries = os.listdir(tool_path) + dirs = [d for d in dentries if os.path.isdir(os.path.join(tool_path, d))] + for version in sorted(dirs, reverse=True): + # get_path_for_version() has assert to check if version is in versions + # dict, so get_export_paths() cannot be used. Let's just create the + # export paths list directly here. + paths = [os.path.join(tool_path, version, *p) for p in self._current_options.export_paths] + try: + ver_str = self.get_version(paths) + except (ToolNotFound, ToolExecError): + continue + if ver_str != version: + continue + return version + + return None + def download(self, version): # type: (str) -> None assert version in self.versions download_obj = self.versions[version].get_download_for_platform(self._platform) @@ -1495,7 +1520,7 @@ def active_repo_id() -> str: return global_idf_path + '-v' + get_idf_version() -def action_list(args): # type: ignore +def list_default(args): # type: ignore tools_info = load_tools_info() for name, tool in tools_info.items(): if tool.get_install_type() == IDFTool.INSTALL_NEVER: @@ -1514,6 +1539,29 @@ def action_list(args): # type: ignore ', installed' if version in tool.versions_installed else '')) +def list_outdated(args): # type: ignore + tools_info = load_tools_info() + for name, tool in tools_info.items(): + if tool.get_install_type() == IDFTool.INSTALL_NEVER: + continue + versions_for_platform = {k: v for k, v in tool.versions.items() if v.compatible_with_platform()} + if not versions_for_platform: + continue + version_installed = tool.latest_installed_version() + if not version_installed: + continue + version_available = sorted(versions_for_platform.keys(), key=tool.versions.get, reverse=True)[0] + if version_installed < version_available: + info(f'{name}: version {version_installed} is outdated by {version_available}') + + +def action_list(args): # type: ignore + if args.outdated: + list_outdated(args) + else: + list_default(args) + + def action_check(args): # type: ignore tools_info = load_tools_info() tools_info = filter_tools_info(IDFEnv.get_idf_env(), tools_info) @@ -2447,7 +2495,8 @@ def main(argv): # type: (list[str]) -> None parser.add_argument('--idf-path', help='ESP-IDF path to use') subparsers = parser.add_subparsers(dest='action') - subparsers.add_parser('list', help='List tools and versions available') + list_parser = subparsers.add_parser('list', help='List tools and versions available') + list_parser.add_argument('--outdated', help='Print only outdated installed tools', action='store_true') subparsers.add_parser('check', help='Print summary of tools installed or found in PATH') export = subparsers.add_parser('export', help='Output command for setting tool paths, suitable for shell') export.add_argument('--format', choices=[EXPORT_SHELL, EXPORT_KEY_VALUE], default=EXPORT_SHELL, diff --git a/tools/test_idf_tools/test_idf_tools.py b/tools/test_idf_tools/test_idf_tools.py index 77260938cd..24219f4a10 100755 --- a/tools/test_idf_tools/test_idf_tools.py +++ b/tools/test_idf_tools/test_idf_tools.py @@ -190,6 +190,37 @@ class TestUsage(unittest.TestCase): self.assertIn('%s/tools/esp-rom-elfs/%s/' % (self.temp_tools_dir, ESP_ROM_ELFS_VERSION), output) + output = self.run_idf_tools_with_action(['list', '--outdated']) + self.assertEqual('', output) + + tools_json_outdated = os.path.join(self.temp_tools_dir, 'tools', 'tools.outdated.json') + new_version = 'zzzzzz' + self.run_idf_tools_with_action( + [ + 'add-version', + '--tool', + XTENSA_ESP32_ELF, + '--url-prefix', + 'http://test.com', + '--version', + new_version, + '--override', + '--checksum-file', + 'add_version/checksum.sha256', + '--output', + tools_json_outdated + ]) + + output = self.run_idf_tools_with_action( + [ + '--tools-json', + tools_json_outdated, + 'list', + '--outdated' + ]) + self.assertIn((f'{XTENSA_ESP32_ELF}: version {XTENSA_ESP32_ELF_VERSION} ' + f'is outdated by {new_version}'), output) + def test_tools_for_esp32(self): required_tools_installed = 5 output = self.run_idf_tools_with_action(['install', '--targets=esp32'])