diff --git a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/util/ToolsUtility.java b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/util/ToolsUtility.java index 065c71759..de12aafc5 100644 --- a/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/util/ToolsUtility.java +++ b/bundles/com.espressif.idf.core/src/com/espressif/idf/core/tools/util/ToolsUtility.java @@ -6,9 +6,15 @@ import java.io.BufferedReader; import java.io.File; +import java.io.IOException; import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.eclipse.core.runtime.Platform; @@ -25,12 +31,96 @@ */ public class ToolsUtility { + private static final Pattern IDF_VERSION_MAJOR_PATTERN = Pattern.compile("IDF_VERSION_MAJOR\\s+(\\d+)"); //$NON-NLS-1$ + private static final Pattern IDF_VERSION_MINOR_PATTERN = Pattern.compile("IDF_VERSION_MINOR\\s+(\\d+)"); //$NON-NLS-1$ + private static final Pattern IDF_VERSION_PATCH_PATTERN = Pattern.compile("IDF_VERSION_PATCH\\s+(\\d+)"); //$NON-NLS-1$ + + /** + * Detects the ESP-IDF version for the given installation. + *

+ * Prefers the full {@code MAJOR.MINOR.PATCH} read directly from {@code tools/cmake/version.cmake} + * under the installation's {@code IDF_PATH}. This is intentionally not derived from the + * {@code ESP_IDF_VERSION} environment variable: ESP-IDF and EIM define that variable as + * {@code MAJOR.MINOR} only (components consume it in Kconfig, e.g. {@code Kconfig.idf_v5.5.in}), so + * it never carries the patch component. When {@code version.cmake} cannot be read, falls back to the + * {@code ESP_IDF_VERSION} value printed by the activation script. + */ public static String getIdfVersion(EimInstallationModel installation) { + String versionFromCMake = readIdfVersionFromCMake(installation.getPath()); + if (!StringUtil.isEmpty(versionFromCMake)) + { + return versionFromCMake; + } + return installation.getActivationScript().map(ToolsUtility::readIdfVersionFromScript) .orElse(StringUtil.EMPTY); } + private static String readIdfVersionFromCMake(String idfPath) + { + if (StringUtil.isEmpty(idfPath)) + { + return StringUtil.EMPTY; + } + + try + { + Path versionCMakeFile = Path.of(idfPath, "tools", "cmake", "version.cmake"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + return parseVersionCMake(versionCMakeFile); + + } + catch (InvalidPathException e) + { + Logger.log(e); + return StringUtil.EMPTY; + } + + } + + /** + * Parses {@code version.cmake} and returns {@code MAJOR.MINOR.PATCH} (or {@code MAJOR.MINOR} when + * the patch entry is absent). Returns an empty string when the file is missing or the required + * major/minor entries cannot be found. + */ + public static String parseVersionCMake(Path versionCMakeFile) + { + if (versionCMakeFile == null || !Files.isRegularFile(versionCMakeFile)) + { + return StringUtil.EMPTY; + } + + try + { + String content = Files.readString(versionCMakeFile); + String major = firstGroup(IDF_VERSION_MAJOR_PATTERN, content); + String minor = firstGroup(IDF_VERSION_MINOR_PATTERN, content); + if (major == null || minor == null) + { + return StringUtil.EMPTY; + } + + String patch = firstGroup(IDF_VERSION_PATCH_PATTERN, content); + if (patch == null) + { + return major + "." + minor; //$NON-NLS-1$ + } + + return major + "." + minor + "." + patch; //$NON-NLS-1$ //$NON-NLS-2$ + } + catch (IOException e) + { + Logger.log(e); + return StringUtil.EMPTY; + } + } + + private static String firstGroup(Pattern pattern, String content) + { + Matcher matcher = pattern.matcher(content); + return matcher.find() ? matcher.group(1) : null; + } + private static String readIdfVersionFromScript(String activationScript) { String espIdfVersion = StringUtil.EMPTY; diff --git a/tests/com.espressif.idf.core.test/src/com/espressif/idf/core/tools/test/ToolsUtilityTest.java b/tests/com.espressif.idf.core.test/src/com/espressif/idf/core/tools/test/ToolsUtilityTest.java new file mode 100644 index 000000000..b9ab6706e --- /dev/null +++ b/tests/com.espressif.idf.core.test/src/com/espressif/idf/core/tools/test/ToolsUtilityTest.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright 2026 Espressif Systems (Shanghai) PTE LTD. All rights reserved. + * Use is subject to license terms. + *******************************************************************************/ +package com.espressif.idf.core.tools.test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import com.espressif.idf.core.tools.util.ToolsUtility; + +/** + * Unit tests for {@link ToolsUtility#parseVersionCMake(Path)}. + */ +public class ToolsUtilityTest +{ + @Test + void full_version_includes_patch(@TempDir Path tempDir) throws IOException + { + Path file = writeVersionCMake(tempDir, """ + set(IDF_VERSION_MAJOR 6) + set(IDF_VERSION_MINOR 0) + set(IDF_VERSION_PATCH 1) + """); + Assertions.assertEquals("6.0.1", ToolsUtility.parseVersionCMake(file)); + } + + @Test + void missing_patch_falls_back_to_major_minor(@TempDir Path tempDir) throws IOException + { + Path file = writeVersionCMake(tempDir, """ + set(IDF_VERSION_MAJOR 5) + set(IDF_VERSION_MINOR 3) + """); + Assertions.assertEquals("5.3", ToolsUtility.parseVersionCMake(file)); + } + + @Test + void missing_minor_returns_empty(@TempDir Path tempDir) throws IOException + { + Path file = writeVersionCMake(tempDir, "set(IDF_VERSION_MAJOR 6)\n"); + Assertions.assertEquals("", ToolsUtility.parseVersionCMake(file)); + } + + @Test + void nonexistent_file_returns_empty(@TempDir Path tempDir) + { + Assertions.assertEquals("", ToolsUtility.parseVersionCMake(tempDir.resolve("version.cmake"))); + } + + @Test + void null_file_returns_empty() + { + Assertions.assertEquals("", ToolsUtility.parseVersionCMake(null)); + } + + private static Path writeVersionCMake(Path dir, String content) throws IOException + { + Path file = dir.resolve("version.cmake"); + Files.writeString(file, content); + return file; + } +}