Merge branch 'fix/idf_tools_install_tool_version' into 'master'

fix(tools): fixed command `idf_tools.py install tool@version`

Closes IDF-12845

See merge request espressif/esp-idf!39588
This commit is contained in:
Roland Dobai
2025-06-19 15:46:16 +02:00
4 changed files with 62 additions and 16 deletions

View File

@@ -113,7 +113,8 @@ test_cli_installer:
script:
# Tools must be downloaded for testing
# We could use "idf_tools.py download all", but we don't want to install clang because of its huge size
- python3 ${IDF_PATH}/tools/idf_tools.py download required qemu-riscv32 qemu-xtensa cmake
# cmake@version that is supported
- python3 ${IDF_PATH}/tools/idf_tools.py download required qemu-riscv32 qemu-xtensa cmake cmake@3.16.3
- cd ${IDF_PATH}/tools/test_idf_tools
- python3 -m pip install jsonschema
- python3 ./test_idf_tools.py -v

View File

@@ -35,7 +35,8 @@ test_cli_installer_win:
timeout: 3h
script:
# Tools must be downloaded for testing
- python ${IDF_PATH}\tools\idf_tools.py download required qemu-riscv32 qemu-xtensa cmake
# cmake@version that is supported
- python ${IDF_PATH}\tools\idf_tools.py download required qemu-riscv32 qemu-xtensa cmake cmake@3.16.3
- cd ${IDF_PATH}\tools\test_idf_tools
- python -m pip install jsonschema
- python .\test_idf_tools.py

View File

@@ -1089,17 +1089,37 @@ class IDFTool(object):
def get_preferred_installed_version(self) -> Optional[str]:
"""
Get the preferred installed version of the tool. If more versions installed, return the highest.
Get the preferred installed version of the tool.
If more versions installed, return recommended version if exists, otherwise return the highest supported version
"""
recommended_versions = [
try:
self.find_installed_versions()
except ToolBinaryError:
pass
if self.get_recommended_version() in self.versions_installed:
return self.get_recommended_version()
supported_installed_versions = [
k
for k in self.versions_installed
if self.versions[k].status == IDFToolVersion.STATUS_RECOMMENDED
if self.versions[k].status == IDFToolVersion.STATUS_SUPPORTED
and self.versions[k].compatible_with_platform(self._platform)
]
assert len(recommended_versions) <= 1
if recommended_versions:
return recommended_versions[0]
sorted_supported_installed_versions = sorted(
supported_installed_versions, key=lambda x: self.versions[x], reverse=True
)
if sorted_supported_installed_versions:
warn(
''.join(
[
f'Using supported version {sorted_supported_installed_versions[0]} for tool {self.name} ',
f'as recommended version {self.get_recommended_version()} is not installed.',
]
)
)
return sorted_supported_installed_versions[0]
return None
def find_installed_versions(self) -> None:
@@ -1152,8 +1172,7 @@ class IDFTool(object):
else:
if ver_str != version:
warn(f'tool {self.name} version {version} is installed, but has reported version {ver_str}')
else:
self.versions_installed.append(version)
self.versions_installed.append(version)
if tool_error:
raise ToolBinaryError
@@ -1900,6 +1919,9 @@ def expand_tools_arg(tools_spec: List[str], overall_tools: OrderedDict, targets:
# Filtering by ESP_targets
tools = [k for k in tools if overall_tools[k].is_supported_for_any_of_targets(targets)]
# Processing specific version of tool - defined with '@'
tools.extend([tool_pattern for tool_pattern in tools_spec if '@' in tool_pattern])
return tools
@@ -2250,10 +2272,6 @@ def process_tool(
tool_export_paths: List[str] = []
tool_export_vars: Dict[str, str] = {}
try:
tool.find_installed_versions()
except ToolBinaryError:
pass
recommended_version_to_use = tool.get_preferred_installed_version()
if not tool.is_executable and recommended_version_to_use:
@@ -3132,7 +3150,7 @@ def action_uninstall(args: Any) -> None:
os.listdir(os.path.join(tools_path, tool)) if os.path.isdir(os.path.join(tools_path, tool)) else []
)
try:
unused_versions = [x for x in tool_versions if x != tools_info[tool].get_recommended_version()]
unused_versions = [x for x in tool_versions if x != tools_info[tool].get_preferred_installed_version()]
except (
KeyError
): # When tool that is not supported by tools_info (tools.json) anymore, remove the whole tool file
@@ -3188,7 +3206,7 @@ def action_uninstall(args: Any) -> None:
tool_name, tool_version = tool_spec.split('@', 1)
tool_obj = tools_info_for_platform[tool_name]
if tool_version is None:
tool_version = tool_obj.get_recommended_version()
tool_version = tool_obj.get_preferred_installed_version()
# mypy-checks
if tool_version is not None:
archive_version = tool_obj.versions[tool_version].get_download_for_platform(CURRENT_PLATFORM)

View File

@@ -313,6 +313,32 @@ class TestUsage(TestUsageBase):
self.assertIn(os.path.join(tool_to_test, tool_version), output)
def test_export_supported_version_cmake(self):
tool_to_test = 'cmake'
supported_version = ''
recommended_version = ''
for tool in self.tools_dict['tools']:
if tool['name'] != tool_to_test:
continue
for version in tool['versions']:
if version['status'] == 'supported':
supported_version = version['name']
elif version['status'] == 'recommended':
recommended_version = version['name']
self.run_idf_tools_with_action(['install'])
output = self.run_idf_tools_with_action(['install', f'{tool_to_test}@{supported_version}'])
self.assert_tool_installed(output, tool_to_test, supported_version)
# Remove the recommended version folder installed by install command (in case of Windows)
recommended_version_folder = os.path.join(self.temp_tools_dir, 'tools', tool_to_test, recommended_version)
if os.path.exists(recommended_version_folder):
shutil.rmtree(recommended_version_folder)
output = self.run_idf_tools_with_action(['export'])
self.assertIn(os.path.join(tool_to_test, supported_version), output)
self.assertNotIn(os.path.join(tool_to_test, recommended_version), output)
def test_export_prefer_system_cmake(self):
tool_to_test = 'cmake'
self.run_idf_tools_with_action(['install'])