DYA9Q)yO3h8v9^O6MUVYP@z}BhcmPG0t9NA3`pxRyf|9GiI}8!f*bIhWVIuI^
zWyBbb2vKS$Kt*zCQ$L))iOX2hmOS$cY!?`_kYc=t&X3SEl761I6ngo)s@JrFSYqm$
zfp?O~mGm0Qe#?+2e1vYX<`#|b3D_4AO+KQwwAP{itBwxP6UfW4XHsV%pDa+&lz*%5
zvG(J!Wy#f_dKtEiX^ln6(9s=x;I#P`=#4Ju$=6}sHONQ*EFn?v*1@07kNi0LnTQLz
z1~(olm^{%7Pa&H57Wf{`^-r6?PT-vn)&^M?Sz@0h(v4k|F4;U}21ck3?NTmson92`
z8QuwZETcf)4woQOX#MCLO;X}yc}OTdYP0{yaks>ISFBl+t`Eny019j}VaqyMr>Nfk
zalzNzI3#Ynzx06@Yh{(vXu@M|*&?5x$Q$5VPn|lKrheNW!Xal|mqCfHSIMDe5L;faSSDyy1gANNfJeTUi8-($!n<(nN6<$)+4T1XoK)rDVEE+I
z!g0zJkQH2mU#u&DMqkartb26hS`hr#Scw1_C)eO9!>tG*%kB
zqrX1m7mHF}&u~BOEauy!ME`xaA9q_}W$U`t4J8y>DIO)26~cBl+qu9989%OOvb}r!J}0q~d6XCc|uCv_GK8
zWxv~{E{LY#?uWvkv{k!pJA_d2#|qpe#yLmm3-NbdPp
zt*}1rp%SzpaZf^~`~-+<(M|%58*Bk&XMo2@t@IuDRXt`*DF@cwE<$gu+6|$oTN)mH
z*~Z*w&^o0DF_a}3cU(}&$B;@ZOx|$~uBtz4NqaU2VPcATz;w{g|6}U?-(z%>!8;kJ
VlKx`n^!M4WbyHWPNd11`e*m!Y=zjnJ
diff --git a/media/walkthrough/python-selection.png b/media/walkthrough/python-selection.png
deleted file mode 100644
index 4874457222ff6119532f964dd8aae5909e1e0204..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 14441
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
ZQr-m&@@N8Wf8T {
return {
- [AdvancedCommandKeys.Setup]: {
+ [AdvancedCommandKeys.InstallManager]: {
checkboxState: undefined,
- iconId: "extensions",
- tooltip: l10n.t("Configure ESP-IDF Extension"),
+ iconId: "link-external",
+ tooltip: l10n.t("Open ESP-IDF Installation Manager"),
},
[AdvancedCommandKeys.NewProject]: {
checkboxState: undefined,
@@ -130,11 +129,6 @@ export function createAdvancedCommandDictionary(): Record<
iconId: "project",
tooltip: l10n.t("Project Configuration editor"),
},
- [AdvancedCommandKeys.InstallIdfPythonReqs]: {
- checkboxState: undefined,
- iconId: "extensions",
- tooltip: l10n.t("Install Extension Python Requirements"),
- },
[AdvancedCommandKeys.InstallMatterPythonReqs]: {
checkboxState: undefined,
iconId: "extensions",
diff --git a/src/common/abstractCloning.ts b/src/common/abstractCloning.ts
index 38a1c1b7d..40896058b 100644
--- a/src/common/abstractCloning.ts
+++ b/src/common/abstractCloning.ts
@@ -91,7 +91,14 @@ export class AbstractCloning {
workspace?: Uri,
recursiveDownload?: boolean
) {
- const toolsDir = await idfConf.readParameter("idf.toolsPath", workspace);
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const customExtraVars = idfConf.readParameter(
+ "idf.customExtraVars",
+ workspace
+ ) as { [key: string]: string };
+ const toolsDir = currentEnvVars["IDF_TOOLS_PATH"];
const installDir = await window.showQuickPick(
[
{
@@ -150,7 +157,12 @@ export class AbstractCloning {
if (installDir.target === "existing") {
const target = idfConf.readParameter("idf.saveScope");
- await idfConf.writeParameter(configurationId, installDirPath, target);
+ customExtraVars[configurationId] = installDirPath;
+ await idfConf.writeParameter(
+ "idf.customExtraVars",
+ customExtraVars,
+ target
+ );
Logger.infoNotify(`${this.name} has been installed`);
return;
}
@@ -204,11 +216,20 @@ export class AbstractCloning {
await this.updateSubmodules(resultingPath, undefined, progress);
}
const target = idfConf.readParameter("idf.saveScope");
- await idfConf.writeParameter(configurationId, resultingPath, target);
+ customExtraVars[configurationId] = installDirPath;
+ await idfConf.writeParameter(
+ "idf.customExtraVars",
+ customExtraVars,
+ target
+ );
Logger.infoNotify(`${this.name} has been installed`);
} catch (error) {
OutputChannel.appendLine(error.message);
- Logger.errorNotify(error.message, error, "AbstractCloning getRepository");
+ Logger.errorNotify(
+ error.message,
+ error,
+ "AbstractCloning getRepository"
+ );
}
}
);
@@ -377,7 +398,11 @@ export class AbstractCloning {
if (!signal && code !== 0) {
const msg = `Submodules clone has exit with ${code}`;
OutputChannel.appendLine(msg);
- Logger.errorNotify("Submodules cloning error", new Error(msg), "AbstractCloning spawnWithProgress");
+ Logger.errorNotify(
+ "Submodules cloning error",
+ new Error(msg),
+ "AbstractCloning spawnWithProgress"
+ );
return reject(new Error(msg));
}
return resolve();
@@ -414,7 +439,11 @@ export class AbstractCloning {
Logger.infoNotify(`${repoName} submodules checked out successfully`);
} catch (error) {
OutputChannel.appendLine(error.message);
- Logger.errorNotify(error.message, error, "AbstractCloning getSubmodules");
+ Logger.errorNotify(
+ error.message,
+ error,
+ "AbstractCloning getSubmodules"
+ );
}
}
);
diff --git a/src/common/prepareEnv.ts b/src/common/prepareEnv.ts
new file mode 100644
index 000000000..2697963e8
--- /dev/null
+++ b/src/common/prepareEnv.ts
@@ -0,0 +1,254 @@
+/*
+ * Project: ESP-IDF VSCode Extension
+ * File Created: Tuesday, 25th February 2025 2:10:52 pm
+ * Copyright 2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Uri } from "vscode";
+import { readParameter } from "../idfConfiguration";
+import { Logger } from "../logger/logger";
+import { delimiter, dirname, join } from "path";
+import { getIdfTargetFromSdkconfig } from "../workspaceConfig";
+import { ESP } from "../config";
+import { isBinInPath } from "../utils";
+import { pathExists } from "fs-extra";
+
+/**
+ * Configures and prepares environment variables necessary for executing ESP-IDF tasks.
+ *
+ * Merges environment variables from the current process (`process.env`),
+ * stored project configuration, and relevant VS Code settings (e.g., `idf.customExtraVars`,
+ * `idf.gitPath`, `idf.sdkconfigFilePath`, `idf.enableIdfComponentManager`).
+ *
+ * Key actions include:
+ * - Setting default `IDF_PATH` and `IDF_TOOLS_PATH` if not already defined.
+ * - Prepending required toolchain, Python virtual environment, IDF Tools, Git
+ * and component directories to the system `PATH`.
+ * - Determining and setting `IDF_TARGET` based on the workspace's sdkconfig.
+ * - Setting the `IDF_COMPONENT_MANAGER` flag and `SDKCONFIG` path based on settings.
+ *
+ * @async
+ * @param {Uri} curWorkspace - The Uri of the current workspace, used to access settings and sdkconfig.
+ * @returns {Promise<{[key: string]: string}>} A promise resolving to the configured
+ * environment variables object, ready for use in ESP-IDF tasks.
+ */
+export async function configureEnvVariables(
+ curWorkspace: Uri
+): Promise<{ [key: string]: string }> {
+ const modifiedEnv: { [key: string]: string } = <{ [key: string]: string }>(
+ Object.assign({}, process.env)
+ );
+
+ let pathNameInEnv: string = Object.keys(process.env).find(
+ (k) => k.toUpperCase() == "PATH"
+ );
+
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+
+ if (currentEnvVars) {
+ try {
+ for (const envVar in currentEnvVars) {
+ if (envVar && envVar.toUpperCase() !== "PATH") {
+ modifiedEnv[envVar] = currentEnvVars[envVar];
+ } else if (envVar.toUpperCase() === "PATH") {
+ modifiedEnv[
+ pathNameInEnv
+ ] = `${currentEnvVars[envVar]}${delimiter}${modifiedEnv[pathNameInEnv]}`;
+ }
+ }
+ } catch (error) {
+ Logger.errorNotify(
+ "Invalid project configuration environment variables format",
+ error,
+ "configureEnvVariables ProjectConfiguration.CURRENT_IDF_CONFIGURATION"
+ );
+ }
+ }
+
+ const customExtraVars = readParameter(
+ "idf.customExtraVars",
+ curWorkspace
+ ) as { [key: string]: string };
+ if (customExtraVars) {
+ try {
+ for (const envVar in customExtraVars) {
+ if (envVar && envVar.toUpperCase() !== "PATH") {
+ modifiedEnv[envVar] = customExtraVars[envVar];
+ }
+ }
+ } catch (error) {
+ Logger.errorNotify(
+ "Invalid user environment variables format",
+ error,
+ "configureEnvVariables idf.customExtraVars"
+ );
+ }
+ }
+
+ try {
+ const openOcdPath = await isBinInPath("openocd", modifiedEnv, [
+ "openocd-esp32",
+ ]);
+ if (openOcdPath) {
+ const openOcdDir = dirname(openOcdPath);
+ const openOcdScriptsPath = join(
+ openOcdDir,
+ "..",
+ "share",
+ "openocd",
+ "scripts"
+ );
+ const scriptsExists = await pathExists(openOcdScriptsPath);
+ if (scriptsExists && modifiedEnv.OPENOCD_SCRIPTS !== openOcdScriptsPath) {
+ modifiedEnv.OPENOCD_SCRIPTS = openOcdScriptsPath;
+ }
+ }
+ } catch (error) {
+ Logger.error(
+ `Error processing OPENOCD_SCRIPTS path: ${error.message}`,
+ error,
+ "configureEnvVariables OPENOCD_SCRIPTS"
+ );
+ }
+
+ const containerPath =
+ process.platform === "win32" ? modifiedEnv.USERPROFILE : modifiedEnv.HOME;
+ const defaultEspIdfPath = join(containerPath, "esp", "esp-idf");
+
+ modifiedEnv.IDF_PATH = modifiedEnv.IDF_PATH || defaultEspIdfPath;
+
+ const defaultToolsPath = join(containerPath, ".espressif");
+ modifiedEnv.IDF_TOOLS_PATH = modifiedEnv.IDF_TOOLS_PATH || defaultToolsPath;
+
+ let pathToPigweed: string;
+
+ if (modifiedEnv.ESP_MATTER_PATH) {
+ pathToPigweed = join(
+ modifiedEnv.ESP_MATTER_PATH,
+ "connectedhomeip",
+ "connectedhomeip",
+ ".environment",
+ "cipd",
+ "packages",
+ "pigweed"
+ );
+ modifiedEnv.ZAP_INSTALL_PATH = join(
+ modifiedEnv.ESP_MATTER_PATH,
+ "connectedhomeip",
+ "connectedhomeip",
+ ".environment",
+ "cipd",
+ "packages",
+ "zap"
+ );
+ }
+
+ const gitPath = readParameter("idf.gitPath", curWorkspace) as string;
+ let pathToGitDir;
+ if (gitPath && gitPath !== "git") {
+ pathToGitDir = dirname(gitPath);
+ }
+
+ if (
+ pathToGitDir &&
+ !modifiedEnv[pathNameInEnv].split(delimiter).includes(pathToGitDir)
+ ) {
+ modifiedEnv[pathNameInEnv] += delimiter + pathToGitDir;
+ }
+ if (
+ pathToPigweed &&
+ !modifiedEnv[pathNameInEnv].split(delimiter).includes(pathToPigweed)
+ ) {
+ modifiedEnv[pathNameInEnv] += delimiter + pathToPigweed;
+ }
+ if (currentEnvVars["IDF_PYTHON_ENV_PATH"]) {
+ const pyDir = process.platform === "win32" ? "Scripts" : "bin";
+ const venvPyContainer = join(currentEnvVars["IDF_PYTHON_ENV_PATH"], pyDir);
+ if (
+ modifiedEnv[pathNameInEnv] &&
+ !modifiedEnv[pathNameInEnv].includes(venvPyContainer)
+ ) {
+ modifiedEnv[pathNameInEnv] =
+ venvPyContainer + delimiter + modifiedEnv[pathNameInEnv];
+ }
+ }
+ if (
+ modifiedEnv[pathNameInEnv] &&
+ !modifiedEnv[pathNameInEnv].includes(join(modifiedEnv.IDF_PATH, "tools"))
+ ) {
+ modifiedEnv[pathNameInEnv] =
+ join(modifiedEnv.IDF_PATH, "tools") +
+ delimiter +
+ modifiedEnv[pathNameInEnv];
+ }
+
+ if (
+ currentEnvVars[pathNameInEnv] &&
+ currentEnvVars[pathNameInEnv].length > 0
+ ) {
+ const extraPathsArray = currentEnvVars[pathNameInEnv].split(delimiter);
+ for (let extraPath of extraPathsArray) {
+ if (
+ modifiedEnv[pathNameInEnv] &&
+ !modifiedEnv[pathNameInEnv].includes(extraPath)
+ ) {
+ modifiedEnv[pathNameInEnv] =
+ extraPath + delimiter + modifiedEnv[pathNameInEnv];
+ }
+ }
+ }
+
+ let IDF_ADD_PATHS_EXTRAS = join(
+ modifiedEnv.IDF_PATH,
+ "components",
+ "espcoredump"
+ );
+ IDF_ADD_PATHS_EXTRAS = `${IDF_ADD_PATHS_EXTRAS}${delimiter}${join(
+ modifiedEnv.IDF_PATH,
+ "components",
+ "partition_table"
+ )}`;
+
+ modifiedEnv[
+ pathNameInEnv
+ ] = `${IDF_ADD_PATHS_EXTRAS}${delimiter}${modifiedEnv[pathNameInEnv]}`;
+
+ let idfTarget = await getIdfTargetFromSdkconfig(curWorkspace);
+ if (idfTarget) {
+ modifiedEnv.IDF_TARGET =
+ modifiedEnv.IDF_TARGET || idfTarget || process.env.IDF_TARGET;
+ }
+
+ let enableComponentManager = readParameter(
+ "idf.enableIdfComponentManager",
+ curWorkspace
+ ) as boolean;
+
+ if (enableComponentManager) {
+ modifiedEnv.IDF_COMPONENT_MANAGER = "1";
+ }
+
+ let sdkconfigFilePath = readParameter(
+ "idf.sdkconfigFilePath",
+ curWorkspace
+ ) as string;
+ if (sdkconfigFilePath) {
+ modifiedEnv.SDKCONFIG = sdkconfigFilePath;
+ }
+
+ return modifiedEnv;
+}
diff --git a/src/common/store.ts b/src/common/store.ts
index 7ab37a522..66bcd5b2d 100644
--- a/src/common/store.ts
+++ b/src/common/store.ts
@@ -17,7 +17,6 @@
*/
import { ExtensionContext } from "vscode";
-import { ESP } from "../config";
export class ExtensionConfigStore {
private static self: ExtensionConfigStore;
@@ -41,26 +40,4 @@ export class ExtensionConfigStore {
public clear(key: string) {
return this.set(key, undefined);
}
-
- public clearIdfSetup(key: string) {
- this.clear(key);
- let currSetups = this.getIdfSetupKeys();
- const idfSetupIndex = currSetups.findIndex((s) => s === key);
- if (idfSetupIndex === -1) {
- return;
- }
- currSetups.splice(idfSetupIndex, 1);
- this.updateIdfSetupKeys(currSetups);
- }
-
- public getIdfSetupKeys() {
- return this.ctx.globalState.get(
- ESP.GlobalConfiguration.IDF_SETUPS,
- []
- );
- }
-
- public updateIdfSetupKeys(setupKeys: string[]) {
- this.ctx.globalState.update(ESP.GlobalConfiguration.IDF_SETUPS, setupKeys);
- }
}
diff --git a/src/component-manager/utils.ts b/src/component-manager/utils.ts
index b40a2e088..6499f5060 100644
--- a/src/component-manager/utils.ts
+++ b/src/component-manager/utils.ts
@@ -16,12 +16,14 @@
import { existsSync } from "fs";
import { Logger } from "../logger/logger";
-import { spawn, appendIdfAndToolsToPath } from "../utils";
+import { spawn } from "../utils";
import { CancellationToken, Uri, l10n } from "vscode";
import { readParameter } from "../idfConfiguration";
import { join } from "path";
import { getVirtualEnvPythonPath } from "../pythonManager";
import { EOL } from "os";
+import { configureEnvVariables } from "../common/prepareEnv";
+import { ESP } from "../config";
export async function addDependency(
workspace: Uri,
@@ -30,10 +32,13 @@ export async function addDependency(
cancelToken: CancellationToken
) {
try {
- const idfPathDir = readParameter("idf.espIdfPath", workspace);
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const idfPathDir = currentEnvVars["IDF_PATH"];
const idfPy = join(idfPathDir, "tools", "idf.py");
- const modifiedEnv = await appendIdfAndToolsToPath(workspace);
- const pythonBinPath = await getVirtualEnvPythonPath(workspace);
+ const modifiedEnv = await configureEnvVariables(workspace);
+ const pythonBinPath = await getVirtualEnvPythonPath();
const enableCCache = readParameter(
"idf.enableCCache",
workspace
@@ -78,10 +83,13 @@ export async function createProject(
example: string
): Promise {
try {
- const idfPathDir = readParameter("idf.espIdfPath");
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const idfPathDir = currentEnvVars["IDF_PATH"];
const idfPy = join(idfPathDir, "tools", "idf.py");
- const modifiedEnv = await appendIdfAndToolsToPath(workspace);
- const pythonBinPath = await getVirtualEnvPythonPath(workspace);
+ const modifiedEnv = await configureEnvVariables(workspace);
+ const pythonBinPath = await getVirtualEnvPythonPath();
if (
!existsSync(idfPathDir) ||
diff --git a/src/config.ts b/src/config.ts
index eec066350..6ebd78399 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -30,6 +30,7 @@ export namespace ESP {
export namespace ProjectConfiguration {
export let store: ProjectConfigStore;
export const SELECTED_CONFIG = "SELECTED_PROJECT_CONFIG";
+ export const CURRENT_IDF_CONFIGURATION = "CURRENT_IDF_CONFIGURATION";
export const PROJECT_CONFIGURATION_FILENAME =
"esp_idf_project_configuration.json";
}
@@ -53,21 +54,15 @@ export namespace ESP {
export namespace GlobalConfiguration {
export let store: ExtensionConfigStore;
- export const IDF_SETUPS = "IDF_SETUPS";
export const SELECTED_WORKSPACE_FOLDER = "SELECTED_WORKSPACE_FOLDER";
}
export const platformDepConfigurations: string[] = [
- "idf.buildPath",
"idf.espIdfPath",
- "idf.espAdfPath",
- "idf.espMdfPath",
- "idf.espRainmakerPath",
- "idf.espHomeKitSdkPath",
+ "idf.toolsPath",
+ "idf.buildPath",
"idf.gitPath",
- "idf.pythonBinPath",
"idf.port",
- "idf.toolsPath",
];
export namespace Rainmaker {
@@ -140,6 +135,10 @@ export namespace ESP {
export const README = ESP.URL.GithubRepository + "/blob/master/README.md";
export const FLASH_ENCRYPTION = "/security/flash-encryption.html";
}
+
+ export namespace InstallManager {
+ export const Releases = "https://dl.espressif.com/dl/eim/index.html";
+ }
}
export namespace Webview {
diff --git a/src/coverage/configureProject.ts b/src/coverage/configureProject.ts
index f7247f34e..5abe01e41 100644
--- a/src/coverage/configureProject.ts
+++ b/src/coverage/configureProject.ts
@@ -16,7 +16,11 @@
* limitations under the License.
*/
-import { extensionContext, getConfigValueFromSDKConfig, getEspIdfFromCMake } from "../utils";
+import {
+ extensionContext,
+ getConfigValueFromSDKConfig,
+ getEspIdfFromCMake,
+} from "../utils";
import { NotificationMode, readParameter } from "../idfConfiguration";
import { ConfserverProcess } from "../espIdf/menuconfig/confServerProcess";
import {
@@ -27,8 +31,12 @@ import {
Progress,
CancellationToken,
} from "vscode";
-import { getDocsLocaleLang, getDocsVersion } from "../espIdf/documentation/getDocsVersion";
+import {
+ getDocsLocaleLang,
+ getDocsVersion,
+} from "../espIdf/documentation/getDocsVersion";
import { getIdfTargetFromSdkconfig } from "../workspaceConfig";
+import { ESP } from "../config";
export async function configureProjectWithGcov(workspacePath: Uri) {
const appTraceDestTrax = await getConfigValueFromSDKConfig(
@@ -70,7 +78,9 @@ export async function configureProjectWithGcov(workspacePath: Uri) {
appTraceGcovEnable === "y";
if (isGcovEnabled) {
- return window.showInformationMessage("Code coverage is already enabled in sdkconfig");
+ return window.showInformationMessage(
+ "Code coverage is already enabled in sdkconfig"
+ );
}
if (!ConfserverProcess.exists()) {
@@ -120,13 +130,13 @@ export async function configureProjectWithGcov(workspacePath: Uri) {
export async function openCoverageUrl(workspacePath: Uri) {
const docsVersions = await getDocsVersion();
- const idfPath =
- readParameter("idf.espIdfPath", workspacePath) || process.env.IDF_PATH;
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const idfPath = currentEnvVars["IDF_PATH"];
let idfVersion = "v" + (await getEspIdfFromCMake(idfPath));
let idfTarget = await getIdfTargetFromSdkconfig(workspacePath);
- let docVersion = docsVersions.find(
- (docVer) => docVer.name === idfVersion
- );
+ let docVersion = docsVersions.find((docVer) => docVer.name === idfVersion);
let targetToUse: string = "esp32";
if (!docVersion) {
docVersion = docsVersions.find((docVer) => docVer.name === "latest");
diff --git a/src/coverage/gcdaPaths.ts b/src/coverage/gcdaPaths.ts
index ace4eb81a..21c45791b 100644
--- a/src/coverage/gcdaPaths.ts
+++ b/src/coverage/gcdaPaths.ts
@@ -20,12 +20,11 @@ import { readdir, stat } from "fs-extra";
import { extname, join } from "path";
import { Uri } from "vscode";
import { getGcovExecutable } from "./coverageService";
-import { readParameter } from "../idfConfiguration";
import { exec } from "child_process";
-import { appendIdfAndToolsToPath } from "../utils";
import { IGcovOutput } from "./gcovData";
import { Logger } from "../logger/logger";
import { getIdfTargetFromSdkconfig } from "../workspaceConfig";
+import { configureEnvVariables } from "../common/prepareEnv";
export async function getGcdaPaths(workspaceFolder: Uri) {
const gcdaPaths: Set = new Set();
@@ -64,7 +63,7 @@ export async function getGcovData(workspaceFolder: Uri) {
}
return new Promise(async (resolve, reject) => {
- const modifiedEnv = await appendIdfAndToolsToPath(workspaceFolder);
+ const modifiedEnv = await configureEnvVariables(workspaceFolder);
exec(
command,
{
diff --git a/src/customTasks/customTaskProvider.ts b/src/customTasks/customTaskProvider.ts
index 6456ca0c0..5eb0bf366 100644
--- a/src/customTasks/customTaskProvider.ts
+++ b/src/customTasks/customTaskProvider.ts
@@ -28,8 +28,8 @@ import {
} from "vscode";
import { NotificationMode, readParameter } from "../idfConfiguration";
import { TaskManager } from "../taskManager";
-import { appendIdfAndToolsToPath } from "../utils";
import { ShellOutputCapturingExecution } from "../taskManager/customExecution";
+import { configureEnvVariables } from "../common/prepareEnv";
export enum CustomTaskType {
Custom = "custom",
@@ -84,7 +84,7 @@ export class CustomTask {
if (!command) {
return;
}
- const modifiedEnv = await appendIdfAndToolsToPath(this.currentWorkspace);
+ const modifiedEnv = await configureEnvVariables(this.currentWorkspace);
const options: ShellExecutionOptions = {
cwd: this.currentWorkspace.fsPath,
env: modifiedEnv,
diff --git a/src/downloadManager.ts b/src/downloadManager.ts
deleted file mode 100644
index b1c3b61bb..000000000
--- a/src/downloadManager.ts
+++ /dev/null
@@ -1,564 +0,0 @@
-// Copyright 2019 Espressif Systems (Shanghai) CO LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-import * as del from "del";
-import * as fs from "fs";
-import { ensureDir, pathExists } from "fs-extra";
-import * as path from "path";
-import * as vscode from "vscode";
-import axios, { AxiosRequestConfig, AxiosResponse, CancelToken } from "axios";
-import { IdfToolsManager } from "./idfToolsManager";
-import { IFileInfo, IPackage } from "./IPackage";
-import { Logger } from "./logger/logger";
-import { OutputChannel } from "./logger/outputChannel";
-import { PackageError } from "./packageError";
-import { PackageProgress } from "./PackageProgress";
-import { PackageManagerWebError } from "./packageWebError";
-import * as utils from "./utils";
-import { ESP } from "./config";
-
-export class DownloadManager {
- private readonly MAX_RETRIES = 5;
- private readonly TIMEOUT = 30000; // 30 seconds
-
- constructor(
- private installPath: string,
- private refreshUIRate: number = 0.5
- ) {}
-
- public getToolPackagesPath(toolPackage: string[]) {
- return path.resolve(this.installPath, ...toolPackage);
- }
-
- public downloadPackages(
- idfToolsManager: IdfToolsManager,
- progress: vscode.Progress<{ message?: string; increment?: number }>,
- mirror: ESP.IdfMirror,
- pkgsProgress?: PackageProgress[],
- cancelToken?: vscode.CancellationToken,
- onReqPkgs?: string[]
- ): Promise {
- return idfToolsManager.getPackageList(onReqPkgs).then((packages) => {
- let count: number = 1;
- return utils.buildPromiseChain(
- packages,
- (pkg): Promise => {
- let pkgProgressToUse: PackageProgress;
- if (pkgsProgress) {
- pkgProgressToUse = pkgsProgress.find((pkgProgress) => {
- return pkgProgress.name === pkg.name;
- });
- }
- const p: Promise = this.downloadPackage(
- idfToolsManager,
- mirror,
- pkg,
- `${count}/${packages.length}`,
- progress,
- pkgProgressToUse,
- cancelToken
- );
- count += 1;
- return p;
- }
- );
- });
- }
-
- public async downloadPackage(
- idfToolsManager: IdfToolsManager,
- mirror: ESP.IdfMirror,
- pkg: IPackage,
- progressCount: string,
- progress: vscode.Progress<{ message?: string; increment?: number }>,
- pkgProgress?: PackageProgress,
- cancelToken?: vscode.CancellationToken
- ): Promise {
- progress.report({ message: `Downloading ${progressCount}: ${pkg.name}` });
- this.appendChannel(`Downloading ${pkg.description}`);
- const urlInfoToUse = idfToolsManager.obtainUrlInfoForPlatform(pkg);
- if (mirror == ESP.IdfMirror.Espressif) {
- urlInfoToUse.url = idfToolsManager.applyGithubAssetsMapping(
- urlInfoToUse.url
- );
- }
- this.appendChannel(
- `Using mirror ${
- mirror == ESP.IdfMirror.Espressif ? "Espressif" : "Github"
- } with URL ${urlInfoToUse.url}`
- );
- await this.downloadPackageWithRetries(
- pkg,
- urlInfoToUse,
- pkgProgress,
- cancelToken
- );
- }
-
- public async downloadPackageWithRetries(
- pkg: IPackage,
- urlInfoToUse: IFileInfo,
- pkgProgress?: PackageProgress,
- cancelToken?: vscode.CancellationToken
- ): Promise {
- const fileName = utils.fileNameFromUrl(urlInfoToUse.url);
- const destPath = this.getToolPackagesPath(["dist"]);
- const absolutePath: string = this.getToolPackagesPath(["dist", fileName]);
-
- const pkgExists = await pathExists(absolutePath);
- if (pkgExists) {
- const checksumEqual = await utils.validateFileSizeAndChecksum(
- absolutePath,
- urlInfoToUse.sha256,
- urlInfoToUse.size
- );
- if (checksumEqual) {
- pkgProgress.FileMatchChecksum = checksumEqual;
- this.appendChannel(
- `Found ${pkg.name} in ${this.installPath + path.sep}dist`
- );
- pkgProgress.Progress = `100.00%`;
- pkgProgress.ProgressDetail = `(${(urlInfoToUse.size / 1024).toFixed(
- 2
- )} / ${(urlInfoToUse.size / 1024).toFixed(2)}) KB`;
- return;
- } else {
- this.appendChannel(
- `Checksum mismatch for ${pkg.name}, will resume download`
- );
- // Don't delete the file - we'll resume from where we left off
- }
- }
-
- // Download with resume capability
- await this.downloadWithResume(
- urlInfoToUse.url,
- destPath,
- pkgProgress,
- cancelToken,
- urlInfoToUse.size
- );
-
- // Validate the downloaded file
- pkgProgress.FileMatchChecksum = await utils.validateFileSizeAndChecksum(
- absolutePath,
- urlInfoToUse.sha256,
- urlInfoToUse.size
- );
- }
-
- /**
- * Download file with resume capability using HTTP Range requests
- */
- public async downloadWithResume(
- urlToUse: string,
- destPath: string,
- pkgProgress?: PackageProgress,
- cancelToken?: vscode.CancellationToken,
- expectedSize?: number
- ): Promise {
- const fileName = utils.fileNameFromUrl(urlToUse);
- const absolutePath = path.resolve(destPath, fileName);
-
- // Ensure destination directory exists
- await ensureDir(destPath, { mode: 0o775 });
-
- let retryCount = 0;
- let lastError: Error;
-
- while (retryCount < this.MAX_RETRIES) {
- try {
- let startByte = 0;
-
- if (await pathExists(absolutePath)) {
- const stats = await fs.promises.stat(absolutePath);
- startByte = stats.size;
-
- // Check if file is already complete
- if (expectedSize && startByte >= expectedSize) {
- this.appendChannel(
- `File ${fileName} already exists and appears to be complete (${startByte} bytes >= ${expectedSize} expected)`
- );
- if (pkgProgress) {
- pkgProgress.Progress = "100.00%";
- pkgProgress.ProgressDetail = `(${(expectedSize / 1024).toFixed(
- 2
- )} / ${(expectedSize / 1024).toFixed(2)}) KB`;
- }
- return { status: 200, data: null }; // Return success for completed download
- }
-
- if (startByte > 0) {
- this.appendChannel(
- `Resuming download of ${fileName} from byte ${startByte}`
- );
- }
- }
-
- // Check if server supports range requests
- const supportsRange = await this.checkRangeSupport(urlToUse);
-
- let response: any;
-
- if (supportsRange && startByte > 0) {
- // Resume download using range requests
- response = await this.downloadWithRange(
- urlToUse,
- absolutePath,
- startByte,
- pkgProgress,
- cancelToken,
- expectedSize
- );
- } else {
- // Full download (either no range support or starting from beginning)
- if (startByte > 0) {
- this.appendChannel(
- `Server doesn't support range requests, starting fresh download`
- );
- await del(absolutePath, { force: true });
- }
- response = await this.downloadFull(
- urlToUse,
- absolutePath,
- pkgProgress,
- cancelToken,
- expectedSize
- );
- }
-
- this.appendChannel(`Successfully downloaded ${fileName}`);
- return response;
- } catch (error) {
- lastError = error;
- retryCount++;
-
- // Handle 416 Range Not Satisfiable error specifically
- if (error.response && error.response.status === 416) {
- this.appendChannel(
- `Received 416 Range Not Satisfiable for ${fileName}. File may already be complete.`
- );
-
- // Check if the file exists and has reasonable size
- if (await pathExists(absolutePath)) {
- const stats = await fs.promises.stat(absolutePath);
- if (stats.size > 0) {
- this.appendChannel(
- `File ${fileName} exists with size ${stats.size} bytes. Treating as complete.`
- );
- if (pkgProgress) {
- const size = expectedSize || stats.size;
- pkgProgress.Progress = "100.00%";
- pkgProgress.ProgressDetail = `(${(size / 1024).toFixed(2)} / ${(
- size / 1024
- ).toFixed(2)}) KB`;
- }
- return { status: 200, data: null }; // Return success for completed download
- }
- }
- }
-
- if (cancelToken && cancelToken.isCancellationRequested) {
- throw new PackageError(
- "Download cancelled by user",
- "downloadWithResume"
- );
- }
-
- if (retryCount >= this.MAX_RETRIES) {
- this.appendChannel(
- `Failed to download ${urlToUse} after ${this.MAX_RETRIES} attempts`
- );
- throw error;
- }
-
- // Calculate exponential backoff delay
- const delay = Math.min(1000 * Math.pow(2, retryCount - 1), 10000);
- this.appendChannel(
- `Download failed (attempt ${retryCount}/${this.MAX_RETRIES}). Retrying in ${delay}ms...`
- );
- this.appendChannel(`Error: ${error.message || error}`);
-
- // Wait before retry
- await new Promise((resolve) => setTimeout(resolve, delay));
- }
- }
-
- throw lastError;
- }
-
- /**
- * Check if server supports HTTP Range requests
- */
- private async checkRangeSupport(url: string): Promise {
- try {
- const config: AxiosRequestConfig = {
- method: "HEAD",
- timeout: this.TIMEOUT,
- headers: {
- "User-Agent": ESP.HTTP_USER_AGENT,
- },
- };
-
- const response = await axios.head(url, config);
- const acceptRanges = response.headers["accept-ranges"];
- const contentRange = response.headers["content-range"];
-
- return acceptRanges === "bytes" || !!contentRange;
- } catch (error) {
- this.appendChannel(`Could not check range support: ${error.message}`);
- return false;
- }
- }
-
- /**
- * Download file using HTTP Range requests for resume capability
- */
- private async downloadWithRange(
- url: string,
- filePath: string,
- startByte: number,
- pkgProgress?: PackageProgress,
- cancelToken?: vscode.CancellationToken,
- expectedSize?: number
- ): Promise {
- const fileName = path.basename(filePath);
- const fileStream = fs.createWriteStream(filePath, {
- flags: "a",
- mode: 0o775,
- }); // Append mode
-
- const config: AxiosRequestConfig = {
- method: "GET",
- timeout: this.TIMEOUT,
- responseType: "stream",
- headers: {
- "User-Agent": ESP.HTTP_USER_AGENT,
- Range: `bytes=${startByte}-`,
- },
- cancelToken: cancelToken
- ? this.createCancelToken(cancelToken)
- : undefined,
- };
-
- this.appendChannel(
- `Downloading ${fileName} with range request from byte ${startByte}`
- );
-
- try {
- const response = await axios.get(url, config);
-
- if (response.status !== 206 && response.status !== 200) {
- throw new PackageManagerWebError(
- null,
- "HTTP Range Request Error",
- "downloadWithRange",
- `Unexpected status code: ${response.status}`,
- response.status.toString()
- );
- }
- const contentRange = response.headers["content-range"];
- let totalSize = expectedSize || 0;
-
- // Parse content-range header to get total size
- if (contentRange) {
- const match = contentRange.match(/bytes \d+-\d+\/(\d+)/);
- if (match) {
- totalSize = parseInt(match[1], 10);
- }
- }
-
- let downloadedSize = startByte;
- let lastProgressUpdate = 0;
-
- response.data.on("data", (chunk: Buffer) => {
- downloadedSize += chunk.length;
-
- if (pkgProgress && totalSize > 0) {
- const progress = (downloadedSize / totalSize) * 100;
- const progressDetail = `(${(downloadedSize / 1024).toFixed(2)} / ${(
- totalSize / 1024
- ).toFixed(2)}) KB`;
-
- // Update progress only if significant change
- if (
- progress - lastProgressUpdate >= this.refreshUIRate ||
- downloadedSize === totalSize
- ) {
- pkgProgress.Progress = `${progress.toFixed(2)}%`;
- pkgProgress.ProgressDetail = progressDetail;
- lastProgressUpdate = progress;
- }
- }
- });
-
- response.data.pipe(fileStream);
-
- return new Promise((resolve, reject) => {
- fileStream.on("finish", () => {
- this.appendChannel(`Range download completed: ${fileName}`);
- resolve(response);
- });
-
- fileStream.on("error", (error) => {
- reject(
- new PackageError(
- `File write error: ${error.message}`,
- "downloadWithRange",
- error
- )
- );
- });
-
- response.data.on("error", (error) => {
- reject(
- new PackageError(
- `Download stream error: ${error.message}`,
- "downloadWithRange",
- error
- )
- );
- });
- });
- } catch (error) {
- // Close the file stream if it was opened
- if (fileStream) {
- fileStream.end();
- }
-
- // Re-throw the error to be handled by the calling method
- throw error;
- }
- }
-
- /**
- * Download full file (no resume capability)
- */
- private async downloadFull(
- url: string,
- filePath: string,
- pkgProgress?: PackageProgress,
- cancelToken?: vscode.CancellationToken,
- expectedSize?: number
- ): Promise {
- const fileName = path.basename(filePath);
- const fileStream = fs.createWriteStream(filePath, { mode: 0o775 });
-
- const config: AxiosRequestConfig = {
- method: "GET",
- timeout: this.TIMEOUT,
- responseType: "stream",
- headers: {
- "User-Agent": ESP.HTTP_USER_AGENT,
- },
- cancelToken: cancelToken
- ? this.createCancelToken(cancelToken)
- : undefined,
- };
-
- this.appendChannel(`Downloading ${fileName} (full download)`);
-
- const response = await axios.get(url, config);
-
- if (response.status !== 200) {
- throw new PackageManagerWebError(
- null,
- "HTTP Download Error",
- "downloadFull",
- `Unexpected status code: ${response.status}`,
- response.status.toString()
- );
- }
-
- const contentLength = parseInt(
- response.headers["content-length"] || "0",
- 10
- );
- const totalSize = expectedSize || contentLength;
- let downloadedSize = 0;
- let lastProgressUpdate = 0;
-
- response.data.on("data", (chunk: Buffer) => {
- downloadedSize += chunk.length;
-
- if (pkgProgress && totalSize > 0) {
- const progress = (downloadedSize / totalSize) * 100;
- const progressDetail = `(${(downloadedSize / 1024).toFixed(2)} / ${(
- totalSize / 1024
- ).toFixed(2)}) KB`;
-
- // Update progress only if significant change
- if (
- progress - lastProgressUpdate >= this.refreshUIRate ||
- downloadedSize === totalSize
- ) {
- pkgProgress.Progress = `${progress.toFixed(2)}%`;
- pkgProgress.ProgressDetail = progressDetail;
- lastProgressUpdate = progress;
- }
- }
- });
-
- response.data.pipe(fileStream);
-
- return new Promise((resolve, reject) => {
- fileStream.on("finish", () => {
- this.appendChannel(
- `Full download completed: ${fileName} into ${filePath}`
- );
- resolve(response);
- });
-
- fileStream.on("error", (error) => {
- reject(
- new PackageError(
- `File write error: ${error.message}`,
- "downloadFull",
- error
- )
- );
- });
-
- response.data.on("error", (error) => {
- reject(
- new PackageError(
- `Download stream error: ${error.message}`,
- "downloadFull",
- error
- )
- );
- });
- });
- }
-
- /**
- * Create axios cancel token from VS Code cancellation token
- */
- private createCancelToken(
- cancelToken: vscode.CancellationToken
- ): CancelToken {
- const source = axios.CancelToken.source();
-
- cancelToken.onCancellationRequested(() => {
- source.cancel("Download cancelled by user");
- });
-
- return source.token;
- }
-
- private appendChannel(text: string): void {
- OutputChannel.appendLine(text);
- Logger.info(text);
- }
-}
diff --git a/src/efuse/index.ts b/src/efuse/index.ts
index 274893c77..2fbe98303 100644
--- a/src/efuse/index.ts
+++ b/src/efuse/index.ts
@@ -25,6 +25,7 @@ import { Logger } from "../logger/logger";
import { Uri, l10n } from "vscode";
import { getVirtualEnvPythonPath } from "../pythonManager";
import { getIdfTargetFromSdkconfig } from "../workspaceConfig";
+import { ESP } from "../config";
export type ESPEFuseSummary = {
[category: string]: [
@@ -51,8 +52,10 @@ export class ESPEFuseManager {
private idfPath: string;
constructor(private workspace: Uri) {
- this.idfPath =
- readParameter("idf.espIdfPath", workspace) || process.env.IDF_PATH;
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ this.idfPath = currentEnvVars["IDF_PATH"] || process.env.IDF_PATH;
}
async summary(): Promise {
@@ -78,7 +81,7 @@ export class ESPEFuseManager {
async readSummary() {
const tempFile = join(tmpdir(), "espefusejsondump.tmp");
- const pythonPath = await getVirtualEnvPythonPath(this.workspace);
+ const pythonPath = await getVirtualEnvPythonPath();
const port = await readSerialPort(this.workspace, false);
if (!port) {
diff --git a/src/eim/downloadInstall.ts b/src/eim/downloadInstall.ts
new file mode 100644
index 000000000..23dafd80c
--- /dev/null
+++ b/src/eim/downloadInstall.ts
@@ -0,0 +1,466 @@
+/*
+ * Project: ESP-IDF VSCode Extension
+ * File Created: Monday, 28th April 2025 4:34:49 pm
+ * Copyright 2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import axios from "axios";
+import { tmpdir } from "os";
+import { basename, dirname, extname, join, resolve as pathResolve } from "path";
+import {
+ createWriteStream,
+ ensureDir,
+ move,
+ pathExists,
+ ReadStream,
+ remove,
+ WriteStream,
+} from "fs-extra";
+import { CancellationToken, env, Progress, window } from "vscode";
+import { OutputChannel } from "../logger/outputChannel";
+import del from "del";
+import { dirExistPromise } from "../utils";
+import * as yauzl from "yauzl";
+import { Logger } from "../logger/logger";
+import { getEimIdfJson } from "./getExistingSetups";
+import { readParameter } from "../idfConfiguration";
+
+export async function runExistingEIM(
+ progress: Progress<{ message: string; increment: number }>,
+ cancelToken: CancellationToken
+): Promise {
+ let eimPath = "";
+ if (cancelToken.isCancellationRequested) {
+ return false;
+ }
+ progress.report({
+ message: `Checking EIM already exists...`,
+ increment: 0,
+ });
+ try {
+ const eimJSON = await getEimIdfJson();
+ if (eimJSON && eimJSON.eimPath) {
+ const doesEimPathExists = await pathExists(eimJSON.eimPath);
+ if (doesEimPathExists) {
+ eimPath = eimJSON.eimPath;
+ }
+ }
+ // 2. Check EIM_PATH env variable if not found in eim_idf.json
+ if (!eimPath) {
+ eimPath = process.env.EIM_PATH || "";
+ }
+ // 3. Use default path based on platform if still not found
+ if (!eimPath) {
+ if (process.platform === "win32") {
+ eimPath = join(
+ process.env.USERPROFILE || "",
+ ".espressif",
+ "eim_gui",
+ "eim.exe"
+ );
+ } else if (process.platform === "darwin") {
+ eimPath = "/Applications/eim.app";
+ } else if (process.platform === "linux") {
+ eimPath = join(process.env.HOME || "", ".espressif", "eim_gui", "eim");
+ }
+ }
+ const doesEimPathExists = await pathExists(eimPath);
+ if (!doesEimPathExists) {
+ throw new Error(`EIM not found at ${eimPath}`);
+ }
+ } catch (error) {
+ Logger.error(
+ `Error while running existing EIM: ${error.message}`,
+ error,
+ "runExistingEIM"
+ );
+ return false;
+ }
+
+ progress.report({
+ message: `EIM found at ${eimPath}. Launching...`,
+ increment: 0,
+ });
+
+ // Read idf.eimExecutableArgs with utils.readParameter and use it to run EIM
+ const idfEimExecutableArgs = readParameter(
+ "idf.eimExecutableArgs"
+ ) as string[];
+ const argsString = idfEimExecutableArgs.join(" ");
+
+ let binaryPath = "";
+ if (process.platform === "win32") {
+ binaryPath = `& '${eimPath.replace(/'/g, "''")}'${
+ argsString ? " " + argsString : ""
+ }`;
+ } else if (process.platform === "linux") {
+ binaryPath = `./${basename(eimPath)}${argsString ? " " + argsString : ""}`;
+ } else if (process.platform === "darwin") {
+ binaryPath = `open ${eimPath}${argsString ? " --args " + argsString : ""}`;
+ }
+ const shellPath =
+ process.platform === "win32"
+ ? "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
+ : env.shell;
+ const espIdfTerminal = window.createTerminal({
+ name: "ESP-IDF EIM",
+ shellPath: shellPath,
+ cwd: dirname(eimPath),
+ });
+ espIdfTerminal.sendText(binaryPath, true);
+ espIdfTerminal.sendText("exit");
+ return true;
+}
+
+export async function downloadExtractAndRunEIM(
+ progress: Progress<{ message: string; increment: number }>,
+ cancelToken: CancellationToken,
+ useMirror: boolean = false
+): Promise {
+ const jsonUrl = "https://dl.espressif.com/dl/eim/eim_unified_release.json";
+ // Determine EIM install path
+ let eimInstallPath = "";
+ if (process.platform === "win32") {
+ eimInstallPath = join(
+ process.env.USERPROFILE || "",
+ ".espressif",
+ "eim_gui"
+ );
+ } else if (process.platform === "darwin") {
+ eimInstallPath = "/Applications";
+ } else if (process.platform === "linux") {
+ eimInstallPath = join(process.env.HOME || "", ".espressif", "eim_gui");
+ }
+
+ try {
+ progress.report({
+ message: `Downloading EIM versions...`,
+ increment: 0,
+ });
+ const response = await axios.get(jsonUrl, {
+ headers: { "Cache-Control": "no-cache" },
+ });
+ const data = response.data;
+
+ const arch = process.arch;
+ let osKey: string;
+
+ if (process.platform === "darwin") {
+ osKey =
+ arch === "arm64"
+ ? "eim-gui-macos-aarch64.zip"
+ : "eim-gui-macos-x64.zip";
+ } else if (process.platform === "win32") {
+ osKey = "eim-gui-windows-x64.exe";
+ } else if (process.platform === "linux") {
+ osKey =
+ arch === "arm64"
+ ? "eim-gui-linux-aarch64.zip"
+ : "eim-gui-linux-x64.zip";
+ } else {
+ throw new Error(`Unsupported platform: ${process.platform}`);
+ }
+ const fileInfo = data.assets.find((asset: any) => asset.name === osKey);
+ if (!fileInfo) {
+ throw new Error(`No file found for OS and architecture: ${osKey}`);
+ }
+
+ progress.report({
+ message: `Downloading EIM: ${fileInfo.browser_download_url}`,
+ increment: 0,
+ });
+ OutputChannel.appendLine(
+ `Downloading EIM: ${fileInfo.browser_download_url}`,
+ "EIM Download"
+ );
+
+ let downloadUrl = fileInfo.browser_download_url;
+ const fileName = basename(downloadUrl);
+ const downloadPath = join(eimInstallPath, fileName);
+
+ const doesDownloadPathExists = await pathExists(downloadPath);
+ if (!doesDownloadPathExists) {
+ if (useMirror) {
+ downloadUrl = downloadUrl.replace(
+ "https://github.com",
+ "https://dl.espressif.com/github_assets"
+ );
+ }
+
+ const doesTmpDirExists = await pathExists(eimInstallPath);
+ if (!doesTmpDirExists) {
+ await ensureDir(eimInstallPath);
+ }
+
+ const writeStream: WriteStream = createWriteStream(downloadPath, {
+ mode: 0o755,
+ });
+ const fileResponseStream = await axios({
+ method: "get",
+ url: downloadUrl,
+ responseType: "stream",
+ });
+ const { headers } = await axios.head(downloadUrl);
+ const totalSize = parseInt(headers["content-length"], 10);
+
+ let downloadedSize = 0;
+ fileResponseStream.data.on("data", (chunk: Buffer) => {
+ if (cancelToken.isCancellationRequested) {
+ fileResponseStream.data.destroy();
+ throw new Error("Download canceled by user.");
+ }
+ downloadedSize += chunk.length;
+ if (totalSize) {
+ const percentCompleted = Math.round(
+ (downloadedSize * 100) / totalSize
+ );
+ const increment = Math.round((chunk.length * 100) / totalSize);
+ progress.report({
+ message: `Downloading EIM... ${percentCompleted}%`,
+ increment,
+ });
+ }
+ });
+ fileResponseStream.data.pipe(writeStream);
+
+ await new Promise((resolve, reject) => {
+ fileResponseStream.data.on("end", resolve);
+ fileResponseStream.data.on("error", reject);
+ });
+ OutputChannel.appendLine(
+ `File downloaded and extracted to: ${downloadPath}`
+ );
+ } else {
+ OutputChannel.appendLine(`Using existing: ${downloadPath}`);
+ progress.report({
+ message: `Using existing: ${downloadPath}`,
+ increment: 0,
+ });
+ }
+
+ if (process.platform !== "win32") {
+ await installZipFile(downloadPath, eimInstallPath, cancelToken);
+ Logger.infoNotify(`File ${osKey} extracted to: ${eimInstallPath}`);
+ }
+
+ let binaryPath = "";
+ if (process.platform === "win32") {
+ binaryPath = `Start-Process -FilePath "${join(
+ eimInstallPath,
+ "eim.exe"
+ )}"`;
+ } else if (process.platform === "linux") {
+ binaryPath = `./eim`;
+ } else if (process.platform === "darwin") {
+ binaryPath = `open ${join(eimInstallPath, "eim.app")}`;
+ }
+ const shellPath =
+ process.platform === "win32"
+ ? "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
+ : env.shell;
+ const espIdfTerminal = window.createTerminal({
+ name: "ESP-IDF EIM",
+ shellPath: shellPath,
+ cwd: eimInstallPath,
+ });
+ espIdfTerminal.sendText(binaryPath, true);
+ espIdfTerminal.sendText("exit");
+ } catch (error) {
+ Logger.errorNotify(
+ `Error during download and extraction: ${error.message}`,
+ error,
+ "downloadAndExtractEIM"
+ );
+ }
+}
+
+export class ZipFileError extends Error {
+ constructor(
+ public message: string,
+ public methodName: string,
+ public innerError: any = null,
+ public errorCode: string = " "
+ ) {
+ super(message);
+ this.errorCode = errorCode;
+ this.innerError = innerError;
+ this.methodName = methodName;
+ }
+}
+
+export async function installZipFile(
+ zipFilePath: string,
+ destPath: string,
+ cancelToken?: CancellationToken
+) {
+ return new Promise(async (resolve, reject) => {
+ const doesZipFileExists = await pathExists(zipFilePath);
+ if (!doesZipFileExists) {
+ return reject(`File ${zipFilePath} doesn't exist.`);
+ }
+ yauzl.open(zipFilePath, { lazyEntries: true }, (error, zipfile) => {
+ if (error) {
+ return reject(
+ new ZipFileError("Zip file error", "InstallZipFile", error)
+ );
+ }
+ if (cancelToken && cancelToken.isCancellationRequested) {
+ return reject(
+ new ZipFileError("Install cancelled by user", "InstallZipFile")
+ );
+ }
+
+ zipfile.on("end", () => {
+ return resolve();
+ });
+ zipfile.on("error", (err) => {
+ return reject(
+ new ZipFileError("Zip File error", "InstallZipFile", err)
+ );
+ });
+
+ zipfile.readEntry();
+ zipfile.on("entry", async (entry: yauzl.Entry) => {
+ const absolutePath: string = pathResolve(destPath, entry.fileName);
+ const dirExists = await dirExistPromise(absolutePath);
+ if (dirExists) {
+ try {
+ await del(absolutePath, { force: true });
+ } catch (err) {
+ OutputChannel.appendLine(
+ `Error deleting previous ${absolutePath}: ${err.message}`
+ );
+ return reject(
+ new ZipFileError(
+ "Install folder cant be deleted",
+ "InstallZipFile",
+ err,
+ err.errorCode
+ )
+ );
+ }
+ }
+ if (entry.fileName.endsWith("/")) {
+ try {
+ await ensureDir(absolutePath, { mode: 0o775 });
+ zipfile.readEntry();
+ } catch (err) {
+ return reject(
+ new ZipFileError(
+ "Error creating directory",
+ "InstallZipFile",
+ err
+ )
+ );
+ }
+ } else {
+ const exists = await pathExists(absolutePath);
+ if (!exists) {
+ zipfile.openReadStream(
+ entry,
+ async (err, readStream: ReadStream) => {
+ if (err) {
+ return reject(
+ new ZipFileError(
+ "Error reading zip stream",
+ "InstallZipFile",
+ err
+ )
+ );
+ }
+ readStream.on("error", (openErr) => {
+ return reject(
+ new ZipFileError(
+ "Error in readstream",
+ "InstallZipFile",
+ openErr
+ )
+ );
+ });
+
+ try {
+ await ensureDir(dirname(absolutePath), {
+ mode: 0o775,
+ });
+ } catch (mkdirErr) {
+ return reject(
+ new ZipFileError(
+ "Error creating directory",
+ "InstallZipFile",
+ mkdirErr
+ )
+ );
+ }
+ const absoluteEntryTmpPath: string = absolutePath + ".tmp";
+ const doesTmpPathExists = await pathExists(
+ absoluteEntryTmpPath
+ );
+ if (doesTmpPathExists) {
+ try {
+ await remove(absoluteEntryTmpPath);
+ } catch (rmError) {
+ return reject(
+ new ZipFileError(
+ `Error unlinking tmp file ${absoluteEntryTmpPath}`,
+ "InstallZipFile",
+ rmError
+ )
+ );
+ }
+ }
+ const writeStream: WriteStream = createWriteStream(
+ absoluteEntryTmpPath,
+ { mode: 0o755 }
+ );
+ writeStream.on("error", (writeStreamErr) => {
+ return reject(
+ new ZipFileError(
+ "Error in writeStream",
+ "InstallZipFile",
+ writeStreamErr
+ )
+ );
+ });
+ writeStream.on("close", async () => {
+ try {
+ await move(absoluteEntryTmpPath, absolutePath);
+ } catch (closeWriteStreamErr) {
+ return reject(
+ new ZipFileError(
+ `Error renaming file ${absoluteEntryTmpPath}`,
+ "InstallZipFile",
+ closeWriteStreamErr
+ )
+ );
+ }
+ zipfile.readEntry();
+ });
+ readStream.pipe(writeStream);
+ }
+ );
+ } else {
+ if (extname(absolutePath) !== ".txt") {
+ OutputChannel.appendLine(
+ `Warning File ${absolutePath}
+ already exists and was not updated.`
+ );
+ }
+ zipfile.readEntry();
+ }
+ }
+ });
+ });
+ });
+}
diff --git a/src/eim/getExistingSetups.ts b/src/eim/getExistingSetups.ts
new file mode 100644
index 000000000..e23d675e3
--- /dev/null
+++ b/src/eim/getExistingSetups.ts
@@ -0,0 +1,133 @@
+/*
+ * Project: ESP-IDF VSCode Extension
+ * File Created: Friday, 29th November 2024 3:28:00 pm
+ * Copyright 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { pathExists, readJson } from "fs-extra";
+import { join } from "path";
+import { readParameter } from "../idfConfiguration";
+import { Logger } from "../logger/logger";
+import { EspIdfJson, IdfSetup } from "./types";
+import { getEspIdfFromCMake } from "../utils";
+import { loadIdfSetupsFromEspIdfJson } from "./migrationTool";
+import { ESP } from "../config";
+import { Uri } from "vscode";
+
+export async function getIdfSetups(workspaceFolder: Uri) {
+ const workspaceFolderUri = ESP.GlobalConfiguration.store.get(
+ ESP.GlobalConfiguration.SELECTED_WORKSPACE_FOLDER
+ );
+ const customVars = readParameter(
+ "idf.customExtraVars",
+ workspaceFolderUri
+ ) as {
+ [key: string]: string;
+ };
+ const eimIDFSetups = await loadIdfSetupsFromEimIdfJson();
+ let resultingIdfSetups = eimIDFSetups;
+ if (customVars["IDF_TOOLS_PATH"]) {
+ const espIdfCustomVarsJsonSetups = await loadIdfSetupsFromEspIdfJson(
+ customVars["IDF_TOOLS_PATH"]
+ );
+ resultingIdfSetups = resultingIdfSetups.concat(espIdfCustomVarsJsonSetups);
+ }
+ if (process.env.IDF_TOOLS_PATH) {
+ const espIdfSysJsonSetups = await loadIdfSetupsFromEspIdfJson(
+ process.env["IDF_TOOLS_PATH"]
+ );
+ resultingIdfSetups = resultingIdfSetups.concat(espIdfSysJsonSetups);
+ }
+ const containerPath =
+ process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
+ const defaultIdfToolsPath = join(containerPath, ".espressif");
+ const espIdfSysJsonSetups = await loadIdfSetupsFromEspIdfJson(
+ defaultIdfToolsPath
+ );
+ resultingIdfSetups = resultingIdfSetups.concat(espIdfSysJsonSetups);
+
+
+ const oldIdfToolsPath = readParameter("idf.toolsPath", workspaceFolder) as string;
+ if (oldIdfToolsPath) {
+ const oldIdfSetups = await loadIdfSetupsFromEspIdfJson(oldIdfToolsPath);
+ resultingIdfSetups = resultingIdfSetups.concat(oldIdfSetups);
+ }
+
+ resultingIdfSetups = resultingIdfSetups.filter(
+ (setup, index, self) =>
+ index ===
+ self.findIndex(
+ (s) => s.idfPath === setup.idfPath && s.toolsPath === setup.toolsPath
+ )
+ );
+
+ return resultingIdfSetups;
+}
+
+export async function loadIdfSetupsFromEimIdfJson() {
+ let idfSetups: IdfSetup[] = [];
+ const espIdfJson = await getEimIdfJson();
+ if (
+ espIdfJson &&
+ espIdfJson.idfInstalled &&
+ Object.keys(espIdfJson.idfInstalled).length
+ ) {
+ for (let idfInstalled of espIdfJson.idfInstalled) {
+ const idfVersion = await getEspIdfFromCMake(idfInstalled.path);
+ let setupConf: IdfSetup = {
+ activationScript: idfInstalled.activationScript,
+ id: idfInstalled.id,
+ idfPath: idfInstalled.path,
+ isValid: false,
+ gitPath: espIdfJson.gitPath,
+ version: idfVersion,
+ toolsPath: idfInstalled.idfToolsPath,
+ python: idfInstalled.python,
+ sysPythonPath: "",
+ } as IdfSetup;
+ idfSetups.push(setupConf);
+ }
+ }
+ return idfSetups;
+}
+
+export async function getEimIdfJson() {
+ const espIdeJsonCustomPath = readParameter("idf.eimIdfJsonPath");
+ const espIdePathExists = await pathExists(espIdeJsonCustomPath);
+ let eimIdfJsonPath = "";
+ if (espIdePathExists) {
+ eimIdfJsonPath = espIdeJsonCustomPath;
+ } else {
+ eimIdfJsonPath =
+ process.platform === "win32"
+ ? join("C:", "Espressif", "tools", "eim_idf.json")
+ : join(process.env.HOME, ".espressif", "tools", "eim_idf.json");
+ }
+ const espIdfJsonExists = await pathExists(eimIdfJsonPath);
+ let espIdfJson: EspIdfJson;
+ try {
+ if (!espIdfJsonExists) {
+ throw new Error(`${eimIdfJsonPath} doesn't exists.`);
+ }
+ espIdfJson = await readJson(eimIdfJsonPath);
+ return espIdfJson;
+ } catch (error) {
+ const msg =
+ error && error.message
+ ? error.message
+ : `Error reading ${eimIdfJsonPath}.`;
+ Logger.error(msg, error, "getEimIdfJson");
+ }
+}
diff --git a/src/eim/loadIdfSetup.ts b/src/eim/loadIdfSetup.ts
new file mode 100644
index 000000000..3969dae80
--- /dev/null
+++ b/src/eim/loadIdfSetup.ts
@@ -0,0 +1,179 @@
+/*
+ * Project: ESP-IDF VSCode Extension
+ * File Created: Monday, 24th February 2025 5:31:26 pm
+ * Copyright 2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { commands, ConfigurationTarget, l10n, Uri, window } from "vscode";
+import { ESP } from "../config";
+import { getIdfSetups } from "./getExistingSetups";
+import { IdfSetup } from "./types";
+import { getEnvVariables } from "./loadSettings";
+import { readParameter, writeParameter } from "../idfConfiguration";
+import { getEspIdfFromCMake } from "../utils";
+import { join } from "path";
+import { isIdfSetupValid } from "./verifySetup";
+import { Logger } from "../logger/logger";
+import { createHash } from "crypto";
+
+export async function loadIdfSetup(workspaceFolder: Uri) {
+ const idfSetups = await getIdfSetups(workspaceFolder);
+
+ if (!idfSetups || idfSetups.length < 1) {
+ window.showInformationMessage(l10n.t("No ESP-IDF Setups found"));
+ Logger.info("Using loadEnvVarsAsIdfSetup to configure extension");
+ const idfEnvSetup = await loadEnvVarsAsIdfSetup(workspaceFolder);
+ if (idfEnvSetup) {
+ return idfEnvSetup;
+ }
+ }
+ const currentIdfConfigurationName = readParameter(
+ "idf.currentSetup",
+ workspaceFolder
+ ) as string;
+
+ let idfSetupToUse: IdfSetup;
+ if (idfSetups.length > 0) {
+ if (currentIdfConfigurationName) {
+ idfSetupToUse = idfSetups.find((idfSetup) => {
+ return idfSetup.idfPath === currentIdfConfigurationName;
+ });
+ } else {
+ const oldIdfPath = readParameter(
+ "idf.espIdfPath",
+ workspaceFolder
+ ) as string;
+ if (oldIdfPath) {
+ idfSetupToUse = idfSetups.find((idfSetup) => {
+ return idfSetup.idfPath === oldIdfPath;
+ });
+ }
+ if (!idfSetupToUse) {
+ idfSetupToUse = idfSetups[0];
+ }
+ await writeParameter(
+ "idf.currentSetup",
+ idfSetupToUse.idfPath,
+ ConfigurationTarget.WorkspaceFolder,
+ workspaceFolder
+ );
+ }
+ }
+
+ if (!idfSetupToUse) {
+ Logger.infoNotify("Current ESP-IDF setup is not found.");
+
+ const openESPIDFManager = l10n.t(
+ "Open ESP-IDF Installation Manager"
+ ) as string;
+ const action = await window.showInformationMessage(
+ l10n.t("The extension configuration is not valid. Choose an action:"),
+ openESPIDFManager
+ );
+ if (!action) {
+ return;
+ }
+
+ if (action === openESPIDFManager) {
+ commands.executeCommand("espIdf.installManager");
+ return;
+ }
+ }
+
+ await writeParameter(
+ "idf.gitPath",
+ idfSetupToUse.gitPath,
+ ConfigurationTarget.Global
+ );
+
+ const envVars = await getEnvVariables(idfSetupToUse);
+
+ ESP.ProjectConfiguration.store.set(
+ ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION,
+ envVars
+ );
+ return idfSetupToUse;
+}
+
+function getIdfMd5sum(idfPath: string) {
+ if (!idfPath) {
+ return "";
+ }
+ const md5Value = createHash("md5")
+ .update(idfPath.replace(/\\/g, "/"))
+ .digest("hex");
+ return `esp-idf-${md5Value}`;
+}
+
+export async function loadEnvVarsAsIdfSetup(workspaceFolder: Uri) {
+ const customVars = readParameter("idf.customExtraVars", workspaceFolder) as {
+ [key: string]: string;
+ };
+
+ const idfPath = process.env.IDF_PATH || customVars["IDF_PATH"];
+ const containerPath =
+ process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME;
+ const defaultIdfToolsPath = join(containerPath, ".espressif");
+ const idfToolsPath =
+ process.env.IDF_TOOLS_PATH ||
+ customVars["IDF_TOOLS_PATH"] ||
+ defaultIdfToolsPath;
+ const gitPath = "/usr/bin/git";
+ const idfSetupId = getIdfMd5sum(idfPath);
+ const idfVersion = await getEspIdfFromCMake(idfPath);
+ const pyDir =
+ process.platform === "win32"
+ ? ["Scripts", "python.exe"]
+ : ["bin", "python3"];
+ let venvPythonPath = "";
+ if (process.env.IDF_PYTHON_ENV_PATH || customVars["IDF_PYTHON_ENV_PATH"]) {
+ venvPythonPath = join(
+ process.env.IDF_PYTHON_ENV_PATH || customVars["IDF_PYTHON_ENV_PATH"],
+ ...pyDir
+ );
+ }
+ const envDefinedIdfSetup: IdfSetup = {
+ id: idfSetupId,
+ activationScript: "",
+ idfPath,
+ gitPath,
+ toolsPath: idfToolsPath,
+ sysPythonPath: "",
+ version: idfVersion,
+ python: venvPythonPath,
+ isValid: false,
+ };
+ let reason = "";
+ [envDefinedIdfSetup.isValid, reason] = await isIdfSetupValid(
+ envDefinedIdfSetup
+ );
+
+ if (!envDefinedIdfSetup.isValid) {
+ Logger.infoNotify(l10n.t("ESP-IDF Setup is not valid: {0}", reason), {
+ category: "espIdf.installManager",
+ reason,
+ });
+ return;
+ }
+
+ if (envDefinedIdfSetup.isValid) {
+ const envVars = await getEnvVariables(envDefinedIdfSetup);
+ ESP.ProjectConfiguration.store.set(
+ ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION,
+ envVars
+ );
+ return envDefinedIdfSetup;
+ }
+}
diff --git a/src/eim/loadSettings.ts b/src/eim/loadSettings.ts
new file mode 100644
index 000000000..6f3d53886
--- /dev/null
+++ b/src/eim/loadSettings.ts
@@ -0,0 +1,100 @@
+/*
+ * Project: ESP-IDF VSCode Extension
+ * File Created: Thursday, 6th February 2025 11:42:36 am
+ * Copyright 2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { EOL } from "os";
+import { spawn } from "../utils";
+import { IdfSetup } from "./types";
+import { delimiter, join } from "path";
+import { getEnvVariablesFromIdfSetup } from "./migrationTool";
+import { Logger } from "../logger/logger";
+import { env } from "vscode";
+
+export async function getEnvVariables(idfSetup: IdfSetup) {
+ if (idfSetup.activationScript) {
+ return await getEnvVariablesFromActivationScript(idfSetup.activationScript);
+ } else {
+ return await getEnvVariablesFromIdfSetup(idfSetup);
+ }
+}
+
+export async function getEnvVariablesFromActivationScript(
+ activationScriptPath: string
+) {
+ try {
+ const args =
+ process.platform === "win32"
+ ? [
+ "-ExecutionPolicy",
+ "Bypass",
+ "-NoProfile",
+ `'${activationScriptPath.replace(/'/g, "''")}'`,
+ "-e",
+ ]
+ : [`"${activationScriptPath}"`, "-e"];
+ const shellPath =
+ process.platform === "win32"
+ ? "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
+ : env.shell;
+ const envVarsOutput = await spawn(shellPath, args, {
+ maxBuffer: 500 * 1024,
+ cwd: process.cwd(),
+ shell: shellPath,
+ });
+ const envVarsArray = envVarsOutput.toString().trim().split(/\r?\n/g);
+ let envDict: { [key: string]: string } = {};
+ for (const envVar of envVarsArray) {
+ let keyIndex = envVar.indexOf("=");
+ if (keyIndex === -1) {
+ continue;
+ }
+ let varKey = envVar.slice(0, keyIndex).trim();
+ let varValue = envVar.slice(keyIndex + 1).trim();
+ envDict[varKey] = varValue;
+ }
+
+ let pathNameInEnv: string = Object.keys(process.env).find(
+ (k) => k.toUpperCase() == "PATH"
+ );
+
+ if (envDict[pathNameInEnv]) {
+ envDict[pathNameInEnv] = envDict[pathNameInEnv]
+ .replace(process.env[pathNameInEnv], "")
+ .replace(new RegExp(`(^${delimiter}|${delimiter}$)`, "g"), "");
+ }
+
+ const pyDir =
+ process.platform === "win32"
+ ? ["Scripts", "python.exe"]
+ : ["bin", "python"];
+ envDict["PYTHON"] = join(envDict["IDF_PYTHON_ENV_PATH"], ...pyDir);
+
+ return envDict;
+ } catch (error) {
+ const errMsg =
+ error && error.message
+ ? error.message
+ : "Error getting Env variables from EIM activation script";
+ Logger.error(
+ errMsg,
+ error,
+ "loadSettings getEnvVariablesFromActivationScript",
+ undefined,
+ false
+ );
+ }
+}
diff --git a/src/eim/migrationTool.ts b/src/eim/migrationTool.ts
new file mode 100644
index 000000000..103dbc21b
--- /dev/null
+++ b/src/eim/migrationTool.ts
@@ -0,0 +1,211 @@
+/*
+ * Project: ESP-IDF VSCode Extension
+ * File Created: Monday, 16th December 2024 5:48:20 pm
+ * Copyright 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { dirname, join } from "path";
+import { execChildProcess, getEspIdfFromCMake } from "../utils";
+import { IdfSetup } from "./types";
+import { pathExists, readJson } from "fs-extra";
+import { ESP } from "../config";
+import { getEnvVarsFromIdfTools, getUnixPythonList } from "../pythonManager";
+import { IdfToolsManager } from "../idfToolsManager";
+
+export async function getSystemPython(
+ espIdfPath: string,
+ espIdfToolsPath: string
+) {
+ if (process.platform !== "win32") {
+ const sysPythonList = await getUnixPythonList(__dirname);
+ return sysPythonList && sysPythonList.length ? sysPythonList[0] : "python3";
+ } else {
+ const idfVersion = await getEspIdfFromCMake(espIdfPath);
+ const pythonVersionToUse =
+ idfVersion >= "5.0"
+ ? ESP.URL.IDF_EMBED_PYTHON.VERSION
+ : ESP.URL.OLD_IDF_EMBED_PYTHON.VERSION;
+ const idfPythonPath = join(
+ espIdfToolsPath,
+ "tools",
+ "idf-python",
+ pythonVersionToUse,
+ "python.exe"
+ );
+ const idfPythonPathExists = await pathExists(idfPythonPath);
+ return idfPythonPathExists ? idfPythonPath : "";
+ }
+}
+
+export async function getIdfPythonEnvPath(
+ espIdfDir: string,
+ idfToolsDir: string,
+ pythonBin: string
+) {
+ const pythonCode = `import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor))`;
+ const args = ["-c", pythonCode];
+ const rawPythonVersion = await execChildProcess(pythonBin, args, espIdfDir);
+ if (!rawPythonVersion) {
+ throw new Error("Failed to retrieve Python version. The result is empty.");
+ }
+ const pythonVersion = rawPythonVersion.replace(/(\n|\r|\r\n)/gm, "");
+ const fullEspIdfVersion = await getEspIdfFromCMake(espIdfDir);
+ const majorMinorMatches = fullEspIdfVersion.match(/([0-9]+\.[0-9]+).*/);
+ const espIdfVersion =
+ majorMinorMatches && majorMinorMatches.length > 0
+ ? majorMinorMatches[1]
+ : "x.x";
+ const resultVersion = `idf${espIdfVersion}_py${pythonVersion}_env`;
+ return join(idfToolsDir, "python_env", resultVersion);
+}
+
+export async function getPythonEnvPath(
+ espIdfDir: string,
+ idfToolsDir: string,
+ pythonBin: string
+) {
+ const idfPyEnvPath = await getIdfPythonEnvPath(
+ espIdfDir,
+ idfToolsDir,
+ pythonBin
+ );
+ const pyDir =
+ process.platform === "win32"
+ ? ["Scripts", "python.exe"]
+ : ["bin", "python"];
+ const fullIdfPyEnvPath = join(idfPyEnvPath, ...pyDir);
+ const pyEnvPathExists = await pathExists(fullIdfPyEnvPath);
+ return pyEnvPathExists ? fullIdfPyEnvPath : "";
+}
+
+export async function getEnvVariablesFromIdfSetup(idfSetup: IdfSetup) {
+ let envVars: { [key: string]: string } = {};
+ envVars["IDF_PATH"] = idfSetup.idfPath;
+ envVars["IDF_TOOLS_PATH"] = idfSetup.toolsPath;
+ envVars["ESP_IDF_VERSION"] = idfSetup.version;
+ const idfToolsManager = await IdfToolsManager.createIdfToolsManager(
+ idfSetup.idfPath
+ );
+ const exportedToolsPaths = await idfToolsManager.exportPathsInString(
+ join(idfSetup.toolsPath, "tools"),
+ ["cmake", "ninja"]
+ );
+ envVars["PATH"] = exportedToolsPaths;
+ const idfToolsVars = await idfToolsManager.exportVars(idfSetup.toolsPath);
+
+ for (const toolVar in idfToolsVars) {
+ envVars[toolVar] = idfToolsVars[toolVar];
+ }
+
+ if (!idfSetup.python) {
+ if (!idfSetup.sysPythonPath) {
+ idfSetup.sysPythonPath = await getSystemPython(
+ idfSetup.idfPath,
+ idfSetup.toolsPath
+ );
+ }
+
+ idfSetup.python = await getPythonEnvPath(
+ idfSetup.idfPath,
+ idfSetup.toolsPath,
+ idfSetup.sysPythonPath
+ );
+ }
+ const pythonExists = await pathExists(idfSetup.python);
+
+ if (pythonExists) {
+ envVars["PYTHON"] = idfSetup.python;
+ envVars["IDF_PYTHON_ENV_PATH"] = dirname(dirname(idfSetup.python));
+ const idfVars = await getEnvVarsFromIdfTools(
+ idfSetup.idfPath,
+ idfSetup.toolsPath,
+ idfSetup.python
+ );
+ for (const idfVar in idfVars) {
+ envVars[idfVar] = idfVars[idfVar];
+ }
+ }
+ return envVars;
+}
+
+export async function loadIdfSetupsFromEspIdfJson(toolsPath: string) {
+ const espIdfJson = await loadEspIdfJson(toolsPath);
+ let idfSetups: IdfSetup[] = [];
+ if (
+ espIdfJson &&
+ espIdfJson.idfInstalled &&
+ Object.keys(espIdfJson.idfInstalled).length
+ ) {
+ for (let idfInstalledKey of Object.keys(espIdfJson.idfInstalled)) {
+ let setupConf: IdfSetup = {
+ id: idfInstalledKey,
+ idfPath: espIdfJson.idfInstalled[idfInstalledKey].path,
+ gitPath: espIdfJson.gitPath,
+ version: espIdfJson.idfInstalled[idfInstalledKey].version,
+ python: espIdfJson.idfInstalled[idfInstalledKey].python,
+ toolsPath: toolsPath,
+ isValid: false,
+ } as IdfSetup;
+ idfSetups.push(setupConf);
+ }
+ }
+ return idfSetups;
+}
+
+export interface EspIdfJson {
+ $schema: string;
+ $id: string;
+ _comment: string;
+ _warning: string;
+ gitPath: string;
+ idfToolsPath: string;
+ idfSelectedId: string;
+ idfInstalled: { [key: string]: IdfInstalled };
+}
+
+export interface IdfInstalled {
+ version: string;
+ python: string;
+ path: string;
+}
+
+export function getEspIdfJsonTemplate(toolsPath: string) {
+ return {
+ $schema: "http://json-schema.org/schema#",
+ $id: "http://dl.espressif.com/dl/schemas/esp_idf",
+ _comment: "Configuration file for ESP-IDF IDEs.",
+ _warning:
+ "Use / or \\ when specifying path. Single backslash is not allowed by JSON format.",
+ gitPath: "",
+ idfToolsPath: toolsPath,
+ idfSelectedId: "",
+ idfInstalled: {},
+ } as EspIdfJson;
+}
+
+export async function loadEspIdfJson(toolsPath: string) {
+ const espIdfJsonPath = join(toolsPath, "esp_idf.json");
+ const espIdfJsonExists = await pathExists(espIdfJsonPath);
+ let espIdfJson: EspIdfJson;
+ try {
+ if (!espIdfJsonExists) {
+ throw new Error(`${espIdfJsonPath} doesn't exists.`);
+ }
+ espIdfJson = await readJson(espIdfJsonPath);
+ } catch (error) {
+ espIdfJson = getEspIdfJsonTemplate(toolsPath);
+ }
+ return espIdfJson;
+}
diff --git a/src/views/setup/types.ts b/src/eim/types.ts
similarity index 51%
rename from src/views/setup/types.ts
rename to src/eim/types.ts
index a43d90868..9043f789b 100644
--- a/src/views/setup/types.ts
+++ b/src/eim/types.ts
@@ -1,27 +1,20 @@
/*
* Project: ESP-IDF VSCode Extension
- * File Created: Thursday, 31st August 2023 8:11:39 pm
- * Copyright 2023 Espressif Systems (Shanghai) CO LTD
- *
+ * File Created: Wednesday, 11th December 2024 2:32:14 pm
+ * Copyright 2024 Espressif Systems (Shanghai) CO LTD
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export interface IEspIdfLink {
- filename: string;
- name: string;
- mirror: string;
- url: string;
- version: string;
-}
export interface IdfSetup {
id: string;
@@ -29,43 +22,29 @@ export interface IdfSetup {
toolsPath: string;
idfPath: string;
gitPath: string;
- python?: string;
- sysPythonPath?: string;
isValid: boolean;
+ activationScript: string;
+ python: string;
+ sysPythonPath: string;
}
-export enum IdfMirror {
- Espressif,
- Github,
+export interface EspIdfJson {
+ $schema: string;
+ $id: string;
+ _comment: string;
+ _warning: string;
+ gitPath: string;
+ idfToolsPath: string;
+ idfSelectedId: string;
+ idfInstalled: IdfInstalled[];
+ eimPath: string;
}
-export interface IDownload {
+export interface IdfInstalled {
+ activationScript: string;
id: string;
- progress: string;
- progressDetail: string;
-}
-
-export interface IEspIdfTool extends IDownload {
- actual: string;
- description: string;
- doesToolExist: boolean;
- env: {};
- expected: string;
- hashResult: boolean;
- hasFailed: boolean;
+ idfToolsPath: string;
name: string;
path: string;
-}
-
-export enum StatusType {
- failed,
- installed,
- pending,
- started,
-}
-
-export enum SetupMode {
- advanced,
- express,
- existing,
-}
+ python: string;
+}
\ No newline at end of file
diff --git a/src/eim/verifySetup.ts b/src/eim/verifySetup.ts
new file mode 100644
index 000000000..188fb6585
--- /dev/null
+++ b/src/eim/verifySetup.ts
@@ -0,0 +1,191 @@
+/*
+ * Project: ESP-IDF VSCode Extension
+ * File Created: Wednesday, 11th December 2024 3:05:43 pm
+ * Copyright 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { pathExists } from "fs-extra";
+import { Logger } from "../logger/logger";
+import { IdfSetup } from "./types";
+import { startPythonReqsProcess } from "../utils";
+import { IdfToolsManager, IEspIdfTool } from "../idfToolsManager";
+import { join } from "path";
+import { ConfigurationTarget, StatusBarItem, Uri } from "vscode";
+import { writeParameter } from "../idfConfiguration";
+import { CommandKeys, createCommandDictionary } from "../cmdTreeView/cmdStore";
+import { getEnvVariables } from "./loadSettings";
+import { ESP } from "../config";
+
+/**
+ * Validate that given IDF Setup is valid.
+ * @param {IdfSetup} idfSetup IDF Setup to validate.
+ * @param logToChannel If output IDF Tools validation result in output channel
+ * @returns {[boolean, string]} Tuple: True if IDF Setup is valid, False otherwise, and fail reason
+ */
+export async function isIdfSetupValid(
+ idfSetup: IdfSetup,
+ logToChannel = true
+): Promise<[boolean, string]> {
+ try {
+ let envVars: { [key: string]: string } = await getEnvVariables(idfSetup);
+ let venvPythonPath: string = "";
+ if (idfSetup.python) {
+ venvPythonPath = idfSetup.python;
+ } else {
+ const pyDir =
+ process.platform === "win32"
+ ? ["Scripts", "python.exe"]
+ : ["bin", "python3"];
+ venvPythonPath = join(envVars["IDF_PYTHON_ENV_PATH"], ...pyDir);
+ }
+
+ if (!envVars["IDF_PATH"]) {
+ return [false, "IDF_PATH is not set in environment variables"];
+ }
+ const doesIdfPathExists = await pathExists(envVars["IDF_PATH"]);
+ if (!doesIdfPathExists) {
+ return [false, `IDF_PATH does not exist: ${envVars["IDF_PATH"]}`];
+ }
+
+ const pathNameInEnv: string = Object.keys(envVars).find(
+ (k) => k.toUpperCase() == "PATH"
+ );
+
+ const idfToolsManager = await IdfToolsManager.createIdfToolsManager(
+ envVars["IDF_PATH"]
+ );
+ let toolsInfo: IEspIdfTool[] = [];
+ const activationScriptPathExists = await pathExists(
+ idfSetup.activationScript
+ );
+ if (!activationScriptPathExists) {
+ const exportedToolsPaths = await idfToolsManager.exportPathsInString(
+ join(idfSetup.toolsPath, "tools"),
+ ["cmake", "ninja"]
+ );
+ toolsInfo = await idfToolsManager.getRequiredToolsInfo(
+ join(idfSetup.toolsPath, "tools"),
+ exportedToolsPaths,
+ ["cmake", "ninja"],
+ logToChannel
+ );
+ } else {
+ toolsInfo = await idfToolsManager.getEIMToolsInfo(
+ envVars[pathNameInEnv],
+ ["cmake", "ninja"],
+ logToChannel
+ );
+ }
+
+ const failedToolsResult = toolsInfo.filter(
+ (tInfo) =>
+ !tInfo.doesToolExist && ["cmake", "ninja"].indexOf(tInfo.name) === -1
+ );
+
+ if (failedToolsResult.length) {
+ const missingTools = failedToolsResult.map((t) => t.name).join(", ");
+ return [false, `Missing required tools: ${missingTools}`];
+ }
+ const [pyEnvReqsValid, pyEnvReqsMsg] = await checkPyVenv(
+ venvPythonPath,
+ envVars["IDF_PATH"]
+ );
+ if (!pyEnvReqsValid) {
+ return [
+ pyEnvReqsValid,
+ pyEnvReqsMsg ||
+ "Python virtual environment or requirements are not satisfied",
+ ];
+ }
+ return [true, ""];
+ } catch (error) {
+ const msg =
+ error && error.message
+ ? error.message
+ : `Error checking EIM Idf Setup for script ${idfSetup.activationScript}`;
+ Logger.error(msg, error, "verifySetup isIdfSetupValid");
+ return [false, msg];
+ }
+}
+
+export async function checkPyVenv(
+ pyVenvPath: string,
+ espIdfPath: string
+): Promise<[boolean, string]> {
+ const pyExists = await pathExists(pyVenvPath);
+ if (!pyExists) {
+ return [false, `${pyVenvPath} does not exist.`];
+ }
+ let requirements: string;
+ requirements = join(
+ espIdfPath,
+ "tools",
+ "requirements",
+ "requirements.core.txt"
+ );
+ const coreRequirementsExists = await pathExists(requirements);
+ if (!coreRequirementsExists) {
+ requirements = join(espIdfPath, "requirements.txt");
+ const requirementsExists = await pathExists(requirements);
+ if (!requirementsExists) {
+ return [false, `${requirements} doesn't exist.`];
+ }
+ }
+ const reqsResults = await startPythonReqsProcess(
+ pyVenvPath,
+ espIdfPath,
+ requirements
+ );
+ if (reqsResults.indexOf("are not satisfied") > -1) {
+ return [false, reqsResults];
+ }
+ return [true, ""];
+}
+
+export async function saveSettings(
+ setupConf: IdfSetup,
+ workspaceFolderUri: Uri,
+ espIdfStatusBar: StatusBarItem
+) {
+ await writeParameter(
+ "idf.currentSetup",
+ setupConf.idfPath,
+ ConfigurationTarget.WorkspaceFolder,
+ workspaceFolderUri
+ );
+
+ const envVars = await getEnvVariables(setupConf);
+
+ if (setupConf.python) {
+ envVars["PYTHON"] = setupConf.python;
+ }
+
+ ESP.ProjectConfiguration.store.set(
+ ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION,
+ envVars
+ );
+ await writeParameter(
+ "idf.gitPath",
+ setupConf.gitPath,
+ ConfigurationTarget.Global
+ );
+ if (espIdfStatusBar) {
+ const commandDictionary = createCommandDictionary();
+ espIdfStatusBar.text = `$(${
+ commandDictionary[CommandKeys.SelectCurrentIdfVersion].iconId
+ }) ESP-IDF v${setupConf.version}`;
+ }
+ Logger.infoNotify("ESP-IDF has been configured");
+}
diff --git a/src/espAdf/espAdfDownload.ts b/src/espAdf/espAdfDownload.ts
index babd1cef5..5a49e7904 100644
--- a/src/espAdf/espAdfDownload.ts
+++ b/src/espAdf/espAdfDownload.ts
@@ -30,5 +30,5 @@ export class AdfCloning extends AbstractCloning {
export async function getEspAdf(workspace?: Uri) {
const gitPath = (await readParameter("idf.gitPath", workspace)) || "git";
const adfInstaller = new AdfCloning(gitPath);
- await adfInstaller.getRepository("idf.espAdfPath", workspace);
+ await adfInstaller.getRepository("ADF_PATH", workspace);
}
diff --git a/src/espBom/index.ts b/src/espBom/index.ts
index 485351655..b9ed2c19b 100644
--- a/src/espBom/index.ts
+++ b/src/espBom/index.ts
@@ -27,7 +27,6 @@ import {
ProcessExecution,
} from "vscode";
import {
- appendIdfAndToolsToPath,
canAccessFile,
execChildProcess,
} from "../utils";
@@ -38,6 +37,7 @@ import { pathExists, lstat, constants } from "fs-extra";
import { Logger } from "../logger/logger";
import { TaskManager } from "../taskManager";
import { getVirtualEnvPythonPath } from "../pythonManager";
+import { configureEnvVariables } from "../common/prepareEnv";
export async function createSBOM(workspaceUri: Uri) {
try {
@@ -52,7 +52,7 @@ export async function createSBOM(workspaceUri: Uri) {
`${projectDescriptionJson} doesn't exists for ESP-IDF SBOM tasks.`
);
}
- const modifiedEnv = await appendIdfAndToolsToPath(workspaceUri);
+ const modifiedEnv = await configureEnvVariables(workspaceUri);
const sbomFilePath = readParameter(
"idf.sbomFilePath",
workspaceUri
@@ -142,8 +142,8 @@ export async function createSBOM(workspaceUri: Uri) {
}
export async function installEspSBOM(workspace: Uri) {
- const pythonBinPath = await getVirtualEnvPythonPath(workspace);
- const modifiedEnv = await appendIdfAndToolsToPath(workspace);
+ const pythonBinPath = await getVirtualEnvPythonPath();
+ const modifiedEnv = await configureEnvVariables(workspace);
try {
const showResult = await execChildProcess(
pythonBinPath,
diff --git a/src/espHomekit/espHomekitDownload.ts b/src/espHomekit/espHomekitDownload.ts
index 295bf1f63..f142d070f 100644
--- a/src/espHomekit/espHomekitDownload.ts
+++ b/src/espHomekit/espHomekitDownload.ts
@@ -35,5 +35,5 @@ export class EspHomekitCloning extends AbstractCloning {
export async function getEspHomeKitSdk(workspace: Uri) {
const gitPath = (await readParameter("idf.gitPath", workspace)) || "git";
const homeKitInstaller = new EspHomekitCloning(gitPath);
- await homeKitInstaller.getRepository("idf.espHomeKitSdkPath", workspace);
+ await homeKitInstaller.getRepository("HOMEKIT_PATH", workspace);
}
diff --git a/src/espIdf/core-dump/esp-core-dump-py-tool.ts b/src/espIdf/core-dump/esp-core-dump-py-tool.ts
index a84523eec..327125550 100644
--- a/src/espIdf/core-dump/esp-core-dump-py-tool.ts
+++ b/src/espIdf/core-dump/esp-core-dump-py-tool.ts
@@ -2,13 +2,13 @@
* Project: ESP-IDF VSCode Extension
* File Created: Friday, 3rd July 2020 5:49:06 pm
* Copyright 2020 Espressif Systems (Shanghai) CO LTD
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -17,9 +17,10 @@
*/
import { join } from "path";
-import { appendIdfAndToolsToPath, spawn } from "../../utils";
+import { spawn } from "../../utils";
import { Logger } from "../../logger/logger";
import { Uri } from "vscode";
+import { configureEnvVariables } from "../../common/prepareEnv";
export enum InfoCoreFileFormat {
Base64 = "b64",
@@ -49,7 +50,7 @@ export class ESPCoreDumpPyTool {
public async generateCoreELFFile(options: CoreELFGenerationOptions) {
let resp: Buffer;
try {
- const env = await appendIdfAndToolsToPath(options.workspaceUri);
+ const env = await configureEnvVariables(options.workspaceUri);
resp = await spawn(
options.pythonBinPath,
[
diff --git a/src/espIdf/debugAdapter/checkPyReqs.ts b/src/espIdf/debugAdapter/checkPyReqs.ts
deleted file mode 100644
index 99ca49c36..000000000
--- a/src/espIdf/debugAdapter/checkPyReqs.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Project: ESP-IDF VSCode Extension
- * File Created: Friday, 23rd February 2024 6:13:58 pm
- * Copyright 2024 Espressif Systems (Shanghai) CO LTD
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { join } from "path";
-import { readParameter } from "../../idfConfiguration";
-import { pathExists } from "fs-extra";
-import { Uri } from "vscode";
-import { extensionContext, startPythonReqsProcess } from "../../utils";
-import { getVirtualEnvPythonPath } from "../../pythonManager";
-
-export async function checkDebugAdapterRequirements(workspaceFolder: Uri) {
- const idfPath = readParameter("idf.espIdfPath", workspaceFolder);
- const pythonBinPath = await getVirtualEnvPythonPath(workspaceFolder);
- let requirementsPath = join(
- extensionContext.extensionPath,
- "esp_debug_adapter",
- "requirements.txt"
- );
- let checkResult: string;
- try {
- const doesPyTestRequirementsExists = await pathExists(requirementsPath);
- if (!doesPyTestRequirementsExists) {
- return false;
- }
- checkResult = await startPythonReqsProcess(
- pythonBinPath,
- idfPath,
- requirementsPath
- );
- } catch (error) {
- checkResult = error && error.message ? error.message : " are not satisfied";
- }
- if (checkResult.indexOf("are satisfied") > -1) {
- return true;
- }
- return false;
-}
diff --git a/src/espIdf/debugAdapter/debugAdapterManager.ts b/src/espIdf/debugAdapter/debugAdapterManager.ts
deleted file mode 100644
index 6fd5e52eb..000000000
--- a/src/espIdf/debugAdapter/debugAdapterManager.ts
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- * Project: ESP-IDF VSCode Extension
- * File Created: Friday, 12th July 2019 5:59:07 pm
- * Copyright 2019 Espressif Systems (Shanghai) CO LTD
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { ChildProcess, spawn } from "child_process";
-import { EventEmitter } from "events";
-import * as path from "path";
-import * as vscode from "vscode";
-import * as idfConf from "../../idfConfiguration";
-import { Logger } from "../../logger/logger";
-import {
- appendIdfAndToolsToPath,
- canAccessFile,
- getToolchainToolName,
- isBinInPath,
- PreCheck,
-} from "../../utils";
-import { EOL } from "os";
-import { outputFile, constants } from "fs-extra";
-import { createFlashModel } from "../../flash/flashModelBuilder";
-import { OutputChannel } from "../../logger/outputChannel";
-import { getVirtualEnvPythonPath } from "../../pythonManager";
-import { getIdfTargetFromSdkconfig } from "../../workspaceConfig";
-
-export interface IDebugAdapterConfig {
- appOffset?: string;
- tmoScaleFactor?: number;
- coreDumpFile?: string;
- currentWorkspace?: vscode.Uri;
- debugAdapterPort?: number;
- elfFile?: string;
- env?: NodeJS.ProcessEnv;
- gdbinitFilePath?: string;
- initGdbCommands?: string[];
- isPostMortemDebugMode: boolean;
- isOocdDisabled: boolean;
- logLevel?: number;
- target?: string;
-}
-
-export class DebugAdapterManager extends EventEmitter {
- public static init(context: vscode.ExtensionContext): DebugAdapterManager {
- if (!DebugAdapterManager.instance) {
- DebugAdapterManager.instance = new DebugAdapterManager(context);
- }
- return DebugAdapterManager.instance;
- }
- private static instance: DebugAdapterManager;
-
- private adapter: ChildProcess;
- private appOffset: string;
- private chan: Buffer;
- private coreDumpFile: string;
- private currentWorkspace: vscode.Uri;
- private debugAdapterPath: string;
- private elfFile: string;
- private env;
- private gdbinitFilePath: string;
- private initGdbCommands: string[];
- private tmoScaleFactor?: number;
- private isPostMortemDebugMode: boolean;
- private isOocdDisabled: boolean;
- private logLevel: number;
- private port: number;
- private target: string;
-
- private constructor(context: vscode.ExtensionContext) {
- super();
- this.configureWithDefaultValues(context.extensionUri);
- OutputChannel.init();
- this.chan = Buffer.alloc(0);
- }
-
- public start() {
- return new Promise(async (resolve, reject) => {
- if (this.isRunning()) {
- return;
- }
- if (!isBinInPath("openocd", this.env)) {
- return reject(
- new Error("Invalid OpenOCD bin path or access is denied for the user")
- );
- }
- if (
- this.env &&
- !this.isOocdDisabled &&
- typeof this.env.OPENOCD_SCRIPTS === "undefined"
- ) {
- return reject(
- new Error(
- "Invalid OpenOCD script path or access is denied for the user"
- )
- );
- }
- if (!canAccessFile(this.elfFile, constants.R_OK)) {
- return reject(new Error(`${this.elfFile} doesn't exist. Build first.`));
- }
- const logFile = path.join(this.currentWorkspace.fsPath, "debug") + ".log";
-
- if (!this.appOffset) {
- const serialPort = await idfConf.readSerialPort(this.currentWorkspace, false);
- if (!serialPort) {
- return reject(
- new Error(
- vscode.l10n.t(
- "No serial port found for current IDF_TARGET: {0}",
- this.target
- )
- )
- );
- }
- const flashBaudRate = await idfConf.readParameter(
- "idf.flashBaudRate",
- this.currentWorkspace
- );
- const buildDirPath = idfConf.readParameter(
- "idf.buildPath",
- this.currentWorkspace
- ) as string;
- const flasherArgsJsonPath = path.join(
- buildDirPath,
- "flasher_args.json"
- );
- if (!canAccessFile(flasherArgsJsonPath, constants.R_OK)) {
- return reject(
- new Error(`${flasherArgsJsonPath} doesn't exist. Build first.`)
- );
- }
- const model = await createFlashModel(
- flasherArgsJsonPath,
- serialPort,
- flashBaudRate
- );
- this.appOffset = model.app.address;
- }
- const pythonBinPath = await getVirtualEnvPythonPath(
- this.currentWorkspace
- );
-
- const toolchainPrefix = getToolchainToolName(this.target, "");
- const adapterArgs = [
- this.debugAdapterPath,
- "-d",
- this.logLevel.toString(),
- "-e",
- this.elfFile,
- "-l",
- logFile,
- "-p",
- this.port.toString(),
- "-dn",
- this.target,
- "-a",
- this.appOffset,
- "-t",
- toolchainPrefix,
- ];
- if (this.isPostMortemDebugMode) {
- adapterArgs.push("-pm");
- }
- if (this.coreDumpFile) {
- adapterArgs.push("-c", this.coreDumpFile);
- }
- if (this.isOocdDisabled) {
- adapterArgs.push("-om", "without_oocd");
- }
- const resultGdbInitFile = this.gdbinitFilePath
- ? this.gdbinitFilePath
- : await this.makeGdbinitFile();
- if (resultGdbInitFile) {
- adapterArgs.push("-x", resultGdbInitFile);
- }
- if (this.tmoScaleFactor) {
- adapterArgs.push("-tsf", this.tmoScaleFactor.toString());
- }
- this.adapter = spawn(pythonBinPath, adapterArgs, { env: this.env });
-
- this.adapter.stderr.on("data", (data) => {
- data = typeof data === "string" ? Buffer.from(data) : data;
- this.sendToOutputChannel(data);
- OutputChannel.appendLine(data.toString(), "Debug Adapter");
- Logger.info(data.toString());
- this.emit("error", data, this.chan);
- });
-
- this.adapter.stdout.on("data", (data) => {
- data = typeof data === "string" ? Buffer.from(data) : data;
- this.sendToOutputChannel(data);
- OutputChannel.appendLine(data.toString(), "Debug Adapter");
- Logger.info(data.toString());
- this.emit("data", this.chan);
- if (data.toString().trim().endsWith("DEBUG_ADAPTER_READY2CONNECT")) {
- return resolve(true);
- }
- });
-
- this.adapter.on("error", (error) => {
- this.emit("error", error, this.chan);
- this.stop();
- return reject(error);
- });
-
- this.adapter.on("close", (code: number, signal: string) => {
- if (!signal && code && code !== 0) {
- Logger.errorNotify(
- `ESP-IDF Debug Adapter exit with error code ${code}`,
- new Error("Spawn exit with non-zero" + code),
- "DebugAdapterManager start"
- );
- }
- this.stop();
- });
- OutputChannel.show();
- });
- }
-
- public stop() {
- if (this.adapter && !this.adapter.killed) {
- this.isPostMortemDebugMode = false;
- this.initGdbCommands = [];
- this.adapter.kill("SIGKILL");
- this.adapter = undefined;
- const stoppedMsg = "[Stopped] : ESP-IDF Debug Adapter";
- Logger.info(stoppedMsg);
- OutputChannel.appendLine(stoppedMsg);
- }
- }
-
- public configureAdapter(config: IDebugAdapterConfig) {
- if (config.coreDumpFile) {
- this.coreDumpFile = config.coreDumpFile;
- }
- if (config.currentWorkspace) {
- this.currentWorkspace = config.currentWorkspace;
- }
- if (config.debugAdapterPort) {
- this.port = config.debugAdapterPort;
- }
- if (config.elfFile) {
- this.elfFile = config.elfFile;
- }
- if (config.env) {
- for (const envVar of Object.keys(config.env)) {
- this.env[envVar] = config.env[envVar];
- }
- }
- if (config.gdbinitFilePath) {
- this.gdbinitFilePath = config.gdbinitFilePath;
- }
- if (config.initGdbCommands) {
- this.initGdbCommands = config.initGdbCommands;
- }
- this.isPostMortemDebugMode = config.isPostMortemDebugMode;
- if (config.logLevel) {
- this.logLevel = config.logLevel;
- }
- if (config.target) {
- this.target = config.target;
- }
- if (config.isOocdDisabled) {
- this.isOocdDisabled = config.isOocdDisabled;
- }
- this.tmoScaleFactor = config.tmoScaleFactor;
- this.appOffset = config.appOffset;
- }
-
- public isRunning(): boolean {
- return this.adapter && !this.adapter.killed;
- }
-
- private async configureWithDefaultValues(extensionPath: vscode.Uri) {
- this.currentWorkspace = PreCheck.isWorkspaceFolderOpen()
- ? vscode.workspace.workspaceFolders[0].uri
- : extensionPath;
- this.debugAdapterPath = path.join(
- extensionPath.fsPath,
- "esp_debug_adapter",
- "debug_adapter_main.py"
- );
- this.isPostMortemDebugMode = false;
- this.isOocdDisabled = false;
- this.port = 43474;
- this.logLevel = 0;
- let idfTarget = await getIdfTargetFromSdkconfig(this.currentWorkspace);
- this.target = idfTarget;
- this.env = await appendIdfAndToolsToPath(this.currentWorkspace);
- this.env.PYTHONPATH = path.join(
- extensionPath.fsPath,
- "esp_debug_adapter",
- "debug_adapter"
- );
- this.initGdbCommands = [];
- this.elfFile = "";
- if (this.currentWorkspace) {
- const buildDirPath = idfConf.readParameter(
- "idf.buildPath",
- this.currentWorkspace
- ) as string;
- this.elfFile = `${path.join(buildDirPath, "project-name")}.elf`;
- }
- }
-
- private sendToOutputChannel(data: Buffer) {
- this.chan = Buffer.concat([this.chan, data]);
- }
-
- private async makeGdbinitFile() {
- try {
- if (this.initGdbCommands && this.initGdbCommands.length > 0) {
- let result = "";
- for (const initCmd of this.initGdbCommands) {
- result = result + initCmd + EOL;
- }
- const lastValue = result.lastIndexOf(EOL);
- result = result.substring(0, lastValue);
-
- const resultGdbInitPath = path.join(
- this.currentWorkspace.fsPath,
- "esp-idf-vscode-generated.gdb"
- );
- await outputFile(resultGdbInitPath, result);
- return resultGdbInitPath;
- }
- } catch (error) {
- Logger.errorNotify(
- "Error creating gdbinit file",
- error,
- "DebugAdapterManager makeGdbinitFile"
- );
- }
- return;
- }
-}
diff --git a/src/espIdf/debugAdapter/verifyApp.ts b/src/espIdf/debugAdapter/verifyApp.ts
index d2fc9357e..1b0a3e768 100644
--- a/src/espIdf/debugAdapter/verifyApp.ts
+++ b/src/espIdf/debugAdapter/verifyApp.ts
@@ -21,12 +21,13 @@ import { l10n, Uri } from "vscode";
import { createFlashModel } from "../../flash/flashModelBuilder";
import { readParameter, readSerialPort } from "../../idfConfiguration";
import { Logger } from "../../logger/logger";
-import { appendIdfAndToolsToPath, spawn } from "../../utils";
+import { spawn } from "../../utils";
import { pathExists } from "fs-extra";
import { getVirtualEnvPythonPath } from "../../pythonManager";
+import { configureEnvVariables } from "../../common/prepareEnv";
export async function verifyAppBinary(workspaceFolder: Uri) {
- const modifiedEnv = await appendIdfAndToolsToPath(workspaceFolder);
+ const modifiedEnv = await configureEnvVariables(workspaceFolder);
const serialPort = await readSerialPort(workspaceFolder, false);
if (!serialPort) {
return Logger.warnNotify(
@@ -37,10 +38,9 @@ export async function verifyAppBinary(workspaceFolder: Uri) {
);
}
const flashBaudRate = readParameter("idf.flashBaudRate", workspaceFolder);
- const idfPath = readParameter("idf.espIdfPath", workspaceFolder);
- const pythonBinPath = await getVirtualEnvPythonPath(workspaceFolder);
+ const pythonBinPath = await getVirtualEnvPythonPath();
const esptoolPath = join(
- idfPath,
+ modifiedEnv["IDF_PATH"],
"components",
"esptool_py",
"esptool",
diff --git a/src/espIdf/documentation/getDocsVersion.ts b/src/espIdf/documentation/getDocsVersion.ts
index e172be774..4917694c9 100644
--- a/src/espIdf/documentation/getDocsVersion.ts
+++ b/src/espIdf/documentation/getDocsVersion.ts
@@ -13,16 +13,22 @@
// limitations under the License.
import { ESP } from "../../config";
-import { pathExists, readFile, readJSON, stat, writeJSON } from "fs-extra";
+import {
+ createWriteStream,
+ pathExists,
+ readFile,
+ readJSON,
+ stat,
+ writeJSON,
+} from "fs-extra";
import { tmpdir } from "os";
import { basename, join } from "path";
-import { DownloadManager } from "../../downloadManager";
import jsonic from "jsonic";
import { Logger } from "../../logger/logger";
import { extensionContext, getEspIdfFromCMake } from "../../utils";
import * as vscode from "vscode";
-import * as idfConf from "../../idfConfiguration";
import { getIdfTargetFromSdkconfig } from "../../workspaceConfig";
+import axios from "axios";
export interface IEspIdfDocVersion {
name: string;
@@ -110,9 +116,8 @@ export async function getDocsIndex(
}
export async function readObjectFromUrlFile(objectUrl: string) {
- const downloadManager = new DownloadManager(tmpdir());
- await downloadManager.downloadWithResume(objectUrl, tmpdir());
const fileName = join(tmpdir(), basename(objectUrl));
+ await downloadFile(objectUrl, fileName);
const objectStr = await readFile(fileName, "utf-8");
const objectMatches = objectStr.match(/{[\s\S]+}/g);
if (objectMatches && objectMatches.length > 0) {
@@ -120,6 +125,29 @@ export async function readObjectFromUrlFile(objectUrl: string) {
}
}
+async function downloadFile(url: string, outputLocationPath: string) {
+ const writer = createWriteStream(outputLocationPath);
+ try {
+ const response = await axios({
+ url,
+ method: "GET",
+ responseType: "stream",
+ });
+ response.data.pipe(writer);
+ return new Promise((resolve, reject) => {
+ writer.on("finish", resolve);
+ writer.on("error", reject);
+ });
+ } catch (error) {
+ Logger.error(
+ `Error downloading ${basename(url)}: ${error}`,
+ error,
+ "getDocsVersion downloadFile"
+ );
+ throw error;
+ }
+}
+
/**
* Retrieves the URL for the specified documentation part based on the ESP-IDF version and workspace.
* @param documentationPart - The documentation part to retrieve the URL for.
@@ -130,10 +158,10 @@ export async function getDocsUrl(
documentationPart: string,
workspace: vscode.Uri
) {
- const espIdfPath = idfConf.readParameter(
- "idf.espIdfPath",
- workspace
- ) as string;
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const espIdfPath = currentEnvVars["IDF_PATH"];
const adapterTargetName = await getIdfTargetFromSdkconfig(workspace);
const idfVersion = await getEspIdfFromCMake(espIdfPath);
diff --git a/src/espIdf/documentation/getSearchResults.ts b/src/espIdf/documentation/getSearchResults.ts
index 3fb5651e1..4c99df688 100644
--- a/src/espIdf/documentation/getSearchResults.ts
+++ b/src/espIdf/documentation/getSearchResults.ts
@@ -17,6 +17,7 @@ import * as idfConf from "../../idfConfiguration";
import { getEspIdfFromCMake } from "../../utils";
import { getDocsBaseUrl, getDocsIndex, getDocsVersion } from "./getDocsVersion";
import { getIdfTargetFromSdkconfig } from "../../workspaceConfig";
+import { ESP } from "../../config";
export class IDocResult {
public name: string;
@@ -44,9 +45,10 @@ export async function seachInEspDocs(
workspaceFolder: Uri
) {
const docsVersions = await getDocsVersion();
- const idfPath =
- idfConf.readParameter("idf.espIdfPath", workspaceFolder) ||
- process.env.IDF_PATH;
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const idfPath = currentEnvVars["IDF_PATH"];
let idfVersion = "v" + (await getEspIdfFromCMake(idfPath));
let idfTarget = await getIdfTargetFromSdkconfig(workspaceFolder);
let docVersion = docsVersions.find((docVer) => docVer.name === idfVersion);
diff --git a/src/espIdf/menuconfig/confServerProcess.ts b/src/espIdf/menuconfig/confServerProcess.ts
index 1d076be68..27793a9a4 100644
--- a/src/espIdf/menuconfig/confServerProcess.ts
+++ b/src/espIdf/menuconfig/confServerProcess.ts
@@ -24,7 +24,6 @@ import * as idfConf from "../../idfConfiguration";
import { Logger } from "../../logger/logger";
import { OutputChannel } from "../../logger/outputChannel";
import {
- appendIdfAndToolsToPath,
delConfigFile,
getSDKConfigFilePath,
isStringNotEmpty,
@@ -33,6 +32,8 @@ import { KconfigMenuLoader } from "./kconfigMenuLoader";
import { Menu, menuType } from "./Menu";
import { MenuConfigPanel } from "./MenuconfigPanel";
import { getVirtualEnvPythonPath } from "../../pythonManager";
+import { configureEnvVariables } from "../../common/prepareEnv";
+import { ESP } from "../../config";
export class ConfserverProcess {
public static async initWithProgress(
@@ -71,8 +72,8 @@ export class ConfserverProcess {
public static async init(workspaceFolder: vscode.Uri, extensionPath: string) {
return new Promise(async (resolve) => {
- const pythonBinPath = await getVirtualEnvPythonPath(workspaceFolder);
- const modifiedEnv = await appendIdfAndToolsToPath(workspaceFolder);
+ const pythonBinPath = await getVirtualEnvPythonPath();
+ const modifiedEnv = await configureEnvVariables(workspaceFolder);
if (!ConfserverProcess.instance) {
const configFile = await getSDKConfigFilePath(workspaceFolder);
ConfserverProcess.instance = new ConfserverProcess(
@@ -207,12 +208,13 @@ export class ConfserverProcess {
ConfserverProcess.instance.areValuesSaved = true;
const currWorkspace = ConfserverProcess.instance.workspaceFolder;
await delConfigFile(currWorkspace);
- const guiconfigEspPath =
- idfConf.readParameter("idf.espIdfPath", currWorkspace) ||
- process.env.IDF_PATH;
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const guiconfigEspPath = currentEnvVars["IDF_PATH"];
const idfPyPath = path.join(guiconfigEspPath, "tools", "idf.py");
- const modifiedEnv = await appendIdfAndToolsToPath(currWorkspace);
- const pythonBinPath = await getVirtualEnvPythonPath(currWorkspace);
+ const modifiedEnv = await configureEnvVariables(currWorkspace);
+ const pythonBinPath = await getVirtualEnvPythonPath();
const enableCCache = idfConf.readParameter(
"idf.enableCCache",
currWorkspace
@@ -329,9 +331,10 @@ export class ConfserverProcess {
this.workspaceFolder = workspaceFolder;
this.extensionPath = extensionPath;
this.emitter = new EventEmitter();
- this.espIdfPath =
- idfConf.readParameter("idf.espIdfPath", workspaceFolder).toString() ||
- process.env.IDF_PATH;
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ this.espIdfPath = currentEnvVars["IDF_PATH"];
modifiedEnv.PYTHONUNBUFFERED = "0";
this.configFile = configFile;
const idfPath = path.join(this.espIdfPath, "tools", "idf.py");
diff --git a/src/espIdf/menuconfig/saveDefConfig.ts b/src/espIdf/menuconfig/saveDefConfig.ts
index ea16f7b18..2607204a5 100644
--- a/src/espIdf/menuconfig/saveDefConfig.ts
+++ b/src/espIdf/menuconfig/saveDefConfig.ts
@@ -31,9 +31,10 @@ import { TaskManager } from "../../taskManager";
import { NotificationMode, readParameter } from "../../idfConfiguration";
import { Logger } from "../../logger/logger";
import { join } from "path";
-import { appendIdfAndToolsToPath } from "../../utils";
import { pathExists } from "fs-extra";
import { getVirtualEnvPythonPath } from "../../pythonManager";
+import { configureEnvVariables } from "../../common/prepareEnv";
+import { ESP } from "../../config";
export async function saveDefSdkconfig(
workspaceFolder: Uri,
@@ -45,7 +46,10 @@ export async function saveDefSdkconfig(
TaskManager.disposeListeners();
});
}
- const idfPath = readParameter("idf.espIdfPath", workspaceFolder);
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const idfPath = currentEnvVars["IDF_PATH"];
const notificationMode = readParameter(
"idf.notificationMode",
workspaceFolder
@@ -92,12 +96,12 @@ export async function getSaveDefConfigExecution(
wsFolder: Uri
) {
const saveDefConfArgs = [join(idfPath, "tools", "idf.py"), "save-defconfig"];
- const modifiedEnv = await appendIdfAndToolsToPath(wsFolder);
+ const modifiedEnv = await configureEnvVariables(wsFolder);
const options: ProcessExecutionOptions = {
cwd: wsFolder.fsPath,
env: modifiedEnv,
};
- const pythonBinPath = await getVirtualEnvPythonPath(wsFolder);
+ const pythonBinPath = await getVirtualEnvPythonPath();
const pythonBinExists = await pathExists(pythonBinPath);
if (!pythonBinExists) {
throw new Error(
diff --git a/src/espIdf/monitor/checkWebsocketClient.ts b/src/espIdf/monitor/checkWebsocketClient.ts
index 1293af551..116ad7e6a 100644
--- a/src/espIdf/monitor/checkWebsocketClient.ts
+++ b/src/espIdf/monitor/checkWebsocketClient.ts
@@ -18,12 +18,13 @@
import { Uri } from "vscode";
import { OutputChannel } from "../../logger/outputChannel";
-import { appendIdfAndToolsToPath, execChildProcess } from "../../utils";
+import { execChildProcess } from "../../utils";
import { getVirtualEnvPythonPath } from "../../pythonManager";
+import { configureEnvVariables } from "../../common/prepareEnv";
export async function installWebsocketClient(workspace: Uri) {
- const pythonBinPath = await getVirtualEnvPythonPath(workspace);
- const modifiedEnv = await appendIdfAndToolsToPath(workspace);
+ const pythonBinPath = await getVirtualEnvPythonPath();
+ const modifiedEnv = await configureEnvVariables(workspace);
try {
const showResult = await execChildProcess(
pythonBinPath,
diff --git a/src/espIdf/monitor/command.ts b/src/espIdf/monitor/command.ts
index b975ed6c0..580c6eaf7 100644
--- a/src/espIdf/monitor/command.ts
+++ b/src/espIdf/monitor/command.ts
@@ -71,7 +71,7 @@ export async function createNewIdfMonitor(
"createNewIdfMonitor select a serial port"
);
}
- const pythonBinPath = await getVirtualEnvPythonPath(workspaceFolder);
+ const pythonBinPath = await getVirtualEnvPythonPath();
if (!utils.canAccessFile(pythonBinPath, R_OK)) {
Logger.errorNotify(
"Python binary path is not defined",
@@ -79,7 +79,10 @@ export async function createNewIdfMonitor(
"createNewIdfMonitor pythonBinPath not defined"
);
}
- const idfPath = readParameter("idf.espIdfPath", workspaceFolder) as string;
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const idfPath = currentEnvVars["IDF_PATH"];
const idfVersion = await utils.getEspIdfFromCMake(idfPath);
let sdkMonitorBaudRate: string = await utils.getMonitorBaudRate(
workspaceFolder
@@ -138,8 +141,6 @@ export async function createNewIdfMonitor(
}
IDFMonitor.start();
if (noReset) {
- const idfPath = readParameter("idf.espIdfPath", workspaceFolder) as string;
- const idfVersion = await utils.getEspIdfFromCMake(idfPath);
if (idfVersion <= "5.0") {
const monitorDelay = readParameter(
"idf.monitorDelay",
diff --git a/src/espIdf/monitor/index.ts b/src/espIdf/monitor/index.ts
index 9d9bdbae2..93480a6b5 100644
--- a/src/espIdf/monitor/index.ts
+++ b/src/espIdf/monitor/index.ts
@@ -16,8 +16,9 @@
* limitations under the License.
*/
+import { configureEnvVariables } from "../../common/prepareEnv";
import { ESP } from "../../config";
-import { appendIdfAndToolsToPath, getUserShell } from "../../utils";
+import { getUserShell } from "../../utils";
import { window, Terminal, Uri, env, debug } from "vscode";
import { join } from "path";
@@ -48,9 +49,7 @@ export class IDFMonitor {
}
static async start() {
- const modifiedEnv = await appendIdfAndToolsToPath(
- this.config.workspaceFolder
- );
+ const modifiedEnv = await configureEnvVariables(this.config.workspaceFolder);
if (!IDFMonitor.terminal) {
IDFMonitor.terminal = window.createTerminal({
name: `ESP-IDF Monitor ${this.config.wsPort ? "(--ws enabled)" : ""}`,
diff --git a/src/espIdf/nvs/partitionTable/panel.ts b/src/espIdf/nvs/partitionTable/panel.ts
index 0e648a352..4a099fad0 100644
--- a/src/espIdf/nvs/partitionTable/panel.ts
+++ b/src/espIdf/nvs/partitionTable/panel.ts
@@ -23,6 +23,7 @@ import * as idfConf from "../../../idfConfiguration";
import { canAccessFile, execChildProcess } from "../../../utils";
import { OutputChannel } from "../../../logger/outputChannel";
import { getVirtualEnvPythonPath } from "../../../pythonManager";
+import { ESP } from "../../../config";
export class NVSPartitionTable {
private static currentPanel: NVSPartitionTable;
@@ -142,11 +143,12 @@ export class NVSPartitionTable {
partitionSize: string
) {
try {
- const idfPathDir =
- idfConf.readParameter("idf.espIdfPath", this.workspaceFolder) ||
- process.env.IDF_PATH;
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const idfPathDir = currentEnvVars["IDF_PATH"];
- const pythonBinPath = await getVirtualEnvPythonPath(this.workspaceFolder);
+ const pythonBinPath = await getVirtualEnvPythonPath();
const dirPath = dirname(this.filePath);
const fileName = basename(this.filePath);
const resultName = fileName.replace(".csv", ".bin");
@@ -173,7 +175,7 @@ export class NVSPartitionTable {
Logger.errorNotify(
"nvs_partition_gen.py is not defined",
new Error(
- "nvs_partition_gen.py is not defined, Make sure idf.espIdfPath is correct."
+ "nvs_partition_gen.py is not defined, Make sure IDF_PATH in your setup is correct."
),
"NVSPartitionTable generateNvsPartition, toolsPath"
);
diff --git a/src/espIdf/openOcd/boardConfiguration.ts b/src/espIdf/openOcd/boardConfiguration.ts
index f00eaff33..a60e50ea1 100644
--- a/src/espIdf/openOcd/boardConfiguration.ts
+++ b/src/espIdf/openOcd/boardConfiguration.ts
@@ -21,7 +21,6 @@ import { readJSON } from "fs-extra";
import { Logger } from "../../logger/logger";
import { commands, ConfigurationTarget, l10n, Uri, window } from "vscode";
import { defaultBoards } from "./defaultBoards";
-import { IdfToolsManager } from "../../idfToolsManager";
import { getIdfTargetFromSdkconfig } from "../../workspaceConfig";
export interface IdfBoard {
@@ -32,22 +31,13 @@ export interface IdfBoard {
}
export async function getOpenOcdScripts(workspace: Uri): Promise {
- const idfPathDir = readParameter("idf.espIdfPath", workspace) as string;
- const toolsPath = readParameter("idf.toolsPath", workspace) as string;
- const userExtraVars = readParameter("idf.customExtraVars", workspace) as {
- [key: string]: string;
- };
- const idfToolsManager = await IdfToolsManager.createIdfToolsManager(
- idfPathDir
- );
- const idfExtraVars = await idfToolsManager.exportVars(
- join(toolsPath, "tools")
- );
+ const userExtraVars = readParameter(
+ "idf.customExtraVars",
+ workspace
+ ) as { [key: string]: string };
let openOcdScriptsPath: string;
try {
- openOcdScriptsPath = idfExtraVars.hasOwnProperty("OPENOCD_SCRIPTS")
- ? idfExtraVars.OPENOCD_SCRIPTS
- : userExtraVars.hasOwnProperty("OPENOCD_SCRIPTS")
+ openOcdScriptsPath = userExtraVars.hasOwnProperty("OPENOCD_SCRIPTS")
? userExtraVars.OPENOCD_SCRIPTS
: process.env.OPENOCD_SCRIPTS
? process.env.OPENOCD_SCRIPTS
diff --git a/src/espIdf/openOcd/openOcdManager.ts b/src/espIdf/openOcd/openOcdManager.ts
index d01e0c0b6..654128e94 100644
--- a/src/espIdf/openOcd/openOcdManager.ts
+++ b/src/espIdf/openOcd/openOcdManager.ts
@@ -23,7 +23,6 @@ import * as idfConf from "../../idfConfiguration";
import { Logger } from "../../logger/logger";
import { OutputChannel } from "../../logger/outputChannel";
import {
- appendIdfAndToolsToPath,
isBinInPath,
PreCheck,
spawn as sspawn,
@@ -35,6 +34,7 @@ import {
CommandKeys,
createCommandDictionary,
} from "../../cmdTreeView/cmdStore";
+import { configureEnvVariables } from "../../common/prepareEnv";
export interface IOpenOCDConfig {
workspace: vscode.Uri;
@@ -60,7 +60,7 @@ export class OpenOCDManager extends EventEmitter {
}
public async version(): Promise {
- const modifiedEnv = await appendIdfAndToolsToPath(this.workspace);
+ const modifiedEnv = await configureEnvVariables(this.workspace);
const openOcdPath = await isBinInPath("openocd", modifiedEnv, [
"openocd-esp32",
]);
@@ -159,7 +159,7 @@ export class OpenOCDManager extends EventEmitter {
if (this.isRunning()) {
return;
}
- const modifiedEnv = await appendIdfAndToolsToPath(this.workspace);
+ const modifiedEnv = await configureEnvVariables(this.workspace);
const openOcdPath = await isBinInPath("openocd", modifiedEnv, [
"openocd-esp32",
]);
diff --git a/src/espIdf/partition-table/partitionFlasher.ts b/src/espIdf/partition-table/partitionFlasher.ts
index db5917e86..77fd92b13 100644
--- a/src/espIdf/partition-table/partitionFlasher.ts
+++ b/src/espIdf/partition-table/partitionFlasher.ts
@@ -2,13 +2,13 @@
* Project: ESP-IDF VSCode Extension
* File Created: Monday, 19th July 2021 7:11:49 pm
* Copyright 2021 Espressif Systems (Shanghai) CO LTD
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -24,8 +24,9 @@ import {
readSerialPort,
} from "../../idfConfiguration";
import { Logger } from "../../logger/logger";
-import { appendIdfAndToolsToPath, spawn } from "../../utils";
+import { spawn } from "../../utils";
import { getVirtualEnvPythonPath } from "../../pythonManager";
+import { configureEnvVariables } from "../../common/prepareEnv";
export async function flashBinaryToPartition(
offset: string,
@@ -49,7 +50,7 @@ export async function flashBinaryToPartition(
},
async (progress: Progress<{ message: string; increment: number }>) => {
try {
- const modifiedEnv = await appendIdfAndToolsToPath(workspaceFolder);
+ const modifiedEnv = await configureEnvVariables(workspaceFolder);
const serialPort = await readSerialPort(workspaceFolder, false);
if (!serialPort) {
return Logger.warnNotify(
@@ -59,8 +60,8 @@ export async function flashBinaryToPartition(
)
);
}
- const idfPath = readParameter("idf.espIdfPath", workspaceFolder);
- const pythonBinPath = await getVirtualEnvPythonPath(workspaceFolder);
+ const idfPath = modifiedEnv["IDF_PATH"];
+ const pythonBinPath = await getVirtualEnvPythonPath();
const esptoolPath = join(
idfPath,
"components",
diff --git a/src/espIdf/partition-table/partitionReader.ts b/src/espIdf/partition-table/partitionReader.ts
index 3d4b70063..dff8ba661 100644
--- a/src/espIdf/partition-table/partitionReader.ts
+++ b/src/espIdf/partition-table/partitionReader.ts
@@ -19,9 +19,10 @@ import { dirname, join } from "path";
import { l10n, Progress, ProgressLocation, Uri, window } from "vscode";
import { NotificationMode, readParameter, readSerialPort } from "../../idfConfiguration";
import { Logger } from "../../logger/logger";
-import { appendIdfAndToolsToPath, spawn } from "../../utils";
+import { spawn } from "../../utils";
import { getVirtualEnvPythonPath } from "../../pythonManager";
import { ensureDir } from "fs-extra";
+import { configureEnvVariables } from "../../common/prepareEnv";
export async function readPartition(
name: string,
@@ -46,7 +47,7 @@ export async function readPartition(
},
async (progress: Progress<{ message: string; increment: number }>) => {
try {
- const modifiedEnv = await appendIdfAndToolsToPath(workspaceFolder);
+ const modifiedEnv = await configureEnvVariables(this.config.workspaceFolder);
const serialPort = await readSerialPort(workspaceFolder, false);
if (!serialPort) {
return Logger.warnNotify(
@@ -56,8 +57,8 @@ export async function readPartition(
)
);
}
- const idfPath = readParameter("idf.espIdfPath", workspaceFolder);
- const pythonBinPath = await getVirtualEnvPythonPath(workspaceFolder);
+ const idfPath = modifiedEnv.IDF_PATH;
+ const pythonBinPath = await getVirtualEnvPythonPath();
const esptoolPath = join(
idfPath,
"components",
diff --git a/src/espIdf/partition-table/tree.ts b/src/espIdf/partition-table/tree.ts
index a39bc2732..adcb18396 100644
--- a/src/espIdf/partition-table/tree.ts
+++ b/src/espIdf/partition-table/tree.ts
@@ -17,7 +17,6 @@
*/
import { ensureDir, pathExists, readFile, stat } from "fs-extra";
-import { EOL } from "os";
import { join } from "path";
import {
Disposable,
@@ -29,20 +28,15 @@ import {
TreeItem,
Uri,
window,
- workspace,
} from "vscode";
import { readParameter, readSerialPort } from "../../idfConfiguration";
import { Logger } from "../../logger/logger";
-import {
- appendIdfAndToolsToPath,
- getConfigValueFromSDKConfig,
- PreCheck,
- spawn,
-} from "../../utils";
import { CSV2JSON } from "../../views/partition-table/util";
import { getVirtualEnvPythonPath } from "../../pythonManager";
import { createFlashModel } from "../../flash/flashModelBuilder";
import { formatAsPartitionSize } from "./partitionReader";
+import { spawn } from "../../utils";
+import { configureEnvVariables } from "../../common/prepareEnv";
export class PartitionItem extends TreeItem {
name: string;
@@ -84,10 +78,7 @@ export class PartitionTreeDataProvider
public async populatePartitionItems(workspace: Uri) {
this.partitionItems = Array(0);
try {
- const idfPath = readParameter("idf.espIdfPath", workspace);
- const buildPath = readParameter("idf.buildPath", workspace);
- const flashBaudRate = readParameter("idf.flashBaudRate", workspace);
- const modifiedEnv = await appendIdfAndToolsToPath(workspace);
+ const modifiedEnv = await configureEnvVariables(workspace);
const serialPort = readParameter("idf.port", workspace) as string;
if (!serialPort) {
return Logger.warnNotify(
@@ -97,7 +88,10 @@ export class PartitionTreeDataProvider
)
);
}
- const pythonBinPath = await getVirtualEnvPythonPath(workspace);
+ const buildPath = readParameter("idf.buildPath", workspace);
+ const flashBaudRate = readParameter("idf.flashBaudRate", workspace);
+ const idfPath = modifiedEnv["IDF_PATH"];
+ const pythonBinPath = await getVirtualEnvPythonPath();
const flasherArgsPath = join(buildPath, "flasher_args.json");
diff --git a/src/espIdf/reconfigure/task.ts b/src/espIdf/reconfigure/task.ts
index 2e3c666d1..03bc16809 100644
--- a/src/espIdf/reconfigure/task.ts
+++ b/src/espIdf/reconfigure/task.ts
@@ -27,24 +27,23 @@ import {
workspace,
} from "vscode";
import { NotificationMode, readParameter } from "../../idfConfiguration";
-import { appendIdfAndToolsToPath, getSDKConfigFilePath } from "../../utils";
+import { getSDKConfigFilePath } from "../../utils";
import { join } from "path";
import { TaskManager } from "../../taskManager";
import { getVirtualEnvPythonPath } from "../../pythonManager";
+import { configureEnvVariables } from "../../common/prepareEnv";
export class IdfReconfigureTask {
private buildDirPath: string;
private curWorkspace: Uri;
- private idfPathDir: string;
constructor(workspace: Uri) {
this.curWorkspace = workspace;
- this.idfPathDir = readParameter("idf.espIdfPath", workspace) as string;
this.buildDirPath = readParameter("idf.buildPath", workspace) as string;
}
public async reconfigure() {
- const modifiedEnv = await appendIdfAndToolsToPath(this.curWorkspace);
+ const modifiedEnv = await configureEnvVariables(this.curWorkspace);
const options: ProcessExecutionOptions = {
cwd: this.curWorkspace.fsPath,
env: modifiedEnv,
@@ -63,7 +62,7 @@ export class IdfReconfigureTask {
? TaskRevealKind.Always
: TaskRevealKind.Silent;
- const idfPy = join(this.idfPathDir, "tools", "idf.py");
+ const idfPy = join(modifiedEnv["IDF_PATH"], "tools", "idf.py");
const reconfigureArgs = [idfPy];
let buildPathArgsIndex = reconfigureArgs.indexOf("-B");
@@ -102,7 +101,7 @@ export class IdfReconfigureTask {
reconfigureArgs.push("reconfigure");
- const pythonBinPath = await getVirtualEnvPythonPath(this.curWorkspace);
+ const pythonBinPath = await getVirtualEnvPythonPath();
const reconfigureExecution = new ProcessExecution(
pythonBinPath,
diff --git a/src/espIdf/serial/serialPort.ts b/src/espIdf/serial/serialPort.ts
index ac0ae756e..cfe811e58 100644
--- a/src/espIdf/serial/serialPort.ts
+++ b/src/espIdf/serial/serialPort.ts
@@ -27,6 +27,7 @@ import * as SerialPortLib from "serialport";
import { getVirtualEnvPythonPath } from "../../pythonManager";
import { getIdfTargetFromSdkconfig } from "../../workspaceConfig";
import { showInfoNotificationWithAction } from "../../logger/utils";
+import { ESP } from "../../config";
export class SerialPort {
/**
@@ -98,7 +99,7 @@ export class SerialPort {
"idf.espIdfPath",
workspaceFolder
) as string;
- const pythonBinPath = await getVirtualEnvPythonPath(workspaceFolder);
+ const pythonBinPath = await getVirtualEnvPythonPath();
const esptoolPath = join(
idfPath,
"components",
@@ -365,11 +366,11 @@ export class SerialPort {
);
});
- const pythonBinPath = await getVirtualEnvPythonPath(workspaceFolder);
- const idfPath = idfConf.readParameter(
- "idf.espIdfPath",
- workspaceFolder
- );
+ const pythonBinPath = await getVirtualEnvPythonPath();
+ const currentEnvVars = ESP.ProjectConfiguration.store.get<{
+ [key: string]: string;
+ }>(ESP.ProjectConfiguration.CURRENT_IDF_CONFIGURATION, {});
+ const idfPath = currentEnvVars["IDF_PATH"];
const enableSerialPortChipIdRequest = idfConf.readParameter(
"idf.enableSerialPortChipIdRequest",
workspaceFolder
diff --git a/src/espIdf/setTarget/DevkitsCommand.ts b/src/espIdf/setTarget/DevkitsCommand.ts
index fd906de5e..99541b719 100644
--- a/src/espIdf/setTarget/DevkitsCommand.ts
+++ b/src/espIdf/setTarget/DevkitsCommand.ts
@@ -17,27 +17,28 @@
*/
import * as vscode from "vscode";
-import * as fs from "fs"
+import * as fs from "fs";
import { join } from "path";
import * as idfConf from "../../idfConfiguration";
import { Logger } from "../../logger/logger";
import { getVirtualEnvPythonPath } from "../../pythonManager";
import { OpenOCDManager } from "../openOcd/openOcdManager";
-import { appendIdfAndToolsToPath, execChildProcess } from "../../utils";
+import { execChildProcess } from "../../utils";
import { OutputChannel } from "../../logger/outputChannel";
import { getOpenOcdScripts } from "../openOcd/boardConfiguration";
+import { configureEnvVariables } from "../../common/prepareEnv";
export class DevkitsCommand {
- private workspaceRoot: vscode.Uri;
+ private workspaceFolderUri: vscode.Uri;
- constructor(workspaceRoot: vscode.Uri) {
- this.workspaceRoot = workspaceRoot;
+ constructor(workspaceFolder: vscode.Uri) {
+ this.workspaceFolderUri = workspaceFolder;
}
public async runDevkitsScript(): Promise