diff --git a/README.md b/README.md index 5f163fd40..23630d131 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,8 @@ Install ESP-IDF and ESP-IDF Tools by following the [install tutorial](./docs/tut > **NOTE:** Please take a look at [Working with multiple projects](./docs/MULTI_PROJECTS.md) for more information. Default is User settings. -- On the first time using the extension, press F1 to show the Visual Studio Code Command Palette and type **ESP-IDF: Configure ESP-IDF Extension** to open the extension configuration wizard. This will install ESP-IDF, ESP-IDF tools, create a virtual python environment with ESP-IDF and this extension python packages and configure the extension settings with these values. **NOTE: Make sure that there is no spaces in any configured path since [ESP-IDF build system doesn't support spaces yet.](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html#start-a-project)**. +- On the first time using the extension, press F1 to show the Visual Studio Code Command Palette and type **ESP-IDF: Configure ESP-IDF extension** to open the extension configuration wizard. This will install ESP-IDF, ESP-IDF tools, create a virtual python environment with ESP-IDF and this extension python packages and configure the extension settings with these values. + > **NOTE:** For versions of ESP-IDF < 5.0, spaces are not supported inside configured paths. > **NOTE:** Please take a look at [Install tutorial](./docs/tutorial/install.md) documentation or the [Setup documentation](./docs/SETUP.md) for details about extension setup and configuration. diff --git a/docs/SETUP.md b/docs/SETUP.md index d83cb14d8..237a56def 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -55,7 +55,7 @@ so make sure that if using an existing python virtual environment that installin > **NOTE:** Currently the python package `pygdbmi` used by the debug adapter still depends on some Python 2.7 libraries (libpython2.7.so.1.0) so make sure that the Python executable in `idf.pythonBinPath` you use contains these libraries. This will be dropped in later versions of ESP-IDF. -> **NOTE:** Make sure that `IDF_PATH` and `IDF_TOOLS_PATH` doesn't have any spaces to avoid any build issues since [ESP-IDF Build System does NOT support spaces yet.](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html#start-a-project). +> **NOTE**: If you want to use an ESP-IDF version < 5.0, make sure that IDF_PATH and IDF_TOOLS_PATH don't have any spaces since they were no suported in previous versions. After choosing any of the previous options, a status page is displayed showing ESP-IDF, tools and python environment setup progress status. When the setup is finished, a message is shown that "All settings have been configured. You can close this window." @@ -114,8 +114,6 @@ where: **DO NOT USE ~, $HOME OR %USERPROFILE% ENVIRONMENT VARIABLES ARE NOT RESOLVED IN THIS CONFIGURATION SETTINGS. You must use ${env:HOME} instead of \$HOME (Linux/MacOS) or %HOME% (Windows).** -> **NOTE:** Make sure that your configurations settings doesn't have any spaces to avoid any build issues. - Make sure to install the extension and extension debug adapter Python requirements by running the following commands in your terminal: ``` diff --git a/docs/tutorial/install.md b/docs/tutorial/install.md index a39619b30..74c79a4af 100644 --- a/docs/tutorial/install.md +++ b/docs/tutorial/install.md @@ -26,9 +26,7 @@ - Pick an ESP-IDF version to download (or find ESP-IDF in your system) and the python executable to create the virtual environment. - Choose the location for ESP-IDF Tools and python virtual environment (also known as `IDF_TOOLS_PATH`) which is `$HOME\.espressif` on MacOS/Linux and `%USERPROFILE%\.espressif` on Windows by default. > **NOTE:** Windows users don't need to select a python executable since it is part of the setup. - -> **NOTE:** Make sure that `IDF_PATH` and `IDF_TOOLS_PATH` doesn't have any spaces to avoid any build issues. - + > **NOTE:** Make sure that `IDF_TOOLS_PATH` doesn't have any spaces to avoid any build issues.

Select ESP-IDF

@@ -60,7 +58,7 @@ Install complete

-> **NOTE**: The advance mode allows the user to choose to use existing ESP-IDF tools by manually entering each ESP-IDF tool absolute path. Make sure each ESP-IDF tool path doesn't have any spaces. +> **NOTE**: > The advance mode allows the user to choose to use existing ESP-IDF tools by manually entering each ESP-IDF tool absolute path. Again, if chose an ESP-IDF version < 5.0, make sure each ESP-IDF tool path doesn't have any spaces, since they were no suported in previous versions.. 15. Now that the extension setup is finally done, check the [Basic use](./basic_use.md) to learn how to use the SDK Configuration editor, build, flash and monitor your Espressif device. diff --git a/src/PlatformInformation.ts b/src/PlatformInformation.ts index 6862c7e61..bf192845f 100644 --- a/src/PlatformInformation.ts +++ b/src/PlatformInformation.ts @@ -80,8 +80,10 @@ export class PlatformInformation { } public static GetUnixArchitecture(): Promise { + const command = "uname"; + const args = ["-m"]; return utils - .execChildProcess("uname -m", utils.extensionContext.extensionPath) + .execChildProcess(command, args, utils.extensionContext.extensionPath) .then((architecture) => { if (architecture) { return architecture.trim(); @@ -90,11 +92,10 @@ export class PlatformInformation { } private static GetWindowsArchitecture(): Promise { + const command = "wmic"; + const args = ["os", "get", "osarchitecture"]; return utils - .execChildProcess( - "wmic os get osarchitecture", - utils.extensionContext.extensionPath - ) + .execChildProcess(command, args, utils.extensionContext.extensionPath) .then((architecture) => { if (architecture) { const archArray: string[] = architecture.split(os.EOL); diff --git a/src/build/buildTask.ts b/src/build/buildTask.ts index ff6e4753b..031e744ee 100644 --- a/src/build/buildTask.ts +++ b/src/build/buildTask.ts @@ -28,23 +28,35 @@ import { selectedDFUAdapterId } from "../flash/dfu"; export class BuildTask { public static isBuilding: boolean; private buildDirPath: string; - private curWorkspace: vscode.Uri; + private currentWorkspace: vscode.Uri; private idfPathDir: string; private adapterTargetName: string; + private processOptions: vscode.ProcessExecutionOptions; + private modifiedEnv: { [key: string]: string }; + private pythonBinPath: string; - constructor(workspace: vscode.Uri) { - this.curWorkspace = workspace; + constructor(workspaceUri: vscode.Uri) { + this.currentWorkspace = workspaceUri; this.idfPathDir = idfConf.readParameter( "idf.espIdfPath", - workspace + workspaceUri ) as string; this.adapterTargetName = idfConf.readParameter( "idf.adapterTargetName", - workspace + workspaceUri ) as string; this.buildDirPath = idfConf.readParameter( "idf.buildPath", - workspace + workspaceUri + ) as string; + this.modifiedEnv = appendIdfAndToolsToPath(workspaceUri); + this.processOptions = { + cwd: this.buildDirPath, + env: this.modifiedEnv, + }; + this.pythonBinPath = idfConf.readParameter( + "idf.pythonBinPath", + workspaceUri ) as string; } @@ -55,45 +67,13 @@ export class BuildTask { private async saveBeforeBuild() { const shallSaveBeforeBuild = idfConf.readParameter( "idf.saveBeforeBuild", - this.curWorkspace + this.currentWorkspace ); if (shallSaveBeforeBuild) { await vscode.workspace.saveAll(); } } - public getShellExecution( - args: string[], - options?: vscode.ShellExecutionOptions - ) { - return new vscode.ShellExecution(`cmake ${args.join(" ")}`, options); - } - - public getNinjaShellExecution( - args: string[], - options?: vscode.ShellExecutionOptions - ) { - return new vscode.ShellExecution(`ninja ${args.join(" ")}`, options); - } - - public dfuShellExecution(options?: vscode.ShellExecutionOptions) { - const pythonBinPath = idfConf.readParameter( - "idf.pythonBinPath", - this.curWorkspace - ) as string; - return new vscode.ShellExecution( - `${pythonBinPath} ${join( - this.idfPathDir, - "tools", - "mkdfu.py" - )} write -o ${join(this.buildDirPath, "dfu.bin")} --json ${join( - this.buildDirPath, - "flasher_args.json" - )} --pid ${selectedDFUAdapterId(this.adapterTargetName)}`, - options - ); - } - public async build() { try { await this.saveBeforeBuild(); @@ -107,17 +87,16 @@ export class BuildTask { throw new Error("ALREADY_BUILDING"); } this.building(true); - const modifiedEnv = appendIdfAndToolsToPath(this.curWorkspace); await ensureDir(this.buildDirPath); const canAccessCMake = await isBinInPath( "cmake", - this.curWorkspace.fsPath, - modifiedEnv + this.currentWorkspace.fsPath, + this.modifiedEnv ); const canAccessNinja = await isBinInPath( "ninja", - this.curWorkspace.fsPath, - modifiedEnv + this.currentWorkspace.fsPath, + this.modifiedEnv ); const cmakeCachePath = join(this.buildDirPath, "CMakeCache.txt"); @@ -127,33 +106,13 @@ export class BuildTask { throw new Error("CMake or Ninja executables not found"); } - const options: vscode.ShellExecutionOptions = { - cwd: this.buildDirPath, - env: modifiedEnv, - }; - const shellExecutablePath = idfConf.readParameter( - "idf.customTerminalExecutable", - this.curWorkspace - ) as string; - const shellExecutableArgs = idfConf.readParameter( - "idf.customTerminalExecutableArgs", - this.curWorkspace - ) as string[]; - if (shellExecutablePath) { - options.executable = shellExecutablePath; - } - - if (shellExecutableArgs && shellExecutableArgs.length) { - options.shellArgs = shellExecutableArgs; - } - - const curWorkspaceFolder = vscode.workspace.workspaceFolders.find( - (w) => w.uri === this.curWorkspace + const currentWorkspaceFolder = vscode.workspace.workspaceFolders.find( + (w) => w.uri === this.currentWorkspace ); const notificationMode = idfConf.readParameter( "idf.notificationMode", - this.curWorkspace + this.currentWorkspace ) as string; const showTaskOutput = notificationMode === idfConf.NotificationMode.All || @@ -164,7 +123,7 @@ export class BuildTask { if (!cmakeCacheExists) { let compilerArgs = (idfConf.readParameter( "idf.cmakeCompilerArgs", - this.curWorkspace + this.currentWorkspace ) as Array) || [ "-G", "Ninja", @@ -178,7 +137,7 @@ export class BuildTask { compilerArgs.push("-B", this.buildDirPath); if (compilerArgs.indexOf("-S") === -1) { - compilerArgs.push("-S", this.curWorkspace.fsPath); + compilerArgs.push("-S", this.currentWorkspace.fsPath); } const sdkconfigDefaults = @@ -196,7 +155,7 @@ export class BuildTask { const enableCCache = idfConf.readParameter( "idf.enableCCache", - this.curWorkspace + this.currentWorkspace ) as boolean; if (enableCCache && compilerArgs && compilerArgs.length) { const indexOfCCache = compilerArgs.indexOf("-DCCACHE_ENABLE=1"); @@ -204,7 +163,7 @@ export class BuildTask { compilerArgs.push("-DCCACHE_ENABLE=1"); } } - const compileExecution = this.getShellExecution(compilerArgs, options); + const compileExecution = new vscode.ProcessExecution(canAccessCMake, compilerArgs, this.processOptions); const compilePresentationOptions = { reveal: showTaskOutput, showReuseMessage: false, @@ -217,7 +176,7 @@ export class BuildTask { command: "ESP-IDF Compile", taskId: "idf-compile-task", }, - curWorkspaceFolder || vscode.TaskScope.Workspace, + currentWorkspaceFolder || vscode.TaskScope.Workspace, "ESP-IDF Compile", compileExecution, ["espIdf"], @@ -227,10 +186,11 @@ export class BuildTask { } const buildArgs = - (idfConf.readParameter("idf.ninjaArgs", this.curWorkspace) as Array< + (idfConf.readParameter("idf.ninjaArgs", this.currentWorkspace) as Array< string >) || []; - const buildExecution = this.getNinjaShellExecution(buildArgs, options); + const ninjaCommand = "ninja"; + const buildExecution = new vscode.ProcessExecution(ninjaCommand, buildArgs, this.processOptions); const buildPresentationOptions = { reveal: showTaskOutput, showReuseMessage: false, @@ -239,7 +199,7 @@ export class BuildTask { } as vscode.TaskPresentationOptions; TaskManager.addTask( { type: "esp-idf", command: "ESP-IDF Build", taskId: "idf-build-task" }, - curWorkspaceFolder || vscode.TaskScope.Workspace, + currentWorkspaceFolder || vscode.TaskScope.Workspace, "ESP-IDF Build", buildExecution, ["espIdf"], @@ -249,36 +209,16 @@ export class BuildTask { public async buildDfu() { this.building(true); - const modifiedEnv = appendIdfAndToolsToPath(this.curWorkspace); + const modifiedEnv = appendIdfAndToolsToPath(this.currentWorkspace); await ensureDir(this.buildDirPath); - const options: vscode.ShellExecutionOptions = { - cwd: this.curWorkspace.fsPath, - env: modifiedEnv, - }; - - const shellExecutablePath = idfConf.readParameter( - "idf.customTerminalExecutable", - this.curWorkspace - ) as string; - const shellExecutableArgs = idfConf.readParameter( - "idf.customTerminalExecutableArgs", - this.curWorkspace - ) as string[]; - if (shellExecutablePath) { - options.executable = shellExecutablePath; - } - if (shellExecutableArgs && shellExecutableArgs.length) { - options.shellArgs = shellExecutableArgs; - } - - const curWorkspaceFolder = vscode.workspace.workspaceFolders.find( - (w) => w.uri === this.curWorkspace + const currentWorkspaceFolder = vscode.workspace.workspaceFolders.find( + (w) => w.uri === this.currentWorkspace ); const notificationMode = idfConf.readParameter( "idf.notificationMode", - this.curWorkspace + this.currentWorkspace ) as string; const showTaskOutput = notificationMode === idfConf.NotificationMode.All || @@ -286,7 +226,17 @@ export class BuildTask { ? vscode.TaskRevealKind.Always : vscode.TaskRevealKind.Silent; - const writeExecution = this.dfuShellExecution(options); + const args = [ + join(this.idfPathDir, "tools", "mkdfu.py"), + "write", + "-o", + join(this.buildDirPath, "dfu.bin"), + "--json", + join(this.buildDirPath, "flasher_args.json"), + "--pid", + selectedDFUAdapterId(this.adapterTargetName) + ]; + const writeExecution = new vscode.ProcessExecution(this.pythonBinPath, args, this.processOptions); const buildPresentationOptions = { reveal: showTaskOutput, showReuseMessage: false, @@ -299,7 +249,7 @@ export class BuildTask { command: "ESP-IDF Write DFU.bin", taskId: "idf-write-dfu-task", }, - curWorkspaceFolder || vscode.TaskScope.Workspace, + currentWorkspaceFolder || vscode.TaskScope.Workspace, "ESP-IDF Write DFU.bin", writeExecution, ["espIdf"], diff --git a/src/common/abstractCloning.ts b/src/common/abstractCloning.ts index 61d781c7e..cb9046b53 100644 --- a/src/common/abstractCloning.ts +++ b/src/common/abstractCloning.ts @@ -246,12 +246,14 @@ export class AbstractCloning { "esp-components", ]; await execChildProcess( - `${this.gitBinPath} submodule init`, + this.gitBinPath, + ["submodule", "init"], repoPath, OutputChannel.init() ); const gitModules = await execChildProcess( - `${this.gitBinPath} config -f .gitmodules --list`, + this.gitBinPath, + ["config", "-f", ".gitmodules", "--list"], repoPath, OutputChannel.init() ); @@ -297,7 +299,8 @@ export class AbstractCloning { } await execChildProcess( - `${this.gitBinPath} config submodule.${subPath}.url ${subUrl}`, + this.gitBinPath, + ["config", `submodule.${subPath}.url`, subUrl], repoPath, OutputChannel.init() ); diff --git a/src/customTasks/customTaskProvider.ts b/src/customTasks/customTaskProvider.ts index acffaa10c..5c071536f 100644 --- a/src/customTasks/customTaskProvider.ts +++ b/src/customTasks/customTaskProvider.ts @@ -17,8 +17,8 @@ */ import { - ShellExecution, - ShellExecutionOptions, + ProcessExecution, + ProcessExecutionOptions, TaskPanelKind, TaskPresentationOptions, TaskRevealKind, @@ -40,68 +40,57 @@ export enum CustomTaskType { export class CustomTask { public static isRunningCustomTask: boolean; + private processOptions: ProcessExecutionOptions; + private buildDirPath: string; + private currentWorkspace: Uri; + private modifiedEnv: { [key: string]: string }; - constructor(private currentWorkspace: Uri) {} + constructor(private workspaceUri: Uri) { + this.currentWorkspace = workspaceUri; + this.buildDirPath = readParameter( + "idf.buildPath", + this.currentWorkspace + ) as string; + this.modifiedEnv = appendIdfAndToolsToPath(workspaceUri); + this.processOptions = { + cwd: this.buildDirPath, + env: this.modifiedEnv, + }; + } public isRunning(flag: boolean) { CustomTask.isRunningCustomTask = flag; } - public getProcessExecution( - cmdString: string, - options: ShellExecutionOptions - ) { - return new ShellExecution(`${cmdString}`, options); - } - public addCustomTask(taskType: CustomTaskType) { - let cmd: string; + let command: string; let taskName: string; switch (taskType) { case CustomTaskType.PreBuild: - cmd = readParameter("idf.preBuildTask", this.currentWorkspace); + command = readParameter("idf.preBuildTask", this.currentWorkspace); taskName = "Pre Build"; break; case CustomTaskType.PostBuild: - cmd = readParameter("idf.postBuildTask", this.currentWorkspace); + command = readParameter("idf.postBuildTask", this.currentWorkspace); taskName = "Post Build"; break; case CustomTaskType.PreFlash: - cmd = readParameter("idf.preFlashTask", this.currentWorkspace); + command = readParameter("idf.preFlashTask", this.currentWorkspace); taskName = "Pre Flash"; break; case CustomTaskType.PostFlash: - cmd = readParameter("idf.postFlashTask", this.currentWorkspace); + command = readParameter("idf.postFlashTask", this.currentWorkspace); taskName = "Post Flash"; break; case CustomTaskType.Custom: - cmd = readParameter("idf.customTask", this.currentWorkspace); + command = readParameter("idf.customTask", this.currentWorkspace); taskName = "Custom task"; default: break; } - if (!cmd) { + if (!command) { return; } - const modifiedEnv = appendIdfAndToolsToPath(this.currentWorkspace); - const options: ShellExecutionOptions = { - cwd: this.currentWorkspace.fsPath, - env: modifiedEnv, - }; - const shellExecutablePath = readParameter( - "idf.customTerminalExecutable", - this.currentWorkspace - ) as string; - const shellExecutableArgs = readParameter( - "idf.customTerminalExecutableArgs", - this.currentWorkspace - ) as string[]; - if (shellExecutablePath) { - options.executable = shellExecutablePath; - } - if (shellExecutableArgs && shellExecutableArgs.length) { - options.shellArgs = shellExecutableArgs; - } const notificationMode = readParameter( "idf.notificationMode", this.currentWorkspace @@ -111,14 +100,14 @@ export class CustomTask { notificationMode === NotificationMode.Output ? TaskRevealKind.Always : TaskRevealKind.Silent; - const customExecution = this.getProcessExecution(cmd, options); + const customExecution = new ProcessExecution(command, this.processOptions); const customTaskPresentationOptions = { reveal: showTaskOutput, showReuseMessage: false, clear: false, panel: TaskPanelKind.Dedicated, } as TaskPresentationOptions; - const curWorkspaceFolder = workspace.workspaceFolders.find( + const currentWorkspaceFolder = workspace.workspaceFolders.find( (w) => w.uri === this.currentWorkspace ); TaskManager.addTask( @@ -127,7 +116,7 @@ export class CustomTask { command: `ESP-IDF ${taskName}`, taskId: `idf-${taskType}-task`, }, - curWorkspaceFolder || TaskScope.Workspace, + currentWorkspaceFolder || TaskScope.Workspace, `ESP-IDF ${taskName}`, customExecution, ["espIdf"], @@ -136,38 +125,38 @@ export class CustomTask { } public async runTasks(taskType: CustomTaskType) { - let cmd: string; + let command: string; switch (taskType) { case CustomTaskType.PreBuild: - cmd = readParameter( + command = readParameter( "idf.preBuildTask", this.currentWorkspace ) as string; break; case CustomTaskType.PostBuild: - cmd = readParameter( + command = readParameter( "idf.postBuildTask", this.currentWorkspace ) as string; break; case CustomTaskType.PreFlash: - cmd = readParameter( + command = readParameter( "idf.preFlashTask", this.currentWorkspace ) as string; break; case CustomTaskType.PostFlash: - cmd = readParameter( + command = readParameter( "idf.postFlashTask", this.currentWorkspace ) as string; break; case CustomTaskType.Custom: - cmd = readParameter("idf.customTask", this.currentWorkspace) as string; + command = readParameter("idf.customTask", this.currentWorkspace) as string; default: break; } - if (cmd) { + if (command) { await TaskManager.runTasks(); } } diff --git a/src/espBom/index.ts b/src/espBom/index.ts index 4bdc44776..1f6c21bbf 100644 --- a/src/espBom/index.ts +++ b/src/espBom/index.ts @@ -23,8 +23,8 @@ import { TaskPanelKind, TaskPresentationOptions, TaskScope, - ShellExecutionOptions, - ShellExecution, + ProcessExecutionOptions, + ProcessExecution, } from "vscode"; import { appendIdfAndToolsToPath, @@ -66,24 +66,10 @@ export async function createSBOM(workspaceUri: Uri) { ); } } - const options: ShellExecutionOptions = { + const options: ProcessExecutionOptions = { cwd: workspaceUri.fsPath, env: modifiedEnv, }; - const shellExecutablePath = readParameter( - "idf.customTerminalExecutable", - workspaceUri - ) as string; - const shellExecutableArgs = readParameter( - "idf.customTerminalExecutableArgs", - workspaceUri - ) as string[]; - if (shellExecutablePath) { - options.executable = shellExecutablePath; - } - if (shellExecutableArgs && shellExecutableArgs.length) { - options.shellArgs = shellExecutableArgs; - } const notificationMode = readParameter( "idf.notificationMode", workspaceUri @@ -102,12 +88,23 @@ export async function createSBOM(workspaceUri: Uri) { clear: false, panel: TaskPanelKind.Shared, } as TaskPresentationOptions; - const sbomCreateExecution = new ShellExecution( - `esp-idf-sbom create ${projectDescriptionJson} --output-file ${sbomFilePath}`, + const command = "esp-idf-sbom"; + const argsCreating = [ + "create", + projectDescriptionJson, + "--output-file", + sbomFilePath, + ]; + const sbomCreateExecution = new ProcessExecution( + command, + argsCreating, options ); - const sbomCheckExecution = new ShellExecution( - `esp-idf-sbom check ${sbomFilePath}`, + + const argsChecking = ["check", sbomFilePath]; + const sbomCheckExecution = new ProcessExecution( + command, + argsChecking, options ); TaskManager.addTask( @@ -148,7 +145,8 @@ export async function installEspSBOM(workspace: Uri) { const modifiedEnv = appendIdfAndToolsToPath(workspace); try { const showResult = await execChildProcess( - `"${pythonBinPath}" -m pip show esp-idf-sbom`, + pythonBinPath, + ["-m", "pip", "show", "esp-idf-sbom"], workspace.fsPath, OutputChannel.init(), { env: modifiedEnv } @@ -156,7 +154,8 @@ export async function installEspSBOM(workspace: Uri) { OutputChannel.appendLine(showResult); } catch (error) { const installResult = await execChildProcess( - `"${pythonBinPath}" -m pip install esp-idf-sbom`, + pythonBinPath, + ["-m", "pip", "install", "esp-idf-sbom"], workspace.fsPath, OutputChannel.init(), { env: modifiedEnv } diff --git a/src/espIdf/monitor/index.ts b/src/espIdf/monitor/index.ts index 1222672f3..fc61baabe 100644 --- a/src/espIdf/monitor/index.ts +++ b/src/espIdf/monitor/index.ts @@ -17,7 +17,7 @@ */ import { ESP } from "../../config"; -import { appendIdfAndToolsToPath } from "../../utils"; +import { appendIdfAndToolsToPath, getUserShell } from "../../utils"; import { window, Terminal, Uri, env } from "vscode"; export interface MonitorConfig { @@ -60,14 +60,20 @@ export class IDFMonitor { }); this.terminal.show(); this.terminal.dispose = this.dispose.bind(this); + const shellType = getUserShell(); + // Function to quote paths; PowerShell requires single quotes for paths with spaces + const quotePath = (path) => + shellType === "PowerShell" + ? `'${path.replace(/'/g, "''")}'` + : `"${path}"`; const baudRateToUse = this.config.baudRate || modifiedEnv.IDF_MONITOR_BAUD || modifiedEnv.MONITORBAUD || "115200"; const args = [ - this.config.pythonBinPath, - this.config.idfMonitorToolPath, + quotePath(this.config.pythonBinPath), + quotePath(this.config.idfMonitorToolPath), "-p", this.config.port, "-b", @@ -96,10 +102,18 @@ export class IDFMonitor { if (this.config.wsPort) { args.push("--ws", `ws://localhost:${this.config.wsPort}`); } - args.push(this.config.elfFilePath); + args.push(quotePath(this.config.elfFilePath)); const envSetCmd = process.platform === "win32" ? "set" : "export"; - this.terminal.sendText(`${envSetCmd} IDF_PATH=${modifiedEnv.IDF_PATH}`); - this.terminal.sendText(args.join(" ")); + const quotedIdfPath = quotePath(modifiedEnv.IDF_PATH); + + if (shellType === "PowerShell") { + this.terminal.sendText(`& ${envSetCmd} IDF_PATH=${quotedIdfPath}`); + this.terminal.sendText(`& ${args.join(" ")}`); + } else { + this.terminal.sendText(`${envSetCmd} IDF_PATH=${modifiedEnv.IDF_PATH}`); + this.terminal.sendText(args.join(" ")); + } + return this.terminal; } async dispose() { diff --git a/src/espIdf/nvs/partitionTable/panel.ts b/src/espIdf/nvs/partitionTable/panel.ts index 2a1ec0437..934bb47e2 100644 --- a/src/espIdf/nvs/partitionTable/panel.ts +++ b/src/espIdf/nvs/partitionTable/panel.ts @@ -182,7 +182,8 @@ export class NVSPartitionTable { } OutputChannel.appendLine(`${pythonBinPath} ${partToolArgs.join(" ")}`); const result = await execChildProcess( - `${pythonBinPath} ${partToolArgs.join(" ")}`, + pythonBinPath, + partToolArgs, dirPath ); OutputChannel.appendLine(result); diff --git a/src/espIdf/size/idfSizeTask.ts b/src/espIdf/size/idfSizeTask.ts index c066b2dbb..9e6ef8cad 100644 --- a/src/espIdf/size/idfSizeTask.ts +++ b/src/espIdf/size/idfSizeTask.ts @@ -19,14 +19,14 @@ import { ensureDir } from "fs-extra"; import { join } from "path"; import { - ShellExecution, - ShellExecutionOptions, TaskPanelKind, + ProcessExecutionOptions, TaskPresentationOptions, TaskRevealKind, TaskScope, Uri, workspace, + ProcessExecution } from "vscode"; import { NotificationMode, readParameter } from "../../idfConfiguration"; import { TaskManager } from "../../taskManager"; @@ -34,28 +34,27 @@ import { appendIdfAndToolsToPath } from "../../utils"; import { getProjectName } from "../../workspaceConfig"; export class IdfSizeTask { - private curWorkspace: Uri; + private currentWorkspace: Uri; private pythonBinPath: string; private idfSizePath: string; private buildDirPath: string; + private processOptions: ProcessExecutionOptions; + private modifiedEnv: { [key: string]: string }; - constructor(workspacePath: Uri) { - this.curWorkspace = workspacePath; + constructor(workspaceUri: Uri) { + this.currentWorkspace = workspaceUri; this.pythonBinPath = readParameter( "idf.pythonBinPath", - workspacePath + workspaceUri ) as string; - const idfPathDir = readParameter("idf.espIdfPath", workspacePath) as string; + const idfPathDir = readParameter("idf.espIdfPath", workspaceUri) as string; this.idfSizePath = join(idfPathDir, "tools", "idf_size.py"); - this.buildDirPath = readParameter("idf.buildPath", workspacePath) as string; - } - - public async getShellExecution(options: ShellExecutionOptions) { - const mapFilePath = await this.mapFilePath(); - return new ShellExecution( - `${this.pythonBinPath} ${this.idfSizePath} ${mapFilePath}`, - options - ); + this.buildDirPath = readParameter("idf.buildPath", workspaceUri) as string; + this.modifiedEnv = appendIdfAndToolsToPath(this.currentWorkspace); + this.processOptions = { + cwd: this.buildDirPath, + env: this.modifiedEnv, + }; } private async mapFilePath() { @@ -64,33 +63,18 @@ export class IdfSizeTask { } public async getSizeInfo() { - const modifiedEnv = appendIdfAndToolsToPath(this.curWorkspace); await ensureDir(this.buildDirPath); - const options: ShellExecutionOptions = { - cwd: this.curWorkspace.fsPath, - env: modifiedEnv, - }; - const shellExecutablePath = readParameter( - "idf.customTerminalExecutable", - this.curWorkspace - ) as string; - const shellExecutableArgs = readParameter( - "idf.customTerminalExecutableArgs", - this.curWorkspace - ) as string[]; - if (shellExecutablePath) { - options.executable = shellExecutablePath; - } - if (shellExecutableArgs && shellExecutableArgs.length) { - options.shellArgs = shellExecutableArgs; - } - const sizeExecution = await this.getShellExecution(options); + const pythonCommand = this.pythonBinPath; + const mapFilePath = await this.mapFilePath(); + const args = [this.idfSizePath, mapFilePath]; + + const sizeExecution = new ProcessExecution(pythonCommand, args, this.processOptions); const notificationMode = readParameter( "idf.notificationMode", - this.curWorkspace + this.currentWorkspace ) as string; - const curWorkspaceFolder = workspace.workspaceFolders.find( - (w) => w.uri === this.curWorkspace + const currentWorkspaceFolder = workspace.workspaceFolders.find( + (w) => w.uri === this.currentWorkspace ); const showTaskOutput = notificationMode === NotificationMode.All || @@ -105,7 +89,7 @@ export class IdfSizeTask { } as TaskPresentationOptions; TaskManager.addTask( { type: "esp-idf", command: "ESP-IDF Size", taskId: "idf-size-task" }, - curWorkspaceFolder || TaskScope.Workspace, + currentWorkspaceFolder || TaskScope.Workspace, "ESP-IDF Size", sizeExecution, ["espIdf"], diff --git a/src/extension.ts b/src/extension.ts index cb3aacf95..d140c0eba 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -680,8 +680,10 @@ export async function activate(context: vscode.ExtensionContext) { cancelToken: vscode.CancellationToken ) => { try { + const args = [flashScriptPath, "-p", port, "erase_flash"]; const result = await utils.execChildProcess( - `${pythonBinPath} ${flashScriptPath} -p ${port} erase_flash`, + pythonBinPath, + args, process.cwd(), OutputChannel.init(), null, @@ -3462,8 +3464,10 @@ export async function activate(context: vscode.ExtensionContext) { "idf.buildPath", workspaceRoot ) as string; + const args = [ninjaSummaryScript, "-C", buildDir]; const summaryResult = await utils.execChildProcess( - `${pythonBinPath} ${ninjaSummaryScript} -C ${buildDir}`, + pythonBinPath, + args, workspaceRoot.fsPath, OutputChannel.init() ); diff --git a/src/flash/dfu.ts b/src/flash/dfu.ts index d383d625f..a6d37217d 100644 --- a/src/flash/dfu.ts +++ b/src/flash/dfu.ts @@ -31,10 +31,11 @@ function deviceLabel(selectedDevice: string) { return "ESP32-S3"; } -export async function getDfuList(workspace: vscode.Uri) { - const modifiedEnv = appendIdfAndToolsToPath(workspace); +export async function getDfuList(workspaceUri: vscode.Uri) { + const modifiedEnv = appendIdfAndToolsToPath(workspaceUri); return await execChildProcess( - "dfu-util --list", + "dfu-util", + ["--list"], process.cwd(), OutputChannel.init(), { @@ -64,14 +65,14 @@ export async function listAvailableDfuDevices(text) { * @param {string} chip - String to identify the chip (IDF_TARGET) * @returns {number} PID Number for DFU */ - export function selectedDFUAdapterId(chip: string): number { + export function selectedDFUAdapterId(chip: string): string { switch (chip) { case "esp32s2": - return 2; + return "2"; case "esp32s3": - return 9; + return "9"; default: - return -1; + return "-1"; } } diff --git a/src/flash/flashTask.ts b/src/flash/flashTask.ts index 7cd6f7a48..8d58f83fc 100644 --- a/src/flash/flashTask.ts +++ b/src/flash/flashTask.ts @@ -28,19 +28,23 @@ import { ESP } from "../config"; export class FlashTask { public static isFlashing: boolean; - private workspaceUri: vscode.Uri; + private currentWorkspace: vscode.Uri; private flashScriptPath: string; private model: FlashModel; private buildDirPath: string; private encryptPartitions: boolean; + private idfPathDir: string; + private pythonBinPath: string; + private modifiedEnv: { [key: string]: string }; + private processOptions: vscode.ProcessExecutionOptions; constructor( - workspace: vscode.Uri, + workspaceUri: vscode.Uri, idfPath: string, model: FlashModel, - encryptPartitions: boolean + encryptPartitions: boolean, ) { - this.workspaceUri = workspace; + this.currentWorkspace = workspaceUri; this.flashScriptPath = join( idfPath, "components", @@ -51,9 +55,22 @@ export class FlashTask { this.model = model; this.buildDirPath = idfConf.readParameter( "idf.buildPath", - workspace + workspaceUri ) as string; this.encryptPartitions = encryptPartitions; + this.idfPathDir = idfConf.readParameter( + "idf.espIdfPath", + this.currentWorkspace + ) as string; + this.pythonBinPath = idfConf.readParameter( + "idf.pythonBinPath", + this.currentWorkspace + ) as string; + this.modifiedEnv = appendIdfAndToolsToPath(workspaceUri); + this.processOptions = { + cwd: this.buildDirPath, + env: this.modifiedEnv, + }; } public flashing(flag: boolean) { @@ -83,17 +100,17 @@ export class FlashTask { this.verifyArgs(); const notificationMode = idfConf.readParameter( "idf.notificationMode", - this.workspaceUri + this.currentWorkspace ) as string; - const curWorkspaceFolder = vscode.workspace.workspaceFolders.find( - (w) => w.uri === this.workspaceUri + const currentWorkspaceFolder = vscode.workspace.workspaceFolders.find( + (w) => w.uri === this.currentWorkspace ); const showTaskOutput = notificationMode === idfConf.NotificationMode.All || notificationMode === idfConf.NotificationMode.Output ? vscode.TaskRevealKind.Always : vscode.TaskRevealKind.Silent; - let flashExecution: vscode.ShellExecution | vscode.ProcessExecution; + let flashExecution: vscode.ProcessExecution; switch (flashType) { case "UART": flashExecution = this._flashExecution(); @@ -112,7 +129,7 @@ export class FlashTask { } as vscode.TaskPresentationOptions; TaskManager.addTask( { type: "esp-idf", command: "ESP-IDF Flash", taskId: "idf-flash-task" }, - curWorkspaceFolder || vscode.TaskScope.Workspace, + currentWorkspaceFolder || vscode.TaskScope.Workspace, "ESP-IDF Flash", flashExecution, ["espIdf"], @@ -122,49 +139,39 @@ export class FlashTask { public _flashExecution() { this.flashing(true); - const modifiedEnv = appendIdfAndToolsToPath(this.workspaceUri); const flasherArgs = this.getFlasherArgs(this.flashScriptPath); - const options: vscode.ShellExecutionOptions = { - cwd: this.buildDirPath, - env: modifiedEnv, - }; - const pythonBinPath = idfConf.readParameter( - "idf.pythonBinPath", - this.workspaceUri - ) as string; - return new vscode.ProcessExecution(pythonBinPath, flasherArgs, options); + return new vscode.ProcessExecution(this.pythonBinPath, flasherArgs, this.processOptions); } public _dfuFlashing() { this.flashing(true); const selectedDfuPath = idfConf.readParameter( "idf.selectedDfuDevicePath", - this.workspaceUri + this.currentWorkspace ); const listDfuDevices = idfConf.readParameter( "idf.listDfuDevices", - this.workspaceUri + this.currentWorkspace ); if (listDfuDevices.length > 1) { - const idfPathDir = idfConf.readParameter( - "idf.espIdfPath", - this.workspaceUri - ) as string; - const pythonPath = idfConf.readParameter( - "idf.pythonBinPath", - this.workspaceUri - ) as string; - const idfPy = path.join(idfPathDir, "tools", "idf.py"); - return new vscode.ShellExecution( - `${pythonPath} ${idfPy} dfu-flash --path ${selectedDfuPath}` - ); + const pythonCommand = this.pythonBinPath; + const idfPy = path.join(this.idfPathDir, "tools", "idf.py"); + const args = [ + idfPy, + 'dfu-flash', + '--path', + selectedDfuPath + ]; + return new vscode.ProcessExecution(pythonCommand, args, this.processOptions); } - return new vscode.ShellExecution( - `dfu-util -d 303a:${selectedDFUAdapterId(this.model.chip)} -D ${join( - this.buildDirPath, - "dfu.bin" - )}` - ); + const dfuCommand = "dfu-util"; + const args = [ + "-d", + `303a:${Number(selectedDFUAdapterId(this.model.chip)).toString(16)}`, + "-D", + join(this.buildDirPath, "dfu.bin") + ]; + return new vscode.ProcessExecution(dfuCommand, args, this.processOptions); } public getFlasherArgs(toolPath: string, replacePathSep: boolean = false) { diff --git a/src/flash/jtag.ts b/src/flash/jtag.ts index 31df16d51..62e725c2b 100644 --- a/src/flash/jtag.ts +++ b/src/flash/jtag.ts @@ -20,11 +20,17 @@ import { TCLClient } from "../espIdf/openOcd/tcl/tclClient"; export class JTAGFlash { constructor(private readonly client: TCLClient) {} - async flash(command: string) { + + async flash(command: string, ...args: string[]) { + const fullCommand = `${command} ${args.map((arg) => `"${arg}"`).join(" ")}`; + return new Promise((resolve, reject) => { this.client .on("response", (data) => { - const response = data.toString().replace("\x1a", "").trim(); + const response = data + .toString() + .replace(TCLClient.DELIMITER, "") + .trim(); if (response !== "0") { return reject( `Failed to flash the device (JTag), please try again [got response: '${response}', expecting: '0']` @@ -39,7 +45,7 @@ export class JTAGFlash { "Failed to flash (via JTag), due to some unknown error in tcl, please try to relaunch open-ocd" ); }) - .sendCommand(command); + .sendCommand(fullCommand); }); } } diff --git a/src/flash/jtagCmd.ts b/src/flash/jtagCmd.ts index 511c3827e..0eccad407 100644 --- a/src/flash/jtagCmd.ts +++ b/src/flash/jtagCmd.ts @@ -32,7 +32,8 @@ export async function jtagFlashCommand(workspace: Uri) { let continueFlag = true; const isOpenOCDLaunched = await OpenOCDManager.init().promptUserToLaunchOpenOCDServer(); if (!isOpenOCDLaunched) { - const errStr = "Can't perform JTag flash, because OpenOCD server is not running!"; + const errStr = + "Can't perform JTag flash, because OpenOCD server is not running!"; OutputChannel.appendLineAndShow(errStr, "Flash"); return Logger.warnNotify(errStr); } @@ -57,7 +58,11 @@ export async function jtagFlashCommand(workspace: Uri) { customTask.addCustomTask(CustomTaskType.PreFlash); await customTask.runTasks(CustomTaskType.PreFlash); await jtag.flash( - `program_esp_bins ${buildPath} flasher_args.json verify reset` + "program_esp_bins", + buildPath, + "flasher_args.json", + "verify", + "reset" ); customTask.addCustomTask(CustomTaskType.PostFlash); await customTask.runTasks(CustomTaskType.PostFlash); diff --git a/src/idfToolsManager.ts b/src/idfToolsManager.ts index 9cf19f3d0..227f4ad20 100644 --- a/src/idfToolsManager.ts +++ b/src/idfToolsManager.ts @@ -221,13 +221,15 @@ export class IdfToolsManager { ) { modifiedEnv[pathNameInEnv] = modifiedPath; } - const versionCmd = pkg.version_cmd.join(" "); - if (versionCmd === "") { + if (pkg.version_cmd.length === 0) { return "No command version"; } + const command = pkg.version_cmd[0]; + const args = pkg.version_cmd.slice(1); try { const binVersionResponse = await utils.execChildProcess( - versionCmd, + command, + args, process.cwd(), logToChannel ? this.toolsManagerChannel : undefined, { diff --git a/src/pythonManager.ts b/src/pythonManager.ts index f56280a07..b1a8a3350 100644 --- a/src/pythonManager.ts +++ b/src/pythonManager.ts @@ -39,8 +39,10 @@ export async function installEspIdfToolFromIdf( return reject(new Error("Process cancelled by user")); } try { + const args = [idfToolsPyPath, "install", toolName]; const processResult = await utils.execChildProcess( - `"${pythonBinPath}" ${idfToolsPyPath} install ${toolName}`, + pythonBinPath, + args, idfToolsPath, channel, { cwd: idfToolsPath, env: modifiedEnv }, @@ -101,7 +103,8 @@ export async function installPythonEnvFromIdfTools( const pyEnvPath = await getPythonEnvPath(espDir, idfToolsDir, pythonBinPath); await execProcessWithLog( - `"${pythonBinPath}" "${idfToolsPyPath}" install-python-env`, + pythonBinPath, + [idfToolsPyPath, "install-python-env"], idfToolsDir, pyTracker, channel, @@ -145,19 +148,19 @@ export async function installExtensionPyReqs( `espidf.constraints.v${espIdfVersion}.txt` ); const constrainsFileExists = await pathExists(constrainsFile); - let constraintArg = ""; + let constraintArg = []; if (constrainsFileExists) { - constraintArg = `--constraint "${constrainsFile}" `; + constraintArg = ["--constraint", constrainsFile]; } else { const extensionConstraintsFile = join( utils.extensionContext.extensionPath, - `espidf.constraints.txt` + "espidf.constraints.txt" ); const extensionConstraintsFileExists = await pathExists( extensionConstraintsFile ); if (extensionConstraintsFileExists) { - constraintArg = `--constraint "${extensionConstraintsFile}" `; + constraintArg = ["--constraint", extensionConstraintsFile]; } } const installDAPyPkgsMsg = `Installing ESP-IDF Debug Adapter python packages in ${virtualEnvPython} ...\n`; @@ -168,8 +171,19 @@ export async function installExtensionPyReqs( if (channel) { channel.appendLine(installDAPyPkgsMsg + "\n"); } + const args = [ + "-m", + "pip", + "install", + "--upgrade", + ...constraintArg, + "--no-warn-script-location", + "-r", + debugAdapterRequirements, + ]; await execProcessWithLog( - `"${virtualEnvPython}" -m pip install --upgrade ${constraintArg}--no-warn-script-location -r "${debugAdapterRequirements}"`, + virtualEnvPython, + args, idfToolsDir, pyTracker, channel, @@ -215,8 +229,18 @@ export async function installEspMatterPyReqs( if (channel) { channel.appendLine(installMatterPyPkgsMsg + "\n"); } + const args = [ + "-m", + "pip", + "install", + "--upgrade", + "--no-warn-script-location", + "-r", + matterRequirements, + ]; await execProcessWithLog( - `"${virtualEnvPython}" -m pip install --upgrade --no-warn-script-location -r "${matterRequirements}"`, + virtualEnvPython, + args, idfToolsDir, pyTracker, channel, @@ -227,6 +251,7 @@ export async function installEspMatterPyReqs( } export async function execProcessWithLog( cmd: string, + args: string[], workDir: string, pyTracker?: PyReqLog, channel?: OutputChannel, @@ -235,6 +260,7 @@ export async function execProcessWithLog( ) { const processResult = await utils.execChildProcess( cmd, + args, workDir, channel, opts, @@ -254,11 +280,10 @@ export async function getPythonEnvPath( idfToolsDir: string, pythonBin: string ) { + const pythonCode = `import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor))`; + const args = ["-c", pythonCode]; const pythonVersion = ( - await utils.execChildProcess( - `"${pythonBin}" -c "import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor))"`, - espIdfDir - ) + await utils.execChildProcess(pythonBin, args, espIdfDir) ).replace(/(\n|\r|\r\n)/gm, ""); const fullEspIdfVersion = await utils.getEspIdfFromCMake(espIdfDir); const majorMinorMatches = fullEspIdfVersion.match(/([0-9]+\.[0-9]+).*/); @@ -275,7 +300,8 @@ export async function getPythonEnvPath( export async function checkPythonExists(pythonBin: string, workingDir: string) { try { const versionResult = await utils.execChildProcess( - `"${pythonBin}" --version`, + pythonBin, + ["--version"], workingDir ); if (versionResult) { @@ -302,10 +328,8 @@ export async function checkPythonExists(pythonBin: string, workingDir: string) { export async function checkPipExists(pyBinPath: string, workingDir: string) { try { - const pipResult = await utils.execChildProcess( - `"${pyBinPath}" -m pip --version`, - workingDir - ); + const args = ["-m", "pip", "--version"]; + const pipResult = await utils.execChildProcess(pyBinPath, args, workingDir); if (pipResult) { const match = pipResult.match(/pip\s\d+(.\d+)?(.\d+)?/g); if (match && match.length > 0) { @@ -325,7 +349,8 @@ export async function checkPipExists(pyBinPath: string, workingDir: string) { export async function checkVenvExists(pyBinPath: string, workingDir: string) { try { const pipResult = await utils.execChildProcess( - `"${pyBinPath}" -c "import venv"`, + pyBinPath, + ["-c", "import venv"], workingDir ); return true; @@ -349,16 +374,42 @@ export async function getPythonBinList(workingDir: string) { export async function getUnixPythonList(workingDir: string) { try { - const pyVersionsStr = await utils.execChildProcess( - "which -a python; which -a python3", - workingDir - ); - if (pyVersionsStr) { - const resultList = pyVersionsStr.trim().split("\n"); - const uniquePathsSet = new Set(resultList); - const uniquePathsArray = Array.from(uniquePathsSet); - return uniquePathsArray; + let pythonVersions: string[] = []; + let python3Versions: string[] = []; + + try { + const pythonVersionsRaw = await utils.execChildProcess( + "which", + ["-a", "python"], + workingDir + ); + pythonVersions = pythonVersionsRaw.trim() + ? pythonVersionsRaw.trim().split("\n") + : []; + } catch (pythonError) { + Logger.warn("Error finding python versions", pythonError); } + + try { + const python3VersionsRaw = await utils.execChildProcess( + "which", + ["-a", "python3"], + workingDir + ); + python3Versions = python3VersionsRaw.trim() + ? python3VersionsRaw.trim().split("\n") + : []; + } catch (python3Error) { + Logger.warn("Error finding python3 versions", python3Error); + } + + const combinedVersionsArray = [...pythonVersions, ...python3Versions]; + const uniquePathsSet = new Set( + combinedVersionsArray.filter((path) => path.length > 0) + ); + const uniquePathsArray = Array.from(uniquePathsSet); + + return uniquePathsArray; } catch (error) { Logger.errorNotify("Error looking for python in system", error); return ["Not found"]; @@ -370,11 +421,12 @@ export async function checkIfNotVirtualEnv( workDir: string ) { try { - const isVirtualEnvBuffer = await utils.execChildProcess( - `"${pythonBinPath}" -c "import sys; print('{}'.format(sys.prefix == sys.base_prefix))"`, + const isVirtualEnv = await utils.execChildProcess( + pythonBinPath, + ["-c", "import sys; print(sys.prefix == sys.base_prefix)"], workDir ); - return isVirtualEnvBuffer.toString().indexOf("True") !== -1 ? true : false; + return isVirtualEnv.trim() === "True"; } catch (error) { Logger.errorNotify("Error checking Python is virtualenv", error); return false; diff --git a/src/setup/SetupPanel.ts b/src/setup/SetupPanel.ts index 0259ddef8..981923042 100644 --- a/src/setup/SetupPanel.ts +++ b/src/setup/SetupPanel.ts @@ -441,10 +441,7 @@ export class SetupPanel { ? espIdfPath : idfContainerPath; this.checkSpacesInPaths( - pathToCheck, toolsPath, - idfGitPath, - idfPythonPath ); await expressInstall( selectedIdfVersion, @@ -576,10 +573,7 @@ export class SetupPanel { idfPythonPath = embedPaths.idfPythonPath; } this.checkSpacesInPaths( - idfPath, toolsPath, - idfGitPath, - idfPythonPath ); await downloadIdfTools( idfPath, @@ -694,29 +688,22 @@ export class SetupPanel { return { idfPythonPath, idfGitPath }; } + private async getOpenOcdRulesPath() { + try { + await getOpenOcdRules(Uri.file(this.context.extensionPath)); + } catch (error) { + this.setupErrHandler(error); + } + } + private checkSpacesInPaths( - idfPath: string, idfToolsPath: string, - gitPath: string, - pythonBinPath: string ) { - const doesIdfPathHasSpaces = checkSpacesInPath(idfPath); const doesIdfToolsPathHasSpaces = checkSpacesInPath(idfToolsPath); - const doesGitPathHasSpaces = checkSpacesInPath(gitPath); - const doesPythonBinPath = checkSpacesInPath(pythonBinPath); let pathHasSpaces = ""; - if (doesIdfPathHasSpaces) { - pathHasSpaces = `${idfPath} has spaces. Use another location. (IDF_PATH_WITH_SPACES)`; - } if (doesIdfToolsPathHasSpaces) { pathHasSpaces = `${idfToolsPath} has spaces. Use another location. (IDF_TOOLS_PATH_WITH_SPACES)`; } - if (doesGitPathHasSpaces) { - pathHasSpaces = `${gitPath} has spaces. Use another location. (GIT_PATH_WITH_SPACES)`; - } - if (doesPythonBinPath) { - pathHasSpaces = `${pythonBinPath} has spaces. Use another location. (PYTHON_BIN_PATH_WITH_SPACES)`; - } if (pathHasSpaces) { OutputChannel.appendLine(pathHasSpaces); Logger.infoNotify(pathHasSpaces); @@ -724,14 +711,6 @@ export class SetupPanel { } } - private async getOpenOcdRulesPath() { - try { - await getOpenOcdRules(Uri.file(this.context.extensionPath)); - } catch (error) { - this.setupErrHandler(error); - } - } - private async openFolder() { const selectedFolder = await window.showOpenDialog({ canSelectFolders: true, diff --git a/src/support/checkEspIdfRequirements.ts b/src/support/checkEspIdfRequirements.ts index eb33763df..49a8e8ec9 100644 --- a/src/support/checkEspIdfRequirements.ts +++ b/src/support/checkEspIdfRequirements.ts @@ -64,7 +64,8 @@ export async function checkRequirements( ); modifiedEnv.IDF_PATH = reportedResult.configurationSettings.espIdfPath; const requirementsResult = await execChildProcess( - `${reportedResult.configurationSettings.pythonBinPath} ${checkPythonDepsScript} -r "${requirementsPath}"`, + reportedResult.configurationSettings.pythonBinPath, + [checkPythonDepsScript, "-r", requirementsPath], context.extensionPath, { env: modifiedEnv, cwd: context.extensionPath } ); diff --git a/src/support/configurationAccess.ts b/src/support/configurationAccess.ts index 5d7357e37..2de83a07a 100644 --- a/src/support/configurationAccess.ts +++ b/src/support/configurationAccess.ts @@ -71,13 +71,15 @@ export async function getConfigurationAccess( } if (process.platform !== "win32") { const cmakePathInEnv = await execChildProcess( - `which cmake`, + "which", + ["cmake"], context.extensionPath ); reportedResult.configurationAccess.cmakeInEnv = cmakePathInEnv && cmakePathInEnv.indexOf("not found") === -1; const ninjaPathInEnv = await execChildProcess( - `which ninja`, + "which", + ["ninja"], context.extensionPath ); reportedResult.configurationAccess.ninjaInEnv = diff --git a/src/support/execChildProcess.ts b/src/support/execChildProcess.ts index 61e0e04f7..381d81be0 100644 --- a/src/support/execChildProcess.ts +++ b/src/support/execChildProcess.ts @@ -15,10 +15,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { exec, ExecOptions } from "child_process"; +import { execFile, ExecOptions } from "child_process"; export function execChildProcess( - cmd: string, + command: string, + args: string[] = [], pathWhereToExecute: string, opts?: ExecOptions ) { @@ -29,7 +30,7 @@ export function execChildProcess( }; } return new Promise((resolve, reject) => { - exec(cmd, opts, (error: Error, stdout: string, stderr: string) => { + execFile(command, args, opts, (error: Error, stdout: string, stderr: string) => { if (error) { return reject(error); } diff --git a/src/support/gitVersion.ts b/src/support/gitVersion.ts index 04a62f1f0..c5c3eb0d7 100644 --- a/src/support/gitVersion.ts +++ b/src/support/gitVersion.ts @@ -26,7 +26,8 @@ export async function getGitVersion( context: vscode.ExtensionContext, ) { const rawGitVersion = await execChildProcess( - `"${reportedResult.configurationSettings.gitPath}" --version`, + reportedResult.configurationSettings.gitPath, + ["--version"], context.extensionPath ); reportedResult.gitVersion.output = rawGitVersion; diff --git a/src/support/pipVersion.ts b/src/support/pipVersion.ts index 5cfbd2529..813360b06 100644 --- a/src/support/pipVersion.ts +++ b/src/support/pipVersion.ts @@ -26,7 +26,8 @@ export async function getPipVersion( ) { try { const rawPipVersion = await execChildProcess( - `"${reportedResult.configurationSettings.pythonBinPath}" -m pip --version`, + reportedResult.configurationSettings.pythonBinPath, + ["-m", "pip", "--version"], context.extensionPath ); reportedResult.pipVersion.output = rawPipVersion; diff --git a/src/support/pythonPackages.ts b/src/support/pythonPackages.ts index 77701f974..0ea287d71 100644 --- a/src/support/pythonPackages.ts +++ b/src/support/pythonPackages.ts @@ -24,7 +24,8 @@ export async function getPythonPackages( context: vscode.ExtensionContext ) { const rawPythonPackagesList = await execChildProcess( - `${reportedResult.configurationSettings.pythonBinPath} -m pip list --format json`, + reportedResult.configurationSettings.pythonBinPath, + ["-m", "pip", "list", "--format", "json"], context.extensionPath ); reportedResult.pythonPackages.output = rawPythonPackagesList; diff --git a/src/support/pythonVersion.ts b/src/support/pythonVersion.ts index 5931537f9..b9b2846f8 100644 --- a/src/support/pythonVersion.ts +++ b/src/support/pythonVersion.ts @@ -26,7 +26,8 @@ export async function getPythonVersion( ) { try { const rawPythonVersion = await execChildProcess( - `"${reportedResult.configurationSettings.pythonBinPath}" --version`, + reportedResult.configurationSettings.pythonBinPath, + ["--version"], context.extensionPath ); reportedResult.pythonVersion.output = rawPythonVersion; diff --git a/src/utils.ts b/src/utils.ts index 2b64a6c22..993b4b325 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -29,7 +29,7 @@ import { } from "fs-extra"; import * as HttpsProxyAgent from "https-proxy-agent"; import { marked } from "marked"; -import { EOL } from "os"; +import { EOL, platform } from "os"; import * as path from "path"; import * as url from "url"; import * as vscode from "vscode"; @@ -495,23 +495,25 @@ export function isJson(jsonString: string) { } export function execChildProcess( - processStr: string, + command: string, + args: string[] = [], workingDirectory: string, channel?: vscode.OutputChannel, - opts?: childProcess.ExecOptions, + opts?: childProcess.ExecFileOptions, cancelToken?: vscode.CancellationToken ): Promise { - const execOpts: childProcess.ExecOptions = opts + const execOpts: childProcess.ExecFileOptions = opts ? opts : { cwd: workingDirectory, maxBuffer: 500 * 1024, }; return new Promise((resolve, reject) => { - childProcess.exec( - processStr, + childProcess.execFile( + command, + args, execOpts, - (error: Error, stdout: string, stderr: string) => { + (error: Error | null, stdout: string, stderr: string) => { if (cancelToken && cancelToken.isCancellationRequested) { return reject(new Error("Process cancelled by user")); } @@ -523,7 +525,7 @@ export function execChildProcess( } if (stderr && stderr.length > 0) { message += stderr; - if (stderr.indexOf("Error") !== -1) { + if (stderr.includes("Error")) { err = true; } } @@ -545,8 +547,8 @@ export function execChildProcess( } if (stderr && stderr.length > 2) { Logger.error(stderr, new Error(stderr)); - if (stderr.indexOf("Error") !== -1) { - return reject(stderr); + if (stderr.includes("Error")) { + return reject(new Error(stderr)); } } return resolve(stdout.concat(stderr)); @@ -766,7 +768,8 @@ export async function checkGitExists(workingDir: string, gitPath: string) { gitPath = gitInPath; } const gitRawVersion = await execChildProcess( - `"${gitPath}" --version`, + gitPath, + ["--version"], workingDir ); const match = gitRawVersion.match( @@ -795,7 +798,8 @@ export async function cleanDirtyGitRepository( const workingDirUri = vscode.Uri.file(workingDir); const modifiedEnv = appendIdfAndToolsToPath(workingDirUri); const resetResult = await execChildProcess( - `"${gitPath}" reset --hard --recurse-submodule`, + gitPath, + ["reset", "--hard", "--recurse-submodule"], workingDir, OutputChannel.init(), { env: modifiedEnv, cwd: workingDir } @@ -820,13 +824,24 @@ export async function fixFileModeGitRepository( const workingDirUri = vscode.Uri.file(workingDir); const modifiedEnv = appendIdfAndToolsToPath(workingDirUri); const fixFileModeResult = await execChildProcess( - `"${gitPath}" config --local core.fileMode false`, + gitPath, + ["config", "--local", "core.fileMode", "false"], workingDir, OutputChannel.init(), { env: modifiedEnv, cwd: workingDir } ); const fixSubmodulesFileModeResult = await execChildProcess( - `"${gitPath}" submodule foreach --recursive git config --local core.fileMode false`, + gitPath, + [ + "submodule", + "foreach", + "--recursive", + "git", + "config", + "--local", + "core.fileMode", + "false", + ], workingDir, OutputChannel.init(), { env: modifiedEnv, cwd: workingDir } @@ -1103,7 +1118,8 @@ export async function startPythonReqsProcess( ); const modifiedEnv = appendIdfAndToolsToPath(extensionContext.extensionUri); return execChildProcess( - `"${pythonBinPath}" "${reqFilePath}" -r "${requirementsPath}"`, + pythonBinPath, + [reqFilePath, "-r", requirementsPath], extensionContext.extensionPath, OutputChannel.init(), { env: modifiedEnv, cwd: extensionContext.extensionPath } @@ -1227,3 +1243,42 @@ export function markdownToWebviewHtml( cleanHtml = cleanHtml.replace(/</g, "<").replace(/>/g, ">"); return cleanHtml; } + +export function getUserShell() { + if (idfConf.readParameter("idf.customTerminalExecutable")) { + return "custom"; + } + + const config = vscode.workspace.getConfiguration("terminal.integrated"); + + const shellWindows = config.get("defaultProfile.windows") as string; + const shellMac = config.get("defaultProfile.osx") as string; + const shellLinux = config.get("defaultProfile.linux") as string; + + // list of shells to check + const shells = ["PowerShell", "Command Prompt", "bash", "zsh"]; + + // if user's shell is in the list, return it + for (let i = 0; i < shells.length; i++) { + if (shellWindows && shellWindows.includes(shells[i])) { + return shells[i]; + } else if (shellMac && shellMac.includes(shells[i])) { + return shells[i]; + } else if (shellLinux && shellLinux.includes(shells[i])) { + return shells[i]; + } + } + + // if no match or no defaultProfile, pick one based on user's OS + const userOS = platform(); + if (userOS === "win32") { + return "PowerShell"; + } else if (userOS === "darwin") { + return "zsh"; + } else if (userOS === "linux") { + return "bash"; + } + + // if no match, return null + return null; +} diff --git a/src/views/setup/Install.vue b/src/views/setup/Install.vue index ffc796edf..1fcda6c40 100644 --- a/src/views/setup/Install.vue +++ b/src/views/setup/Install.vue @@ -2,7 +2,7 @@ import { storeToRefs } from "pinia"; import { useSetupStore } from "./store"; import { SetupMode } from "./types"; -import { computed } from "vue"; +import { computed, watchEffect } from "vue"; import folderOpen from "./components/folderOpen.vue"; import selectEspIdf from "./components/selectEspIdf.vue"; import selectPyVersion from "./components/selectPyVersion.vue"; @@ -17,6 +17,9 @@ const { pyExecErrorStatus, toolsFolder, setupMode, + selectedEspIdfVersion, + espIdf, + espIdfContainer, } = storeToRefs(store); const isNotWinPlatform = computed(() => { @@ -27,6 +30,64 @@ const actionButtonText = computed(() => { return setupMode.value === SetupMode.advanced ? "Configure Tools" : "Install"; }); +const hasToolsWhitespace = computed(() => { + return /\s/.test(toolsFolder.value); +}); + +const isVersionLessThanFive = computed(() => { + if (!selectedEspIdfVersion.value || !selectedEspIdfVersion.value.version) { + return false; + } + + const versionString = selectedEspIdfVersion.value.version.match( + /(\d+\.\d+\.\d+|\d+\.\d+)/ + ); + if (!versionString) { + return false; + } + + const version = versionString[0].split(".").map((num) => parseInt(num)); + if (version[0] < 5) { + return true; + } else if (version[0] === 5 && version[1] === 0 && version[2] === 0) { + return true; + } + + return false; +}); + +const hasWhitespaceInEspIdf = computed(() => { + return /\s/.test(espIdf.value); +}); + +const hasWhitespaceInEspIdfContainer = computed(() => { + return /\s/.test(espIdfContainer.value); +}); + +const isInstallDisabled = computed(() => { + return ( + hasToolsWhitespace.value || + (selectedEspIdfVersion.value.filename !== "manual" && + isVersionLessThanFive.value && + hasWhitespaceInEspIdfContainer.value) || + !espIdf.value || + !espIdfContainer.value || + !toolsFolder.value + ); +}); + +watchEffect(() => { + if (!hasWhitespaceInEspIdf.value) { + store.whiteSpaceErrorIDF = ""; + } + if (!hasWhitespaceInEspIdfContainer.value) { + store.whiteSpaceErrorIDFContainer = ""; + } + if (!hasToolsWhitespace.value) { + store.whiteSpaceErrorTools = ""; + } +}); + function setEspIdfErrorStatus() { store.espIdfErrorStatus = ""; } @@ -42,6 +103,20 @@ function setToolsFolder(newToolsPath: string) {