From 071461211a1d487858a16743fc80e2c6e02c79ac Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Wed, 14 May 2025 15:04:02 +0100 Subject: [PATCH 01/62] Add command to setup venv in pico-sdk folder --- package.json | 5 +++ src/commands/getPaths.mts | 85 ++++++++++++++++++++++++++++++++++++++- src/extension.mts | 2 + 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f35bcf2..eb972377 100644 --- a/package.json +++ b/package.json @@ -261,6 +261,11 @@ "title": "Get path of the project release SBOM (rust only)", "category": "Raspberry Pi Pico", "enablement": "false" + }, + { + "command": "raspberry-pi-pico.setupVenv", + "title": "Setup Venv", + "category": "Raspberry Pi Pico" } ], "configuration": { diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index c41cd91b..8b6e589a 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -1,5 +1,8 @@ import { CommandWithResult } from "./command.mjs"; import { commands, type Uri, window, workspace } from "vscode"; +import { type ExecOptions, exec } from "child_process"; +import { join as joinPosix } from "path/posix"; +import { homedir } from "os"; import { getPythonPath, getPath, @@ -15,8 +18,9 @@ import { buildToolchainPath, downloadAndInstallOpenOCD, downloadAndInstallPicotool, + buildSDKPath, } from "../utils/download.mjs"; -import Settings, { SettingsKey } from "../settings.mjs"; +import Settings, { SettingsKey, HOME_VAR } from "../settings.mjs"; import which from "which"; import { execSync } from "child_process"; import { getPicotoolReleases } from "../utils/githubREST.mjs"; @@ -26,6 +30,7 @@ import { getSupportedToolchains } from "../utils/toolchainUtil.mjs"; import Logger from "../logger.mjs"; import { rustProjectGetSelectedChip } from "../utils/rustUtil.mjs"; import { OPENOCD_VERSION } from "../utils/sharedConstants.mjs"; +import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; export class GetPythonPathCommand extends CommandWithResult { constructor() { @@ -505,3 +510,81 @@ export class GetSVDPathCommand extends CommandWithResult { ); } } + +export class SetupVenvCommand extends CommandWithResult { + private running: boolean = false; + + public static readonly id = "setupVenv"; + + constructor() { + super(SetupVenvCommand.id); + } + + private readonly _logger: Logger = new Logger("SetupVenv"); + + private _runSetupVenv( + command: string, + options: ExecOptions + ): Promise { + return new Promise(resolve => { + const generatorProcess = exec( + command, + options, + (error, stdout, stderr) => { + this._logger.debug(stdout); + this._logger.info(stderr); + if (error) { + this._logger.error(`Setup venv error: ${error.message}`); + resolve(null); // indicate error + } + } + ); + + generatorProcess.on("exit", code => { + // Resolve with exit code or -1 if code is undefined + resolve(code); + }); + }); + } + + async execute(): Promise { + if (this.running) { + return undefined; + } + this.running = true; + + window.showInformationMessage("Setup Venv Command Running"); + + const python3Path = await findPython(); + if (!python3Path) { + this._logger.error("Failed to find Python3 executable."); + showPythonNotFoundError(); + + return; + } + + const pythonExe = python3Path.replace( + HOME_VAR, + homedir().replaceAll("\\", "/") + ); + + const command: string = [ + `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, + "-m venv venv", + ].join(" "); + + const homeDirectory: string = homedir(); + const sdkDir: string = joinPosix(homeDirectory, ".pico-sdk"); + + const result = await this._runSetupVenv(command, { + cwd: sdkDir, + windowsHide: true, + }); + + this._logger.info(`${result}`); + + this.running = false; + + return ""; + } +} diff --git a/src/extension.mts b/src/extension.mts index 4b2b2a1f..ce3d0c30 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -50,6 +50,7 @@ import { GetPicotoolPathCommand, GetOpenOCDRootCommand, GetSVDPathCommand, + SetupVenvCommand, } from "./commands/getPaths.mjs"; import { downloadAndInstallCmake, @@ -136,6 +137,7 @@ export async function activate(context: ExtensionContext): Promise { new GetPicotoolPathCommand(), new GetOpenOCDRootCommand(), new GetSVDPathCommand(context.extensionUri), + new SetupVenvCommand(), new CompileProjectCommand(), new RunProjectCommand(), new FlashProjectSWDCommand(), From 62af60d6bc251879e5bc66f1576c4107f06efdac Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 12:03:12 +0100 Subject: [PATCH 02/62] Install west in venv python --- src/commands/getPaths.mts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 8b6e589a..f97ebd94 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -18,7 +18,6 @@ import { buildToolchainPath, downloadAndInstallOpenOCD, downloadAndInstallPicotool, - buildSDKPath, } from "../utils/download.mjs"; import Settings, { SettingsKey, HOME_VAR } from "../settings.mjs"; import which from "which"; @@ -576,13 +575,33 @@ export class SetupVenvCommand extends CommandWithResult { const homeDirectory: string = homedir(); const sdkDir: string = joinPosix(homeDirectory, ".pico-sdk"); - const result = await this._runSetupVenv(command, { + let result = await this._runSetupVenv(command, { cwd: sdkDir, windowsHide: true, }); this._logger.info(`${result}`); + const venvPythonExe: string = joinPosix( + homeDirectory, + ".pico-sdk", + "venv", + "Scripts", + "python.exe" + ); + + const command2: string = [ + `${ + process.env.ComSpec === "powershell.exe" ? "&" : "" + }"${venvPythonExe}"`, + "-m pip install west pyelftools", + ].join(" "); + + result = await this._runSetupVenv(command2, { + cwd: sdkDir, + windowsHide: true, + }); + this.running = false; return ""; From 7eb5a0d28f1303fc2f18be5b31ff51dd0f35a2ba Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 12:09:24 +0100 Subject: [PATCH 03/62] Create zephyr manifest in pico directory --- src/commands/getPaths.mts | 38 +++++++++++++++++++++++++++++++++++++ src/commands/newProject.mts | 1 + 2 files changed, 39 insertions(+) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index f97ebd94..5c152528 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -602,6 +602,44 @@ export class SetupVenvCommand extends CommandWithResult { windowsHide: true, }); + // Create a Zephyr workspace, copy the west manifest in and initialise the workspace + const zephyrWorkspaceDirectory: string = joinPosix( + homeDirectory, + ".pico-sdk", + "zephyr_workspace" + ); + const zephyrManifestFile: string = joinPosix( + zephyrWorkspaceDirectory, + "west.yml" + ); + + const zephyrManifestContent: string = ` +manifest: + self: + west-commands: scripts/west-commands.yml + + remotes: + - name: zephyrproject-rtos + url-base: https://github.com/zephyrproject-rtos + + projects: + - name: zephyr + remote: zephyrproject-rtos + revision: main + import: + # By using name-allowlist we can clone only the modules that are + # strictly needed by the application. + name-allowlist: + - cmsis # required by the ARM port + - hal_rpi_pico # required for Pico board support + - hal_infineon +`; + + workspace.fs.writeFile( + Uri.file(zephyrManifestFile), + Buffer.from(zephyrManifestContent) + ); + this.running = false; return ""; diff --git a/src/commands/newProject.mts b/src/commands/newProject.mts index 8e9f4cdc..8c191198 100644 --- a/src/commands/newProject.mts +++ b/src/commands/newProject.mts @@ -15,6 +15,7 @@ export enum ProjectLang { cCpp = 1, micropython = 2, rust = 3, + zephyr = 5, } export default class NewProjectCommand extends CommandWithArgs { From 1435d7acbfc3a7db83de824f0eb3fab77226e172 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 13:31:51 +0100 Subject: [PATCH 04/62] Perform west init and update to setup Zephyr --- src/commands/getPaths.mts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 5c152528..e5922b4b 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -640,6 +640,36 @@ manifest: Buffer.from(zephyrManifestContent) ); + const westInitCommand: string = [ + `${ + process.env.ComSpec === "powershell.exe" ? "&" : "" + }"${venvPythonExe}"`, + "-m west init -l .", + ].join(" "); + + result = await this._runSetupVenv(westInitCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + }); + + this._logger.info(`${result}`); + + const westUpdateCommand: string = [ + `${ + process.env.ComSpec === "powershell.exe" ? "&" : "" + }"${venvPythonExe}"`, + "-m west update", + ].join(" "); + + result = await this._runSetupVenv(westUpdateCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + }); + + this._logger.info(`${result}`); + + this._logger.info("Complete"); + this.running = false; return ""; From 43dce25d12d0aaf76ea79a9c5baecf5dfa5657d3 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 13:45:32 +0100 Subject: [PATCH 05/62] Install zephyr sdk and blobs --- src/commands/getPaths.mts | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index e5922b4b..9b785f41 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -668,6 +668,48 @@ manifest: this._logger.info(`${result}`); + const westPipPackagesCommand: string = [ + `${ + process.env.ComSpec === "powershell.exe" ? "&" : "" + }"${venvPythonExe}"`, + "-m west packages pip --install", + ].join(" "); + + result = await this._runSetupVenv(westPipPackagesCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + }); + + this._logger.info(`${result}`); + + const westBlobsFetchCommand: string = [ + `${ + process.env.ComSpec === "powershell.exe" ? "&" : "" + }"${venvPythonExe}"`, + "-m west blobs fetch hal_infineon", + ].join(" "); + + result = await this._runSetupVenv(westBlobsFetchCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + }); + + this._logger.info(`${result}`); + + const westInstallSDKCommand: string = [ + `${ + process.env.ComSpec === "powershell.exe" ? "&" : "" + }"${venvPythonExe}"`, + "-m west sdk install -t arm-zephyr-eabi", + ].join(" "); + + result = await this._runSetupVenv(westInstallSDKCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + }); + + this._logger.info(`${result}`); + this._logger.info("Complete"); this.running = false; From 61e07b9d322da707c3820635f2e42410ef67966b Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 15:18:06 +0100 Subject: [PATCH 06/62] Move all zephyr downloads to zephyr_workspace directory --- src/commands/getPaths.mts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 9b785f41..6ed406b6 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -608,11 +608,14 @@ export class SetupVenvCommand extends CommandWithResult { ".pico-sdk", "zephyr_workspace" ); - const zephyrManifestFile: string = joinPosix( + + const zephyrManifestDir: string = joinPosix( zephyrWorkspaceDirectory, - "west.yml" + "manifest" ); + const zephyrManifestFile: string = joinPosix(zephyrManifestDir, "west.yml"); + const zephyrManifestContent: string = ` manifest: self: @@ -630,9 +633,9 @@ manifest: # By using name-allowlist we can clone only the modules that are # strictly needed by the application. name-allowlist: - - cmsis # required by the ARM port + - cmsis_6 # required by the ARM Cortex-M port - hal_rpi_pico # required for Pico board support - - hal_infineon + - hal_infineon # required for Wifi chip support `; workspace.fs.writeFile( @@ -644,7 +647,7 @@ manifest: `${ process.env.ComSpec === "powershell.exe" ? "&" : "" }"${venvPythonExe}"`, - "-m west init -l .", + "-m west init -l manifest", ].join(" "); result = await this._runSetupVenv(westInitCommand, { @@ -701,6 +704,7 @@ manifest: process.env.ComSpec === "powershell.exe" ? "&" : "" }"${venvPythonExe}"`, "-m west sdk install -t arm-zephyr-eabi", + `-b ${zephyrWorkspaceDirectory}`, ].join(" "); result = await this._runSetupVenv(westInstallSDKCommand, { From f2bdb3c7f9cccee56f0d86d65af4797e6c2841db Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 16:50:32 +0100 Subject: [PATCH 07/62] Add debug information to zephyr setup --- src/commands/getPaths.mts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 6ed406b6..c7788727 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -575,6 +575,7 @@ export class SetupVenvCommand extends CommandWithResult { const homeDirectory: string = homedir(); const sdkDir: string = joinPosix(homeDirectory, ".pico-sdk"); + this._logger.info("Setting up virtual environment for Zephyr"); let result = await this._runSetupVenv(command, { cwd: sdkDir, windowsHide: true, @@ -597,6 +598,7 @@ export class SetupVenvCommand extends CommandWithResult { "-m pip install west pyelftools", ].join(" "); + this._logger.info("Installing Python dependencies for Zephyr"); result = await this._runSetupVenv(command2, { cwd: sdkDir, windowsHide: true, @@ -650,6 +652,7 @@ manifest: "-m west init -l manifest", ].join(" "); + this._logger.info("Initialising West workspace"); result = await this._runSetupVenv(westInitCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, @@ -664,6 +667,7 @@ manifest: "-m west update", ].join(" "); + this._logger.info("Updating West workspace"); result = await this._runSetupVenv(westUpdateCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, @@ -678,6 +682,7 @@ manifest: "-m west packages pip --install", ].join(" "); + this._logger.info("Installing West Python packages"); result = await this._runSetupVenv(westPipPackagesCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, @@ -692,6 +697,7 @@ manifest: "-m west blobs fetch hal_infineon", ].join(" "); + this._logger.info("Fetching binary blobs for Zephyr"); result = await this._runSetupVenv(westBlobsFetchCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, @@ -707,6 +713,7 @@ manifest: `-b ${zephyrWorkspaceDirectory}`, ].join(" "); + this._logger.info("Installing Zephyr SDK"); result = await this._runSetupVenv(westInstallSDKCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, From d2616a7baf62fb22ea83176a4b1dafb4a2aaa061 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 17:00:53 +0100 Subject: [PATCH 08/62] Move zephyr venv to zephyr_workspace directory --- src/commands/getPaths.mts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index c7788727..363681ab 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -573,19 +573,26 @@ export class SetupVenvCommand extends CommandWithResult { ].join(" "); const homeDirectory: string = homedir(); - const sdkDir: string = joinPosix(homeDirectory, ".pico-sdk"); + + // Create a Zephyr workspace, copy the west manifest in and initialise the workspace + const zephyrWorkspaceDirectory: string = joinPosix( + homeDirectory, + ".pico-sdk", + "zephyr_workspace" + ); + + workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); this._logger.info("Setting up virtual environment for Zephyr"); let result = await this._runSetupVenv(command, { - cwd: sdkDir, + cwd: zephyrWorkspaceDirectory, windowsHide: true, }); this._logger.info(`${result}`); const venvPythonExe: string = joinPosix( - homeDirectory, - ".pico-sdk", + zephyrWorkspaceDirectory, "venv", "Scripts", "python.exe" @@ -600,17 +607,10 @@ export class SetupVenvCommand extends CommandWithResult { this._logger.info("Installing Python dependencies for Zephyr"); result = await this._runSetupVenv(command2, { - cwd: sdkDir, + cwd: zephyrWorkspaceDirectory, windowsHide: true, }); - // Create a Zephyr workspace, copy the west manifest in and initialise the workspace - const zephyrWorkspaceDirectory: string = joinPosix( - homeDirectory, - ".pico-sdk", - "zephyr_workspace" - ); - const zephyrManifestDir: string = joinPosix( zephyrWorkspaceDirectory, "manifest" From 5e1b6cbfddbab50016d6dc1bb24ee166ad705921 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 17:07:37 +0100 Subject: [PATCH 09/62] Rename command and refactor internal functions --- package.json | 4 ++-- src/commands/getPaths.mts | 26 ++++++++++++++------------ src/extension.mts | 4 ++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index eb972377..c0db03e3 100644 --- a/package.json +++ b/package.json @@ -263,8 +263,8 @@ "enablement": "false" }, { - "command": "raspberry-pi-pico.setupVenv", - "title": "Setup Venv", + "command": "raspberry-pi-pico.setupZephyr", + "title": "Setup Zephyr Toolchain", "category": "Raspberry Pi Pico" } ], diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 363681ab..9cd41b4d 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -510,21 +510,23 @@ export class GetSVDPathCommand extends CommandWithResult { } } -export class SetupVenvCommand extends CommandWithResult { +export class SetupZephyrCommand extends CommandWithResult { private running: boolean = false; - public static readonly id = "setupVenv"; + public static readonly id = "setupZephyr"; constructor() { - super(SetupVenvCommand.id); + super(SetupZephyrCommand.id); } - private readonly _logger: Logger = new Logger("SetupVenv"); + private readonly _logger: Logger = new Logger("SetupZephyr"); - private _runSetupVenv( + private _runCommand( command: string, options: ExecOptions ): Promise { + this._logger.debug(`Running: ${command}`); + return new Promise(resolve => { const generatorProcess = exec( command, @@ -584,7 +586,7 @@ export class SetupVenvCommand extends CommandWithResult { workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); this._logger.info("Setting up virtual environment for Zephyr"); - let result = await this._runSetupVenv(command, { + let result = await this._runCommand(command, { cwd: zephyrWorkspaceDirectory, windowsHide: true, }); @@ -606,7 +608,7 @@ export class SetupVenvCommand extends CommandWithResult { ].join(" "); this._logger.info("Installing Python dependencies for Zephyr"); - result = await this._runSetupVenv(command2, { + result = await this._runCommand(command2, { cwd: zephyrWorkspaceDirectory, windowsHide: true, }); @@ -653,7 +655,7 @@ manifest: ].join(" "); this._logger.info("Initialising West workspace"); - result = await this._runSetupVenv(westInitCommand, { + result = await this._runCommand(westInitCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, }); @@ -668,7 +670,7 @@ manifest: ].join(" "); this._logger.info("Updating West workspace"); - result = await this._runSetupVenv(westUpdateCommand, { + result = await this._runCommand(westUpdateCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, }); @@ -683,7 +685,7 @@ manifest: ].join(" "); this._logger.info("Installing West Python packages"); - result = await this._runSetupVenv(westPipPackagesCommand, { + result = await this._runCommand(westPipPackagesCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, }); @@ -698,7 +700,7 @@ manifest: ].join(" "); this._logger.info("Fetching binary blobs for Zephyr"); - result = await this._runSetupVenv(westBlobsFetchCommand, { + result = await this._runCommand(westBlobsFetchCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, }); @@ -714,7 +716,7 @@ manifest: ].join(" "); this._logger.info("Installing Zephyr SDK"); - result = await this._runSetupVenv(westInstallSDKCommand, { + result = await this._runCommand(westInstallSDKCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, }); diff --git a/src/extension.mts b/src/extension.mts index ce3d0c30..fe42e9ab 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -50,7 +50,7 @@ import { GetPicotoolPathCommand, GetOpenOCDRootCommand, GetSVDPathCommand, - SetupVenvCommand, + SetupZephyrCommand, } from "./commands/getPaths.mjs"; import { downloadAndInstallCmake, @@ -137,7 +137,7 @@ export async function activate(context: ExtensionContext): Promise { new GetPicotoolPathCommand(), new GetOpenOCDRootCommand(), new GetSVDPathCommand(context.extensionUri), - new SetupVenvCommand(), + new SetupZephyrCommand(), new CompileProjectCommand(), new RunProjectCommand(), new FlashProjectSWDCommand(), From d57342b3345fbff320baca336b0a4b0d8de549ba Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 18:02:39 +0100 Subject: [PATCH 10/62] Refactor west initialisation and make exe paths portable --- src/commands/getPaths.mts | 70 +++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 9cd41b4d..f07c0d71 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -594,16 +594,15 @@ export class SetupZephyrCommand extends CommandWithResult { this._logger.info(`${result}`); const venvPythonExe: string = joinPosix( + process.env.ComSpec === "powershell.exe" ? "&" : "", zephyrWorkspaceDirectory, "venv", - "Scripts", - "python.exe" + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "python.exe" : "python" ); const command2: string = [ - `${ - process.env.ComSpec === "powershell.exe" ? "&" : "" - }"${venvPythonExe}"`, + venvPythonExe, "-m pip install west pyelftools", ].join(" "); @@ -613,6 +612,14 @@ export class SetupZephyrCommand extends CommandWithResult { windowsHide: true, }); + const westExe: string = joinPosix( + process.env.ComSpec === "powershell.exe" ? "&" : "", + zephyrWorkspaceDirectory, + "venv", + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "west.exe" : "west" + ); + const zephyrManifestDir: string = joinPosix( zephyrWorkspaceDirectory, "manifest" @@ -647,27 +654,26 @@ manifest: Buffer.from(zephyrManifestContent) ); - const westInitCommand: string = [ - `${ - process.env.ComSpec === "powershell.exe" ? "&" : "" - }"${venvPythonExe}"`, - "-m west init -l manifest", - ].join(" "); + const zephyrWorkspaceFiles = await workspace.fs.readDirectory( + Uri.file(zephyrWorkspaceDirectory) + ); - this._logger.info("Initialising West workspace"); - result = await this._runCommand(westInitCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - }); + const westAlreadyExists = zephyrWorkspaceFiles.find(x => x[0] === ".west"); + if (westAlreadyExists) { + this._logger.info("West workspace already initialised."); + } else { + this._logger.info("No West workspace found. Initialising..."); - this._logger.info(`${result}`); + const westInitCommand: string = [westExe, "init -l manifest"].join(" "); + result = await this._runCommand(westInitCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + }); - const westUpdateCommand: string = [ - `${ - process.env.ComSpec === "powershell.exe" ? "&" : "" - }"${venvPythonExe}"`, - "-m west update", - ].join(" "); + this._logger.info(`${result}`); + } + + const westUpdateCommand: string = [westExe, "update"].join(" "); this._logger.info("Updating West workspace"); result = await this._runCommand(westUpdateCommand, { @@ -678,10 +684,8 @@ manifest: this._logger.info(`${result}`); const westPipPackagesCommand: string = [ - `${ - process.env.ComSpec === "powershell.exe" ? "&" : "" - }"${venvPythonExe}"`, - "-m west packages pip --install", + westExe, + "packages pip --install", ].join(" "); this._logger.info("Installing West Python packages"); @@ -693,10 +697,8 @@ manifest: this._logger.info(`${result}`); const westBlobsFetchCommand: string = [ - `${ - process.env.ComSpec === "powershell.exe" ? "&" : "" - }"${venvPythonExe}"`, - "-m west blobs fetch hal_infineon", + westExe, + "blobs fetch hal_infineon", ].join(" "); this._logger.info("Fetching binary blobs for Zephyr"); @@ -708,10 +710,8 @@ manifest: this._logger.info(`${result}`); const westInstallSDKCommand: string = [ - `${ - process.env.ComSpec === "powershell.exe" ? "&" : "" - }"${venvPythonExe}"`, - "-m west sdk install -t arm-zephyr-eabi", + westExe, + "sdk install -t arm-zephyr-eabi", `-b ${zephyrWorkspaceDirectory}`, ].join(" "); From ff029b99d3f6928d5905139916943437c93a76be Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 19:19:58 +0100 Subject: [PATCH 11/62] Trigger openocd download and install at end of zephyr setup --- src/commands/getPaths.mts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index f07c0d71..54f62a3a 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -723,6 +723,10 @@ manifest: this._logger.info(`${result}`); + this._logger.info("Installing OpenOCD"); + const openocdResult = await downloadAndInstallOpenOCD(openOCDVersion); + this._logger.info(`${openocdResult}`); + this._logger.info("Complete"); this.running = false; From 5215232999fa06b4b20f51899999f95456d294f1 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 15 May 2025 21:02:52 +0100 Subject: [PATCH 12/62] Add command to get west path --- package.json | 6 ++++++ src/commands/getPaths.mts | 30 ++++++++++++++++++++++++++++++ src/extension.mts | 2 ++ src/utils/download.mts | 11 +++++++++++ 4 files changed, 49 insertions(+) diff --git a/package.json b/package.json index c0db03e3..99ef3f87 100644 --- a/package.json +++ b/package.json @@ -166,6 +166,12 @@ "category": "Raspberry Pi Pico", "enablement": "false" }, + { + "command": "raspberry-pi-pico.getWestPath", + "title": "Get West path", + "category": "Raspberry Pi Pico", + "enablement": "false" + }, { "command": "raspberry-pi-pico.compileProject", "title": "Compile Pico Project", diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 54f62a3a..f568e461 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -16,6 +16,7 @@ import { buildPicotoolPath, buildSDKPath, buildToolchainPath, + buildWestPath, downloadAndInstallOpenOCD, downloadAndInstallPicotool, } from "../utils/download.mjs"; @@ -510,6 +511,35 @@ export class GetSVDPathCommand extends CommandWithResult { } } +export class GetWestPathCommand extends CommandWithResult { + private running: boolean = false; + + public static readonly id = "getWestPath"; + + constructor() { + super(GetWestPathCommand.id); + } + + execute(): string | undefined { + if (this.running) { + return undefined; + } + this.running = true; + + const result = buildWestPath(); + + if (result === null || !result) { + this.running = false; + + return undefined; + } + + this.running = false; + + return result; + } +} + export class SetupZephyrCommand extends CommandWithResult { private running: boolean = false; diff --git a/src/extension.mts b/src/extension.mts index fe42e9ab..b7ca3f07 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -51,6 +51,7 @@ import { GetOpenOCDRootCommand, GetSVDPathCommand, SetupZephyrCommand, + GetWestPathCommand, } from "./commands/getPaths.mjs"; import { downloadAndInstallCmake, @@ -137,6 +138,7 @@ export async function activate(context: ExtensionContext): Promise { new GetPicotoolPathCommand(), new GetOpenOCDRootCommand(), new GetSVDPathCommand(context.extensionUri), + new GetWestPathCommand(), new SetupZephyrCommand(), new CompileProjectCommand(), new RunProjectCommand(), diff --git a/src/utils/download.mts b/src/utils/download.mts index 9258dec4..284c540e 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -224,6 +224,17 @@ export async function downloadAndReadFile( return response.statusCode === HTTP_STATUS_OK ? response.body : undefined; } +export function buildWestPath(): string { + return joinPosix( + homeDirectory.replaceAll("\\", "/"), + ".pico-sdk", + "zephyr_workspace", + "venv", + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "west.exe" : "west" + ); +} + /** * Downloads and installs an archive from a URL. * From 883272f389d277df1555bbd78aa40ef74d1165b2 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 16 May 2025 10:54:01 +0100 Subject: [PATCH 13/62] Add command to get Zephyr workspace path --- package.json | 8 +++++++- src/commands/getPaths.mts | 32 ++++++++++++++++++++++++++++++++ src/extension.mts | 2 ++ src/utils/download.mts | 8 ++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 99ef3f87..a1a74635 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,7 @@ "title": "Get OpenOCD root", "category": "Raspberry Pi Pico", "enablement": "false" - }, + }, { "command": "raspberry-pi-pico.getSVDPath", "title": "Get SVD Path (rust only)", @@ -172,6 +172,12 @@ "category": "Raspberry Pi Pico", "enablement": "false" }, + { + "command": "raspberry-pi-pico.getZephyrWorkspacePath", + "title": "Get Zephyr workspace path", + "category": "Raspberry Pi Pico", + "enablement": "false" + }, { "command": "raspberry-pi-pico.compileProject", "title": "Compile Pico Project", diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index f568e461..06e8a168 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -17,6 +17,7 @@ import { buildSDKPath, buildToolchainPath, buildWestPath, + buildZephyrWorkspacePath, downloadAndInstallOpenOCD, downloadAndInstallPicotool, } from "../utils/download.mjs"; @@ -540,6 +541,37 @@ export class GetWestPathCommand extends CommandWithResult { } } +export class GetZephyrWorkspacePathCommand extends CommandWithResult< + string | undefined +> { + private running: boolean = false; + + public static readonly id = "getZephyrWorkspacePath"; + + constructor() { + super(GetZephyrWorkspacePathCommand.id); + } + + execute(): string | undefined { + if (this.running) { + return undefined; + } + this.running = true; + + const result = buildZephyrWorkspacePath(); + + if (result === null || !result) { + this.running = false; + + return undefined; + } + + this.running = false; + + return result; + } +} + export class SetupZephyrCommand extends CommandWithResult { private running: boolean = false; diff --git a/src/extension.mts b/src/extension.mts index b7ca3f07..6e2c0c33 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -52,6 +52,7 @@ import { GetSVDPathCommand, SetupZephyrCommand, GetWestPathCommand, + GetZephyrWorkspacePathCommand, } from "./commands/getPaths.mjs"; import { downloadAndInstallCmake, @@ -139,6 +140,7 @@ export async function activate(context: ExtensionContext): Promise { new GetOpenOCDRootCommand(), new GetSVDPathCommand(context.extensionUri), new GetWestPathCommand(), + new GetZephyrWorkspacePathCommand(), new SetupZephyrCommand(), new CompileProjectCommand(), new RunProjectCommand(), diff --git a/src/utils/download.mts b/src/utils/download.mts index 284c540e..71d8ab34 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -235,6 +235,14 @@ export function buildWestPath(): string { ); } +export function buildZephyrWorkspacePath(): string { + return joinPosix( + homeDirectory.replaceAll("\\", "/"), + ".pico-sdk", + "zephyr_workspace" + ); +} + /** * Downloads and installs an archive from a URL. * From f3607ce83a0f2203fe1ba66f7e3087c9dd83bec8 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 16 May 2025 13:54:21 +0100 Subject: [PATCH 14/62] Create new Zephyr project based on app example --- package.json | 5 +++ src/commands/getPaths.mts | 92 +++++++++++++++++++++++++++++++++++++++ src/extension.mts | 2 + 3 files changed, 99 insertions(+) diff --git a/package.json b/package.json index a1a74635..a9940f3b 100644 --- a/package.json +++ b/package.json @@ -278,6 +278,11 @@ "command": "raspberry-pi-pico.setupZephyr", "title": "Setup Zephyr Toolchain", "category": "Raspberry Pi Pico" + }, + { + "command": "raspberry-pi-pico.newZephyrProject", + "title": "New Zephyr Project", + "category": "Raspberry Pi Pico" } ], "configuration": { diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 06e8a168..581025b4 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -796,3 +796,95 @@ manifest: return ""; } } + +export class NewZephyrProjectCommand extends CommandWithResult< + string | undefined +> { + private running: boolean = false; + + public static readonly id = "newZephyrProject"; + + private readonly _logger: Logger = new Logger("newZephyrProject"); + + constructor() { + super(NewZephyrProjectCommand.id); + } + + async execute(): string | undefined { + if (this.running) { + return undefined; + } + this.running = true; + + this._logger.info("Generating new Zephyr Project"); + + // Create a new directory to put the project in + const homeDirectory: string = homedir(); + const newProjectDir = joinPosix( + homeDirectory, + "zephyr_test", + "zephyr_project" + ); + await workspace.fs.createDirectory(Uri.file(newProjectDir)); + + // Copy the Zephyr App into the new directory + const zephyrAppDir = joinPosix( + buildZephyrWorkspacePath(), + "pico-zephyr", + "app" + ); + await workspace.fs.copy(Uri.file(zephyrAppDir), Uri.file(newProjectDir), { + overwrite: true, + }); + + // Copy the VSCode tasks and launch files into the new workspace folder + const newProjectVSCodeDir = joinPosix(newProjectDir, ".vscode"); + await workspace.fs.createDirectory(Uri.file(newProjectVSCodeDir)); + + const newProjectTasksFile = joinPosix(newProjectVSCodeDir, "tasks.json"); + const newProjectLaunchFile = joinPosix(newProjectVSCodeDir, "launch.json"); + + const zephyrTasksFile = joinPosix( + buildZephyrWorkspacePath(), + "pico-zephyr", + ".vscode", + "tasks.json" + ); + const zephyrLaunchFile = joinPosix( + buildZephyrWorkspacePath(), + "pico-zephyr", + ".vscode", + "launch.json" + ); + + await workspace.fs.copy( + Uri.file(zephyrTasksFile), + Uri.file(newProjectTasksFile), + { + overwrite: true, + } + ); + + await workspace.fs.copy( + Uri.file(zephyrLaunchFile), + Uri.file(newProjectLaunchFile), + { + overwrite: true, + } + ); + + const result = true; + + if (result === null || !result) { + this.running = false; + + return undefined; + } + + this._logger.info(`Zephyr Project generated at ${newProjectDir}`); + + this.running = false; + + return "hello"; + } +} diff --git a/src/extension.mts b/src/extension.mts index 6e2c0c33..1777a630 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -53,6 +53,7 @@ import { SetupZephyrCommand, GetWestPathCommand, GetZephyrWorkspacePathCommand, + NewZephyrProjectCommand, } from "./commands/getPaths.mjs"; import { downloadAndInstallCmake, @@ -142,6 +143,7 @@ export async function activate(context: ExtensionContext): Promise { new GetWestPathCommand(), new GetZephyrWorkspacePathCommand(), new SetupZephyrCommand(), + new NewZephyrProjectCommand(), new CompileProjectCommand(), new RunProjectCommand(), new FlashProjectSWDCommand(), From 4ac556b4954cb862634ad827c34b4c6c2432acc4 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 16 May 2025 15:17:41 +0100 Subject: [PATCH 15/62] Check for Zephyr projects which can use the pico extension --- package.json | 10 +++++----- src/contextKeys.mts | 1 + src/extension.mts | 48 +++++++++++++++++++++++++++++++-------------- 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index a9940f3b..74bf2eb8 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "command": "raspberry-pi-pico.switchSDK", "title": "Switch Pico SDK", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject" + "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isPicoZephyrProject" }, { "command": "raspberry-pi-pico.switchBoard", @@ -188,7 +188,7 @@ "command": "raspberry-pi-pico.runProject", "title": "Run Pico Project (USB)", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject" + "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isPicoZephyrProject" }, { "command": "raspberry-pi-pico.clearGithubApiCache", @@ -216,13 +216,13 @@ "command": "raspberry-pi-pico.configureCmake", "title": "Configure CMake", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject" + "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isPicoZephyrProject" }, { "command": "raspberry-pi-pico.switchBuildType", "title": "Switch Build Type", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject" + "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isPicoZephyrProject" }, { "command": "raspberry-pi-pico.importProject", @@ -254,7 +254,7 @@ "command": "raspberry-pi-pico.cleanCmake", "title": "Clean CMake", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject" + "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isPicoZephyrProject" }, { "command": "raspberry-pi-pico.getRTTDecoderPath", diff --git a/src/contextKeys.mts b/src/contextKeys.mts index 4b515be4..300fc722 100644 --- a/src/contextKeys.mts +++ b/src/contextKeys.mts @@ -3,4 +3,5 @@ import { extensionName } from "./commands/command.mjs"; export enum ContextKeys { isPicoProject = `${extensionName}.isPicoProject`, isRustProject = `${extensionName}.isRustProject`, + isPicoZephyrProject = `${extensionName}.isPicoZephyrProject`, } diff --git a/src/extension.mts b/src/extension.mts index 1777a630..0a4631a8 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -259,21 +259,39 @@ export async function activate(context: ExtensionContext): Promise { return; } - // check for pico_sdk_init() in CMakeLists.txt - if ( - !readFileSync(cmakeListsFilePath) - .toString("utf-8") - .includes("pico_sdk_init()") - ) { - Logger.warn( - LoggerSource.extension, - "No pico_sdk_init() in CMakeLists.txt found." - ); - await commands.executeCommand( - "setContext", - ContextKeys.isPicoProject, - false - ); + // Set Pico Zephyr Project false by default + await commands.executeCommand( + "setContext", + ContextKeys.isPicoZephyrProject, + false + ); + + // Check for pico_zephyr in CMakeLists.txt + if ( + readFileSync(cmakeListsFilePath).toString("utf-8").includes("pico_zephyr") + ) { + Logger.info(LoggerSource.extension, "Pico Zephyr Project"); + await commands.executeCommand( + "setContext", + ContextKeys.isPicoZephyrProject, + true + ); + } + // check for pico_sdk_init() in CMakeLists.txt + else if ( + !readFileSync(cmakeListsFilePath) + .toString("utf-8") + .includes("pico_sdk_init()") + ) { + Logger.warn( + LoggerSource.extension, + "No pico_sdk_init() in CMakeLists.txt found." + ); + await commands.executeCommand( + "setContext", + ContextKeys.isPicoProject, + false + ); return; } From b98e090b2f29c34c1b02a31e61fda65aaca44314 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 16 May 2025 15:33:24 +0100 Subject: [PATCH 16/62] Move Zephyr project generation to new file --- src/commands/getPaths.mts | 92 --------------------------- src/extension.mts | 2 +- src/utils/generateZephyrProject.mts | 99 +++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 93 deletions(-) create mode 100644 src/utils/generateZephyrProject.mts diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 581025b4..06e8a168 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -796,95 +796,3 @@ manifest: return ""; } } - -export class NewZephyrProjectCommand extends CommandWithResult< - string | undefined -> { - private running: boolean = false; - - public static readonly id = "newZephyrProject"; - - private readonly _logger: Logger = new Logger("newZephyrProject"); - - constructor() { - super(NewZephyrProjectCommand.id); - } - - async execute(): string | undefined { - if (this.running) { - return undefined; - } - this.running = true; - - this._logger.info("Generating new Zephyr Project"); - - // Create a new directory to put the project in - const homeDirectory: string = homedir(); - const newProjectDir = joinPosix( - homeDirectory, - "zephyr_test", - "zephyr_project" - ); - await workspace.fs.createDirectory(Uri.file(newProjectDir)); - - // Copy the Zephyr App into the new directory - const zephyrAppDir = joinPosix( - buildZephyrWorkspacePath(), - "pico-zephyr", - "app" - ); - await workspace.fs.copy(Uri.file(zephyrAppDir), Uri.file(newProjectDir), { - overwrite: true, - }); - - // Copy the VSCode tasks and launch files into the new workspace folder - const newProjectVSCodeDir = joinPosix(newProjectDir, ".vscode"); - await workspace.fs.createDirectory(Uri.file(newProjectVSCodeDir)); - - const newProjectTasksFile = joinPosix(newProjectVSCodeDir, "tasks.json"); - const newProjectLaunchFile = joinPosix(newProjectVSCodeDir, "launch.json"); - - const zephyrTasksFile = joinPosix( - buildZephyrWorkspacePath(), - "pico-zephyr", - ".vscode", - "tasks.json" - ); - const zephyrLaunchFile = joinPosix( - buildZephyrWorkspacePath(), - "pico-zephyr", - ".vscode", - "launch.json" - ); - - await workspace.fs.copy( - Uri.file(zephyrTasksFile), - Uri.file(newProjectTasksFile), - { - overwrite: true, - } - ); - - await workspace.fs.copy( - Uri.file(zephyrLaunchFile), - Uri.file(newProjectLaunchFile), - { - overwrite: true, - } - ); - - const result = true; - - if (result === null || !result) { - this.running = false; - - return undefined; - } - - this._logger.info(`Zephyr Project generated at ${newProjectDir}`); - - this.running = false; - - return "hello"; - } -} diff --git a/src/extension.mts b/src/extension.mts index 0a4631a8..2b111d1a 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -53,7 +53,6 @@ import { SetupZephyrCommand, GetWestPathCommand, GetZephyrWorkspacePathCommand, - NewZephyrProjectCommand, } from "./commands/getPaths.mjs"; import { downloadAndInstallCmake, @@ -65,6 +64,7 @@ import { downloadAndInstallOpenOCD, installLatestRustRequirements, } from "./utils/download.mjs"; +import { NewZephyrProjectCommand } from "./utils/generateZephyrProject.mjs"; import { SDK_REPOSITORY_URL } from "./utils/githubREST.mjs"; import { getSupportedToolchains } from "./utils/toolchainUtil.mjs"; import { diff --git a/src/utils/generateZephyrProject.mts b/src/utils/generateZephyrProject.mts new file mode 100644 index 00000000..1af5c0d1 --- /dev/null +++ b/src/utils/generateZephyrProject.mts @@ -0,0 +1,99 @@ +import { homedir } from "os"; +import { join as joinPosix } from "path/posix"; +import { workspace, Uri } from "vscode"; + +import { CommandWithResult } from "../commands/command.mjs"; +import { buildZephyrWorkspacePath } from "./download.mjs"; +import Logger from "../logger.mjs"; + +export class NewZephyrProjectCommand extends CommandWithResult< + string | undefined +> { + private running: boolean = false; + + public static readonly id = "newZephyrProject"; + + private readonly _logger: Logger = new Logger("newZephyrProject"); + + constructor() { + super(NewZephyrProjectCommand.id); + } + + async execute(): Promise { + if (this.running) { + return undefined; + } + this.running = true; + + this._logger.info("Generating new Zephyr Project"); + + // Create a new directory to put the project in + const homeDirectory: string = homedir(); + const newProjectDir = joinPosix( + homeDirectory, + "zephyr_test", + "zephyr_project" + ); + await workspace.fs.createDirectory(Uri.file(newProjectDir)); + + // Copy the Zephyr App into the new directory + const zephyrAppDir = joinPosix( + buildZephyrWorkspacePath(), + "pico-zephyr", + "app" + ); + await workspace.fs.copy(Uri.file(zephyrAppDir), Uri.file(newProjectDir), { + overwrite: true, + }); + + // Copy the VSCode tasks and launch files into the new workspace folder + const newProjectVSCodeDir = joinPosix(newProjectDir, ".vscode"); + await workspace.fs.createDirectory(Uri.file(newProjectVSCodeDir)); + + const newProjectTasksFile = joinPosix(newProjectVSCodeDir, "tasks.json"); + const newProjectLaunchFile = joinPosix(newProjectVSCodeDir, "launch.json"); + + const zephyrTasksFile = joinPosix( + buildZephyrWorkspacePath(), + "pico-zephyr", + ".vscode", + "tasks.json" + ); + const zephyrLaunchFile = joinPosix( + buildZephyrWorkspacePath(), + "pico-zephyr", + ".vscode", + "launch.json" + ); + + await workspace.fs.copy( + Uri.file(zephyrTasksFile), + Uri.file(newProjectTasksFile), + { + overwrite: true, + } + ); + + await workspace.fs.copy( + Uri.file(zephyrLaunchFile), + Uri.file(newProjectLaunchFile), + { + overwrite: true, + } + ); + + const result = true; + + if (result === null || !result) { + this.running = false; + + return undefined; + } + + this._logger.info(`Zephyr Project generated at ${newProjectDir}`); + + this.running = false; + + return "hello"; + } +} From deb82f07b2ca4fc5e6a39dc587166fb9e6bd842f Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 16 May 2025 15:58:16 +0100 Subject: [PATCH 17/62] Simplify Zephyr project generation --- src/utils/generateZephyrProject.mts | 36 ----------------------------- 1 file changed, 36 deletions(-) diff --git a/src/utils/generateZephyrProject.mts b/src/utils/generateZephyrProject.mts index 1af5c0d1..13e8edb0 100644 --- a/src/utils/generateZephyrProject.mts +++ b/src/utils/generateZephyrProject.mts @@ -46,42 +46,6 @@ export class NewZephyrProjectCommand extends CommandWithResult< overwrite: true, }); - // Copy the VSCode tasks and launch files into the new workspace folder - const newProjectVSCodeDir = joinPosix(newProjectDir, ".vscode"); - await workspace.fs.createDirectory(Uri.file(newProjectVSCodeDir)); - - const newProjectTasksFile = joinPosix(newProjectVSCodeDir, "tasks.json"); - const newProjectLaunchFile = joinPosix(newProjectVSCodeDir, "launch.json"); - - const zephyrTasksFile = joinPosix( - buildZephyrWorkspacePath(), - "pico-zephyr", - ".vscode", - "tasks.json" - ); - const zephyrLaunchFile = joinPosix( - buildZephyrWorkspacePath(), - "pico-zephyr", - ".vscode", - "launch.json" - ); - - await workspace.fs.copy( - Uri.file(zephyrTasksFile), - Uri.file(newProjectTasksFile), - { - overwrite: true, - } - ); - - await workspace.fs.copy( - Uri.file(zephyrLaunchFile), - Uri.file(newProjectLaunchFile), - { - overwrite: true, - } - ); - const result = true; if (result === null || !result) { From af47a2a03bb3b7333465672af96a50da507b7e05 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 16 May 2025 16:07:46 +0100 Subject: [PATCH 18/62] Move manifest creation to top of Zephyr setup function --- src/commands/getPaths.mts | 78 ++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 06e8a168..be915e43 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -618,6 +618,42 @@ export class SetupZephyrCommand extends CommandWithResult { window.showInformationMessage("Setup Venv Command Running"); + const zephyrWorkspaceDirectory = buildZephyrWorkspacePath(); + + const zephyrManifestDir: string = joinPosix( + zephyrWorkspaceDirectory, + "manifest" + ); + + const zephyrManifestFile: string = joinPosix(zephyrManifestDir, "west.yml"); + + const zephyrManifestContent: string = ` +manifest: + self: + west-commands: scripts/west-commands.yml + + remotes: + - name: zephyrproject-rtos + url-base: https://github.com/zephyrproject-rtos + + projects: + - name: zephyr + remote: zephyrproject-rtos + revision: main + import: + # By using name-allowlist we can clone only the modules that are + # strictly needed by the application. + name-allowlist: + - cmsis_6 # required by the ARM Cortex-M port + - hal_rpi_pico # required for Pico board support + - hal_infineon # required for Wifi chip support +`; + + workspace.fs.writeFile( + Uri.file(zephyrManifestFile), + Buffer.from(zephyrManifestContent) + ); + const python3Path = await findPython(); if (!python3Path) { this._logger.error("Failed to find Python3 executable."); @@ -636,15 +672,7 @@ export class SetupZephyrCommand extends CommandWithResult { "-m venv venv", ].join(" "); - const homeDirectory: string = homedir(); - // Create a Zephyr workspace, copy the west manifest in and initialise the workspace - const zephyrWorkspaceDirectory: string = joinPosix( - homeDirectory, - ".pico-sdk", - "zephyr_workspace" - ); - workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); this._logger.info("Setting up virtual environment for Zephyr"); @@ -682,40 +710,6 @@ export class SetupZephyrCommand extends CommandWithResult { process.platform === "win32" ? "west.exe" : "west" ); - const zephyrManifestDir: string = joinPosix( - zephyrWorkspaceDirectory, - "manifest" - ); - - const zephyrManifestFile: string = joinPosix(zephyrManifestDir, "west.yml"); - - const zephyrManifestContent: string = ` -manifest: - self: - west-commands: scripts/west-commands.yml - - remotes: - - name: zephyrproject-rtos - url-base: https://github.com/zephyrproject-rtos - - projects: - - name: zephyr - remote: zephyrproject-rtos - revision: main - import: - # By using name-allowlist we can clone only the modules that are - # strictly needed by the application. - name-allowlist: - - cmsis_6 # required by the ARM Cortex-M port - - hal_rpi_pico # required for Pico board support - - hal_infineon # required for Wifi chip support -`; - - workspace.fs.writeFile( - Uri.file(zephyrManifestFile), - Buffer.from(zephyrManifestContent) - ); - const zephyrWorkspaceFiles = await workspace.fs.readDirectory( Uri.file(zephyrWorkspaceDirectory) ); From f9d2713ae7c339a2c052ed8a9c40dea606fcf0b0 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 16 May 2025 16:24:43 +0100 Subject: [PATCH 19/62] Fix missing await to create zephyr workspace --- src/commands/getPaths.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index be915e43..35f395b6 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -649,7 +649,7 @@ manifest: - hal_infineon # required for Wifi chip support `; - workspace.fs.writeFile( + await workspace.fs.writeFile( Uri.file(zephyrManifestFile), Buffer.from(zephyrManifestContent) ); From efdf968239f67ca6f7a46e452e67593085df5a80 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Wed, 21 May 2025 11:49:35 +0100 Subject: [PATCH 20/62] Setup embeddable Python for package installation --- src/commands/getPaths.mts | 2 +- src/utils/download.mts | 90 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 35f395b6..572ff8a4 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -669,7 +669,7 @@ manifest: const command: string = [ `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, - "-m venv venv", + "-m virtualenv venv", ].join(" "); // Create a Zephyr workspace, copy the west manifest in and initialise the workspace diff --git a/src/utils/download.mts b/src/utils/download.mts index 71d8ab34..ef219bde 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -7,6 +7,7 @@ import { rmSync, } from "fs"; import { mkdir } from "fs/promises"; +import { type ExecOptions, exec } from "child_process"; import { homedir, tmpdir } from "os"; import { basename, dirname, join } from "path"; import { join as joinPosix } from "path/posix"; @@ -22,7 +23,7 @@ import { cloneRepository, initSubmodules, ensureGit } from "./gitUtil.mjs"; import { HOME_VAR, SettingsKey } from "../settings.mjs"; import Settings from "../settings.mjs"; import which from "which"; -import { ProgressLocation, type Uri, window } from "vscode"; +import { ProgressLocation, type Uri, window, workspace } from "vscode"; import { fileURLToPath } from "url"; import { type GithubReleaseAssetData, @@ -1179,6 +1180,29 @@ export async function downloadAndInstallCmake( ); } +function _runCommand( + command: string, + options: ExecOptions +): Promise { + Logger.info(LoggerSource.downloader, command); + + return new Promise(resolve => { + const generatorProcess = exec(command, options, (error, stdout, stderr) => { + Logger.info(LoggerSource.downloader, stdout); + Logger.info(LoggerSource.downloader, stderr); + if (error) { + Logger.error(LoggerSource.downloader, `${error.message}`); + resolve(null); // indicate error + } + }); + + generatorProcess.on("exit", code => { + // Resolve with exit code or -1 if code is undefined + resolve(code); + }); + }); +} + /** * Downloads and installs Python3 Embed. * @@ -1314,13 +1338,15 @@ export async function downloadEmbedPython( }); }*/ + let pythonExe; + try { // unpack the archive const success = unzipFile(archiveFilePath, targetDirectory); // delete tmp file rmSync(archiveFilePath, { recursive: true, force: true }); - return success ? `${settingsTargetDirectory}/python.exe` : undefined; + pythonExe = success ? `${settingsTargetDirectory}/python.exe` : undefined; } catch (error) { Logger.error( LoggerSource.downloader, @@ -1329,6 +1355,66 @@ export async function downloadEmbedPython( return; } + + // Set up pip for embeddable Python to allow installation of packages + if (pythonExe) { + const fullPythonExe = `${targetDirectory}/python.exe`; + + const getPipURL = new URL("https://bootstrap.pypa.io/get-pip.py"); + const success = await downloadFileGot( + getPipURL, + joinPosix(targetDirectory, "get-pip.py") + ); + + if (!success) { + return undefined; + } + + const dllDir = `${targetDirectory}/DLLs`; + await workspace.fs.createDirectory(Uri.file(dllDir)); + + // Write to *._pth to allow use of installed packages + const pthFile = `${targetDirectory}/python312._pth`; + let pthContents = ( + await workspace.fs.readFile(Uri.file(pthFile)) + ).toString(); + pthContents += "\nimport site"; + await workspace.fs.writeFile(Uri.file(pthFile), Buffer.from(pthContents)); + + const installPipCommand: string = [ + `${ + process.env.ComSpec === "powershell.exe" ? "&" : "" + }"${fullPythonExe}"`, + "get-pip.py", + ].join(" "); + + let commandResult = await _runCommand(installPipCommand, { + cwd: targetDirectory, + windowsHide: true, + }); + + if (commandResult !== 0) { + return undefined; + } + + const installVirtualenvCommand: string = [ + `${ + process.env.ComSpec === "powershell.exe" ? "&" : "" + }"${fullPythonExe}"`, + "-m pip install virtualenv", + ].join(" "); + + commandResult = await _runCommand(installVirtualenvCommand, { + cwd: targetDirectory, + windowsHide: true, + }); + + if (commandResult !== 0) { + return undefined; + } + } + + return pythonExe; } /** From 868a60ad6d54ee75d145d1bde693455ecbe10efe Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 23 May 2025 13:34:54 +0100 Subject: [PATCH 21/62] Setup CMake and Ninja as part of Zephyr install --- src/commands/getPaths.mts | 39 ++++++++++++++++++++++++++++++++++++++- src/utils/cmakeUtil.mts | 2 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 572ff8a4..43a7a41d 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -18,6 +18,8 @@ import { buildToolchainPath, buildWestPath, buildZephyrWorkspacePath, + downloadAndInstallCmake, + downloadAndInstallNinja, downloadAndInstallOpenOCD, downloadAndInstallPicotool, } from "../utils/download.mjs"; @@ -32,6 +34,7 @@ import Logger from "../logger.mjs"; import { rustProjectGetSelectedChip } from "../utils/rustUtil.mjs"; import { OPENOCD_VERSION } from "../utils/sharedConstants.mjs"; import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; +import { ensureGit } from "../utils/gitUtil.mjs"; export class GetPythonPathCommand extends CommandWithResult { constructor() { @@ -616,7 +619,40 @@ export class SetupZephyrCommand extends CommandWithResult { } this.running = true; - window.showInformationMessage("Setup Venv Command Running"); + window.showInformationMessage("Setup Zephyr Command Running"); + + const settings = Settings.getInstance(); + if (settings === undefined) { + this._logger.error("Settings not initialized."); + + return; + } + + // TODO: this does take about 2s - may be reduced + const gitPath = await ensureGit(settings, { returnPath: true }); + if (typeof gitPath !== "string" || gitPath.length === 0) { + return; + } + + this._logger.info("Installing CMake"); + const cmakeResult = await downloadAndInstallCmake("v3.31.5"); + this._logger.info(`${cmakeResult}`); + + this._logger.info("Installing Ninja"); + const ninjaResult = await downloadAndInstallNinja("v1.12.1"); + this._logger.info(`${ninjaResult}`); + + const customEnv = process.env; + const isWindows = process.platform === "win32"; + const customPath = await getPath(); + if (!customPath) { + return; + } + this._logger.info(`Before: ${customPath}`); + customPath.replaceAll("/", "\\"); + this._logger.info(`After: ${customPath}`); + customEnv[isWindows ? "Path" : "PATH"] = + customPath + customEnv[isWindows ? "Path" : "PATH"]; const zephyrWorkspaceDirectory = buildZephyrWorkspacePath(); @@ -775,6 +811,7 @@ manifest: result = await this._runCommand(westInstallSDKCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, + env: customEnv, }); this._logger.info(`${result}`); diff --git a/src/utils/cmakeUtil.mts b/src/utils/cmakeUtil.mts index 7aa0aaab..1cdf482b 100644 --- a/src/utils/cmakeUtil.mts +++ b/src/utils/cmakeUtil.mts @@ -95,7 +95,7 @@ export async function getPath(): Promise { cmakePath.includes("/") ? `${isWindows ? ";" : ":"}${dirname(cmakePath)}` : "" - }${ + }${isWindows ? ";" : ":"}${ pythonPath.includes("/") ? `${dirname(pythonPath)}${isWindows ? ";" : ":"}` : "" From 080e30cbed1e7ebffb875075055e72475fe24879 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Tue, 27 May 2025 11:27:40 +0100 Subject: [PATCH 22/62] Run zephyr export command when setting up Zephyr --- src/commands/getPaths.mts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 43a7a41d..6186c085 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -648,9 +648,7 @@ export class SetupZephyrCommand extends CommandWithResult { if (!customPath) { return; } - this._logger.info(`Before: ${customPath}`); customPath.replaceAll("/", "\\"); - this._logger.info(`After: ${customPath}`); customEnv[isWindows ? "Path" : "PATH"] = customPath + customEnv[isWindows ? "Path" : "PATH"]; @@ -775,6 +773,13 @@ manifest: this._logger.info(`${result}`); + const zephyrExportCommand: string = [westExe, "zephyr-export"].join(" "); + this._logger.info("Exporting Zephyr CMake Files"); + result = await this._runCommand(zephyrExportCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + }); + const westPipPackagesCommand: string = [ westExe, "packages pip --install", From 761012d5f72cfa72a6e238203f9ed26bdaacbbd6 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Wed, 4 Jun 2025 12:12:14 +0100 Subject: [PATCH 23/62] Copy MicroPython project panel for Zephyr --- src/commands/newProject.mts | 9 +- src/webview/newZephyrProjectPanel.mts | 679 ++++++++++++++++++++++++++ 2 files changed, 687 insertions(+), 1 deletion(-) create mode 100644 src/webview/newZephyrProjectPanel.mts diff --git a/src/commands/newProject.mts b/src/commands/newProject.mts index 8c191198..ca90db98 100644 --- a/src/commands/newProject.mts +++ b/src/commands/newProject.mts @@ -4,7 +4,7 @@ import { window, type Uri } from "vscode"; import { NewProjectPanel } from "../webview/newProjectPanel.mjs"; // eslint-disable-next-line max-len import { NewMicroPythonProjectPanel } from "../webview/newMicroPythonProjectPanel.mjs"; -import { NewRustProjectPanel } from "../webview/newRustProjectPanel.mjs"; +import { NewZephyrProjectPanel } from "../webview/newZephyrProjectPanel.mjs"; /** * Enum for the language of the project. @@ -24,6 +24,7 @@ export default class NewProjectCommand extends CommandWithArgs { private static readonly micropythonOption = "MicroPython"; private static readonly cCppOption = "C/C++"; private static readonly rustOption = "Rust (experimental)"; + private static readonly zephyrOption = "Zephyr"; public static readonly id = "newProject"; @@ -40,6 +41,8 @@ export default class NewProjectCommand extends CommandWithArgs { ? NewProjectCommand.micropythonOption : preSelectedType === ProjectLang.rust ? NewProjectCommand.rustOption + : preSelectedType === ProjectLang.zephyr + ? NewProjectCommand.zephyrOption : undefined; } @@ -52,6 +55,7 @@ export default class NewProjectCommand extends CommandWithArgs { NewProjectCommand.cCppOption, NewProjectCommand.micropythonOption, NewProjectCommand.rustOption, + NewProjectCommand.zephyrOption, ], { placeHolder: "Select which language to use for your new project", @@ -71,6 +75,9 @@ export default class NewProjectCommand extends CommandWithArgs { } else if (lang === NewProjectCommand.rustOption) { // create a new project with Rust NewRustProjectPanel.createOrShow(this._extensionUri); + } else if (lang === NewProjectCommand.zephyrOption) { + // create a new project with MicroPython + NewZephyrProjectPanel.createOrShow(this._extensionUri); } else { // show webview where the process of creating a new project is continued NewProjectPanel.createOrShow(this._extensionUri); diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts new file mode 100644 index 00000000..c2b6e9c5 --- /dev/null +++ b/src/webview/newZephyrProjectPanel.mts @@ -0,0 +1,679 @@ +/* eslint-disable max-len */ +import type { Webview, Progress } from "vscode"; +import { + Uri, + ViewColumn, + window, + type WebviewPanel, + type Disposable, + ColorThemeKind, + workspace, + ProgressLocation, + commands, +} from "vscode"; +import Settings from "../settings.mjs"; +import Logger from "../logger.mjs"; +import type { WebviewMessage } from "./newProjectPanel.mjs"; +import { + getNonce, + getProjectFolderDialogOptions, + getWebviewOptions, +} from "./newProjectPanel.mjs"; +import which from "which"; +import { existsSync } from "fs"; +import { join } from "path"; +import { PythonExtension } from "@vscode/python-extension"; +import { unknownErrorToString } from "../utils/errorHelper.mjs"; + +interface SubmitMessageValue { + projectName: string; + pythonMode: number; + pythonPath: string; +} + +export class NewZephyrProjectPanel { + public static currentPanel: NewZephyrProjectPanel | undefined; + + public static readonly viewType = "newZephyrProject"; + + private readonly _panel: WebviewPanel; + private readonly _extensionUri: Uri; + private readonly _settings: Settings; + private readonly _logger: Logger = new Logger("NewZephyrProjectPanel"); + private _disposables: Disposable[] = []; + + private _projectRoot?: Uri; + private _pythonExtensionApi?: PythonExtension; + + public static createOrShow(extensionUri: Uri, projectUri?: Uri): void { + const column = window.activeTextEditor + ? window.activeTextEditor.viewColumn + : undefined; + + if (NewZephyrProjectPanel.currentPanel) { + NewZephyrProjectPanel.currentPanel._panel.reveal(column); + // update already exiting panel with new project root + if (projectUri) { + NewZephyrProjectPanel.currentPanel._projectRoot = projectUri; + // update webview + void NewZephyrProjectPanel.currentPanel._panel.webview.postMessage({ + command: "changeLocation", + value: projectUri?.fsPath, + }); + } + + return; + } + + const panel = window.createWebviewPanel( + NewZephyrProjectPanel.viewType, + "New Zephyr Pico Project", + column || ViewColumn.One, + getWebviewOptions(extensionUri) + ); + + const settings = Settings.getInstance(); + if (!settings) { + panel.dispose(); + + void window + .showErrorMessage( + "Failed to load settings. Please restart VS Code or reload the window.", + "Reload Window" + ) + .then(selected => { + if (selected === "Reload Window") { + commands.executeCommand("workbench.action.reloadWindow"); + } + }); + + return; + } + + NewZephyrProjectPanel.currentPanel = new NewZephyrProjectPanel( + panel, + settings, + extensionUri, + projectUri + ); + } + + public static revive(panel: WebviewPanel, extensionUri: Uri): void { + const settings = Settings.getInstance(); + if (settings === undefined) { + // TODO: maybe add restart button + void window.showErrorMessage( + "Failed to load settings. Please restart VSCode." + ); + + return; + } + + // TODO: reload if it was import panel maybe in state + NewZephyrProjectPanel.currentPanel = new NewZephyrProjectPanel( + panel, + settings, + extensionUri + ); + } + + private constructor( + panel: WebviewPanel, + settings: Settings, + extensionUri: Uri, + projectUri?: Uri + ) { + this._panel = panel; + this._extensionUri = extensionUri; + this._settings = settings; + + this._projectRoot = projectUri ?? this._settings.getLastProjectRoot(); + + void this._update(); + + this._panel.onDidDispose(() => this.dispose(), null, this._disposables); + + // Update the content based on view changes + this._panel.onDidChangeViewState( + async () => { + if (this._panel.visible) { + await this._update(); + } + }, + null, + this._disposables + ); + + workspace.onDidChangeConfiguration( + async () => { + await this._updateTheme(); + }, + null, + this._disposables + ); + + this._panel.webview.onDidReceiveMessage( + async (message: WebviewMessage) => { + switch (message.command) { + case "changeLocation": + { + const newLoc = await window.showOpenDialog( + getProjectFolderDialogOptions(this._projectRoot, false) + ); + + if (newLoc && newLoc[0]) { + // overwrite preview folderUri + this._projectRoot = newLoc[0]; + await this._settings.setLastProjectRoot(newLoc[0]); + + // update webview + await this._panel.webview.postMessage({ + command: "changeLocation", + value: newLoc[0].fsPath, + }); + } + } + break; + case "cancel": + this.dispose(); + break; + case "error": + void window.showErrorMessage(message.value as string); + break; + case "submit": + { + const data = message.value as SubmitMessageValue; + + if ( + this._projectRoot === undefined || + this._projectRoot.fsPath === "" + ) { + void window.showErrorMessage( + "No project root selected. Please select a project root." + ); + await this._panel.webview.postMessage({ + command: "submitDenied", + }); + + return; + } + + if ( + data.projectName === undefined || + data.projectName.length === 0 + ) { + void window.showWarningMessage( + "The project name is empty. Please enter a project name." + ); + await this._panel.webview.postMessage({ + command: "submitDenied", + }); + + return; + } + + // check if projectRoot/projectName folder already exists + if ( + existsSync(join(this._projectRoot.fsPath, data.projectName)) + ) { + void window.showErrorMessage( + "Project already exists. " + + "Please select a different project name or root." + ); + await this._panel.webview.postMessage({ + command: "submitDenied", + }); + + return; + } + + // close panel before generating project + this.dispose(); + + await window.withProgress( + { + location: ProgressLocation.Notification, + title: `Generating MicroPico project ${ + data.projectName ?? "undefined" + } in ${this._projectRoot?.fsPath}...`, + }, + async progress => + this._generateProjectOperation(progress, data, message) + ); + } + break; + } + }, + null, + this._disposables + ); + + if (projectUri !== undefined) { + // update webview + void this._panel.webview.postMessage({ + command: "changeLocation", + value: projectUri.fsPath, + }); + } + } + + private async _generateProjectOperation( + progress: Progress<{ message?: string; increment?: number }>, + data: SubmitMessageValue, + message: WebviewMessage + ): Promise { + const projectPath = this._projectRoot?.fsPath ?? ""; + + if ( + typeof message.value !== "object" || + message.value === null || + projectPath.length === 0 + ) { + void window.showErrorMessage( + "Failed to generate Zephyrproject. " + + "Please try again and check your settings." + ); + + return; + } + + // install python (if necessary) + let python3Path: string | undefined; + if (process.platform === "darwin" || process.platform === "win32") { + switch (data.pythonMode) { + case 0: + python3Path = data.pythonPath; + break; + case 1: + python3Path = process.platform === "win32" ? "python" : "python3"; + break; + case 2: + python3Path = data.pythonPath; + break; + } + + if (python3Path === undefined) { + progress.report({ + message: "Failed", + increment: 100, + }); + await window.showErrorMessage("Failed to find python3 executable."); + + return; + } + } else { + python3Path = "python3"; + } + + // create the folder with project name in project root + // open the folder in vscode and call micropico.initialise + + // create the project folder + const projectFolder = join(projectPath, data.projectName); + progress.report({ + message: `Creating project folder ${projectFolder}`, + increment: 10, + }); + + try { + await workspace.fs.createDirectory(Uri.file(projectFolder)); + // also create a blink.py in it with a import machine + const blinkPyCode = `from machine import Pin +from utime import sleep + +pin = Pin("LED", Pin.OUT) + +print("LED starts flashing...") +while True: + try: + pin.toggle() + sleep(1) # sleep 1sec + except KeyboardInterrupt: + break +pin.off() +print("Finished.")\r\n`; + const filePath = join(projectFolder, "blink.py"); + await workspace.fs.writeFile( + Uri.file(filePath), + new TextEncoder().encode(blinkPyCode) + ); + } catch { + progress.report({ + message: "Failed", + increment: 100, + }); + await window.showErrorMessage( + `Failed to create project folder ${projectFolder}` + ); + + return; + } + + commands.executeCommand("micropico.initialise", projectFolder, python3Path); + progress.report({ + message: "Project initialized", + increment: 90, + }); + + // wait 2 seconds to give user option to read notifications + await new Promise(resolve => setTimeout(resolve, 2000)); + + // open and call initialise + void commands.executeCommand("vscode.openFolder", Uri.file(projectFolder), { + forceNewWindow: (workspace.workspaceFolders?.length ?? 0) > 0, + }); + } + + private async _update(): Promise { + this._panel.title = "New Zephyr Pico Project"; + + this._panel.iconPath = Uri.joinPath( + this._extensionUri, + "web", + "raspberry-128.png" + ); + if (!this._pythonExtensionApi) { + this._pythonExtensionApi = await PythonExtension.api(); + } + const html = await this._getHtmlForWebview(this._panel.webview); + + if (html !== "") { + try { + this._panel.webview.html = html; + } catch (error) { + this._logger.error( + "Failed to set webview html. Webview might have been disposed. Error: ", + unknownErrorToString(error) + ); + // properly dispose panel + this.dispose(); + + return; + } + await this._updateTheme(); + } else { + void window.showErrorMessage( + "Failed to load webview for new Zephyr project" + ); + this.dispose(); + } + } + + private async _updateTheme(): Promise { + try { + await this._panel.webview.postMessage({ + command: "setTheme", + theme: + window.activeColorTheme.kind === ColorThemeKind.Dark || + window.activeColorTheme.kind === ColorThemeKind.HighContrast + ? "dark" + : "light", + }); + } catch (error) { + this._logger.error( + "Failed to update theme in webview. Webview might have been disposed. Error:", + unknownErrorToString(error) + ); + // properly dispose panel + this.dispose(); + } + } + + public dispose(): void { + NewZephyrProjectPanel.currentPanel = undefined; + + this._panel.dispose(); + + while (this._disposables.length) { + const x = this._disposables.pop(); + + if (x) { + x.dispose(); + } + } + } + + private async _getHtmlForWebview(webview: Webview): Promise { + const mainScriptUri = webview.asWebviewUri( + Uri.joinPath(this._extensionUri, "web", "mpy", "main.js") + ); + + const mainStyleUri = webview.asWebviewUri( + Uri.joinPath(this._extensionUri, "web", "main.css") + ); + + const tailwindcssScriptUri = webview.asWebviewUri( + Uri.joinPath(this._extensionUri, "web", "tailwindcss-3_3_5.js") + ); + + // images + const navHeaderSvgUri = webview.asWebviewUri( + Uri.joinPath(this._extensionUri, "web", "raspberrypi-nav-header.svg") + ); + + const navHeaderDarkSvgUri = webview.asWebviewUri( + Uri.joinPath(this._extensionUri, "web", "raspberrypi-nav-header-dark.svg") + ); + + // TODO: add support for onDidChangeActiveEnvironment and filter envs that don't directly point + // to an executable + const environments = this._pythonExtensionApi?.environments; + const knownEnvironments = environments?.known; + const activeEnv = environments?.getActiveEnvironmentPath(); + + // TODO: check python version, workaround, only allow python3 commands on unix + const isPythonSystemAvailable = + (await which("python3", { nothrow: true })) !== null || + (await which("python", { nothrow: true })) !== null; + + // Restrict the webview to only load specific scripts + const nonce = getNonce(); + + const defaultTheme = + window.activeColorTheme.kind === ColorThemeKind.Dark || + window.activeColorTheme.kind === ColorThemeKind.HighContrast + ? "dark" + : "light"; + + return ` + + + + + + + + + + New Pico Zephyr Project + + + + + +
+
+ + +
+
+

Basic Settings

+
+
+ +
+
+ + +
+
+ + +
+ +
+
+ + + ${ + knownEnvironments && knownEnvironments.length > 0 + ? ` +
+ + + + +
+ ` + : "" + } + + ${ + process.platform === "darwin" || + process.platform === "win32" + ? ` + ${ + isPythonSystemAvailable + ? `
+ + +
` + : "" + } + +
+ + + +
` + : "" + } +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ +
+ + +
+
+ + + + `; + } +} From 116d2259b889821f33b5707eaacd3b5d7455d0cd Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Sat, 7 Jun 2025 17:52:22 +0100 Subject: [PATCH 24/62] Zephyr panel performs simple Zephyr project creation --- src/webview/newZephyrProjectPanel.mts | 105 +++++++------------------- 1 file changed, 28 insertions(+), 77 deletions(-) diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index c2b6e9c5..6bdb830c 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -11,6 +11,8 @@ import { ProgressLocation, commands, } from "vscode"; +import { homedir } from "os"; +import { join as joinPosix } from "path/posix"; import Settings from "../settings.mjs"; import Logger from "../logger.mjs"; import type { WebviewMessage } from "./newProjectPanel.mjs"; @@ -24,6 +26,7 @@ import { existsSync } from "fs"; import { join } from "path"; import { PythonExtension } from "@vscode/python-extension"; import { unknownErrorToString } from "../utils/errorHelper.mjs"; +import { buildZephyrWorkspacePath } from "../utils/download.mjs"; interface SubmitMessageValue { projectName: string; @@ -233,7 +236,7 @@ export class NewZephyrProjectPanel { await window.withProgress( { location: ProgressLocation.Notification, - title: `Generating MicroPico project ${ + title: `Generating Zephyr project ${ data.projectName ?? "undefined" } in ${this._projectRoot?.fsPath}...`, }, @@ -277,91 +280,39 @@ export class NewZephyrProjectPanel { return; } - // install python (if necessary) - let python3Path: string | undefined; - if (process.platform === "darwin" || process.platform === "win32") { - switch (data.pythonMode) { - case 0: - python3Path = data.pythonPath; - break; - case 1: - python3Path = process.platform === "win32" ? "python" : "python3"; - break; - case 2: - python3Path = data.pythonPath; - break; - } - - if (python3Path === undefined) { - progress.report({ - message: "Failed", - increment: 100, - }); - await window.showErrorMessage("Failed to find python3 executable."); + this._logger.info("Generating new Zephyr Project"); - return; - } - } else { - python3Path = "python3"; - } - - // create the folder with project name in project root - // open the folder in vscode and call micropico.initialise + // Create a new directory to put the project in + const homeDirectory: string = homedir(); + const newProjectDir = joinPosix( + homeDirectory, + "zephyr_test", + "zephyr_project" + ); + await workspace.fs.createDirectory(Uri.file(newProjectDir)); - // create the project folder - const projectFolder = join(projectPath, data.projectName); - progress.report({ - message: `Creating project folder ${projectFolder}`, - increment: 10, + // Copy the Zephyr App into the new directory + const zephyrAppDir = joinPosix( + buildZephyrWorkspacePath(), + "pico-zephyr", + "app" + ); + await workspace.fs.copy(Uri.file(zephyrAppDir), Uri.file(newProjectDir), { + overwrite: true, }); - try { - await workspace.fs.createDirectory(Uri.file(projectFolder)); - // also create a blink.py in it with a import machine - const blinkPyCode = `from machine import Pin -from utime import sleep - -pin = Pin("LED", Pin.OUT) - -print("LED starts flashing...") -while True: - try: - pin.toggle() - sleep(1) # sleep 1sec - except KeyboardInterrupt: - break -pin.off() -print("Finished.")\r\n`; - const filePath = join(projectFolder, "blink.py"); - await workspace.fs.writeFile( - Uri.file(filePath), - new TextEncoder().encode(blinkPyCode) - ); - } catch { - progress.report({ - message: "Failed", - increment: 100, - }); - await window.showErrorMessage( - `Failed to create project folder ${projectFolder}` - ); + const result = true; - return; + if (result === null || !result) { + return undefined; } - commands.executeCommand("micropico.initialise", projectFolder, python3Path); - progress.report({ - message: "Project initialized", - increment: 90, - }); + this._logger.info(`Zephyr Project generated at ${newProjectDir}`); - // wait 2 seconds to give user option to read notifications - await new Promise(resolve => setTimeout(resolve, 2000)); + // Open the folder + commands.executeCommand(`vscode.openFolder`, Uri.file(newProjectDir)); - // open and call initialise - void commands.executeCommand("vscode.openFolder", Uri.file(projectFolder), { - forceNewWindow: (workspace.workspaceFolders?.length ?? 0) > 0, - }); + return; } private async _update(): Promise { From 32ccd6f1ec270d45dd9d4f628784a74858abe210 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Sat, 7 Jun 2025 17:55:11 +0100 Subject: [PATCH 25/62] Add New Zephyr Project panel to activity bar --- src/webview/activityBar.mts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/webview/activityBar.mts b/src/webview/activityBar.mts index c64602bf..77fd49b8 100644 --- a/src/webview/activityBar.mts +++ b/src/webview/activityBar.mts @@ -45,6 +45,7 @@ const DOCUMENTATION_COMMANDS_PARENT_LABEL = "Documentation"; const NEW_C_CPP_PROJECT_LABEL = "New C/C++ Project"; const NEW_MICROPYTHON_PROJECT_LABEL = "New MicroPython Project"; const NEW_RUST_PROJECT_LABEL = "New Rust Project"; +const NEW_ZEPHYR_PROJECT_LABEL = "New Zephyr Project"; const IMPORT_PROJECT_LABEL = "Import Project"; const EXAMPLE_PROJECT_LABEL = "New Project From Example"; const SWITCH_SDK_LABEL = "Switch SDK"; @@ -111,6 +112,9 @@ export class PicoProjectActivityBar case NEW_RUST_PROJECT_LABEL: element.iconPath = new ThemeIcon("file-directory-create"); break; + case NEW_ZEPHYR_PROJECT_LABEL: + element.iconPath = new ThemeIcon("file-directory-create"); + break; case IMPORT_PROJECT_LABEL: // alt. "repo-pull" element.iconPath = new ThemeIcon("repo-clone"); @@ -220,6 +224,15 @@ export class PicoProjectActivityBar arguments: [ProjectLang.rust], } ), + new QuickAccessCommand( + NEW_ZEPHYR_PROJECT_LABEL, + TreeItemCollapsibleState.None, + { + command: `${extensionName}.${NewProjectCommand.id}`, + title: NEW_ZEPHYR_PROJECT_LABEL, + arguments: [ProjectLang.zephyr], + } + ), new QuickAccessCommand( IMPORT_PROJECT_LABEL, TreeItemCollapsibleState.None, From 38fdd12cd1bbd91f3b253568383ed790532d8860 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Wed, 11 Jun 2025 17:52:19 +0100 Subject: [PATCH 26/62] Create Zephyr project from panel with USB console choice --- src/webview/newZephyrProjectPanel.mts | 64 ++++++- web/zephyr/main.js | 230 ++++++++++++++++++++++++++ 2 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 web/zephyr/main.js diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index 6bdb830c..ee4e004b 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -10,6 +10,8 @@ import { workspace, ProgressLocation, commands, + tasks, + ConfigurationTarget, } from "vscode"; import { homedir } from "os"; import { join as joinPosix } from "path/posix"; @@ -32,6 +34,7 @@ interface SubmitMessageValue { projectName: string; pythonMode: number; pythonPath: string; + console: string; } export class NewZephyrProjectPanel { @@ -260,6 +263,23 @@ export class NewZephyrProjectPanel { } } + private generateZephyrBuildArgs(usbSerialPort: boolean): string[] { + return [ + "build", + "-p", + "auto", + "-b", + "rpi_pico", + "-d", + '"${workspaceFolder}"/build', + '"${workspaceFolder}"', + usbSerialPort ? "-S usb_serial_port" : "", + "--", + "-DOPENOCD=${command:raspberry-pi-pico.getOpenOCDRoot}/openocd.exe", + "-DOPENOCD_DEFAULT_PATH=${command:raspberry-pi-pico.getOpenOCDRoot}/scripts", + ]; + } + private async _generateProjectOperation( progress: Progress<{ message?: string; increment?: number }>, data: SubmitMessageValue, @@ -287,8 +307,9 @@ export class NewZephyrProjectPanel { const newProjectDir = joinPosix( homeDirectory, "zephyr_test", - "zephyr_project" + data.projectName ); + await workspace.fs.createDirectory(Uri.file(newProjectDir)); // Copy the Zephyr App into the new directory @@ -307,6 +328,29 @@ export class NewZephyrProjectPanel { return undefined; } + this._logger.info(`Console: ${data.console}`); + const usbSerialPort = data.console === "USB"; + + const buildArgs = this.generateZephyrBuildArgs(usbSerialPort); + + const taskJsonFile = joinPosix(newProjectDir, ".vscode", "tasks.json"); + + const jsonString = ( + await workspace.fs.readFile(Uri.file(taskJsonFile)) + ).toString(); + const tasksJson: any = JSON.parse(jsonString); + this._logger.error(`${tasksJson}`); + + // Modify task.json + tasksJson.tasks[0].args = buildArgs; + + // Write modified tasks.json to folder + const newTasksJsonString = JSON.stringify(tasksJson, null, 4); + await workspace.fs.writeFile( + Uri.file(taskJsonFile), + Buffer.from(newTasksJsonString) + ); + this._logger.info(`Zephyr Project generated at ${newProjectDir}`); // Open the folder @@ -386,7 +430,7 @@ export class NewZephyrProjectPanel { private async _getHtmlForWebview(webview: Webview): Promise { const mainScriptUri = webview.asWebviewUri( - Uri.joinPath(this._extensionUri, "web", "mpy", "main.js") + Uri.joinPath(this._extensionUri, "web", "zephyr", "main.js") ); const mainStyleUri = webview.asWebviewUri( @@ -615,6 +659,22 @@ export class NewZephyrProjectPanel { +
+
+

Serial Port Over:

+
+
+ + +
+
+ + +
+
+
+
+
- + + +
+ + +
+ +
diff --git a/web/zephyr/main.js b/web/zephyr/main.js index 46416fce..b109c6de 100644 --- a/web/zephyr/main.js +++ b/web/zephyr/main.js @@ -169,6 +169,7 @@ var submitted = false; pythonMode: Number(pythonMode), pythonPath: pythonPath, console: consoleSelection, + boardType: document.getElementById("sel-board-type").value, }, }); }; From 8f2282c86c7886fd8146395cac621e5d48ef13c0 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Wed, 11 Jun 2025 19:32:50 +0100 Subject: [PATCH 28/62] Add command for switching boards for Zephyr --- package.json | 6 ++ src/commands/switchBoardZephyr.mts | 107 +++++++++++++++++++++++++++++ src/extension.mts | 2 + 3 files changed, 115 insertions(+) create mode 100644 src/commands/switchBoardZephyr.mts diff --git a/package.json b/package.json index 74bf2eb8..7f435b45 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,12 @@ "category": "Raspberry Pi Pico", "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject" }, + { + "command": "raspberry-pi-pico.switchBoardZephyr", + "title": "Switch Board for Zephyr", + "category": "Raspberry Pi Pico", + "enablement": "raspberry-pi-pico.isPicoProject && raspberry-pi-pico.isPicoZephyrProject" + }, { "command": "raspberry-pi-pico.launchTargetPath", "title": "Get path of the project executable", diff --git a/src/commands/switchBoardZephyr.mts b/src/commands/switchBoardZephyr.mts new file mode 100644 index 00000000..c5b0642b --- /dev/null +++ b/src/commands/switchBoardZephyr.mts @@ -0,0 +1,107 @@ +import { existsSync, readdirSync, readFileSync } from "fs"; +import { join } from "path"; +import { join as joinPosix } from "path/posix"; +import { commands, ProgressLocation, window, workspace, Uri } from "vscode"; + +import { Command } from "./command.mjs"; +import { buildZephyrWorkspacePath } from "../utils/download.mjs"; + +export default class SwitchZephyrBoardCommand extends Command { + public static readonly id = "switchBoardZephyr"; + + constructor() { + super(SwitchZephyrBoardCommand.id); + } + + async execute(): Promise { + const workspaceFolder = workspace.workspaceFolders?.[0]; + + // check it has a CMakeLists.txt + if ( + workspaceFolder === undefined || + !existsSync(join(workspaceFolder.uri.fsPath, "CMakeLists.txt")) + ) { + return; + } + + // check if pico_zephyr is in CMakeLists.txt - Then update using Zephyr tooling + if ( + readFileSync(join(workspaceFolder.uri.fsPath, "CMakeLists.txt")) + .toString("utf-8") + .includes("pico_zephyr") + ) { + // Get tasks.json, find the task called "Compile Project" + // then edit the build arguments + const taskJsonFile = joinPosix( + workspaceFolder.uri.fsPath, + ".vscode", + "tasks.json" + ); + + if (!existsSync(taskJsonFile)) { + window.showInformationMessage( + "Creating tasks.json file for Zephyr project" + ); + + const zephyrTasksJsonFile = joinPosix( + buildZephyrWorkspacePath(), + "pico-zephyr", + "app", + ".vscode", + "tasks.json" + ); + + await workspace.fs.copy( + Uri.file(zephyrTasksJsonFile), + Uri.file(taskJsonFile) + ); + } + + // Now that we know there is a tasks.json file, we can read it + // and set the board in the "Compile Project" task + const jsonString = ( + await workspace.fs.readFile(Uri.file(taskJsonFile)) + ).toString(); + const tasksJson: any = JSON.parse(jsonString); + + // Check it has a tasks object with + if (tasksJson.tasks === undefined || !Array.isArray(tasksJson.tasks)) { + window.showErrorMessage( + "Could not find task to modify board on.\ + Please see the Pico Zephyr examples for a reference." + ); + + return; + } + + let i; + for (i = 0; i < (tasksJson.tasks as any[]).length; i++) { + if ( + tasksJson.tasks[i].label !== undefined && + tasksJson.tasks[i].label === "Compile Project" + ) { + window.showErrorMessage("Compile Project task found"); + + let args: string[] = tasksJson.tasks[i].args; + + // Get the args array + if (args !== undefined && Array.isArray(args)) { + const buildIndex = args.findIndex(element => element === "-b"); + if (buildIndex >= 0 && buildIndex < args.length - 1) { + args[buildIndex + 1] = "New Board!!!"; + } + } + } + } + + // Write JSON back into file + const newTasksJsonString = JSON.stringify(tasksJson, null, 4); + await workspace.fs.writeFile( + Uri.file(taskJsonFile), + Buffer.from(newTasksJsonString) + ); + + window.showInformationMessage("Board Updated"); + } + } +} diff --git a/src/extension.mts b/src/extension.mts index 2b111d1a..b4e45b15 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -86,6 +86,7 @@ import ImportProjectCommand from "./commands/importProject.mjs"; import { homedir } from "os"; import NewExampleProjectCommand from "./commands/newExampleProject.mjs"; import SwitchBoardCommand from "./commands/switchBoard.mjs"; +import SwitchBoardZephyrCommand from "./commands/switchBoardZephyr.mjs"; import UninstallPicoSDKCommand from "./commands/uninstallPicoSDK.mjs"; import UpdateOpenOCDCommand from "./commands/updateOpenOCD.mjs"; import FlashProjectSWDCommand from "./commands/flashProjectSwd.mjs"; @@ -127,6 +128,7 @@ export async function activate(context: ExtensionContext): Promise { new NewProjectCommand(context.extensionUri), new SwitchSDKCommand(ui, context.extensionUri), new SwitchBoardCommand(ui, context.extensionUri), + new SwitchBoardZephyrCommand(), new LaunchTargetPathCommand(), new LaunchTargetPathReleaseCommand(), new GetPythonPathCommand(), From 7348ae3b91989a3ff68d40bf78a1ff689721cc55 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 12 Jun 2025 10:52:55 +0100 Subject: [PATCH 29/62] Implement board switching for Zephyr projects --- src/commands/switchBoardZephyr.mts | 45 ++++++++++++++++++++++++++- src/webview/newZephyrProjectPanel.mts | 3 +- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/commands/switchBoardZephyr.mts b/src/commands/switchBoardZephyr.mts index c5b0642b..d0a4abc8 100644 --- a/src/commands/switchBoardZephyr.mts +++ b/src/commands/switchBoardZephyr.mts @@ -6,6 +6,12 @@ import { commands, ProgressLocation, window, workspace, Uri } from "vscode"; import { Command } from "./command.mjs"; import { buildZephyrWorkspacePath } from "../utils/download.mjs"; +enum BoardNames { + pico = "Pico", + picoW = "Pico W", + pico2 = "Pico 2", + pico2W = "Pico 2W", +} export default class SwitchZephyrBoardCommand extends Command { public static readonly id = "switchBoardZephyr"; @@ -13,6 +19,20 @@ export default class SwitchZephyrBoardCommand extends Command { super(SwitchZephyrBoardCommand.id); } + private stringToBoard(e: string): string { + if (e === (BoardNames.pico as string)) { + return "rpi_pico"; + } else if (e === (BoardNames.picoW as string)) { + return "rpi_pico/rp2040/w"; + } else if (e === (BoardNames.pico2 as string)) { + return "rpi_pico2/rp2350a/m33"; + } else if (e === (BoardNames.pico2W as string)) { + return "rpi_pico2/rp2350a/m33/w"; + } else { + throw new Error(`Unknown Board Type: ${e}`); + } + } + async execute(): Promise { const workspaceFolder = workspace.workspaceFolders?.[0]; @@ -57,6 +77,29 @@ export default class SwitchZephyrBoardCommand extends Command { ); } + // Get the board name + const quickPickItems = [ + BoardNames.pico, + BoardNames.picoW, + BoardNames.pico2, + BoardNames.pico2W, + ]; + + const board = await window.showQuickPick(quickPickItems, { + placeHolder: "Select Board", + }); + + if (board === undefined) { + window.showErrorMessage( + "Error getting board definition from quck pick.\ + Please try again." + ); + + return; + } + + const boardArg = this.stringToBoard(board); + // Now that we know there is a tasks.json file, we can read it // and set the board in the "Compile Project" task const jsonString = ( @@ -88,7 +131,7 @@ export default class SwitchZephyrBoardCommand extends Command { if (args !== undefined && Array.isArray(args)) { const buildIndex = args.findIndex(element => element === "-b"); if (buildIndex >= 0 && buildIndex < args.length - 1) { - args[buildIndex + 1] = "New Board!!!"; + args[buildIndex + 1] = boardArg; } } } diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index 76b4cec3..d155091e 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -35,7 +35,6 @@ enum BoardType { picoW = "pico_w", pico2 = "pico2", pico2W = "pico2_w", - other = "other", } interface SubmitMessageValue { @@ -641,7 +640,7 @@ export class NewZephyrProjectPanel { }">Pico 2 W
- +
From fd0bbe5fe96a428411c5f72f66945eed18a22f0b Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 12 Jun 2025 11:04:23 +0100 Subject: [PATCH 30/62] Identify possible issues when switching board for Zephyr and fail with error message --- src/commands/switchBoardZephyr.mts | 57 ++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/src/commands/switchBoardZephyr.mts b/src/commands/switchBoardZephyr.mts index d0a4abc8..389db66d 100644 --- a/src/commands/switchBoardZephyr.mts +++ b/src/commands/switchBoardZephyr.mts @@ -117,24 +117,43 @@ export default class SwitchZephyrBoardCommand extends Command { return; } - let i; - for (i = 0; i < (tasksJson.tasks as any[]).length; i++) { - if ( - tasksJson.tasks[i].label !== undefined && - tasksJson.tasks[i].label === "Compile Project" - ) { - window.showErrorMessage("Compile Project task found"); - - let args: string[] = tasksJson.tasks[i].args; - - // Get the args array - if (args !== undefined && Array.isArray(args)) { - const buildIndex = args.findIndex(element => element === "-b"); - if (buildIndex >= 0 && buildIndex < args.length - 1) { - args[buildIndex + 1] = boardArg; - } - } - } + // Find the index of the task called "Compile Project" + const compileIndex = tasksJson.tasks.findIndex( + element => + element.label !== undefined && element.label === "Compile Project" + ); + + if (compileIndex < 0) { + window.showErrorMessage( + "Could not find Compile Project task to modify board on.\ + Please see the Pico Zephyr examples for a reference." + ); + + return; + } + + let args: string[] = tasksJson.tasks[compileIndex].args; + + if (args === undefined || !Array.isArray(args)) { + window.showErrorMessage( + "Could not find args within Compile Project task to modify board on.\ + Please see the Pico Zephyr examples for a reference." + ); + + return; + } + + // Get the args array + const buildIndex = args.findIndex(element => element === "-b"); + if (buildIndex >= 0 && buildIndex < args.length - 1) { + args[buildIndex + 1] = boardArg; + } else { + window.showErrorMessage( + "Could not find board arg within Compile Project task to modify board\ + on. Please see the Pico Zephyr examples for a reference." + ); + + return; } // Write JSON back into file @@ -144,7 +163,7 @@ export default class SwitchZephyrBoardCommand extends Command { Buffer.from(newTasksJsonString) ); - window.showInformationMessage("Board Updated"); + window.showInformationMessage(`Board Updated to ${board}`); } } } From 7341d64185804253bdc73cb6ebc086253753fcee Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 12 Jun 2025 12:29:54 +0100 Subject: [PATCH 31/62] Implement board switching via activity bar and when loading project --- package.json | 2 +- src/commands/switchBoard.mts | 74 +++++++++++++++++----------- src/commands/switchBoardZephyr.mts | 79 +++++++++++++++++++++++++++--- src/extension.mts | 27 +++++++++- 4 files changed, 145 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 7f435b45..5fb46167 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "command": "raspberry-pi-pico.switchBoardZephyr", "title": "Switch Board for Zephyr", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && raspberry-pi-pico.isPicoZephyrProject" + "enablement": "false" }, { "command": "raspberry-pi-pico.launchTargetPath", diff --git a/src/commands/switchBoard.mts b/src/commands/switchBoard.mts index f979c1e0..217f7ce3 100644 --- a/src/commands/switchBoard.mts +++ b/src/commands/switchBoard.mts @@ -1,4 +1,4 @@ -import { Command } from "./command.mjs"; +import { extensionName, Command } from "./command.mjs"; import Logger from "../logger.mjs"; import { commands, @@ -28,6 +28,7 @@ import { getSupportedToolchains } from "../utils/toolchainUtil.mjs"; import VersionBundlesLoader from "../utils/versionBundles.mjs"; import State from "../state.mjs"; import { unknownErrorToString } from "../utils/errorHelper.mjs"; +import SwitchZephyrBoardCommand from "./switchBoardZephyr.mjs"; export default class SwitchBoardCommand extends Command { private _logger: Logger = new Logger("SwitchBoardCommand"); @@ -56,49 +57,53 @@ export default class SwitchBoardCommand extends Command { const sdkPath = buildSDKPath(sdkVersion); const boardHeaderDirList = []; - if(workspaceFolder !== undefined) { + if (workspaceFolder !== undefined) { const ws = workspaceFolder.uri.fsPath; - const cMakeCachePath = join(ws, "build","CMakeCache.txt"); + const cMakeCachePath = join(ws, "build", "CMakeCache.txt"); let picoBoardHeaderDirs = cmakeGetPicoVar( cMakeCachePath, - "PICO_BOARD_HEADER_DIRS"); + "PICO_BOARD_HEADER_DIRS" + ); - if(picoBoardHeaderDirs){ - if(picoBoardHeaderDirs.startsWith("'")){ - const substrLen = picoBoardHeaderDirs.length-1; - picoBoardHeaderDirs = picoBoardHeaderDirs.substring(1,substrLen); + if (picoBoardHeaderDirs) { + if (picoBoardHeaderDirs.startsWith("'")) { + const substrLen = picoBoardHeaderDirs.length - 1; + picoBoardHeaderDirs = picoBoardHeaderDirs.substring(1, substrLen); } const picoBoardHeaderDirList = picoBoardHeaderDirs.split(";"); - picoBoardHeaderDirList.forEach( - item => { - let boardPath = resolve(item); - const normalized = normalize(item); - - //If path is not absolute, join workspace path - if(boardPath !== normalized){ - boardPath = join(ws,normalized); - } + picoBoardHeaderDirList.forEach(item => { + let boardPath = resolve(item); + const normalized = normalize(item); - if(existsSync(boardPath)){ - boardHeaderDirList.push(boardPath); - } + //If path is not absolute, join workspace path + if (boardPath !== normalized) { + boardPath = join(ws, normalized); } - ); + + if (existsSync(boardPath)) { + boardHeaderDirList.push(boardPath); + } + }); } } - const systemBoardHeaderDir = - join(sdkPath,"src", "boards", "include","boards"); + const systemBoardHeaderDir = join( + sdkPath, + "src", + "boards", + "include", + "boards" + ); - boardHeaderDirList.push(systemBoardHeaderDir); + boardHeaderDirList.push(systemBoardHeaderDir); - interface IBoardFile{ + interface IBoardFile { [key: string]: string; - }; + } - const boardFiles:IBoardFile = {}; + const boardFiles: IBoardFile = {}; boardHeaderDirList.forEach( path =>{ @@ -125,7 +130,7 @@ export default class SwitchBoardCommand extends Command { } // Check that board doesn't have an RP2040 on it - const data = readFileSync(boardFiles[board]) + const data = readFileSync(boardFiles[board]); if (data.includes("rp2040")) { return [board, false]; @@ -204,6 +209,19 @@ export default class SwitchBoardCommand extends Command { return; } + // Check if Pico Zephyr project and execute switchBoardZephyr + if ( + readFileSync(join(workspaceFolder.uri.fsPath, "CMakeLists.txt")) + .toString("utf-8") + .includes("pico_zephyr") + ) { + commands.executeCommand( + `${extensionName}.${SwitchZephyrBoardCommand.id}` + ); + + return; + } + const versions = await cmakeGetSelectedToolchainAndSDKVersions( workspaceFolder.uri ); diff --git a/src/commands/switchBoardZephyr.mts b/src/commands/switchBoardZephyr.mts index 389db66d..dacee593 100644 --- a/src/commands/switchBoardZephyr.mts +++ b/src/commands/switchBoardZephyr.mts @@ -1,9 +1,10 @@ -import { existsSync, readdirSync, readFileSync } from "fs"; +import { existsSync, PathOrFileDescriptor, readFileSync } from "fs"; import { join } from "path"; import { join as joinPosix } from "path/posix"; -import { commands, ProgressLocation, window, workspace, Uri } from "vscode"; +import { window, workspace, Uri } from "vscode"; import { Command } from "./command.mjs"; +import type UI from "../ui.mjs"; import { buildZephyrWorkspacePath } from "../utils/download.mjs"; enum BoardNames { @@ -12,10 +13,72 @@ enum BoardNames { pico2 = "Pico 2", pico2W = "Pico 2W", } + +export function findZephyrBoardInTasksJson( + tasksJsonFilePath: PathOrFileDescriptor +): string | undefined { + const tasksJson: any = JSON.parse( + readFileSync(tasksJsonFilePath).toString("utf-8") + ); + + // Check it has a tasks object with + if (tasksJson.tasks === undefined || !Array.isArray(tasksJson.tasks)) { + window.showErrorMessage( + "Could not find task to modify board on.\ + Please see the Pico Zephyr examples for a reference." + ); + + return; + } + + // Find the index of the task called "Compile Project" + const compileIndex = tasksJson.tasks.findIndex( + element => + element.label !== undefined && element.label === "Compile Project" + ); + + if (compileIndex < 0) { + window.showErrorMessage( + "Could not find Compile Project task to modify board on.\ + Please see the Pico Zephyr examples for a reference." + ); + + return; + } + + const args: string[] = tasksJson.tasks[compileIndex].args; + + if (args === undefined || !Array.isArray(args)) { + window.showErrorMessage( + "Could not find args within Compile Project task to modify board on.\ + Please see the Pico Zephyr examples for a reference." + ); + + return; + } + + // Get the args array + const buildIndex = args.findIndex(element => element === "-b"); + let board; + if (buildIndex >= 0 && buildIndex < args.length - 1) { + // Update UI with board description + board = args[buildIndex + 1]; + } else { + window.showErrorMessage( + "Could not find board arg within Compile Project task to modify board\ + on. Please see the Pico Zephyr examples for a reference." + ); + + return; + } + + return board; +} + export default class SwitchZephyrBoardCommand extends Command { public static readonly id = "switchBoardZephyr"; - constructor() { + constructor(private readonly _ui: UI) { super(SwitchZephyrBoardCommand.id); } @@ -85,11 +148,11 @@ export default class SwitchZephyrBoardCommand extends Command { BoardNames.pico2W, ]; - const board = await window.showQuickPick(quickPickItems, { + const selectedBoard = await window.showQuickPick(quickPickItems, { placeHolder: "Select Board", }); - if (board === undefined) { + if (selectedBoard === undefined) { window.showErrorMessage( "Error getting board definition from quck pick.\ Please try again." @@ -98,7 +161,7 @@ export default class SwitchZephyrBoardCommand extends Command { return; } - const boardArg = this.stringToBoard(board); + const boardArg = this.stringToBoard(selectedBoard); // Now that we know there is a tasks.json file, we can read it // and set the board in the "Compile Project" task @@ -163,7 +226,9 @@ export default class SwitchZephyrBoardCommand extends Command { Buffer.from(newTasksJsonString) ); - window.showInformationMessage(`Board Updated to ${board}`); + this._ui.updateBoard(selectedBoard); + + window.showInformationMessage(`Board Updated to ${selectedBoard}`); } } } diff --git a/src/extension.mts b/src/extension.mts index b4e45b15..f3bbc918 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -90,6 +90,7 @@ import SwitchBoardZephyrCommand from "./commands/switchBoardZephyr.mjs"; import UninstallPicoSDKCommand from "./commands/uninstallPicoSDK.mjs"; import UpdateOpenOCDCommand from "./commands/updateOpenOCD.mjs"; import FlashProjectSWDCommand from "./commands/flashProjectSwd.mjs"; +import { findZephyrBoardInTasksJson } from "./commands/switchBoardZephyr.mjs"; // eslint-disable-next-line max-len import { NewMicroPythonProjectPanel } from "./webview/newMicroPythonProjectPanel.mjs"; import type { Progress as GotProgress } from "got"; @@ -128,7 +129,7 @@ export async function activate(context: ExtensionContext): Promise { new NewProjectCommand(context.extensionUri), new SwitchSDKCommand(ui, context.extensionUri), new SwitchBoardCommand(ui, context.extensionUri), - new SwitchBoardZephyrCommand(), + new SwitchBoardZephyrCommand(ui), new LaunchTargetPathCommand(), new LaunchTargetPathReleaseCommand(), new GetPythonPathCommand(), @@ -278,6 +279,30 @@ export async function activate(context: ExtensionContext): Promise { ContextKeys.isPicoZephyrProject, true ); + + // Update the board info if it can be found in tasks.json + const tasksJsonFilePath = join( + workspaceFolder.uri.fsPath, + ".vscode", + "tasks.json" + ); + + // Update UI with board description + const board = findZephyrBoardInTasksJson(tasksJsonFilePath); + + if (board !== undefined) { + if (board === "rpi_pico2/rp2350a/m33/w") { + ui.updateBoard("Pico 2W"); + } else if (board === "rpi_pico2/rp2350a/m33") { + ui.updateBoard("Pico 2"); + } else if (board === "rpi_pico/rp2040/w") { + ui.updateBoard("Pico W"); + } else if (board.includes("rpi_pico")) { + ui.updateBoard("Pico"); + } else { + ui.updateBoard("Other"); + } + } } // check for pico_sdk_init() in CMakeLists.txt else if ( From b5ca17aeac65c5329bc35e797b0269ced3a7ab94 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 12 Jun 2025 16:23:04 +0100 Subject: [PATCH 32/62] Add Kconfig settings to Zephyr project creation --- src/webview/newZephyrProjectPanel.mts | 149 ++++++++++++++++++++++++++ web/zephyr/main.js | 17 +++ 2 files changed, 166 insertions(+) diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index d155091e..00e566eb 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -43,8 +43,80 @@ interface SubmitMessageValue { pythonPath: string; console: string; boardType: BoardType; + spiFeature: boolean; + i2cFeature: boolean; + gpioFeature: boolean; + wifiFeature: boolean; + sensorFeature: boolean; + shellFeature: boolean; } +// Kconfig snippets +const spiKconfig: string = "CONFIG_SPI=y"; +const i2cKconfig: string = "CONFIG_I2C=y"; +const gpioKconfig: string = "CONFIG_GPIO=y"; +const sensorKconfig: string = "CONFIG_GPIO=y"; +const shellKconfig: string = "CONFIG_SHELL=y"; +const wifiKconfig: string = `CONFIG_NETWORKING=y +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_MAIN_STACK_SIZE=5200 +CONFIG_SHELL_STACK_SIZE=5200 +CONFIG_NET_TX_STACK_SIZE=2048 +CONFIG_NET_RX_STACK_SIZE=2048 +CONFIG_LOG_BUFFER_SIZE=4096 + +CONFIG_NET_PKT_RX_COUNT=10 +CONFIG_NET_PKT_TX_COUNT=10 +CONFIG_NET_BUF_RX_COUNT=20 +CONFIG_NET_BUF_TX_COUNT=20 +CONFIG_NET_MAX_CONN=10 +CONFIG_NET_MAX_CONTEXTS=10 +CONFIG_NET_DHCPV4=y + +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=n + +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y + +CONFIG_DNS_RESOLVER=y +CONFIG_DNS_SERVER_IP_ADDRESSES=y +CONFIG_DNS_SERVER1="192.0.2.2" +CONFIG_DNS_RESOLVER_AI_MAX_ENTRIES=10 + +# Network address config +CONFIG_NET_CONFIG_AUTO_INIT=n +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" + +CONFIG_NET_LOG=y +CONFIG_INIT_STACKS=y + +CONFIG_NET_STATISTICS=y +CONFIG_NET_STATISTICS_PERIODIC_OUTPUT=n + +CONFIG_HTTP_CLIENT=y + +CONFIG_WIFI=y +CONFIG_WIFI_LOG_LEVEL_ERR=y +# printing of scan results puts pressure on queues in new locking +# design in net_mgmt. So, use a higher timeout for a crowded +# environment. +CONFIG_NET_MGMT_EVENT_QUEUE_TIMEOUT=5000 +CONFIG_NET_MGMT_EVENT_QUEUE_SIZE=16`; + +// Shell Kconfig values +const shellSpiKconfig: string = "CONFIG_SPI_SHELL=y"; +const shellI2CKconfig: string = "CONFIG_I2C_SHELL=y"; +const shellGPIOKconfig: string = "CONFIG_GPIO_SHELL=y"; +const shellSensorKconfig: string = "CONFIG_SENSOR_SHELL=y"; +const shellWifiKconfig: string = `CONFIG_WIFI_LOG_LEVEL_ERR=y +CONFIG_NET_L2_WIFI_SHELL=y`; + export class NewZephyrProjectPanel { public static currentPanel: NewZephyrProjectPanel | undefined; @@ -381,6 +453,38 @@ export class NewZephyrProjectPanel { Buffer.from(newTasksJsonString) ); + // Enable modules with Kconfig + const kconfigFile = joinPosix(newProjectDir, "prj.conf"); + const kconfigString = ( + await workspace.fs.readFile(Uri.file(kconfigFile)) + ).toString(); + + const newKconfigString = kconfigString.concat( + "\r\n", + "\r\n", + "# Enable Modules:", + "\r\n", + data.gpioFeature ? gpioKconfig + "\r\n" : "", + data.i2cFeature ? i2cKconfig + "\r\n" : "", + data.spiFeature ? spiKconfig + "\r\n" : "", + data.sensorFeature ? sensorKconfig + "\r\n" : "", + data.wifiFeature ? wifiKconfig + "\r\n" : "", + data.shellFeature ? "\r\n" + "# Enabling shells:" + "\r\n" : "", + data.shellFeature ? shellKconfig + "\r\n" : "", + data.shellFeature && data.gpioFeature ? shellGPIOKconfig + "\r\n" : "", + data.shellFeature && data.i2cFeature ? shellI2CKconfig + "\r\n" : "", + data.shellFeature && data.spiFeature ? shellSpiKconfig + "\r\n" : "", + data.shellFeature && data.sensorFeature + ? shellSensorKconfig + "\r\n" + : "", + data.shellFeature && data.wifiFeature ? shellWifiKconfig + "\r\n" : "" + ); + + await workspace.fs.writeFile( + Uri.file(kconfigFile), + Buffer.from(newKconfigString) + ); + this._logger.info(`Zephyr Project generated at ${newProjectDir}`); // Open the folder @@ -641,6 +745,51 @@ export class NewZephyrProjectPanel {
+
+

Modules

+

Kconfig options to enable the below modules

+
    +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
+
    +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
+
+
diff --git a/web/zephyr/main.js b/web/zephyr/main.js index b109c6de..cbfd5ce1 100644 --- a/web/zephyr/main.js +++ b/web/zephyr/main.js @@ -161,6 +161,17 @@ var submitted = false; return; } + const spiFeature = document.getElementById("spi-features-cblist").checked; + const i2cFeature = document.getElementById("i2c-features-cblist").checked; + const gpioFeature = document.getElementById("gpio-features-cblist").checked; + const wifiFeature = document.getElementById("wifi-features-cblist").checked; + const sensorFeature = document.getElementById( + "sensor-features-cblist" + ).checked; + const shellFeature = document.getElementById( + "shell-features-cblist" + ).checked; + //post all data values to the extension vscode.postMessage({ command: CMD_SUBMIT, @@ -170,6 +181,12 @@ var submitted = false; pythonPath: pythonPath, console: consoleSelection, boardType: document.getElementById("sel-board-type").value, + spiFeature: spiFeature, + i2cFeature: i2cFeature, + gpioFeature: gpioFeature, + wifiFeature: wifiFeature, + sensorFeature: sensorFeature, + shellFeature: shellFeature, }, }); }; From 4f25125ac7cbcf54acfec7b06ed108227a21d495 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 12 Jun 2025 20:35:31 +0100 Subject: [PATCH 33/62] Enable Run Project command for Zephyr --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5fb46167..275fc1e7 100644 --- a/package.json +++ b/package.json @@ -194,7 +194,7 @@ "command": "raspberry-pi-pico.runProject", "title": "Run Pico Project (USB)", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isPicoZephyrProject" + "enablement": "raspberry-pi-pico.isPicoProject" }, { "command": "raspberry-pi-pico.clearGithubApiCache", From b98c891efe50da975a9d9cda0e0a407ae6677777 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 12 Jun 2025 20:36:57 +0100 Subject: [PATCH 34/62] Temporarily point to fork which supports Pico 2W --- src/commands/getPaths.mts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 6186c085..3d1dfc57 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -668,12 +668,12 @@ manifest: remotes: - name: zephyrproject-rtos - url-base: https://github.com/zephyrproject-rtos + url-base: https://github.com/magpieembedded projects: - name: zephyr remote: zephyrproject-rtos - revision: main + revision: pico2w import: # By using name-allowlist we can clone only the modules that are # strictly needed by the application. From caedeb93f8529b98b02d2b6edb134cdf4c6e014b Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 13 Jun 2025 12:44:22 +0100 Subject: [PATCH 35/62] Move Python installation further up in Zephyr setup --- src/commands/getPaths.mts | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 3d1dfc57..0f799887 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -636,15 +636,35 @@ export class SetupZephyrCommand extends CommandWithResult { this._logger.info("Installing CMake"); const cmakeResult = await downloadAndInstallCmake("v3.31.5"); - this._logger.info(`${cmakeResult}`); + if (!cmakeResult) { + window.showErrorMessage("Could not install CMake. Exiting Zephyr Setup"); + } + window.showInformationMessage("CMake installed."); this._logger.info("Installing Ninja"); const ninjaResult = await downloadAndInstallNinja("v1.12.1"); - this._logger.info(`${ninjaResult}`); + if (!ninjaResult) { + window.showErrorMessage("Could not install Ninja. Exiting Zephyr Setup"); + } + window.showInformationMessage("Ninja installed."); + + const python3Path = await findPython(); + if (!python3Path) { + this._logger.error("Failed to find Python3 executable."); + showPythonNotFoundError(); + + return; + } + + const pythonExe = python3Path.replace( + HOME_VAR, + homedir().replaceAll("\\", "/") + ); const customEnv = process.env; const isWindows = process.platform === "win32"; const customPath = await getPath(); + this._logger.info(`Path: ${customPath}`); if (!customPath) { return; } @@ -688,19 +708,6 @@ manifest: Buffer.from(zephyrManifestContent) ); - const python3Path = await findPython(); - if (!python3Path) { - this._logger.error("Failed to find Python3 executable."); - showPythonNotFoundError(); - - return; - } - - const pythonExe = python3Path.replace( - HOME_VAR, - homedir().replaceAll("\\", "/") - ); - const command: string = [ `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, "-m virtualenv venv", From 4eec56010dc1b718fd22a5a6b974e84c76926eb5 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 13 Jun 2025 13:04:41 +0100 Subject: [PATCH 36/62] Setup dtc, wget and gperf for windows Zephyr --- src/commands/getPaths.mts | 48 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 0f799887..896644b6 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -18,6 +18,7 @@ import { buildToolchainPath, buildWestPath, buildZephyrWorkspacePath, + downloadAndInstallArchive, downloadAndInstallCmake, downloadAndInstallNinja, downloadAndInstallOpenOCD, @@ -656,13 +657,58 @@ export class SetupZephyrCommand extends CommandWithResult { return; } + const isWindows = process.platform === "win32"; + + if (isWindows) { + // Setup other Zephyr dependencies + this._logger.info("Installing dtc"); + const dtcResult = await downloadAndInstallArchive( + "https://github.com/oss-winget/oss-winget-storage/raw/" + + "96ea1b934342f45628a488d3b50d0c37cf06012c/packages/dtc/" + + "1.6.1/dtc-msys2-1.6.1-x86_64.zip", + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc"), + "dtc-msys2-1.6.1-x86_64.zip", + "dtc" + ); + if (!dtcResult) { + window.showErrorMessage("Could not install DTC. Exiting Zephyr Setup"); + } + window.showInformationMessage("DTC installed."); + + this._logger.info("Installing gperf"); + const gperfResult = await downloadAndInstallArchive( + "https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/" + + "gperf-3.0.1-bin.zip/download", + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf"), + "gperf-3.0.1-bin.zip", + "gperf" + ); + if (!gperfResult) { + window.showErrorMessage( + "Could not install gperf. Exiting Zephyr Setup" + ); + } + window.showInformationMessage("gperf installed."); + + this._logger.info("Installing wget"); + const wgetResult = await downloadAndInstallArchive( + "https:///eternallybored.org/misc/wget/releases/wget-1.21.4-win64.zip", + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), + "wget-1.21.4-win64.zip", + "wget" + ); + if (!wgetResult) { + window.showErrorMessage("Could not install wget. Exiting Zephyr Setup"); + } + window.showInformationMessage("wget installed."); + } + const pythonExe = python3Path.replace( HOME_VAR, homedir().replaceAll("\\", "/") ); const customEnv = process.env; - const isWindows = process.platform === "win32"; const customPath = await getPath(); this._logger.info(`Path: ${customPath}`); if (!customPath) { From b2a25777c3970376c73f456aaa546df66280d137 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 13 Jun 2025 13:22:09 +0100 Subject: [PATCH 37/62] Install 7zip durign Zephyr installation for Windows --- src/commands/getPaths.mts | 47 ++++++++++++++++++++++++++++++++++----- src/utils/download.mts | 2 +- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 896644b6..d1c4077d 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -23,6 +23,7 @@ import { downloadAndInstallNinja, downloadAndInstallOpenOCD, downloadAndInstallPicotool, + downloadFileGot, } from "../utils/download.mjs"; import Settings, { SettingsKey, HOME_VAR } from "../settings.mjs"; import which from "which"; @@ -672,6 +673,8 @@ export class SetupZephyrCommand extends CommandWithResult { ); if (!dtcResult) { window.showErrorMessage("Could not install DTC. Exiting Zephyr Setup"); + + return; } window.showInformationMessage("DTC installed."); @@ -687,6 +690,8 @@ export class SetupZephyrCommand extends CommandWithResult { window.showErrorMessage( "Could not install gperf. Exiting Zephyr Setup" ); + + return; } window.showInformationMessage("gperf installed."); @@ -699,8 +704,36 @@ export class SetupZephyrCommand extends CommandWithResult { ); if (!wgetResult) { window.showErrorMessage("Could not install wget. Exiting Zephyr Setup"); + + return; } window.showInformationMessage("wget installed."); + + this._logger.info("Installing 7zip"); + const szipURL = new URL("https://7-zip.org/a/7z2409-x64.exe"); + const szipResult = await downloadFileGot( + szipURL, + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "7zip-x64.exe") + ); + + if (!szipResult) { + window.showErrorMessage("Could not install 7zip. Exiting Zephyr Setup"); + + return; + } + + const szipCommand: string = "7zip-x64.msi"; + const szipInstallResult = await this._runCommand(szipCommand, { + cwd: joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk"), + }); + + if (!szipInstallResult) { + window.showErrorMessage("Could not install 7zip. Exiting Zephyr Setup"); + + return; + } + + window.showInformationMessage("7zip installed."); } const pythonExe = python3Path.replace( @@ -709,11 +742,14 @@ export class SetupZephyrCommand extends CommandWithResult { ); const customEnv = process.env; - const customPath = await getPath(); - this._logger.info(`Path: ${customPath}`); - if (!customPath) { - return; - } + // const customPath = await getPath(); + // this._logger.info(`Path: ${customPath}`); + // if (!customPath) { + // return; + // } + + const customPath = ``; + customPath.replaceAll("/", "\\"); customEnv[isWindows ? "Path" : "PATH"] = customPath + customEnv[isWindows ? "Path" : "PATH"]; @@ -869,7 +905,6 @@ manifest: result = await this._runCommand(westInstallSDKCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, - env: customEnv, }); this._logger.info(`${result}`); diff --git a/src/utils/download.mts b/src/utils/download.mts index ef219bde..b288ac2a 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -435,7 +435,7 @@ async function downloadFileUndici( }); } -async function downloadFileGot( +export async function downloadFileGot( url: URL, archiveFilePath: string, extraHeaders?: { [key: string]: string }, From 368cbf6364c0db41330a77877ed5a406c6850cce Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 13 Jun 2025 15:59:50 +0100 Subject: [PATCH 38/62] Add Zephyr tools to path during setup --- src/commands/getPaths.mts | 86 +++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index d1c4077d..8bc859c8 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -1,3 +1,4 @@ +import { rmSync } from "fs"; import { CommandWithResult } from "./command.mjs"; import { commands, type Uri, window, workspace } from "vscode"; import { type ExecOptions, exec } from "child_process"; @@ -717,25 +718,45 @@ export class SetupZephyrCommand extends CommandWithResult { ); if (!szipResult) { - window.showErrorMessage("Could not install 7zip. Exiting Zephyr Setup"); + window.showErrorMessage( + "Could not download 7zip. Exiting Zephyr Setup" + ); return; } - const szipCommand: string = "7zip-x64.msi"; + const szipCommand: string = "7zip-x64.exe"; const szipInstallResult = await this._runCommand(szipCommand, { cwd: joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk"), }); - if (!szipInstallResult) { - window.showErrorMessage("Could not install 7zip. Exiting Zephyr Setup"); + if (szipInstallResult !== 0) { + window.showErrorMessage( + "Could not install 7zip. Please ensure 7-Zip is installed." + + "Exiting Zephyr Setup" + ); return; } + // Clean up + rmSync( + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "7zip-x64.exe") + ); + window.showInformationMessage("7zip installed."); } + this._logger.info("Installing OpenOCD"); + const openocdResult = await downloadAndInstallOpenOCD(openOCDVersion); + if (!openocdResult) { + window.showErrorMessage( + "Could not install OpenOCD. Exiting Zephyr Setup" + ); + + return; + } + const pythonExe = python3Path.replace( HOME_VAR, homedir().replaceAll("\\", "/") @@ -748,7 +769,50 @@ export class SetupZephyrCommand extends CommandWithResult { // return; // } - const customPath = ``; + const customPath = + `${joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "cmake", + "v3.31.5", + "bin" + )};` + + `${joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "dtc", + "bin" + )};` + + `${joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "git", + "cmd" + )};` + + `${joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "gperf", + "bin" + )};` + + `${joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "ninja", + "v1.12.1" + )};` + + `${joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "python", + "3.12.6" + )};` + + `${joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget")};` + + `${joinPosix("C:\\Program Files".replaceAll("\\", "/"), "7-Zip")};` + + gitPath + + ";"; + + this._logger.info(`New path: ${customPath}`); customPath.replaceAll("/", "\\"); customEnv[isWindows ? "Path" : "PATH"] = @@ -802,6 +866,7 @@ manifest: let result = await this._runCommand(command, { cwd: zephyrWorkspaceDirectory, windowsHide: true, + env: customEnv, }); this._logger.info(`${result}`); @@ -823,6 +888,7 @@ manifest: result = await this._runCommand(command2, { cwd: zephyrWorkspaceDirectory, windowsHide: true, + env: customEnv, }); const westExe: string = joinPosix( @@ -847,6 +913,7 @@ manifest: result = await this._runCommand(westInitCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, + env: customEnv, }); this._logger.info(`${result}`); @@ -858,6 +925,7 @@ manifest: result = await this._runCommand(westUpdateCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, + env: customEnv, }); this._logger.info(`${result}`); @@ -867,6 +935,7 @@ manifest: result = await this._runCommand(zephyrExportCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, + env: customEnv, }); const westPipPackagesCommand: string = [ @@ -878,6 +947,7 @@ manifest: result = await this._runCommand(westPipPackagesCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, + env: customEnv, }); this._logger.info(`${result}`); @@ -891,6 +961,7 @@ manifest: result = await this._runCommand(westBlobsFetchCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, + env: customEnv, }); this._logger.info(`${result}`); @@ -905,14 +976,11 @@ manifest: result = await this._runCommand(westInstallSDKCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, + env: customEnv, }); this._logger.info(`${result}`); - this._logger.info("Installing OpenOCD"); - const openocdResult = await downloadAndInstallOpenOCD(openOCDVersion); - this._logger.info(`${openocdResult}`); - this._logger.info("Complete"); this.running = false; From 273c5a1e6984b10dc8eb7e03bd3878695d2d5fed Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 13 Jun 2025 17:12:19 +0100 Subject: [PATCH 39/62] Get path set correctly for windows and linux --- src/commands/getPaths.mts | 44 +++++++++++++-------------------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 8bc859c8..2017c688 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -769,48 +769,32 @@ export class SetupZephyrCommand extends CommandWithResult { // return; // } - const customPath = - `${joinPosix( + const customPath = [ + joinPosix( homedir().replaceAll("\\", "/"), ".pico-sdk", "cmake", "v3.31.5", "bin" - )};` + - `${joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "dtc", - "bin" - )};` + - `${joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "git", - "cmd" - )};` + - `${joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "gperf", - "bin" - )};` + - `${joinPosix( + ), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc", "bin"), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf", "bin"), + joinPosix( homedir().replaceAll("\\", "/"), ".pico-sdk", "ninja", "v1.12.1" - )};` + - `${joinPosix( + ), + joinPosix( homedir().replaceAll("\\", "/"), ".pico-sdk", "python", "3.12.6" - )};` + - `${joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget")};` + - `${joinPosix("C:\\Program Files".replaceAll("\\", "/"), "7-Zip")};` + - gitPath + - ";"; + ), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), + joinPosix("C:\\Program Files".replaceAll("\\", "/"), "7-Zip"), + gitPath, + ].join(process.platform === "win32" ? ";" : ":"); this._logger.info(`New path: ${customPath}`); @@ -856,7 +840,7 @@ manifest: const command: string = [ `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, - "-m virtualenv venv", + "-m " + (process.platform === "win32" ? "virtualenv" : "venv") + " venv", ].join(" "); // Create a Zephyr workspace, copy the west manifest in and initialise the workspace From 2cea3e9d8a06e103dd66aff62ed77d750680d465 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 13 Jun 2025 20:49:43 +0100 Subject: [PATCH 40/62] Fix Zephyr SDK installation step --- src/commands/getPaths.mts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 2017c688..c6f08d65 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -1,7 +1,7 @@ import { rmSync } from "fs"; import { CommandWithResult } from "./command.mjs"; import { commands, type Uri, window, workspace } from "vscode"; -import { type ExecOptions, exec } from "child_process"; +import { type ExecOptions, exec, spawnSync } from "child_process"; import { join as joinPosix } from "path/posix"; import { homedir } from "os"; import { @@ -778,6 +778,7 @@ export class SetupZephyrCommand extends CommandWithResult { "bin" ), joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc", "bin"), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "git", "cmd"), joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf", "bin"), joinPosix( homedir().replaceAll("\\", "/"), @@ -793,7 +794,7 @@ export class SetupZephyrCommand extends CommandWithResult { ), joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), joinPosix("C:\\Program Files".replaceAll("\\", "/"), "7-Zip"), - gitPath, + "", // Need this to add separator to end ].join(process.platform === "win32" ? ";" : ":"); this._logger.info(`New path: ${customPath}`); @@ -956,12 +957,27 @@ manifest: `-b ${zephyrWorkspaceDirectory}`, ].join(" "); + // This has to be a spawn due to the way the underlying SDK command calls + // subprocess and needs to inherit the Path variables set in customEnv this._logger.info("Installing Zephyr SDK"); - result = await this._runCommand(westInstallSDKCommand, { + const child = spawnSync(westInstallSDKCommand, { + shell: true, cwd: zephyrWorkspaceDirectory, windowsHide: true, env: customEnv, }); + this._logger.info("stdout: ", child.stdout.toString()); + this._logger.info("stderr: ", child.stderr.toString()); + if (child.status) { + this._logger.info("exit code: ", child.status); + if (child.status !== 0) { + window.showErrorMessage( + "Error installing Zephyr SDK." + "Exiting Zephyr Setup." + ); + } + + return; + } this._logger.info(`${result}`); From ef8999d008f280034820b02b7fd45481e7b397db Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Thu, 19 Jun 2025 13:05:20 +0100 Subject: [PATCH 41/62] Fix sensor kconfig value --- src/webview/newZephyrProjectPanel.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index 00e566eb..4bb728c2 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -55,7 +55,7 @@ interface SubmitMessageValue { const spiKconfig: string = "CONFIG_SPI=y"; const i2cKconfig: string = "CONFIG_I2C=y"; const gpioKconfig: string = "CONFIG_GPIO=y"; -const sensorKconfig: string = "CONFIG_GPIO=y"; +const sensorKconfig: string = "CONFIG_SENSOR=y"; const shellKconfig: string = "CONFIG_SHELL=y"; const wifiKconfig: string = `CONFIG_NETWORKING=y CONFIG_TEST_RANDOM_GENERATOR=y From d29af69a5201bbc38f6dcd7ed3a0a4ccbb3094a7 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 20 Jun 2025 10:22:37 +0100 Subject: [PATCH 42/62] Add picotool installation to Zephyr setup --- src/commands/getPaths.mts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index c6f08d65..191f9d68 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -651,6 +651,13 @@ export class SetupZephyrCommand extends CommandWithResult { } window.showInformationMessage("Ninja installed."); + this._logger.info("Installing Picotool"); + const picotoolResult = await downloadAndInstallPicotool("2.1.1"); + if (!picotoolResult) { + window.showErrorMessage("Could not install Ninja. Exiting Zephyr Setup"); + } + window.showInformationMessage("Picotool installed."); + const python3Path = await findPython(); if (!python3Path) { this._logger.error("Failed to find Python3 executable."); From c0f85320d63af276a2441dee4de4b1c0e3d06626 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 20 Jun 2025 11:03:10 +0100 Subject: [PATCH 43/62] Improve installation process of python, venv and 7zip --- src/commands/getPaths.mts | 134 ++++++++++++++++++++++++++------------ 1 file changed, 94 insertions(+), 40 deletions(-) diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 191f9d68..6076c3ac 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -1,4 +1,4 @@ -import { rmSync } from "fs"; +import { existsSync, readdirSync, rmSync } from "fs"; import { CommandWithResult } from "./command.mjs"; import { commands, type Uri, window, workspace } from "vscode"; import { type ExecOptions, exec, spawnSync } from "child_process"; @@ -718,40 +718,56 @@ export class SetupZephyrCommand extends CommandWithResult { window.showInformationMessage("wget installed."); this._logger.info("Installing 7zip"); - const szipURL = new URL("https://7-zip.org/a/7z2409-x64.exe"); - const szipResult = await downloadFileGot( - szipURL, - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "7zip-x64.exe") - ); - - if (!szipResult) { - window.showErrorMessage( - "Could not download 7zip. Exiting Zephyr Setup" + const szipTargetDirectory = joinPosix("C:\\Program Files\\7-Zip"); + if ( + existsSync(szipTargetDirectory) && + readdirSync(szipTargetDirectory).length !== 0 + ) { + this._logger.info("7-Zip is already installed."); + } else { + const szipURL = new URL("https://7-zip.org/a/7z2409-x64.exe"); + const szipResult = await downloadFileGot( + szipURL, + joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "7zip-x64.exe" + ) ); - return; - } + if (!szipResult) { + window.showErrorMessage( + "Could not download 7-Zip. Exiting Zephyr Setup" + ); - const szipCommand: string = "7zip-x64.exe"; - const szipInstallResult = await this._runCommand(szipCommand, { - cwd: joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk"), - }); + return; + } - if (szipInstallResult !== 0) { - window.showErrorMessage( - "Could not install 7zip. Please ensure 7-Zip is installed." + - "Exiting Zephyr Setup" - ); + const szipCommand: string = "7zip-x64.exe"; + const szipInstallResult = await this._runCommand(szipCommand, { + cwd: joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk"), + }); - return; - } + if (szipInstallResult !== 0) { + window.showErrorMessage( + "Could not install 7-Zip. Please ensure 7-Zip is installed." + + "Exiting Zephyr Setup" + ); - // Clean up - rmSync( - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "7zip-x64.exe") - ); + return; + } + + // Clean up + rmSync( + joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "7zip-x64.exe" + ) + ); - window.showInformationMessage("7zip installed."); + window.showInformationMessage("7-Zip installed."); + } } this._logger.info("Installing OpenOCD"); @@ -846,24 +862,62 @@ manifest: Buffer.from(zephyrManifestContent) ); - const command: string = [ + const createVenvCommandVenv: string = [ + `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, + "-m venv venv", + ].join(" "); + const createVenvCommandVirtualenv: string = [ `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, - "-m " + (process.platform === "win32" ? "virtualenv" : "venv") + " venv", + "-m virtualenv venv", ].join(" "); // Create a Zephyr workspace, copy the west manifest in and initialise the workspace workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); + // Generic result to get value from runCommand calls + let result: number | null; + this._logger.info("Setting up virtual environment for Zephyr"); - let result = await this._runCommand(command, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); + const venvPython: string = joinPosix( + zephyrWorkspaceDirectory, + "venv", + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "python.exe" : "python" + ); + if (existsSync(venvPython)) { + this._logger.info("Existing Python venv found."); + } else { + result = await this._runCommand(createVenvCommandVenv, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + if (result !== 0) { + this._logger.warn( + "Could not create virtual environment with venv," + + "trying with virtualenv..." + ); - this._logger.info(`${result}`); + result = await this._runCommand(createVenvCommandVirtualenv, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + + if (result !== 0) { + this._logger.error("Could not create virtual environment. Exiting."); + window.showErrorMessage( + "Could not install venv. Exiting Zephyr Setup" + ); + + return; + } + } + + this._logger.info("Venv created."); + } - const venvPythonExe: string = joinPosix( + const venvPythonCommand: string = joinPosix( process.env.ComSpec === "powershell.exe" ? "&" : "", zephyrWorkspaceDirectory, "venv", @@ -871,13 +925,13 @@ manifest: process.platform === "win32" ? "python.exe" : "python" ); - const command2: string = [ - venvPythonExe, + const installWestCommand: string = [ + venvPythonCommand, "-m pip install west pyelftools", ].join(" "); this._logger.info("Installing Python dependencies for Zephyr"); - result = await this._runCommand(command2, { + result = await this._runCommand(installWestCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, env: customEnv, From ea4b73415d4c09fd28f3f753ddce298e0ec03776 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 20 Jun 2025 11:35:00 +0100 Subject: [PATCH 44/62] Refactor: Separate out Zephyr setup functionality and add to new Zephyr Project --- src/commands/getPaths.mts | 465 +------------------------- src/utils/setupZephyr.mts | 447 +++++++++++++++++++++++++ src/webview/newZephyrProjectPanel.mts | 4 + 3 files changed, 457 insertions(+), 459 deletions(-) create mode 100644 src/utils/setupZephyr.mts diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 6076c3ac..508fc1bb 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -1,9 +1,6 @@ -import { existsSync, readdirSync, rmSync } from "fs"; import { CommandWithResult } from "./command.mjs"; import { commands, type Uri, window, workspace } from "vscode"; import { type ExecOptions, exec, spawnSync } from "child_process"; -import { join as joinPosix } from "path/posix"; -import { homedir } from "os"; import { getPythonPath, getPath, @@ -19,14 +16,10 @@ import { buildToolchainPath, buildWestPath, buildZephyrWorkspacePath, - downloadAndInstallArchive, - downloadAndInstallCmake, - downloadAndInstallNinja, downloadAndInstallOpenOCD, downloadAndInstallPicotool, - downloadFileGot, } from "../utils/download.mjs"; -import Settings, { SettingsKey, HOME_VAR } from "../settings.mjs"; +import Settings, { SettingsKey } from "../settings.mjs"; import which from "which"; import { execSync } from "child_process"; import { getPicotoolReleases } from "../utils/githubREST.mjs"; @@ -38,6 +31,8 @@ import { rustProjectGetSelectedChip } from "../utils/rustUtil.mjs"; import { OPENOCD_VERSION } from "../utils/sharedConstants.mjs"; import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; import { ensureGit } from "../utils/gitUtil.mjs"; +import { openOCDVersion } from "../webview/newProjectPanel.mjs"; +import { setupZephyr } from "../utils/setupZephyr.mjs"; export class GetPythonPathCommand extends CommandWithResult { constructor() { @@ -589,463 +584,15 @@ export class SetupZephyrCommand extends CommandWithResult { private readonly _logger: Logger = new Logger("SetupZephyr"); - private _runCommand( - command: string, - options: ExecOptions - ): Promise { - this._logger.debug(`Running: ${command}`); - - return new Promise(resolve => { - const generatorProcess = exec( - command, - options, - (error, stdout, stderr) => { - this._logger.debug(stdout); - this._logger.info(stderr); - if (error) { - this._logger.error(`Setup venv error: ${error.message}`); - resolve(null); // indicate error - } - } - ); - - generatorProcess.on("exit", code => { - // Resolve with exit code or -1 if code is undefined - resolve(code); - }); - }); - } - async execute(): Promise { if (this.running) { return undefined; } - this.running = true; - - window.showInformationMessage("Setup Zephyr Command Running"); - - const settings = Settings.getInstance(); - if (settings === undefined) { - this._logger.error("Settings not initialized."); - - return; - } - - // TODO: this does take about 2s - may be reduced - const gitPath = await ensureGit(settings, { returnPath: true }); - if (typeof gitPath !== "string" || gitPath.length === 0) { - return; - } - - this._logger.info("Installing CMake"); - const cmakeResult = await downloadAndInstallCmake("v3.31.5"); - if (!cmakeResult) { - window.showErrorMessage("Could not install CMake. Exiting Zephyr Setup"); - } - window.showInformationMessage("CMake installed."); - - this._logger.info("Installing Ninja"); - const ninjaResult = await downloadAndInstallNinja("v1.12.1"); - if (!ninjaResult) { - window.showErrorMessage("Could not install Ninja. Exiting Zephyr Setup"); - } - window.showInformationMessage("Ninja installed."); - - this._logger.info("Installing Picotool"); - const picotoolResult = await downloadAndInstallPicotool("2.1.1"); - if (!picotoolResult) { - window.showErrorMessage("Could not install Ninja. Exiting Zephyr Setup"); - } - window.showInformationMessage("Picotool installed."); - - const python3Path = await findPython(); - if (!python3Path) { - this._logger.error("Failed to find Python3 executable."); - showPythonNotFoundError(); - - return; - } - - const isWindows = process.platform === "win32"; - - if (isWindows) { - // Setup other Zephyr dependencies - this._logger.info("Installing dtc"); - const dtcResult = await downloadAndInstallArchive( - "https://github.com/oss-winget/oss-winget-storage/raw/" + - "96ea1b934342f45628a488d3b50d0c37cf06012c/packages/dtc/" + - "1.6.1/dtc-msys2-1.6.1-x86_64.zip", - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc"), - "dtc-msys2-1.6.1-x86_64.zip", - "dtc" - ); - if (!dtcResult) { - window.showErrorMessage("Could not install DTC. Exiting Zephyr Setup"); - - return; - } - window.showInformationMessage("DTC installed."); - - this._logger.info("Installing gperf"); - const gperfResult = await downloadAndInstallArchive( - "https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/" + - "gperf-3.0.1-bin.zip/download", - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf"), - "gperf-3.0.1-bin.zip", - "gperf" - ); - if (!gperfResult) { - window.showErrorMessage( - "Could not install gperf. Exiting Zephyr Setup" - ); - - return; - } - window.showInformationMessage("gperf installed."); - - this._logger.info("Installing wget"); - const wgetResult = await downloadAndInstallArchive( - "https:///eternallybored.org/misc/wget/releases/wget-1.21.4-win64.zip", - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), - "wget-1.21.4-win64.zip", - "wget" - ); - if (!wgetResult) { - window.showErrorMessage("Could not install wget. Exiting Zephyr Setup"); - - return; - } - window.showInformationMessage("wget installed."); - - this._logger.info("Installing 7zip"); - const szipTargetDirectory = joinPosix("C:\\Program Files\\7-Zip"); - if ( - existsSync(szipTargetDirectory) && - readdirSync(szipTargetDirectory).length !== 0 - ) { - this._logger.info("7-Zip is already installed."); - } else { - const szipURL = new URL("https://7-zip.org/a/7z2409-x64.exe"); - const szipResult = await downloadFileGot( - szipURL, - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "7zip-x64.exe" - ) - ); - - if (!szipResult) { - window.showErrorMessage( - "Could not download 7-Zip. Exiting Zephyr Setup" - ); - - return; - } - - const szipCommand: string = "7zip-x64.exe"; - const szipInstallResult = await this._runCommand(szipCommand, { - cwd: joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk"), - }); - - if (szipInstallResult !== 0) { - window.showErrorMessage( - "Could not install 7-Zip. Please ensure 7-Zip is installed." + - "Exiting Zephyr Setup" - ); - - return; - } - - // Clean up - rmSync( - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "7zip-x64.exe" - ) - ); - - window.showInformationMessage("7-Zip installed."); - } - } - - this._logger.info("Installing OpenOCD"); - const openocdResult = await downloadAndInstallOpenOCD(openOCDVersion); - if (!openocdResult) { - window.showErrorMessage( - "Could not install OpenOCD. Exiting Zephyr Setup" - ); - - return; - } - - const pythonExe = python3Path.replace( - HOME_VAR, - homedir().replaceAll("\\", "/") - ); - - const customEnv = process.env; - // const customPath = await getPath(); - // this._logger.info(`Path: ${customPath}`); - // if (!customPath) { - // return; - // } - - const customPath = [ - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "cmake", - "v3.31.5", - "bin" - ), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc", "bin"), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "git", "cmd"), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf", "bin"), - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "ninja", - "v1.12.1" - ), - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "python", - "3.12.6" - ), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), - joinPosix("C:\\Program Files".replaceAll("\\", "/"), "7-Zip"), - "", // Need this to add separator to end - ].join(process.platform === "win32" ? ";" : ":"); - - this._logger.info(`New path: ${customPath}`); - - customPath.replaceAll("/", "\\"); - customEnv[isWindows ? "Path" : "PATH"] = - customPath + customEnv[isWindows ? "Path" : "PATH"]; - - const zephyrWorkspaceDirectory = buildZephyrWorkspacePath(); - - const zephyrManifestDir: string = joinPosix( - zephyrWorkspaceDirectory, - "manifest" - ); - - const zephyrManifestFile: string = joinPosix(zephyrManifestDir, "west.yml"); - - const zephyrManifestContent: string = ` -manifest: - self: - west-commands: scripts/west-commands.yml - - remotes: - - name: zephyrproject-rtos - url-base: https://github.com/magpieembedded - - projects: - - name: zephyr - remote: zephyrproject-rtos - revision: pico2w - import: - # By using name-allowlist we can clone only the modules that are - # strictly needed by the application. - name-allowlist: - - cmsis_6 # required by the ARM Cortex-M port - - hal_rpi_pico # required for Pico board support - - hal_infineon # required for Wifi chip support -`; - - await workspace.fs.writeFile( - Uri.file(zephyrManifestFile), - Buffer.from(zephyrManifestContent) - ); - - const createVenvCommandVenv: string = [ - `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, - "-m venv venv", - ].join(" "); - const createVenvCommandVirtualenv: string = [ - `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, - "-m virtualenv venv", - ].join(" "); - - // Create a Zephyr workspace, copy the west manifest in and initialise the workspace - workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); - - // Generic result to get value from runCommand calls - let result: number | null; - - this._logger.info("Setting up virtual environment for Zephyr"); - const venvPython: string = joinPosix( - zephyrWorkspaceDirectory, - "venv", - process.platform === "win32" ? "Scripts" : "bin", - process.platform === "win32" ? "python.exe" : "python" - ); - if (existsSync(venvPython)) { - this._logger.info("Existing Python venv found."); - } else { - result = await this._runCommand(createVenvCommandVenv, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - if (result !== 0) { - this._logger.warn( - "Could not create virtual environment with venv," + - "trying with virtualenv..." - ); - - result = await this._runCommand(createVenvCommandVirtualenv, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - - if (result !== 0) { - this._logger.error("Could not create virtual environment. Exiting."); - window.showErrorMessage( - "Could not install venv. Exiting Zephyr Setup" - ); - - return; - } - } - - this._logger.info("Venv created."); - } - - const venvPythonCommand: string = joinPosix( - process.env.ComSpec === "powershell.exe" ? "&" : "", - zephyrWorkspaceDirectory, - "venv", - process.platform === "win32" ? "Scripts" : "bin", - process.platform === "win32" ? "python.exe" : "python" - ); - - const installWestCommand: string = [ - venvPythonCommand, - "-m pip install west pyelftools", - ].join(" "); - - this._logger.info("Installing Python dependencies for Zephyr"); - result = await this._runCommand(installWestCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - - const westExe: string = joinPosix( - process.env.ComSpec === "powershell.exe" ? "&" : "", - zephyrWorkspaceDirectory, - "venv", - process.platform === "win32" ? "Scripts" : "bin", - process.platform === "win32" ? "west.exe" : "west" - ); - - const zephyrWorkspaceFiles = await workspace.fs.readDirectory( - Uri.file(zephyrWorkspaceDirectory) - ); - - const westAlreadyExists = zephyrWorkspaceFiles.find(x => x[0] === ".west"); - if (westAlreadyExists) { - this._logger.info("West workspace already initialised."); - } else { - this._logger.info("No West workspace found. Initialising..."); - - const westInitCommand: string = [westExe, "init -l manifest"].join(" "); - result = await this._runCommand(westInitCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - - this._logger.info(`${result}`); - } - - const westUpdateCommand: string = [westExe, "update"].join(" "); - - this._logger.info("Updating West workspace"); - result = await this._runCommand(westUpdateCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - - this._logger.info(`${result}`); - - const zephyrExportCommand: string = [westExe, "zephyr-export"].join(" "); - this._logger.info("Exporting Zephyr CMake Files"); - result = await this._runCommand(zephyrExportCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - - const westPipPackagesCommand: string = [ - westExe, - "packages pip --install", - ].join(" "); - - this._logger.info("Installing West Python packages"); - result = await this._runCommand(westPipPackagesCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - - this._logger.info(`${result}`); - - const westBlobsFetchCommand: string = [ - westExe, - "blobs fetch hal_infineon", - ].join(" "); - - this._logger.info("Fetching binary blobs for Zephyr"); - result = await this._runCommand(westBlobsFetchCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - - this._logger.info(`${result}`); - - const westInstallSDKCommand: string = [ - westExe, - "sdk install -t arm-zephyr-eabi", - `-b ${zephyrWorkspaceDirectory}`, - ].join(" "); - - // This has to be a spawn due to the way the underlying SDK command calls - // subprocess and needs to inherit the Path variables set in customEnv - this._logger.info("Installing Zephyr SDK"); - const child = spawnSync(westInstallSDKCommand, { - shell: true, - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - this._logger.info("stdout: ", child.stdout.toString()); - this._logger.info("stderr: ", child.stderr.toString()); - if (child.status) { - this._logger.info("exit code: ", child.status); - if (child.status !== 0) { - window.showErrorMessage( - "Error installing Zephyr SDK." + "Exiting Zephyr Setup." - ); - } - - return; - } - - this._logger.info(`${result}`); - - this._logger.info("Complete"); + this.running = true; + const result = setupZephyr(); this.running = false; - return ""; + return result; } } diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts new file mode 100644 index 00000000..28a613a4 --- /dev/null +++ b/src/utils/setupZephyr.mts @@ -0,0 +1,447 @@ +import { existsSync, readdirSync, rmSync } from "fs"; +import { window, workspace, Uri } from "vscode"; +import { type ExecOptions, exec, spawnSync } from "child_process"; +import { join as joinPosix } from "path/posix"; +import { homedir } from "os"; +import Logger from "../logger.mjs"; + +import { + buildZephyrWorkspacePath, + downloadAndInstallArchive, + downloadAndInstallCmake, + downloadAndInstallNinja, + downloadAndInstallOpenOCD, + downloadAndInstallPicotool, + downloadFileGot, +} from "../utils/download.mjs"; +import Settings, { HOME_VAR } from "../settings.mjs"; +import { openOCDVersion } from "../webview/newProjectPanel.mjs"; +import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; +import { ensureGit } from "../utils/gitUtil.mjs"; + +const _logger = new Logger("zephyrSetup"); + +function _runCommand( + command: string, + options: ExecOptions +): Promise { + _logger.debug(`Running: ${command}`); + + return new Promise(resolve => { + const generatorProcess = exec(command, options, (error, stdout, stderr) => { + _logger.debug(stdout); + _logger.info(stderr); + if (error) { + _logger.error(`Setup venv error: ${error.message}`); + resolve(null); // indicate error + } + }); + + generatorProcess.on("exit", code => { + // Resolve with exit code or -1 if code is undefined + resolve(code); + }); + }); +} + +export async function setupZephyr(): Promise { + window.showInformationMessage("Setup Zephyr Command Running"); + + const settings = Settings.getInstance(); + if (settings === undefined) { + _logger.error("Settings not initialized."); + + return; + } + + // TODO: this does take about 2s - may be reduced + const gitPath = await ensureGit(settings, { returnPath: true }); + if (typeof gitPath !== "string" || gitPath.length === 0) { + return; + } + + _logger.info("Installing CMake"); + const cmakeResult = await downloadAndInstallCmake("v3.31.5"); + if (!cmakeResult) { + window.showErrorMessage("Could not install CMake. Exiting Zephyr Setup"); + } + window.showInformationMessage("CMake installed."); + + _logger.info("Installing Ninja"); + const ninjaResult = await downloadAndInstallNinja("v1.12.1"); + if (!ninjaResult) { + window.showErrorMessage("Could not install Ninja. Exiting Zephyr Setup"); + } + window.showInformationMessage("Ninja installed."); + + _logger.info("Installing Picotool"); + const picotoolResult = await downloadAndInstallPicotool("2.1.1"); + if (!picotoolResult) { + window.showErrorMessage("Could not install Ninja. Exiting Zephyr Setup"); + } + window.showInformationMessage("Picotool installed."); + + const python3Path = await findPython(); + if (!python3Path) { + _logger.error("Failed to find Python3 executable."); + showPythonNotFoundError(); + + return; + } + + const isWindows = process.platform === "win32"; + + if (isWindows) { + // Setup other Zephyr dependencies + _logger.info("Installing dtc"); + const dtcResult = await downloadAndInstallArchive( + "https://github.com/oss-winget/oss-winget-storage/raw/" + + "96ea1b934342f45628a488d3b50d0c37cf06012c/packages/dtc/" + + "1.6.1/dtc-msys2-1.6.1-x86_64.zip", + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc"), + "dtc-msys2-1.6.1-x86_64.zip", + "dtc" + ); + if (!dtcResult) { + window.showErrorMessage("Could not install DTC. Exiting Zephyr Setup"); + + return; + } + window.showInformationMessage("DTC installed."); + + _logger.info("Installing gperf"); + const gperfResult = await downloadAndInstallArchive( + "https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/" + + "gperf-3.0.1-bin.zip/download", + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf"), + "gperf-3.0.1-bin.zip", + "gperf" + ); + if (!gperfResult) { + window.showErrorMessage("Could not install gperf. Exiting Zephyr Setup"); + + return; + } + window.showInformationMessage("gperf installed."); + + _logger.info("Installing wget"); + const wgetResult = await downloadAndInstallArchive( + "https:///eternallybored.org/misc/wget/releases/wget-1.21.4-win64.zip", + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), + "wget-1.21.4-win64.zip", + "wget" + ); + if (!wgetResult) { + window.showErrorMessage("Could not install wget. Exiting Zephyr Setup"); + + return; + } + window.showInformationMessage("wget installed."); + + _logger.info("Installing 7zip"); + const szipTargetDirectory = joinPosix("C:\\Program Files\\7-Zip"); + if ( + existsSync(szipTargetDirectory) && + readdirSync(szipTargetDirectory).length !== 0 + ) { + _logger.info("7-Zip is already installed."); + } else { + const szipURL = new URL("https://7-zip.org/a/7z2409-x64.exe"); + const szipResult = await downloadFileGot( + szipURL, + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "7zip-x64.exe") + ); + + if (!szipResult) { + window.showErrorMessage( + "Could not download 7-Zip. Exiting Zephyr Setup" + ); + + return; + } + + const szipCommand: string = "7zip-x64.exe"; + const szipInstallResult = await _runCommand(szipCommand, { + cwd: joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk"), + }); + + if (szipInstallResult !== 0) { + window.showErrorMessage( + "Could not install 7-Zip. Please ensure 7-Zip is installed." + + "Exiting Zephyr Setup" + ); + + return; + } + + // Clean up + rmSync( + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "7zip-x64.exe") + ); + + window.showInformationMessage("7-Zip installed."); + } + } + + _logger.info("Installing OpenOCD"); + const openocdResult = await downloadAndInstallOpenOCD(openOCDVersion); + if (!openocdResult) { + window.showErrorMessage("Could not install OpenOCD. Exiting Zephyr Setup"); + + return; + } + + const pythonExe = python3Path.replace( + HOME_VAR, + homedir().replaceAll("\\", "/") + ); + + const customEnv = process.env; + // const customPath = await getPath(); + // _logger.info(`Path: ${customPath}`); + // if (!customPath) { + // return; + // } + + const customPath = [ + joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "cmake", + "v3.31.5", + "bin" + ), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc", "bin"), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "git", "cmd"), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf", "bin"), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "ninja", "v1.12.1"), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "python", "3.12.6"), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), + joinPosix("C:\\Program Files".replaceAll("\\", "/"), "7-Zip"), + "", // Need this to add separator to end + ].join(process.platform === "win32" ? ";" : ":"); + + _logger.info(`New path: ${customPath}`); + + customPath.replaceAll("/", "\\"); + customEnv[isWindows ? "Path" : "PATH"] = + customPath + customEnv[isWindows ? "Path" : "PATH"]; + + const zephyrWorkspaceDirectory = buildZephyrWorkspacePath(); + + const zephyrManifestDir: string = joinPosix( + zephyrWorkspaceDirectory, + "manifest" + ); + + const zephyrManifestFile: string = joinPosix(zephyrManifestDir, "west.yml"); + + const zephyrManifestContent: string = ` +manifest: + self: + west-commands: scripts/west-commands.yml + + remotes: + - name: zephyrproject-rtos + url-base: https://github.com/magpieembedded + + projects: + - name: zephyr + remote: zephyrproject-rtos + revision: pico2w + import: + # By using name-allowlist we can clone only the modules that are + # strictly needed by the application. + name-allowlist: + - cmsis_6 # required by the ARM Cortex-M port + - hal_rpi_pico # required for Pico board support + - hal_infineon # required for Wifi chip support +`; + + await workspace.fs.writeFile( + Uri.file(zephyrManifestFile), + Buffer.from(zephyrManifestContent) + ); + + const createVenvCommandVenv: string = [ + `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, + "-m venv venv", + ].join(" "); + const createVenvCommandVirtualenv: string = [ + `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, + "-m virtualenv venv", + ].join(" "); + + // Create a Zephyr workspace, copy the west manifest in and initialise the workspace + workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); + + // Generic result to get value from runCommand calls + let result: number | null; + + _logger.info("Setting up virtual environment for Zephyr"); + const venvPython: string = joinPosix( + zephyrWorkspaceDirectory, + "venv", + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "python.exe" : "python" + ); + if (existsSync(venvPython)) { + _logger.info("Existing Python venv found."); + } else { + result = await _runCommand(createVenvCommandVenv, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + if (result !== 0) { + _logger.warn( + "Could not create virtual environment with venv," + + "trying with virtualenv..." + ); + + result = await _runCommand(createVenvCommandVirtualenv, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + + if (result !== 0) { + _logger.error("Could not create virtual environment. Exiting."); + window.showErrorMessage("Could not install venv. Exiting Zephyr Setup"); + + return; + } + } + + _logger.info("Venv created."); + } + + const venvPythonCommand: string = joinPosix( + process.env.ComSpec === "powershell.exe" ? "&" : "", + zephyrWorkspaceDirectory, + "venv", + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "python.exe" : "python" + ); + + const installWestCommand: string = [ + venvPythonCommand, + "-m pip install west pyelftools", + ].join(" "); + + _logger.info("Installing Python dependencies for Zephyr"); + result = await _runCommand(installWestCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + + const westExe: string = joinPosix( + process.env.ComSpec === "powershell.exe" ? "&" : "", + zephyrWorkspaceDirectory, + "venv", + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "west.exe" : "west" + ); + + const zephyrWorkspaceFiles = await workspace.fs.readDirectory( + Uri.file(zephyrWorkspaceDirectory) + ); + + const westAlreadyExists = zephyrWorkspaceFiles.find(x => x[0] === ".west"); + if (westAlreadyExists) { + _logger.info("West workspace already initialised."); + } else { + _logger.info("No West workspace found. Initialising..."); + + const westInitCommand: string = [westExe, "init -l manifest"].join(" "); + result = await _runCommand(westInitCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + + _logger.info(`${result}`); + } + + const westUpdateCommand: string = [westExe, "update"].join(" "); + + _logger.info("Updating West workspace"); + result = await _runCommand(westUpdateCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + + _logger.info(`${result}`); + + const zephyrExportCommand: string = [westExe, "zephyr-export"].join(" "); + _logger.info("Exporting Zephyr CMake Files"); + result = await _runCommand(zephyrExportCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + + const westPipPackagesCommand: string = [ + westExe, + "packages pip --install", + ].join(" "); + + _logger.info("Installing West Python packages"); + result = await _runCommand(westPipPackagesCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + + _logger.info(`${result}`); + + const westBlobsFetchCommand: string = [ + westExe, + "blobs fetch hal_infineon", + ].join(" "); + + _logger.info("Fetching binary blobs for Zephyr"); + result = await _runCommand(westBlobsFetchCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + + _logger.info(`${result}`); + + const westInstallSDKCommand: string = [ + westExe, + "sdk install -t arm-zephyr-eabi", + `-b ${zephyrWorkspaceDirectory}`, + ].join(" "); + + // This has to be a spawn due to the way the underlying SDK command calls + // subprocess and needs to inherit the Path variables set in customEnv + _logger.info("Installing Zephyr SDK"); + const child = spawnSync(westInstallSDKCommand, { + shell: true, + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + _logger.info("stdout: ", child.stdout.toString()); + _logger.info("stderr: ", child.stderr.toString()); + if (child.status) { + _logger.info("exit code: ", child.status); + if (child.status !== 0) { + window.showErrorMessage( + "Error installing Zephyr SDK." + "Exiting Zephyr Setup." + ); + } + + return; + } + + _logger.info(`${result}`); + + _logger.info("Complete"); + + return ""; +} diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index 4bb728c2..f271a78e 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -29,6 +29,7 @@ import { join } from "path"; import { PythonExtension } from "@vscode/python-extension"; import { unknownErrorToString } from "../utils/errorHelper.mjs"; import { buildZephyrWorkspacePath } from "../utils/download.mjs"; +import { setupZephyr } from "../utils/setupZephyr.mjs"; enum BoardType { pico = "pico", @@ -399,6 +400,9 @@ export class NewZephyrProjectPanel { return; } + // Setup Zephyr before doing anything else + await setupZephyr(); + this._logger.info("Generating new Zephyr Project"); // Create a new directory to put the project in From 31eb6ea4d408347f446784525a91c486042d063d Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 20 Jun 2025 13:35:05 +0100 Subject: [PATCH 45/62] Add kconfig net shell to wifi shell option by default --- src/webview/newZephyrProjectPanel.mts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index f271a78e..9b65b28d 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -116,7 +116,8 @@ const shellI2CKconfig: string = "CONFIG_I2C_SHELL=y"; const shellGPIOKconfig: string = "CONFIG_GPIO_SHELL=y"; const shellSensorKconfig: string = "CONFIG_SENSOR_SHELL=y"; const shellWifiKconfig: string = `CONFIG_WIFI_LOG_LEVEL_ERR=y -CONFIG_NET_L2_WIFI_SHELL=y`; +CONFIG_NET_L2_WIFI_SHELL=y +CONFIG_NET_SHELL=y`; export class NewZephyrProjectPanel { public static currentPanel: NewZephyrProjectPanel | undefined; From 8dfa80624065164edcb22b2febf5ff4b611e249b Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 20 Jun 2025 16:32:21 +0100 Subject: [PATCH 46/62] Remove Python path selection from Zephyr project generation temporarily --- src/webview/newZephyrProjectPanel.mts | 111 +++++++++++++------------- web/zephyr/main.js | 37 ++++----- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index 9b65b28d..3debd364 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -10,8 +10,6 @@ import { workspace, ProgressLocation, commands, - tasks, - ConfigurationTarget, } from "vscode"; import { homedir } from "os"; import { join as joinPosix } from "path/posix"; @@ -679,57 +677,6 @@ export class NewZephyrProjectPanel { Error Please enter a valid project name.

- -
-
- - - ${ - knownEnvironments && knownEnvironments.length > 0 - ? ` -
- - - - -
- ` - : "" - } - - ${ - process.platform === "darwin" || - process.platform === "win32" - ? ` - ${ - isPythonSystemAvailable - ? `
- - -
` - : "" - } - -
- - - -
` - : "" - } -
-
@@ -738,12 +685,12 @@ export class NewZephyrProjectPanel { - + @@ -892,3 +839,55 @@ export class NewZephyrProjectPanel { `; } } + +// Python path option for later +//
+//
+// + +// ${ +// knownEnvironments && knownEnvironments.length > 0 +// ? ` +//
+// +// +// +// +//
+// ` +// : "" +// } + +// ${ +// process.platform === "darwin" || +// process.platform === "win32" +// ? ` +// ${ +// isPythonSystemAvailable +// ? `
+// +// +//
` +// : "" +// } + +//
+// +// +// +//
` +// : "" +// } +//
+//
diff --git a/web/zephyr/main.js b/web/zephyr/main.js index cbfd5ce1..ab145559 100644 --- a/web/zephyr/main.js +++ b/web/zephyr/main.js @@ -116,25 +116,26 @@ var submitted = false; return; } - if (pythonMode === 0) { - const pyenvKnownSel = document.getElementById("sel-pyenv-known"); - pythonPath = pyenvKnownSel.value; - } else if (pythonMode === 2) { - const files = document.getElementById("python-path-executable").files; - - if (files.length == 1) { - pythonPath = files[0].name; - } else { - console.debug("Please select a valid python executable file"); - vscode.postMessage({ - command: CMD_ERROR, - value: "Please select a valid python executable file.", - }); - submitted = false; - return; - } - } + // if (pythonMode === 0) { + // const pyenvKnownSel = document.getElementById("sel-pyenv-known"); + // pythonPath = pyenvKnownSel.value; + // } else if (pythonMode === 2) { + // const files = document.getElementById("python-path-executable").files; + + // if (files.length == 1) { + // pythonPath = files[0].name; + // } else { + // console.debug("Please select a valid python executable file"); + // vscode.postMessage({ + // command: CMD_ERROR, + // value: "Please select a valid python executable file.", + // }); + // submitted = false; + + // return; + // } + // } // Get console mode const consoleRadio = document.getElementsByName("console-radio"); From 399493dedd000b9ba482b2e620024b86f1d88951 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Wed, 30 Jul 2025 21:53:15 +0100 Subject: [PATCH 47/62] Add progress bars and reports to Zephyr setup --- src/utils/setupZephyr.mts | 448 ++++++++++++++++++++++++++++---------- 1 file changed, 331 insertions(+), 117 deletions(-) diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts index 28a613a4..1e506dfa 100644 --- a/src/utils/setupZephyr.mts +++ b/src/utils/setupZephyr.mts @@ -1,9 +1,10 @@ import { existsSync, readdirSync, rmSync } from "fs"; -import { window, workspace, Uri } from "vscode"; +import { window, workspace, ProgressLocation, Uri } from "vscode"; import { type ExecOptions, exec, spawnSync } from "child_process"; import { join as joinPosix } from "path/posix"; import { homedir } from "os"; import Logger from "../logger.mjs"; +import type { Progress as GotProgress } from "got"; import { buildZephyrWorkspacePath, @@ -45,8 +46,6 @@ function _runCommand( } export async function setupZephyr(): Promise { - window.showInformationMessage("Setup Zephyr Command Running"); - const settings = Settings.getInstance(); if (settings === undefined) { _logger.error("Settings not initialized."); @@ -54,134 +53,349 @@ export async function setupZephyr(): Promise { return; } - // TODO: this does take about 2s - may be reduced - const gitPath = await ensureGit(settings, { returnPath: true }); - if (typeof gitPath !== "string" || gitPath.length === 0) { - return; - } - - _logger.info("Installing CMake"); - const cmakeResult = await downloadAndInstallCmake("v3.31.5"); - if (!cmakeResult) { - window.showErrorMessage("Could not install CMake. Exiting Zephyr Setup"); - } - window.showInformationMessage("CMake installed."); - - _logger.info("Installing Ninja"); - const ninjaResult = await downloadAndInstallNinja("v1.12.1"); - if (!ninjaResult) { - window.showErrorMessage("Could not install Ninja. Exiting Zephyr Setup"); - } - window.showInformationMessage("Ninja installed."); + let python3Path = ""; + let isWindows = false; + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Setting up Zephyr Toolchain", + }, + async progress => { + let installedSuccessfully = true; + let prog2LastState = 0; + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Ensuring Git is available", + cancellable: false, + }, + async progress2 => { + // TODO: this does take about 2s - may be reduced + const gitPath = await ensureGit(settings, { returnPath: true }); + if (typeof gitPath !== "string" || gitPath.length === 0) { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + + return; + } + progress2.report({ + message: "Git setup correctly.", + increment: 100, + }); + installedSuccessfully = true; + } + ); - _logger.info("Installing Picotool"); - const picotoolResult = await downloadAndInstallPicotool("2.1.1"); - if (!picotoolResult) { - window.showErrorMessage("Could not install Ninja. Exiting Zephyr Setup"); - } - window.showInformationMessage("Picotool installed."); + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Download and install CMake", + cancellable: false, + }, + async progress2 => { + if ( + await downloadAndInstallCmake("v3.31.5", (prog: GotProgress) => { + const per = prog.percent * 100; + progress2.report({ + increment: per - prog2LastState, + }); + prog2LastState = per; + }) + ) { + progress2.report({ + message: "Successfully downloaded and installed CMake.", + increment: 100, + }); + + installedSuccessfully = true; + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } + ); - const python3Path = await findPython(); - if (!python3Path) { - _logger.error("Failed to find Python3 executable."); - showPythonNotFoundError(); + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Download and install Ninja", + cancellable: false, + }, + async progress2 => { + if ( + await downloadAndInstallNinja("v1.12.1", (prog: GotProgress) => { + const per = prog.percent * 100; + progress2.report({ + increment: per - prog2LastState, + }); + prog2LastState = per; + }) + ) { + progress2.report({ + message: "Successfully downloaded and installed Ninja.", + increment: 100, + }); + + installedSuccessfully = true; + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } + ); - return; - } + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Download and install Picotool", + cancellable: false, + }, + async progress2 => { + if ( + await downloadAndInstallPicotool("2.1.1", (prog: GotProgress) => { + const per = prog.percent * 100; + progress2.report({ + increment: per - prog2LastState, + }); + prog2LastState = per; + }) + ) { + progress2.report({ + message: "Successfully downloaded and installed Picotool.", + increment: 100, + }); + + installedSuccessfully = true; + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } + ); - const isWindows = process.platform === "win32"; - - if (isWindows) { - // Setup other Zephyr dependencies - _logger.info("Installing dtc"); - const dtcResult = await downloadAndInstallArchive( - "https://github.com/oss-winget/oss-winget-storage/raw/" + - "96ea1b934342f45628a488d3b50d0c37cf06012c/packages/dtc/" + - "1.6.1/dtc-msys2-1.6.1-x86_64.zip", - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc"), - "dtc-msys2-1.6.1-x86_64.zip", - "dtc" - ); - if (!dtcResult) { - window.showErrorMessage("Could not install DTC. Exiting Zephyr Setup"); - - return; - } - window.showInformationMessage("DTC installed."); - - _logger.info("Installing gperf"); - const gperfResult = await downloadAndInstallArchive( - "https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/" + - "gperf-3.0.1-bin.zip/download", - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf"), - "gperf-3.0.1-bin.zip", - "gperf" - ); - if (!gperfResult) { - window.showErrorMessage("Could not install gperf. Exiting Zephyr Setup"); - - return; - } - window.showInformationMessage("gperf installed."); - - _logger.info("Installing wget"); - const wgetResult = await downloadAndInstallArchive( - "https:///eternallybored.org/misc/wget/releases/wget-1.21.4-win64.zip", - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), - "wget-1.21.4-win64.zip", - "wget" - ); - if (!wgetResult) { - window.showErrorMessage("Could not install wget. Exiting Zephyr Setup"); - - return; - } - window.showInformationMessage("wget installed."); - - _logger.info("Installing 7zip"); - const szipTargetDirectory = joinPosix("C:\\Program Files\\7-Zip"); - if ( - existsSync(szipTargetDirectory) && - readdirSync(szipTargetDirectory).length !== 0 - ) { - _logger.info("7-Zip is already installed."); - } else { - const szipURL = new URL("https://7-zip.org/a/7z2409-x64.exe"); - const szipResult = await downloadFileGot( - szipURL, - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "7zip-x64.exe") + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Set up Python", + cancellable: false, + }, + async progress2 => { + python3Path = await findPython(); + if (!python3Path) { + _logger.error("Failed to find Python3 executable."); + showPythonNotFoundError(); + + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + + progress2.report({ + message: "Successfully downloaded and installed Picotool.", + increment: 100, + }); + + installedSuccessfully = true; + } ); - if (!szipResult) { - window.showErrorMessage( - "Could not download 7-Zip. Exiting Zephyr Setup" + isWindows = process.platform === "win32"; + + if (isWindows) { + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Download and install DTC", + cancellable: false, + }, + async progress2 => { + if ( + await downloadAndInstallArchive( + "https://github.com/oss-winget/oss-winget-storage/raw/" + + "96ea1b934342f45628a488d3b50d0c37cf06012c/packages/dtc/" + + "1.6.1/dtc-msys2-1.6.1-x86_64.zip", + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc"), + "dtc-msys2-1.6.1-x86_64.zip", + "dtc" + ) + ) { + progress2.report({ + message: "Successfully downloaded and installed DTC.", + increment: 100, + }); + + installedSuccessfully = true; + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } ); - return; - } - - const szipCommand: string = "7zip-x64.exe"; - const szipInstallResult = await _runCommand(szipCommand, { - cwd: joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk"), - }); + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Download and install gperf", + cancellable: false, + }, + async progress2 => { + if ( + await downloadAndInstallArchive( + "https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/" + + "gperf-3.0.1-bin.zip/download", + joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "gperf" + ), + "gperf-3.0.1-bin.zip", + "gperf" + ) + ) { + progress2.report({ + message: "Successfully downloaded and installed DTC.", + increment: 100, + }); + + installedSuccessfully = true; + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } + ); - if (szipInstallResult !== 0) { - window.showErrorMessage( - "Could not install 7-Zip. Please ensure 7-Zip is installed." + - "Exiting Zephyr Setup" + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Download and install wget", + cancellable: false, + }, + async progress2 => { + if ( + await downloadAndInstallArchive( + "https:///eternallybored.org/misc/wget/releases/" + + "wget-1.21.4-win64.zip", + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), + "wget-1.21.4-win64.zip", + "wget" + ) + ) { + progress2.report({ + message: "Successfully downloaded and installed wget.", + increment: 100, + }); + + installedSuccessfully = true; + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } ); - return; + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Download and install 7-zip", + cancellable: false, + }, + async progress2 => { + _logger.info("Installing 7zip"); + const szipTargetDirectory = joinPosix("C:\\Program Files\\7-Zip"); + if ( + existsSync(szipTargetDirectory) && + readdirSync(szipTargetDirectory).length !== 0 + ) { + _logger.info("7-Zip is already installed."); + progress2.report({ + message: "7-zip already installed.", + increment: 100, + }); + } else { + const szipURL = new URL("https://7-zip.org/a/7z2409-x64.exe"); + const szipResult = await downloadFileGot( + szipURL, + joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "7zip-x64.exe" + ) + ); + + if (!szipResult) { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + + return; + } + + const szipCommand: string = "7zip-x64.exe"; + const szipInstallResult = await _runCommand(szipCommand, { + cwd: joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk"), + }); + + if (szipInstallResult !== 0) { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + + return; + } + + // Clean up + rmSync( + joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "7zip-x64.exe" + ) + ); + + progress2.report({ + message: "Successfully downloaded and installed 7-zip.", + increment: 100, + }); + + installedSuccessfully = true; + } + } + ); } - // Clean up - rmSync( - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "7zip-x64.exe") - ); - - window.showInformationMessage("7-Zip installed."); + if (installedSuccessfully) { + window.showInformationMessage("Tool setup complete"); + } } - } + ); _logger.info("Installing OpenOCD"); const openocdResult = await downloadAndInstallOpenOCD(openOCDVersion); From 96c85ab8192b35ed34643f9a750c8ea23f17dfdb Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Fri, 1 Aug 2025 15:45:50 +0100 Subject: [PATCH 48/62] Move the rest of Zephyr setup into async format with progress bars --- src/utils/setupZephyr.mts | 618 ++++++++++++++++++++++++-------------- 1 file changed, 386 insertions(+), 232 deletions(-) diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts index 1e506dfa..453e2f54 100644 --- a/src/utils/setupZephyr.mts +++ b/src/utils/setupZephyr.mts @@ -22,6 +22,28 @@ import { ensureGit } from "../utils/gitUtil.mjs"; const _logger = new Logger("zephyrSetup"); +const zephyrManifestContent: string = ` +manifest: + self: + west-commands: scripts/west-commands.yml + + remotes: + - name: zephyrproject-rtos + url-base: https://github.com/magpieembedded + + projects: + - name: zephyr + remote: zephyrproject-rtos + revision: pico2w + import: + # By using name-allowlist we can clone only the modules that are + # strictly needed by the application. + name-allowlist: + - cmsis_6 # required by the ARM Cortex-M port + - hal_rpi_pico # required for Pico board support + - hal_infineon # required for Wifi chip support +`; + function _runCommand( command: string, options: ExecOptions @@ -391,269 +413,401 @@ export async function setupZephyr(): Promise { ); } - if (installedSuccessfully) { - window.showInformationMessage("Tool setup complete"); - } - } - ); - - _logger.info("Installing OpenOCD"); - const openocdResult = await downloadAndInstallOpenOCD(openOCDVersion); - if (!openocdResult) { - window.showErrorMessage("Could not install OpenOCD. Exiting Zephyr Setup"); - - return; - } + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Download and install OpenOCD", + cancellable: false, + }, + async progress2 => { + if ( + await downloadAndInstallOpenOCD( + openOCDVersion, + (prog: GotProgress) => { + const per = prog.percent * 100; + progress2.report({ + increment: per - prog2LastState, + }); + prog2LastState = per; + } + ) + ) { + progress2.report({ + message: "Successfully downloaded and installed Picotool.", + increment: 100, + }); - const pythonExe = python3Path.replace( - HOME_VAR, - homedir().replaceAll("\\", "/") - ); + installedSuccessfully = true; + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } + ); - const customEnv = process.env; - // const customPath = await getPath(); - // _logger.info(`Path: ${customPath}`); - // if (!customPath) { - // return; - // } - - const customPath = [ - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "cmake", - "v3.31.5", - "bin" - ), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc", "bin"), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "git", "cmd"), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf", "bin"), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "ninja", "v1.12.1"), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "python", "3.12.6"), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), - joinPosix("C:\\Program Files".replaceAll("\\", "/"), "7-Zip"), - "", // Need this to add separator to end - ].join(process.platform === "win32" ? ";" : ":"); - - _logger.info(`New path: ${customPath}`); - - customPath.replaceAll("/", "\\"); - customEnv[isWindows ? "Path" : "PATH"] = - customPath + customEnv[isWindows ? "Path" : "PATH"]; - - const zephyrWorkspaceDirectory = buildZephyrWorkspacePath(); - - const zephyrManifestDir: string = joinPosix( - zephyrWorkspaceDirectory, - "manifest" - ); + const pythonExe = python3Path.replace( + HOME_VAR, + homedir().replaceAll("\\", "/") + ); - const zephyrManifestFile: string = joinPosix(zephyrManifestDir, "west.yml"); + const customEnv = process.env; + + const customPath = [ + joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "cmake", + "v3.31.5", + "bin" + ), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc", "bin"), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "git", "cmd"), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf", "bin"), + joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "ninja", + "v1.12.1" + ), + joinPosix( + homedir().replaceAll("\\", "/"), + ".pico-sdk", + "python", + "3.12.6" + ), + joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), + joinPosix("C:\\Program Files".replaceAll("\\", "/"), "7-Zip"), + "", // Need this to add separator to end + ].join(process.platform === "win32" ? ";" : ":"); + + _logger.info(`New path: ${customPath}`); + + customPath.replaceAll("/", "\\"); + customEnv[isWindows ? "Path" : "PATH"] = + customPath + customEnv[isWindows ? "Path" : "PATH"]; + + const zephyrWorkspaceDirectory = buildZephyrWorkspacePath(); + + const zephyrManifestDir: string = joinPosix( + zephyrWorkspaceDirectory, + "manifest" + ); - const zephyrManifestContent: string = ` -manifest: - self: - west-commands: scripts/west-commands.yml + const zephyrManifestFile: string = joinPosix( + zephyrManifestDir, + "west.yml" + ); - remotes: - - name: zephyrproject-rtos - url-base: https://github.com/magpieembedded + await workspace.fs.writeFile( + Uri.file(zephyrManifestFile), + Buffer.from(zephyrManifestContent) + ); - projects: - - name: zephyr - remote: zephyrproject-rtos - revision: pico2w - import: - # By using name-allowlist we can clone only the modules that are - # strictly needed by the application. - name-allowlist: - - cmsis_6 # required by the ARM Cortex-M port - - hal_rpi_pico # required for Pico board support - - hal_infineon # required for Wifi chip support -`; + const createVenvCommandVenv: string = [ + `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, + "-m venv venv", + ].join(" "); + const createVenvCommandVirtualenv: string = [ + `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, + "-m virtualenv venv", + ].join(" "); - await workspace.fs.writeFile( - Uri.file(zephyrManifestFile), - Buffer.from(zephyrManifestContent) - ); + // Create a Zephyr workspace, copy the west manifest in and initialise the workspace + workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); - const createVenvCommandVenv: string = [ - `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, - "-m venv venv", - ].join(" "); - const createVenvCommandVirtualenv: string = [ - `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, - "-m virtualenv venv", - ].join(" "); - - // Create a Zephyr workspace, copy the west manifest in and initialise the workspace - workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); - - // Generic result to get value from runCommand calls - let result: number | null; - - _logger.info("Setting up virtual environment for Zephyr"); - const venvPython: string = joinPosix( - zephyrWorkspaceDirectory, - "venv", - process.platform === "win32" ? "Scripts" : "bin", - process.platform === "win32" ? "python.exe" : "python" - ); - if (existsSync(venvPython)) { - _logger.info("Existing Python venv found."); - } else { - result = await _runCommand(createVenvCommandVenv, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - if (result !== 0) { - _logger.warn( - "Could not create virtual environment with venv," + - "trying with virtualenv..." - ); + // Generic result to get value from runCommand calls + let result: number | null; - result = await _runCommand(createVenvCommandVirtualenv, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Setup Python virtual environment for Zephyr", + cancellable: false, + }, + async progress2 => { + const venvPython: string = joinPosix( + zephyrWorkspaceDirectory, + "venv", + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "python.exe" : "python" + ); + if (existsSync(venvPython)) { + _logger.info("Existing Python venv found."); + } else { + result = await _runCommand(createVenvCommandVenv, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + if (result !== 0) { + _logger.warn( + "Could not create virtual environment with venv," + + "trying with virtualenv..." + ); - if (result !== 0) { - _logger.error("Could not create virtual environment. Exiting."); - window.showErrorMessage("Could not install venv. Exiting Zephyr Setup"); + result = await _runCommand(createVenvCommandVirtualenv, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); - return; - } - } + if (result === 0) { + progress2.report({ + message: + "Successfully setup Python virtual environment for Zephyr.", + increment: 100, + }); + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } - _logger.info("Venv created."); - } + _logger.info("Venv created."); + } + } + ); - const venvPythonCommand: string = joinPosix( - process.env.ComSpec === "powershell.exe" ? "&" : "", - zephyrWorkspaceDirectory, - "venv", - process.platform === "win32" ? "Scripts" : "bin", - process.platform === "win32" ? "python.exe" : "python" - ); + const venvPythonCommand: string = joinPosix( + process.env.ComSpec === "powershell.exe" ? "&" : "", + zephyrWorkspaceDirectory, + "venv", + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "python.exe" : "python" + ); - const installWestCommand: string = [ - venvPythonCommand, - "-m pip install west pyelftools", - ].join(" "); + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Install Zephyr Python dependencies", + cancellable: false, + }, + async progress2 => { + const installWestCommand: string = [ + venvPythonCommand, + "-m pip install west pyelftools", + ].join(" "); - _logger.info("Installing Python dependencies for Zephyr"); - result = await _runCommand(installWestCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); + if ( + (await _runCommand(installWestCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + })) === 0 + ) { + progress2.report({ + message: "Successfully installed Python dependencies for Zephyr.", + increment: 100, + }); + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } + ); - const westExe: string = joinPosix( - process.env.ComSpec === "powershell.exe" ? "&" : "", - zephyrWorkspaceDirectory, - "venv", - process.platform === "win32" ? "Scripts" : "bin", - process.platform === "win32" ? "west.exe" : "west" - ); + const westExe: string = joinPosix( + process.env.ComSpec === "powershell.exe" ? "&" : "", + zephyrWorkspaceDirectory, + "venv", + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "west.exe" : "west" + ); - const zephyrWorkspaceFiles = await workspace.fs.readDirectory( - Uri.file(zephyrWorkspaceDirectory) - ); + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Setting up West workspace", + cancellable: false, + }, + async progress2 => { + const zephyrWorkspaceFiles = await workspace.fs.readDirectory( + Uri.file(zephyrWorkspaceDirectory) + ); + + const westAlreadyExists = zephyrWorkspaceFiles.find( + x => x[0] === ".west" + ); + if (westAlreadyExists) { + _logger.info("West workspace already initialised."); + } else { + _logger.info("No West workspace found. Initialising..."); + + const westInitCommand: string = [westExe, "init -l manifest"].join( + " " + ); + result = await _runCommand(westInitCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); - const westAlreadyExists = zephyrWorkspaceFiles.find(x => x[0] === ".west"); - if (westAlreadyExists) { - _logger.info("West workspace already initialised."); - } else { - _logger.info("No West workspace found. Initialising..."); - - const westInitCommand: string = [westExe, "init -l manifest"].join(" "); - result = await _runCommand(westInitCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); + _logger.info(`${result}`); + } - _logger.info(`${result}`); - } + const westUpdateCommand: string = [westExe, "update"].join(" "); - const westUpdateCommand: string = [westExe, "update"].join(" "); + if ( + (await _runCommand(westUpdateCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + })) === 0 + ) { + progress2.report({ + message: "Successfully setup West workspace.", + increment: 100, + }); + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } + ); - _logger.info("Updating West workspace"); - result = await _runCommand(westUpdateCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); + const zephyrExportCommand: string = [westExe, "zephyr-export"].join(" "); + _logger.info("Exporting Zephyr CMake Files"); + result = await _runCommand(zephyrExportCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); - _logger.info(`${result}`); + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Install West Python dependencies", + cancellable: false, + }, + async progress2 => { + const westPipPackagesCommand: string = [ + westExe, + "packages pip --install", + ].join(" "); - const zephyrExportCommand: string = [westExe, "zephyr-export"].join(" "); - _logger.info("Exporting Zephyr CMake Files"); - result = await _runCommand(zephyrExportCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); + if ( + (await _runCommand(westPipPackagesCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + })) === 0 + ) { + progress2.report({ + message: "Successfully installed Python dependencies for West.", + increment: 100, + }); + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } + ); - const westPipPackagesCommand: string = [ - westExe, - "packages pip --install", - ].join(" "); + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Fetching Zephyr binary blobs", + cancellable: false, + }, + async progress2 => { + const westBlobsFetchCommand: string = [ + westExe, + "blobs fetch hal_infineon", + ].join(" "); - _logger.info("Installing West Python packages"); - result = await _runCommand(westPipPackagesCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); + if ( + (await _runCommand(westBlobsFetchCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + })) === 0 + ) { + progress2.report({ + message: "Successfully retrieved Zephyr binary blobs.", + increment: 100, + }); + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } + ); - _logger.info(`${result}`); + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Installing Zephyr SDK", + cancellable: false, + }, + async progress2 => { + const westInstallSDKCommand: string = [ + westExe, + "sdk install -t arm-zephyr-eabi", + `-b ${zephyrWorkspaceDirectory}`, + ].join(" "); + + // This has to be a spawn due to the way the underlying SDK command calls + // subprocess and needs to inherit the Path variables set in customEnv + _logger.info("Installing Zephyr SDK"); + const child = spawnSync(westInstallSDKCommand, { + shell: true, + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + _logger.info("stdout: ", child.stdout.toString()); + _logger.info("stderr: ", child.stderr.toString()); + if (child.status) { + _logger.info("exit code: ", child.status); + if (child.status !== 0) { + window.showErrorMessage( + "Error installing Zephyr SDK." + "Exiting Zephyr Setup." + ); + } - const westBlobsFetchCommand: string = [ - westExe, - "blobs fetch hal_infineon", - ].join(" "); + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); - _logger.info("Fetching binary blobs for Zephyr"); - result = await _runCommand(westBlobsFetchCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); + return; + } - _logger.info(`${result}`); - - const westInstallSDKCommand: string = [ - westExe, - "sdk install -t arm-zephyr-eabi", - `-b ${zephyrWorkspaceDirectory}`, - ].join(" "); - - // This has to be a spawn due to the way the underlying SDK command calls - // subprocess and needs to inherit the Path variables set in customEnv - _logger.info("Installing Zephyr SDK"); - const child = spawnSync(westInstallSDKCommand, { - shell: true, - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - _logger.info("stdout: ", child.stdout.toString()); - _logger.info("stderr: ", child.stderr.toString()); - if (child.status) { - _logger.info("exit code: ", child.status); - if (child.status !== 0) { - window.showErrorMessage( - "Error installing Zephyr SDK." + "Exiting Zephyr Setup." + progress2.report({ + message: "Successfully installed Zephyr SDK.", + increment: 100, + }); + } ); - } - return; - } - - _logger.info(`${result}`); + if (installedSuccessfully) { + window.showInformationMessage("Zephyr setup complete"); + progress.report({ + message: "Zephyr setup complete.", + increment: 100, + }); + } + } + ); _logger.info("Complete"); From 42d46b6b95e58e6d3145f22bcd9938d41067c147 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Wed, 20 Aug 2025 19:21:11 +0100 Subject: [PATCH 49/62] Handle different CMake setups --- src/utils/setupZephyr.mts | 131 ++++++++++++++++----- src/webview/newZephyrProjectPanel.mts | 159 +++++++++++++++++++++++++- web/zephyr/main.js | 55 +++++++++ 3 files changed, 316 insertions(+), 29 deletions(-) diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts index 453e2f54..681667fa 100644 --- a/src/utils/setupZephyr.mts +++ b/src/utils/setupZephyr.mts @@ -7,6 +7,7 @@ import Logger from "../logger.mjs"; import type { Progress as GotProgress } from "got"; import { + buildCMakePath, buildZephyrWorkspacePath, downloadAndInstallArchive, downloadAndInstallCmake, @@ -19,6 +20,7 @@ import Settings, { HOME_VAR } from "../settings.mjs"; import { openOCDVersion } from "../webview/newProjectPanel.mjs"; import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; import { ensureGit } from "../utils/gitUtil.mjs"; +import { type VersionBundle } from "../utils/versionBundles.mjs"; const _logger = new Logger("zephyrSetup"); @@ -44,6 +46,17 @@ manifest: - hal_infineon # required for Wifi chip support `; +interface ZephyrSetupValue { + versionBundle: VersionBundle | undefined; + cmakeMode: number; + cmakePath: string; + cmakeVersion: string; +} + +interface ZephyrSetupOutputs { + cmakeExecutable: string; +} + function _runCommand( command: string, options: ExecOptions @@ -67,7 +80,9 @@ function _runCommand( }); } -export async function setupZephyr(): Promise { +export async function setupZephyr( + data: ZephyrSetupValue +): Promise { const settings = Settings.getInstance(); if (settings === undefined) { _logger.error("Settings not initialized."); @@ -75,6 +90,8 @@ export async function setupZephyr(): Promise { return; } + let output: ZephyrSetupOutputs = { cmakeExecutable: "" }; + let python3Path = ""; let isWindows = false; await window.withProgress( @@ -111,38 +128,96 @@ export async function setupZephyr(): Promise { } ); - await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Download and install CMake", - cancellable: false, - }, - async progress2 => { - if ( - await downloadAndInstallCmake("v3.31.5", (prog: GotProgress) => { - const per = prog.percent * 100; - progress2.report({ - increment: per - prog2LastState, - }); - prog2LastState = per; - }) - ) { - progress2.report({ - message: "Successfully downloaded and installed CMake.", - increment: 100, - }); + // Handle CMake install + switch (data.cmakeMode) { + case 0: + if (data.versionBundle !== undefined) { + data.cmakeVersion = data.versionBundle.cmake; + } + // eslint-disable-next-line no-fallthrough + case 2: + installedSuccessfully = false; + prog2LastState = 0; + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Download and install CMake", + cancellable: false, + }, + async progress2 => { + if ( + await downloadAndInstallCmake( + data.cmakeVersion, + (prog: GotProgress) => { + const per = prog.percent * 100; + progress2.report({ + increment: per - prog2LastState, + }); + prog2LastState = per; + } + ) + ) { + progress.report({ + // TODO: maybe just finished or something like that + message: "Successfully downloaded and installed CMake.", + increment: 100, + }); - installedSuccessfully = true; - } else { - installedSuccessfully = false; - progress2.report({ + installedSuccessfully = true; + } else { + installedSuccessfully = false; + progress2.report({ + message: "Failed", + increment: 100, + }); + } + } + ); + + if (!installedSuccessfully) { + progress.report({ message: "Failed", increment: 100, }); + void window.showErrorMessage( + // TODO: maybe remove all requirements met part + "Failed to download and install CMake.\ + Make sure all requirements are met." + ); + + return; + } else { + const cmakeVersionBasePath = buildCMakePath(data.cmakeVersion); + + output.cmakeExecutable = joinPosix( + cmakeVersionBasePath, + "bin", + "cmake" + ); } - } - ); + break; + case 1: + // Don't need to add anything to path if already available via system + output.cmakeExecutable = ""; + break; + case 3: + // normalize path returned by the os selector to posix path for the settings json + // and cross platform compatibility + output.cmakeExecutable = + process.platform === "win32" + ? // TODO: maybe use path.sep for split + joinPosix(...data.cmakePath.split("\\")) + : data.cmakePath; + break; + default: + progress.report({ + message: "Failed", + increment: 100, + }); + void window.showErrorMessage("Unknown cmake selection."); + return; + } await window.withProgress( { location: ProgressLocation.Notification, @@ -811,5 +886,5 @@ export async function setupZephyr(): Promise { _logger.info("Complete"); - return ""; + return output; } diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index 3debd364..d9738778 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -28,6 +28,10 @@ import { PythonExtension } from "@vscode/python-extension"; import { unknownErrorToString } from "../utils/errorHelper.mjs"; import { buildZephyrWorkspacePath } from "../utils/download.mjs"; import { setupZephyr } from "../utils/setupZephyr.mjs"; +import VersionBundlesLoader, { + type VersionBundle, +} from "../utils/versionBundles.mjs"; +import { getCmakeReleases } from "../utils/githubREST.mjs"; enum BoardType { pico = "pico", @@ -48,6 +52,9 @@ interface SubmitMessageValue { wifiFeature: boolean; sensorFeature: boolean; shellFeature: boolean; + cmakeMode: number; + cmakePath: string; + cmakeVersion: string; } // Kconfig snippets @@ -130,6 +137,57 @@ export class NewZephyrProjectPanel { private _projectRoot?: Uri; private _pythonExtensionApi?: PythonExtension; + private _versionBundlesLoader?: VersionBundlesLoader; + private _versionBundle: VersionBundle | undefined; + + // Create settings.json file with correct subsitution for tools such as + // CMake, Ninja, Python, etc + private static createSettingsJson(): string { + const settingsJson = { + /* eslint-disable @typescript-eslint/naming-convention */ + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + build: { + statusBarVisibility: "hidden", + }, + launch: { + statusBarVisibility: "hidden", + }, + debug: { + statusBarVisibility: "hidden", + }, + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "${userHome}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + PICO_SDK_PATH: "${env:USERPROFILE}/.pico-sdk/sdk/2.1.1", + PICO_TOOLCHAIN_PATH: "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1", + Path: "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.1.1/picotool;${env:USERPROFILE}/.pico-sdk/cmake/v3.31.5/bin;${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}", + }, + "terminal.integrated.env.osx": { + PICO_SDK_PATH: "${env:HOME}/.pico-sdk/sdk/2.1.1", + PICO_TOOLCHAIN_PATH: "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + PATH: "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.1.1/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}", + }, + "terminal.integrated.env.linux": { + PICO_SDK_PATH: "${env:HOME}/.pico-sdk/sdk/2.1.1", + PICO_TOOLCHAIN_PATH: "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1", + PATH: "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.1.1/picotool:${env:HOME}/.pico-sdk/cmake/v3.31.5/bin:${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}", + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": + "${HOME}/.pico-sdk/cmake/v3.31.5/bin/cmake", + "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja", + }; + + /* eslint-enable @typescript-eslint/naming-convention */ + return JSON.stringify(settingsJson, null, 2); + } public static createOrShow(extensionUri: Uri, projectUri?: Uri): void { const column = window.activeTextEditor @@ -260,6 +318,23 @@ export class NewZephyrProjectPanel { } } break; + case "versionBundleAvailableTest": + { + // test if versionBundle for sdk version is available + const versionBundle = + await this._versionBundlesLoader?.getModuleVersion( + message.value as string + ); + // return result in message of command versionBundleAvailableTest + await this._panel.webview.postMessage({ + command: "versionBundleAvailableTest", + value: { + result: versionBundle !== undefined, + picotoolVersion: versionBundle?.picotool, + }, + }); + } + break; case "cancel": this.dispose(); break; @@ -399,9 +474,32 @@ export class NewZephyrProjectPanel { return; } + if ( + this._versionBundle === undefined && + // if no versionBundle then all version options the could be dependent on it must be custom (=> independent of versionBundle) + data.cmakeMode === 0 + // (data.ninjaMode === 0 || data.cmakeMode === 0) + ) { + progress.report({ + message: "Failed", + increment: 100, + }); + void window.showErrorMessage("Failed to find selected SDK version."); + + return; + } + // Setup Zephyr before doing anything else - await setupZephyr(); + const zephyrSetupOutputs = await setupZephyr({ + versionBundle: this._versionBundle, + cmakeMode: data.cmakeMode, + cmakePath: data.cmakePath, + cmakeVersion: data.cmakeVersion, + }); + if (zephyrSetupOutputs !== null) { + this._logger.info(zephyrSetupOutputs); + } this._logger.info("Generating new Zephyr Project"); // Create a new directory to put the project in @@ -488,6 +586,19 @@ export class NewZephyrProjectPanel { Buffer.from(newKconfigString) ); + const settingJsonFile = joinPosix( + newProjectDir, + ".vscode", + "settings.json" + ); + + // Create settings JSON and write to new folder + const settingsJson = NewZephyrProjectPanel.createSettingsJson(); + await workspace.fs.writeFile( + Uri.file(settingJsonFile), + Buffer.from(settingsJson) + ); + this._logger.info(`Zephyr Project generated at ${newProjectDir}`); // Open the folder @@ -593,11 +704,22 @@ export class NewZephyrProjectPanel { const knownEnvironments = environments?.known; const activeEnv = environments?.getActiveEnvironmentPath(); + let cmakesHtml = ""; + const cmakeReleases = await getCmakeReleases(); + cmakeReleases.forEach(cmake => { + cmakesHtml += ``; + }); + // TODO: check python version, workaround, only allow python3 commands on unix const isPythonSystemAvailable = (await which("python3", { nothrow: true })) !== null || (await which("python", { nothrow: true })) !== null; + const isCmakeSystemAvailable = + (await which("cmake", { nothrow: true })) !== null; + // Restrict the webview to only load specific scripts const nonce = getNonce(); @@ -826,6 +948,41 @@ export class NewZephyrProjectPanel {
+
+ + ${ + this._versionBundle !== undefined + ? `
+ + +
` + : "" + } + + ${ + isCmakeSystemAvailable + ? `
+ + +
` + : "" + } + +
+ + + +
+ +
+ + + +
+
+
-
- - + + + - - - - -
- -
-

Modules

-

Kconfig options to enable the below modules

-
    -
  • -
    - - -
    -
  • -
  • -
    - - -
    -
  • -
  • -
    - - -
    -
  • -
-
    -
  • -
    - - -
    -
  • -
  • -
    - - -
    -
  • -
  • -
    - - -
    -
  • -
+ + +
+
-
-
+
@@ -957,57 +924,106 @@ export class NewZephyrProjectPanel {
-
-
-
-

Serial Port Over:

-
-
- - -
-
- - -
-
-
-
+
+

Modules

+

Kconfig options to enable the below modules

+
    +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
+
    +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
+
-
- - ${ - this._versionBundle !== undefined - ? `
- - -
` - : "" - } +
+
+

Stdio support:

+
+
+ + +
+
+ + +
+
+
- ${ - isCmakeSystemAvailable - ? `
- - -
` - : "" - } +
+ + ${ + this._versionBundle !== undefined + ? `
+ + +
` + : "" + } + + ${ + isCmakeSystemAvailable + ? `
+ + +
` + : "" + } + +
+ + +
-
- - - -
+
+ + + +
-
- - - -
+
+ + + +
+
From 7d743a0ddaa3d3da2c68035b863d98c49bdd8041 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Mon, 15 Sep 2025 09:06:43 +0100 Subject: [PATCH 52/62] Fix zephyr project creation UI Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/utils/cmakeUtil.mts | 51 +++++++- src/webview/newZephyrProjectPanel.mts | 122 +++++++++++------- web/zephyr/main.js | 176 +++++++++++++++++++------- 3 files changed, 257 insertions(+), 92 deletions(-) diff --git a/src/utils/cmakeUtil.mts b/src/utils/cmakeUtil.mts index 1cdf482b..c68199ca 100644 --- a/src/utils/cmakeUtil.mts +++ b/src/utils/cmakeUtil.mts @@ -1,4 +1,4 @@ -import { exec } from "child_process"; +import { exec, execFile } from "child_process"; import { workspace, type Uri, window, ProgressLocation } from "vscode"; import { showRequirementsNotMetErrorMessage } from "./requirementsUtil.mjs"; import { dirname, join, resolve } from "path"; @@ -541,3 +541,52 @@ export function cmakeGetPicoVar( return match[1]; } + +/** + * Get the version string of a CMake executable. + * Works for both stable releases (e.g. "3.31.5") + * and prereleases like "3.31.0-rc4". + * + * @param cmakePath Path to the cmake executable (absolute or in PATH). + * @returns Promise that resolves to the version string (e.g. "3.31.5" or "3.31.0-rc4"), + * or undefined if not found/parse failed. + */ +export async function getCmakeVersion( + cmakePath: string +): Promise { + return new Promise(resolve => { + execFile(cmakePath, ["--version"], { windowsHide: true }, (err, stdout) => { + if (err) { + console.error(`Failed to run cmake at ${cmakePath}: ${err.message}`); + resolve(undefined); + + return; + } + + const firstLine = stdout.split(/\r?\n/)[0].trim(); + // Expected: "cmake version 3.31.5" or "cmake version 3.31.0-rc4" + const prefix = "cmake version "; + if (firstLine.toLowerCase().startsWith(prefix)) { + const version = firstLine.substring(prefix.length).trim(); + resolve(version); + } else { + console.error(`Unexpected cmake --version output: ${firstLine}`); + resolve(undefined); + } + }); + }); +} + +/** + * Get the version string of the system-installed CMake (in PATH). + * @returns Promise that resolves to a version string (e.g. "3.31.5") + * or undefined if cmake not available. + */ +export async function getSystemCmakeVersion(): Promise { + const cmakePath = await which("cmake", { nothrow: true }); + if (!cmakePath) { + return undefined; + } + + return getCmakeVersion(cmakePath); +} diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index 5d7ea94e..b2f18476 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -21,7 +21,6 @@ import { getProjectFolderDialogOptions, getWebviewOptions, } from "./newProjectPanel.mjs"; -import which from "which"; import { existsSync } from "fs"; import { join, dirname } from "path"; import { PythonExtension } from "@vscode/python-extension"; @@ -31,6 +30,7 @@ import { setupZephyr } from "../utils/setupZephyr.mjs"; import type { VersionBundle } from "../utils/versionBundles.mjs"; import type VersionBundlesLoader from "../utils/versionBundles.mjs"; import { getCmakeReleases } from "../utils/githubREST.mjs"; +import { getSystemCmakeVersion } from "../utils/cmakeUtil.mjs"; enum BoardType { pico = "pico", @@ -144,6 +144,7 @@ export class NewZephyrProjectPanel { private _pythonExtensionApi?: PythonExtension; private _versionBundlesLoader?: VersionBundlesLoader; private _versionBundle: VersionBundle | undefined; + private _systemCmakeVersion: string | undefined; // Create settings.json file with correct subsitution for tools such as // CMake, Ninja, Python, etc @@ -500,17 +501,12 @@ export class NewZephyrProjectPanel { return; } - if ( - this._versionBundle === undefined && - // if no versionBundle then all version options the could be dependent on it must be custom (=> independent of versionBundle) - data.cmakeMode === 0 - // (data.ninjaMode === 0 || data.cmakeMode === 0) - ) { + if (this._versionBundle === undefined && data.cmakeMode === 0) { progress.report({ message: "Failed", increment: 100, }); - void window.showErrorMessage("Failed to find selected SDK version."); + void window.showErrorMessage("Unknown cmake version selected."); return; } @@ -751,8 +747,7 @@ export class NewZephyrProjectPanel { // (await which("python3", { nothrow: true })) !== null || // (await which("python", { nothrow: true })) !== null; - const isCmakeSystemAvailable = - (await which("cmake", { nothrow: true })) !== null; + this._systemCmakeVersion = await getSystemCmakeVersion(); // Restrict the webview to only load specific scripts const nonce = getNonce(); @@ -970,8 +965,8 @@ export class NewZephyrProjectPanel {
-
-
+
+

Stdio support:

@@ -985,45 +980,84 @@ export class NewZephyrProjectPanel {
-
- - ${ - this._versionBundle !== undefined - ? `
- - -
` - : "" - } - - ${ - isCmakeSystemAvailable - ? `
- - -
` - : "" - } - -
- - + +
+

+ CMake Version: +

+ +
+ + + + + + + + + + -
- - - ${cmakesHtml}
-
- - - + + -
+
+
diff --git a/web/zephyr/main.js b/web/zephyr/main.js index ac88e07f..5be3aa8a 100644 --- a/web/zephyr/main.js +++ b/web/zephyr/main.js @@ -12,6 +12,69 @@ var submitted = false; (function () { const vscode = acquireVsCodeApi(); + // CMake version selection handling + { + const modeEl = document.getElementById('cmake-mode'); + const systemRow = document.getElementById('cmake-secondary-system'); + const latestRow = document.getElementById('cmake-secondary-latest'); + const selectRow = document.getElementById('cmake-secondary-select'); + const customRow = document.getElementById('cmake-secondary-custom'); + + const fileInput = document.getElementById('cmake-path-executable'); + const fileLabel = document.getElementById('cmake-file-label'); + const fileBox = document.getElementById('cmake-filebox'); + + // Optional: show the exact latest version, if you have it + const latestValEl = document.getElementById('cmake-latest-val'); + if (latestValEl && typeof window.latestCmakeVersion === 'string') { + latestValEl.textContent = `: ${window.latestCmakeVersion}`; + } + + // Update label text when a file is chosen + fileInput?.addEventListener('change', () => { + const f = fileInput.files && fileInput.files[0]; + fileLabel.textContent = f ? f.name : 'No file selected'; + }); + + // Make label keyboard-activatable (Enter/Space) + fileBox?.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + fileInput?.click(); + } + }); + + // In your toggleSection(), also reflect disabled state on the label + function toggleSection(el, show) { + if (!el) return; + el.classList.toggle('hidden', !show); + el.querySelectorAll('input, select, button, textarea').forEach(ctrl => { + ctrl.disabled = !show; + ctrl.tabIndex = show ? 0 : -1; + }); + // If this is the custom row, also toggle the label interactivity + const label = el.querySelector('#cmake-filebox'); + if (label) { + label.setAttribute('aria-disabled', String(!show)); + label.classList.toggle('pointer-events-none', !show); + label.classList.toggle('opacity-60', !show); + } + } + + function setMode(mode) { + toggleSection(systemRow, mode === 'system'); + toggleSection(latestRow, mode === 'latest' || mode === 'default'); + toggleSection(selectRow, mode === 'select'); + toggleSection(customRow, mode === 'custom'); + } + + // TODO: add state saving/loading via state.js + // modeEl.value = window.savedCmakeMode ?? modeEl.value; + + modeEl.addEventListener('change', e => setMode(e.target.value)); + setMode(modeEl.value); + } + // needed so a element isn't hidden behind the navbar on scroll const navbarOffsetHeight = document.getElementById("top-navbar").offsetHeight; @@ -63,13 +126,6 @@ var submitted = false; }; window.submitBtnClick = () => { - /* Catch silly users who spam the submit button */ - if (submitted) { - console.error("already submitted"); - return; - } - submitted = true; - // get all values of inputs const projectNameElement = document.getElementById("inp-project-name"); // if is project import then the project name element will not be rendered and does not exist in the DOM @@ -173,57 +229,83 @@ var submitted = false; "shell-features-cblist" ).checked; - const cmakeVersionRadio = document.getElementsByName("cmake-version-radio"); - let cmakeMode = null; - let cmakePath = null; - let cmakeVersion = null; - for (let i = 0; i < cmakeVersionRadio.length; i++) { - if (cmakeVersionRadio[i].checked) { - cmakeMode = Number(cmakeVersionRadio[i].value); - break; - } + // --- CMake: collect values from the new controls --- + let cmakeMode = null; // numeric contract: 0..4 + let cmakePath = null; // string | null + let cmakeVersion = null; // string | null + + const cmakeModeSel = document.getElementById('cmake-mode'); + const selCmake = document.getElementById('sel-cmake'); // shown in "select" mode + const cmakeFileInp = document.getElementById('cmake-path-executable'); // shown in "custom" mode + + // Fallback to "latest" if the select isn't there for some reason + const cmakeModeStr = (cmakeModeSel?.value || 'latest'); + + // Map string modes -> numeric API + // 0 = default bundle, 1 = system, 2 = select version, 3 = custom path, 4 = latest + switch (cmakeModeStr) { + // should never happen, but just in case let the backend handle it + case 'default': cmakeMode = 0; break; + case 'system': cmakeMode = 1; break; + case 'select': cmakeMode = 2; break; + case 'custom': cmakeMode = 3; break; + case 'latest': cmakeMode = 4; break; + default: + console.debug('Invalid cmake mode string: ' + cmakeModeStr); + vscode.postMessage({ + command: CMD_ERROR, + value: `Please select a valid CMake mode (got: ${cmakeModeStr}).` + }); + submitted = false; + return; } - if (cmakeVersionRadio.length === 0) { - // default to cmake mode 1 == System version - cmakeMode = 1; + + // Validate + collect per-mode extras + if (cmakeMode === 2) { + // specific version chosen from dropdown + if (!selCmake || !selCmake.value) { + vscode.postMessage({ + command: CMD_ERROR, + value: 'Please select a CMake version.' + }); + submitted = false; + return; + } + cmakeVersion = selCmake.value; + } else if (cmakeMode === 3) { + // custom executable file + const files = cmakeFileInp?.files || []; + if (files.length !== 1) { + console.debug('Please select a valid CMake executable file'); + vscode.postMessage({ + command: CMD_ERROR, + value: 'Please select a valid CMake executable file.' + }); + submitted = false; + return; + } + + cmakePath = files[0].name; } - // if cmake version is null or not a number, smaller than 0 or bigger than 3, set it to 0 - if ( - cmakeMode === null || - isNaN(cmakeMode) || - cmakeMode < 0 || - cmakeMode > 4 - ) { - // TODO: first check if default is supported - cmakeMode = 0; - console.debug("Invalid cmake version value: " + cmakeMode.toString()); + // Final sanity check: numeric range 0..4 + if (cmakeMode === null || isNaN(cmakeMode) || cmakeMode < 0 || cmakeMode > 4) { + console.debug('Invalid cmake version value: ' + cmakeMode); vscode.postMessage({ command: CMD_ERROR, - value: "Please select a valid cmake version.", + value: 'Please select a valid CMake version.' }); submitted = false; - return; } - if (cmakeMode === 2) { - cmakeVersion = document.getElementById("sel-cmake").value; - } else if (cmakeMode == 3) { - const files = document.getElementById("cmake-path-executable").files; - - if (files.length === 1) { - cmakePath = files[0].name; - } else { - console.debug("Please select a valid cmake executable file"); - vscode.postMessage({ - command: CMD_ERROR, - value: "Please select a valid cmake executable file.", - }); - submitted = false; + // --- end CMake block --- - return; - } + /* Catch silly users who spam the submit button */ + if (submitted) { + console.error("already submitted"); + return; } + submitted = true; //post all data values to the extension vscode.postMessage({ From 9effa2fd8b66e1726bf8e7e1c66540043589de32 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:34:34 +0100 Subject: [PATCH 53/62] Fix zephyr integration (part 2) Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/commands/newProject.mts | 1 + src/utils/download.mts | 2 +- src/utils/setupZephyr.mts | 75 ++++++++++++++++----------- src/utils/versionBundles.mts | 12 ++--- src/webview/newZephyrProjectPanel.mts | 41 ++++----------- web/zephyr/main.js | 22 +++++--- 6 files changed, 78 insertions(+), 75 deletions(-) diff --git a/src/commands/newProject.mts b/src/commands/newProject.mts index ca90db98..4d29b833 100644 --- a/src/commands/newProject.mts +++ b/src/commands/newProject.mts @@ -4,6 +4,7 @@ import { window, type Uri } from "vscode"; import { NewProjectPanel } from "../webview/newProjectPanel.mjs"; // eslint-disable-next-line max-len import { NewMicroPythonProjectPanel } from "../webview/newMicroPythonProjectPanel.mjs"; +import { NewRustProjectPanel } from "../webview/newRustProjectPanel.mjs"; import { NewZephyrProjectPanel } from "../webview/newZephyrProjectPanel.mjs"; /** diff --git a/src/utils/download.mts b/src/utils/download.mts index b288ac2a..939ae9c5 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -23,7 +23,7 @@ import { cloneRepository, initSubmodules, ensureGit } from "./gitUtil.mjs"; import { HOME_VAR, SettingsKey } from "../settings.mjs"; import Settings from "../settings.mjs"; import which from "which"; -import { ProgressLocation, type Uri, window, workspace } from "vscode"; +import { ProgressLocation, Uri, window, workspace } from "vscode"; import { fileURLToPath } from "url"; import { type GithubReleaseAssetData, diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts index 064d3e3a..719646ed 100644 --- a/src/utils/setupZephyr.mts +++ b/src/utils/setupZephyr.mts @@ -18,10 +18,10 @@ import { downloadFileGot, } from "../utils/download.mjs"; import Settings, { HOME_VAR } from "../settings.mjs"; -import { openOCDVersion } from "../webview/newProjectPanel.mjs"; import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; import { ensureGit } from "../utils/gitUtil.mjs"; -import { type VersionBundle } from "../utils/versionBundles.mjs"; +import VersionBundlesLoader from "./versionBundles.mjs"; +import { OPENOCD_VERSION } from "./sharedConstants.mjs"; const _logger = new Logger("zephyrSetup"); @@ -48,10 +48,10 @@ manifest: `; interface ZephyrSetupValue { - versionBundle: VersionBundle | undefined; cmakeMode: number; cmakePath: string; cmakeVersion: string; + extUri: Uri; } interface ZephyrSetupOutputs { @@ -90,7 +90,17 @@ export async function setupZephyr( return; } + _logger.info("Setting up Zephyr..."); + const latestVb = await new VersionBundlesLoader(data.extUri).getLatest(); + if (latestVb === undefined) { + _logger.error("Failed to get latest version bundles."); + void window.showErrorMessage( + "Failed to get latest version bundles. Cannot continue Zephyr setup." + ); + + return; + } const output: ZephyrSetupOutputs = { cmakeExecutable: "" }; let python3Path: string | undefined = ""; @@ -131,11 +141,7 @@ export async function setupZephyr( // Handle CMake install switch (data.cmakeMode) { - case 0: - if (data.versionBundle !== undefined) { - data.cmakeVersion = data.versionBundle.cmake; - } - // eslint-disable-next-line no-fallthrough + case 4: case 2: installedSuccessfully = false; prog2LastState = 0; @@ -215,7 +221,7 @@ export async function setupZephyr( message: "Failed", increment: 100, }); - void window.showErrorMessage("Unknown cmake selection."); + void window.showErrorMessage("Unknown CMake version selected."); return; } @@ -227,13 +233,16 @@ export async function setupZephyr( }, async progress2 => { if ( - await downloadAndInstallNinja("v1.12.1", (prog: GotProgress) => { - const per = prog.percent * 100; - progress2.report({ - increment: per - prog2LastState, - }); - prog2LastState = per; - }) + await downloadAndInstallNinja( + latestVb[1].ninja, + (prog: GotProgress) => { + const per = prog.percent * 100; + progress2.report({ + increment: per - prog2LastState, + }); + prog2LastState = per; + } + ) ) { progress2.report({ message: "Successfully downloaded and installed Ninja.", @@ -259,13 +268,16 @@ export async function setupZephyr( }, async progress2 => { if ( - await downloadAndInstallPicotool("2.1.1", (prog: GotProgress) => { - const per = prog.percent * 100; - progress2.report({ - increment: per - prog2LastState, - }); - prog2LastState = per; - }) + await downloadAndInstallPicotool( + latestVb[1].picotool, + (prog: GotProgress) => { + const per = prog.percent * 100; + progress2.report({ + increment: per - prog2LastState, + }); + prog2LastState = per; + } + ) ) { progress2.report({ message: "Successfully downloaded and installed Picotool.", @@ -322,6 +334,7 @@ export async function setupZephyr( }, async progress2 => { if ( + // TODO: integrate into extension system for github api caching await downloadAndInstallArchive( "https://github.com/oss-winget/oss-winget-storage/raw/" + "96ea1b934342f45628a488d3b50d0c37cf06012c/packages/dtc/" + @@ -355,6 +368,7 @@ export async function setupZephyr( }, async progress2 => { if ( + // TODO: integrate into extension system for github api caching await downloadAndInstallArchive( "https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/" + "gperf-3.0.1-bin.zip/download", @@ -391,6 +405,7 @@ export async function setupZephyr( }, async progress2 => { if ( + // TODO: integrate into extension system for github api caching await downloadAndInstallArchive( "https:///eternallybored.org/misc/wget/releases/" + "wget-1.21.4-win64.zip", @@ -498,7 +513,7 @@ export async function setupZephyr( async progress2 => { if ( await downloadAndInstallOpenOCD( - openOCDVersion, + OPENOCD_VERSION, (prog: GotProgress) => { const per = prog.percent * 100; progress2.report({ @@ -844,12 +859,13 @@ export async function setupZephyr( windowsHide: true, env: customEnv, }); - _logger.info("stdout: ", child.stdout.toString()); - _logger.info("stderr: ", child.stderr.toString()); + _logger.debug("stdout: ", child.stdout.toString()); + _logger.debug("stderr: ", child.stderr.toString()); + if (child.status) { - _logger.info("exit code: ", child.status); + _logger.debug("exit code: ", child.status); if (child.status !== 0) { - window.showErrorMessage( + void window.showErrorMessage( "Error installing Zephyr SDK." + "Exiting Zephyr Setup." ); } @@ -873,7 +889,8 @@ export async function setupZephyr( ); if (installedSuccessfully) { - window.showInformationMessage("Zephyr setup complete"); + // TODO: duplicate signaling + void window.showInformationMessage("Zephyr setup complete"); progress.report({ message: "Zephyr setup complete.", increment: 100, diff --git a/src/utils/versionBundles.mts b/src/utils/versionBundles.mts index b7ff3653..6f0ae60a 100644 --- a/src/utils/versionBundles.mts +++ b/src/utils/versionBundles.mts @@ -16,7 +16,7 @@ export interface VersionBundle { picotool: string; toolchain: string; riscvToolchain: string; - modifiers: { [triple: string] : {[tool: string]: string}}; + modifiers: { [triple: string]: { [tool: string]: string } }; } export interface VersionBundles { @@ -108,20 +108,20 @@ export default class VersionBundlesLoader { const platformDouble = `${process.platform}_${process.arch}`; if (modifiers[platformDouble] !== undefined) { chosenBundle.cmake = - modifiers[platformDouble]["cmake"] ?? chosenBundle.cmake + modifiers[platformDouble]["cmake"] ?? chosenBundle.cmake; chosenBundle.ninja = - modifiers[platformDouble]["ninja"] ?? chosenBundle.ninja + modifiers[platformDouble]["ninja"] ?? chosenBundle.ninja; chosenBundle.picotool = - modifiers[platformDouble]["picotool"] ?? chosenBundle.picotool + modifiers[platformDouble]["picotool"] ?? chosenBundle.picotool; chosenBundle.toolchain = - modifiers[platformDouble]["toolchain"] ?? chosenBundle.toolchain + modifiers[platformDouble]["toolchain"] ?? chosenBundle.toolchain; chosenBundle.riscvToolchain = modifiers[platformDouble]["riscvToolchain"] ?? - chosenBundle.riscvToolchain + chosenBundle.riscvToolchain; } } } diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index b2f18476..c92c5d6f 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -27,8 +27,6 @@ import { PythonExtension } from "@vscode/python-extension"; import { unknownErrorToString } from "../utils/errorHelper.mjs"; import { buildZephyrWorkspacePath } from "../utils/download.mjs"; import { setupZephyr } from "../utils/setupZephyr.mjs"; -import type { VersionBundle } from "../utils/versionBundles.mjs"; -import type VersionBundlesLoader from "../utils/versionBundles.mjs"; import { getCmakeReleases } from "../utils/githubREST.mjs"; import { getSystemCmakeVersion } from "../utils/cmakeUtil.mjs"; @@ -142,8 +140,6 @@ export class NewZephyrProjectPanel { private _projectRoot?: Uri; private _pythonExtensionApi?: PythonExtension; - private _versionBundlesLoader?: VersionBundlesLoader; - private _versionBundle: VersionBundle | undefined; private _systemCmakeVersion: string | undefined; // Create settings.json file with correct subsitution for tools such as @@ -254,7 +250,7 @@ export class NewZephyrProjectPanel { ) .then(selected => { if (selected === "Reload Window") { - commands.executeCommand("workbench.action.reloadWindow"); + void commands.executeCommand("workbench.action.reloadWindow"); } }); @@ -280,7 +276,6 @@ export class NewZephyrProjectPanel { return; } - // TODO: reload if it was import panel maybe in state NewZephyrProjectPanel.currentPanel = new NewZephyrProjectPanel( panel, settings, @@ -345,23 +340,6 @@ export class NewZephyrProjectPanel { } } break; - case "versionBundleAvailableTest": - { - // test if versionBundle for sdk version is available - const versionBundle = - await this._versionBundlesLoader?.getModuleVersion( - message.value as string - ); - // return result in message of command versionBundleAvailableTest - await this._panel.webview.postMessage({ - command: "versionBundleAvailableTest", - value: { - result: versionBundle !== undefined, - picotoolVersion: versionBundle?.picotool, - }, - }); - } - break; case "cancel": this.dispose(); break; @@ -445,6 +423,13 @@ export class NewZephyrProjectPanel { } } + /** + * Convert the enum to the Zephyr board name + * + * @param e BoardType enum + * @returns string Zephyr board name + * @throws Error if unknown board type + */ private enumToBoard(e: BoardType): string { switch (e) { case BoardType.pico: @@ -456,7 +441,6 @@ export class NewZephyrProjectPanel { case BoardType.pico2W: return "rpi_pico2/rp2350a/m33/w"; default: - // TODO: maybe just return an empty string throw new Error(`Unknown Board Type: ${e as string}`); } } @@ -501,7 +485,7 @@ export class NewZephyrProjectPanel { return; } - if (this._versionBundle === undefined && data.cmakeMode === 0) { + if (data.cmakeMode === 0) { progress.report({ message: "Failed", increment: 100, @@ -513,10 +497,10 @@ export class NewZephyrProjectPanel { // Setup Zephyr before doing anything else const zephyrSetupOutputs = await setupZephyr({ - versionBundle: this._versionBundle, cmakeMode: data.cmakeMode, cmakePath: data.cmakePath, cmakeVersion: data.cmakeVersion, + extUri: this._extensionUri, }); if (zephyrSetupOutputs === null) { @@ -992,11 +976,6 @@ export class NewZephyrProjectPanel { class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:text-white"> - ${ - this._versionBundle !== undefined - ? `` - : "" - } ${ this._systemCmakeVersion !== undefined ? `` diff --git a/web/zephyr/main.js b/web/zephyr/main.js index 5be3aa8a..864fe303 100644 --- a/web/zephyr/main.js +++ b/web/zephyr/main.js @@ -128,7 +128,7 @@ var submitted = false; window.submitBtnClick = () => { // get all values of inputs const projectNameElement = document.getElementById("inp-project-name"); - // if is project import then the project name element will not be rendered and does not exist in the DOM + const projectName = projectNameElement.value; if ( projectName !== undefined && @@ -235,8 +235,9 @@ var submitted = false; let cmakeVersion = null; // string | null const cmakeModeSel = document.getElementById('cmake-mode'); - const selCmake = document.getElementById('sel-cmake'); // shown in "select" mode - const cmakeFileInp = document.getElementById('cmake-path-executable'); // shown in "custom" mode + const selCmake = document.getElementById('sel-cmake'); // shown in "select" mode + const cmakeFileInp = document.getElementById('cmake-path-executable'); // shown in "custom" mode + const latestCmakeVersion = document.getElementById('cmake-latest-label'); // get latest version // Fallback to "latest" if the select isn't there for some reason const cmakeModeStr = (cmakeModeSel?.value || 'latest'); @@ -244,8 +245,8 @@ var submitted = false; // Map string modes -> numeric API // 0 = default bundle, 1 = system, 2 = select version, 3 = custom path, 4 = latest switch (cmakeModeStr) { - // should never happen, but just in case let the backend handle it - case 'default': cmakeMode = 0; break; + // default to latest + case 'default': cmakeMode = 4; break; case 'system': cmakeMode = 1; break; case 'select': cmakeMode = 2; break; case 'custom': cmakeMode = 3; break; @@ -261,7 +262,12 @@ var submitted = false; } // Validate + collect per-mode extras - if (cmakeMode === 2) { + if (cmakeMode === 4) { + if (!latestCmakeVersion) { + + } + cmakeVersion = latestCmakeVersion.textContent.trim(); + } else if (cmakeMode === 2) { // specific version chosen from dropdown if (!selCmake || !selCmake.value) { vscode.postMessage({ @@ -288,8 +294,8 @@ var submitted = false; cmakePath = files[0].name; } - // Final sanity check: numeric range 0..4 - if (cmakeMode === null || isNaN(cmakeMode) || cmakeMode < 0 || cmakeMode > 4) { + // Final sanity check: numeric range 1..4 + if (cmakeMode === null || isNaN(cmakeMode) || cmakeMode < 1 || cmakeMode > 4) { console.debug('Invalid cmake version value: ' + cmakeMode); vscode.postMessage({ command: CMD_ERROR, From ae0b674750380c5d6a4cbb0397f1a1d1c5918f9e Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Wed, 17 Sep 2025 19:10:27 +0100 Subject: [PATCH 54/62] Fix zephyr integration (part 3) Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- package.json | 1 + src/commands/getPaths.mts | 28 - src/extension.mts | 208 ++--- src/logger.mts | 1 + src/ui.mts | 13 +- src/utils/setupZephyr.mts | 1343 +++++++++++++++++++-------------- src/utils/sharedConstants.mts | 17 + 7 files changed, 846 insertions(+), 765 deletions(-) diff --git a/package.json b/package.json index 275fc1e7..001d8c30 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "activationEvents": [ "workspaceContains:./pico_sdk_import.cmake", "workspaceContains:./.pico-rs", + "workspaceContains:./prj.conf", "onWebviewPanel:newPicoProject", "onWebviewPanel:newPicoMicroPythonProject" ], diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index 508fc1bb..b7bac972 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -1,6 +1,5 @@ import { CommandWithResult } from "./command.mjs"; import { commands, type Uri, window, workspace } from "vscode"; -import { type ExecOptions, exec, spawnSync } from "child_process"; import { getPythonPath, getPath, @@ -29,9 +28,6 @@ import { getSupportedToolchains } from "../utils/toolchainUtil.mjs"; import Logger from "../logger.mjs"; import { rustProjectGetSelectedChip } from "../utils/rustUtil.mjs"; import { OPENOCD_VERSION } from "../utils/sharedConstants.mjs"; -import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; -import { ensureGit } from "../utils/gitUtil.mjs"; -import { openOCDVersion } from "../webview/newProjectPanel.mjs"; import { setupZephyr } from "../utils/setupZephyr.mjs"; export class GetPythonPathCommand extends CommandWithResult { @@ -572,27 +568,3 @@ export class GetZephyrWorkspacePathCommand extends CommandWithResult< return result; } } - -export class SetupZephyrCommand extends CommandWithResult { - private running: boolean = false; - - public static readonly id = "setupZephyr"; - - constructor() { - super(SetupZephyrCommand.id); - } - - private readonly _logger: Logger = new Logger("SetupZephyr"); - - async execute(): Promise { - if (this.running) { - return undefined; - } - - this.running = true; - const result = setupZephyr(); - this.running = false; - - return result; - } -} diff --git a/src/extension.mts b/src/extension.mts index f3bbc918..727586cd 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -50,7 +50,6 @@ import { GetPicotoolPathCommand, GetOpenOCDRootCommand, GetSVDPathCommand, - SetupZephyrCommand, GetWestPathCommand, GetZephyrWorkspacePathCommand, } from "./commands/getPaths.mjs"; @@ -104,6 +103,7 @@ import { cmakeToolsForcePicoKit } from "./utils/cmakeToolsUtil.mjs"; import { NewRustProjectPanel } from "./webview/newRustProjectPanel.mjs"; import { OPENOCD_VERSION } from "./utils/sharedConstants.mjs"; import VersionBundlesLoader from "./utils/versionBundles.mjs"; +import { setupZephyr } from "./utils/setupZephyr.mjs"; export async function activate(context: ExtensionContext): Promise { Logger.info(LoggerSource.extension, "Extension activation triggered"); @@ -145,7 +145,6 @@ export async function activate(context: ExtensionContext): Promise { new GetSVDPathCommand(context.extensionUri), new GetWestPathCommand(), new GetZephyrWorkspacePathCommand(), - new SetupZephyrCommand(), new NewZephyrProjectCommand(), new CompileProjectCommand(), new RunProjectCommand(), @@ -262,63 +261,68 @@ export async function activate(context: ExtensionContext): Promise { return; } - // Set Pico Zephyr Project false by default - await commands.executeCommand( - "setContext", - ContextKeys.isPicoZephyrProject, - false - ); - - // Check for pico_zephyr in CMakeLists.txt - if ( - readFileSync(cmakeListsFilePath).toString("utf-8").includes("pico_zephyr") - ) { - Logger.info(LoggerSource.extension, "Pico Zephyr Project"); + // Set Pico Zephyr Project false by default await commands.executeCommand( "setContext", ContextKeys.isPicoZephyrProject, - true + false ); - // Update the board info if it can be found in tasks.json - const tasksJsonFilePath = join( - workspaceFolder.uri.fsPath, - ".vscode", - "tasks.json" - ); + // Check for pico_zephyr in CMakeLists.txt + if ( + readFileSync(cmakeListsFilePath).toString("utf-8").includes("pico_zephyr") + ) { + Logger.info(LoggerSource.extension, "Project is of type: Zephyr"); + await commands.executeCommand( + "setContext", + ContextKeys.isPicoZephyrProject, + true + ); - // Update UI with board description - const board = findZephyrBoardInTasksJson(tasksJsonFilePath); - - if (board !== undefined) { - if (board === "rpi_pico2/rp2350a/m33/w") { - ui.updateBoard("Pico 2W"); - } else if (board === "rpi_pico2/rp2350a/m33") { - ui.updateBoard("Pico 2"); - } else if (board === "rpi_pico/rp2040/w") { - ui.updateBoard("Pico W"); - } else if (board.includes("rpi_pico")) { - ui.updateBoard("Pico"); - } else { - ui.updateBoard("Other"); + // TODO: !!!!!!!!! IMPORTANT !!!!!!!!! + // TODO: make sure zephy dependencies are installed + + ui.showStatusBarItems(false, true); + + // Update the board info if it can be found in tasks.json + const tasksJsonFilePath = join( + workspaceFolder.uri.fsPath, + ".vscode", + "tasks.json" + ); + + // Update UI with board description + const board = findZephyrBoardInTasksJson(tasksJsonFilePath); + + if (board !== undefined) { + if (board === "rpi_pico2/rp2350a/m33/w") { + ui.updateBoard("Pico 2W"); + } else if (board === "rpi_pico2/rp2350a/m33") { + ui.updateBoard("Pico 2"); + } else if (board === "rpi_pico/rp2040/w") { + ui.updateBoard("Pico W"); + } else if (board.includes("rpi_pico")) { + ui.updateBoard("Pico"); + } else { + ui.updateBoard("Other"); + } } } - } - // check for pico_sdk_init() in CMakeLists.txt - else if ( - !readFileSync(cmakeListsFilePath) - .toString("utf-8") - .includes("pico_sdk_init()") - ) { - Logger.warn( - LoggerSource.extension, - "No pico_sdk_init() in CMakeLists.txt found." - ); - await commands.executeCommand( - "setContext", - ContextKeys.isPicoProject, - false - ); + // check for pico_sdk_init() in CMakeLists.txt + else if ( + !readFileSync(cmakeListsFilePath) + .toString("utf-8") + .includes("pico_sdk_init()") + ) { + Logger.warn( + LoggerSource.extension, + "No pico_sdk_init() in CMakeLists.txt found." + ); + await commands.executeCommand( + "setContext", + ContextKeys.isPicoProject, + false + ); return; } @@ -870,108 +874,6 @@ export async function activate(context: ExtensionContext): Promise { return; } - /* - const pythonPath = settings.getString(SettingsKey.python3Path); - if (pythonPath && pythonPath.includes("/.pico-sdk/python")) { - // check if python path exists - if (!existsSync(pythonPath.replace(HOME_VAR, homedir()))) { - Logger.warn( - LoggerSource.extension, - "Python path in settings does not exist.", - "Installing Python3 to default path." - ); - const pythonVersion = /\/\.pico-sdk\/python\/([.0-9]+)\//.exec( - pythonPath - )?.[1]; - if (pythonVersion === undefined) { - Logger.error( - LoggerSource.extension, - "Failed to get Python version from path." - ); - await commands.executeCommand( - "setContext", - ContextKeys.isPicoProject, - false - ); - - return; - } - - let result: string | undefined; - await window.withProgress( - { - location: ProgressLocation.Notification, - title: - "Downloading and installing Python. This may take a long while...", - cancellable: false, - }, - async progress => { - if (process.platform === "win32") { - const versionBundle = await new VersionBundlesLoader( - context.extensionUri - ).getPythonWindowsAmd64Url(pythonVersion); - - if (versionBundle === undefined) { - Logger.error( - LoggerSource.extension, - "Failed to get Python download url from version bundle." - ); - await commands.executeCommand( - "setContext", - ContextKeys.isPicoProject, - false - ); - - return; - } - - // ! because data.pythonMode === 0 => versionBundle !== undefined - result = await downloadEmbedPython(versionBundle); - } else if (process.platform === "darwin") { - const result1 = await setupPyenv(); - if (!result1) { - progress.report({ - increment: 100, - }); - - return; - } - const result2 = await pyenvInstallPython(pythonVersion); - - if (result2 !== null) { - result = result2; - } - } else { - Logger.info( - LoggerSource.extension, - "Automatic Python installation is only", - "supported on Windows and macOS." - ); - - await window.showErrorMessage( - "Automatic Python installation is only " + - "supported on Windows and macOS." - ); - } - progress.report({ - increment: 100, - }); - } - ); - - if (result === undefined) { - Logger.error(LoggerSource.extension, "Failed to install Python3."); - await commands.executeCommand( - "setContext", - ContextKeys.isPicoProject, - false - ); - - return; - } - } - }*/ - ui.showStatusBarItems(); ui.updateSDKVersion(selectedToolchainAndSDKVersions[0]); diff --git a/src/logger.mts b/src/logger.mts index c88860f2..93d9abe0 100644 --- a/src/logger.mts +++ b/src/logger.mts @@ -44,6 +44,7 @@ export enum LoggerSource { vscodeConfigUtil = "vscodeConfigUtil", rustUtil = "rustUtil", projectRust = "projectRust", + zephyrSetup = "setupZephyr", } /** diff --git a/src/ui.mts b/src/ui.mts index 921d67be..93835b64 100644 --- a/src/ui.mts +++ b/src/ui.mts @@ -17,6 +17,7 @@ const STATUS_BAR_ITEMS: { command: string; tooltip: string; rustSupport: boolean; + zephyrSupport: boolean; }; } = { [StatusBarItemKey.compile]: { @@ -25,6 +26,7 @@ const STATUS_BAR_ITEMS: { command: "raspberry-pi-pico.compileProject", tooltip: "Compile Project", rustSupport: true, + zephyrSupport: true, }, [StatusBarItemKey.run]: { // alt. "$(gear) Compile" @@ -32,12 +34,14 @@ const STATUS_BAR_ITEMS: { command: "raspberry-pi-pico.runProject", tooltip: "Run Project", rustSupport: true, + zephyrSupport: true, }, [StatusBarItemKey.picoSDKQuickPick]: { text: "Pico SDK: ", command: "raspberry-pi-pico.switchSDK", tooltip: "Select Pico SDK", rustSupport: false, + zephyrSupport: false, }, [StatusBarItemKey.picoBoardQuickPick]: { text: "Board: ", @@ -45,6 +49,7 @@ const STATUS_BAR_ITEMS: { command: "raspberry-pi-pico.switchBoard", tooltip: "Select Chip", rustSupport: true, + zephyrSupport: true, }, }; @@ -69,9 +74,15 @@ export default class UI { }); } - public showStatusBarItems(isRustProject = false): void { + public showStatusBarItems( + isRustProject = false, + isZephyrProject = false + ): void { Object.values(this._items) .filter(item => !isRustProject || STATUS_BAR_ITEMS[item.id].rustSupport) + .filter( + item => !isZephyrProject || STATUS_BAR_ITEMS[item.id].zephyrSupport + ) .forEach(item => item.show()); } diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts index 719646ed..18373b8e 100644 --- a/src/utils/setupZephyr.mts +++ b/src/utils/setupZephyr.mts @@ -1,10 +1,10 @@ import { existsSync, readdirSync, rmSync } from "fs"; import { window, workspace, ProgressLocation, Uri } from "vscode"; import { type ExecOptions, exec, spawnSync } from "child_process"; -import { dirname } from "path"; +import { dirname, join } from "path"; import { join as joinPosix } from "path/posix"; -import { homedir } from "os"; -import Logger from "../logger.mjs"; +import { homedir, tmpdir } from "os"; +import Logger, { LoggerSource } from "../logger.mjs"; import type { Progress as GotProgress } from "got"; import { @@ -20,8 +20,18 @@ import { import Settings, { HOME_VAR } from "../settings.mjs"; import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; import { ensureGit } from "../utils/gitUtil.mjs"; -import VersionBundlesLoader from "./versionBundles.mjs"; -import { OPENOCD_VERSION } from "./sharedConstants.mjs"; +import VersionBundlesLoader, { type VersionBundle } from "./versionBundles.mjs"; +import { + CURRENT_7ZIP_VERSION, + CURRENT_DTC_VERSION, + CURRENT_GPERF_VERSION, + CURRENT_WGET_VERSION, + OPENOCD_VERSION, + WINDOWS_X86_7ZIP_DOWNLOAD_URL, + WINDOWS_X86_DTC_DOWNLOAD_URL, + WINDOWS_X86_GPERF_DOWNLOAD_URL, + WINDOWS_X86_WGET_DOWNLOAD_URL, +} from "./sharedConstants.mjs"; const _logger = new Logger("zephyrSetup"); @@ -58,6 +68,68 @@ interface ZephyrSetupOutputs { cmakeExecutable: string; } +// Compute and cache the home directory +const homeDirectory: string = homedir(); + +// TODO: maybe move into download.mts +function buildDtcPath(version: string): string { + return joinPosix( + homeDirectory.replaceAll("\\", "/"), + ".pico-sdk", + "dtc", + version + ); +} + +function buildGperfPath(version: string): string { + return joinPosix( + homeDirectory.replaceAll("\\", "/"), + ".pico-sdk", + "gperf", + version + ); +} + +function buildWgetPath(version: string): string { + return joinPosix( + homeDirectory.replaceAll("\\", "/"), + ".pico-sdk", + "wget", + version + ); +} + +function build7ZipPathWin32(version: string): string { + return join(homeDirectory, ".pico-sdk", "7zip", version); +} + +function generateCustomEnv( + isWindows: boolean, + latestVb: VersionBundle, + cmakeExe: string, + pythonExe: string +): NodeJS.ProcessEnv { + const customEnv = process.env; + + const customPath = [ + dirname(cmakeExe), + join(homedir(), ".pico-sdk", "dtc", CURRENT_DTC_VERSION, "bin"), + join(homedir(), ".pico-sdk", "git", "cmd"), + join(homedir(), ".pico-sdk", "gperf", CURRENT_GPERF_VERSION, "bin"), + join(homedir(), ".pico-sdk", "ninja", latestVb.ninja), + dirname(pythonExe), + join(homedir(), ".pico-sdk", "wget"), + join(homedir(), ".pico-sdk", "7zip", CURRENT_7ZIP_VERSION), + "", // Need this to add separator to end + ].join(process.platform === "win32" ? ";" : ":"); + + customEnv[isWindows ? "Path" : "PATH"] = + customPath + customEnv[isWindows ? "Path" : "PATH"]; + + return customEnv; +} + +// TODO: duplicate code with _runGenerator function _runCommand( command: string, options: ExecOptions @@ -65,522 +137,579 @@ function _runCommand( _logger.debug(`Running: ${command}`); return new Promise(resolve => { - const generatorProcess = exec(command, options, (error, stdout, stderr) => { + const proc = exec(command, options, (error, stdout, stderr) => { _logger.debug(stdout); - _logger.info(stderr); + _logger.error(stderr); if (error) { _logger.error(`Setup venv error: ${error.message}`); resolve(null); // indicate error } }); - generatorProcess.on("exit", code => { + proc.on("exit", code => { // Resolve with exit code or -1 if code is undefined resolve(code); }); }); } -export async function setupZephyr( - data: ZephyrSetupValue -): Promise { +async function checkGit(): Promise { const settings = Settings.getInstance(); if (settings === undefined) { _logger.error("Settings not initialized."); - return; + return false; } - _logger.info("Setting up Zephyr..."); - - const latestVb = await new VersionBundlesLoader(data.extUri).getLatest(); - if (latestVb === undefined) { - _logger.error("Failed to get latest version bundles."); - void window.showErrorMessage( - "Failed to get latest version bundles. Cannot continue Zephyr setup." - ); - - return; - } - const output: ZephyrSetupOutputs = { cmakeExecutable: "" }; - let python3Path: string | undefined = ""; - let isWindows = false; - await window.withProgress( + return window.withProgress( { location: ProgressLocation.Notification, - title: "Setting up Zephyr Toolchain", + title: "Ensuring Git is available", + cancellable: false, }, - async progress => { - let installedSuccessfully = true; - let prog2LastState = 0; - await window.withProgress( + async progress2 => { + // TODO: this does take about 2s - may be reduced + const gitPath = await ensureGit(settings, { returnPath: true }); + if (typeof gitPath !== "string" || gitPath.length === 0) { + progress2.report({ + message: "Failed", + increment: 100, + }); + + return false; + } + + progress2.report({ + message: "Success", + increment: 100, + }); + + return true; + } + ); +} + +async function checkCmake( + cmakeMode: number, + cmakeVersion: string, + cmakePath: string +): Promise { + let progLastState = 0; + let installedSuccessfully = false; + + switch (cmakeMode) { + case 4: + case 2: + installedSuccessfully = await window.withProgress( { location: ProgressLocation.Notification, - title: "Ensuring Git is available", + title: "Downloading and installing CMake", cancellable: false, }, - async progress2 => { - // TODO: this does take about 2s - may be reduced - const gitPath = await ensureGit(settings, { returnPath: true }); - if (typeof gitPath !== "string" || gitPath.length === 0) { - installedSuccessfully = false; - progress2.report({ + async progress => { + const result = await downloadAndInstallCmake( + cmakeVersion, + (prog: GotProgress) => { + const per = prog.percent * 100; + progress.report({ + increment: per - progLastState, + }); + progLastState = per; + } + ); + + if (!result) { + progress.report({ message: "Failed", increment: 100, }); - return; + return false; } - progress2.report({ - message: "Git setup correctly.", + + progress.report({ + message: "Success", increment: 100, }); - installedSuccessfully = true; + + return true; } ); - // Handle CMake install - switch (data.cmakeMode) { - case 4: - case 2: - installedSuccessfully = false; - prog2LastState = 0; - await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Download and install CMake", - cancellable: false, - }, - async progress2 => { - if ( - await downloadAndInstallCmake( - data.cmakeVersion, - (prog: GotProgress) => { - const per = prog.percent * 100; - progress2.report({ - increment: per - prog2LastState, - }); - prog2LastState = per; - } - ) - ) { - progress.report({ - // TODO: maybe just finished or something like that - message: "Successfully downloaded and installed CMake.", - increment: 100, - }); - - installedSuccessfully = true; - } else { - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - } - } - ); + if (!installedSuccessfully) { + return; + } - if (!installedSuccessfully) { - progress.report({ - message: "Failed", - increment: 100, - }); - void window.showErrorMessage( - // TODO: maybe remove all requirements met part - "Failed to download and install CMake.\ - Make sure all requirements are met." - ); + return joinPosix(buildCMakePath(cmakeVersion), "bin", "cmake"); + case 1: + // Don't need to add anything to path if already available via system + return ""; + case 3: + // normalize path returned by the os selector to posix path for the settings json + // and cross platform compatibility + return process.platform === "win32" + ? // TODO: maybe use path.sep for split + joinPosix(...cmakePath.split("\\")) + : cmakePath; + default: + void window.showErrorMessage("Unknown CMake version selected."); + + return; + } +} - return; - } else { - const cmakeVersionBasePath = buildCMakePath(data.cmakeVersion); +async function checkNinja(latestVb: VersionBundle): Promise { + let progLastState = 0; - output.cmakeExecutable = joinPosix( - cmakeVersionBasePath, - "bin", - "cmake" - ); - } - break; - case 1: - // Don't need to add anything to path if already available via system - output.cmakeExecutable = ""; - break; - case 3: - // normalize path returned by the os selector to posix path for the settings json - // and cross platform compatibility - output.cmakeExecutable = - process.platform === "win32" - ? // TODO: maybe use path.sep for split - joinPosix(...data.cmakePath.split("\\")) - : data.cmakePath; - break; - default: + return window.withProgress( + { + location: ProgressLocation.Notification, + title: "Downloading and installing Ninja", + cancellable: false, + }, + async progress => { + const result = await downloadAndInstallNinja( + latestVb.ninja, + (prog: GotProgress) => { + const per = prog.percent * 100; progress.report({ - message: "Failed", - increment: 100, + increment: per - progLastState, }); - void window.showErrorMessage("Unknown CMake version selected."); + progLastState = per; + } + ); + + if (result) { + progress.report({ + message: "Successfully downloaded and installed Ninja.", + increment: 100, + }); - return; + return true; + } else { + progress.report({ + message: "Failed", + increment: 100, + }); + + return false; } - await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Download and install Ninja", - cancellable: false, - }, - async progress2 => { - if ( - await downloadAndInstallNinja( - latestVb[1].ninja, - (prog: GotProgress) => { - const per = prog.percent * 100; - progress2.report({ - increment: per - prog2LastState, - }); - prog2LastState = per; - } - ) - ) { - progress2.report({ - message: "Successfully downloaded and installed Ninja.", - increment: 100, - }); + } + ); +} - installedSuccessfully = true; - } else { - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, +async function checkPicotool(latestVb: VersionBundle): Promise { + let progLastState = 0; + + return window.withProgress( + { + location: ProgressLocation.Notification, + title: "Downloading and installing Picotool", + cancellable: false, + }, + async progress => { + if ( + await downloadAndInstallPicotool( + latestVb.picotool, + (prog: GotProgress) => { + const per = prog.percent * 100; + progress.report({ + increment: per - progLastState, }); + progLastState = per; } - } + ) + ) { + progress.report({ + message: "Success", + increment: 100, + }); + + return true; + } else { + progress.report({ + message: "Failed", + increment: 100, + }); + + return false; + } + } + ); +} + +async function checkDtc(): Promise { + return window.withProgress( + { + location: ProgressLocation.Notification, + title: "Downloading and installing DTC", + cancellable: false, + }, + async progress => { + const result = await downloadAndInstallArchive( + WINDOWS_X86_DTC_DOWNLOAD_URL, + buildDtcPath(CURRENT_DTC_VERSION), + "dtc-msys2-1.6.1-x86_64.zip", + "dtc" ); - await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Download and install Picotool", - cancellable: false, - }, - async progress2 => { - if ( - await downloadAndInstallPicotool( - latestVb[1].picotool, - (prog: GotProgress) => { - const per = prog.percent * 100; - progress2.report({ - increment: per - prog2LastState, - }); - prog2LastState = per; - } - ) - ) { - progress2.report({ - message: "Successfully downloaded and installed Picotool.", - increment: 100, - }); + if (!result) { + Logger.error( + LoggerSource.zephyrSetup, + "Failed to download and install DTC." + ); - installedSuccessfully = true; - } else { - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - } - } + progress.report({ + message: "Failed", + increment: 100, + }); + + return false; + } else { + progress.report({ + message: "Success", + increment: 100, + }); + + return true; + } + } + ); +} + +async function checkGperf(): Promise { + return window.withProgress( + { + location: ProgressLocation.Notification, + title: "Downloading and installing gperf", + cancellable: false, + }, + async progress => { + const result = await downloadAndInstallArchive( + WINDOWS_X86_GPERF_DOWNLOAD_URL, + buildGperfPath(CURRENT_GPERF_VERSION), + `gperf-${CURRENT_GPERF_VERSION}-win64_x64.zip`, + "gperf" + ); + + if (!result) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return false; + } else { + progress.report({ + message: "Success", + increment: 100, + }); + + return true; + } + } + ); +} + +async function checkWget(): Promise { + return window.withProgress( + { + location: ProgressLocation.Notification, + title: "Downloading and installing wget", + cancellable: false, + }, + async progress2 => { + const result = await downloadAndInstallArchive( + WINDOWS_X86_WGET_DOWNLOAD_URL, + buildWgetPath(CURRENT_WGET_VERSION), + `wget-${CURRENT_WGET_VERSION}-win64.zip`, + "wget" + ); + + if (result) { + progress2.report({ + message: "Successfully downloaded and installed wget.", + increment: 100, + }); + + return true; + } else { + progress2.report({ + message: "Failed", + increment: 100, + }); + + return false; + } + } + ); +} + +async function check7Zip(): Promise { + return window.withProgress( + { + location: ProgressLocation.Notification, + title: "Downloading and installing 7-Zip", + cancellable: false, + }, + async progress => { + _logger.info("Installing 7-Zip"); + const targetDirectory = joinPosix("C:\\Program Files\\7-Zip"); + + if ( + existsSync(targetDirectory) && + readdirSync(targetDirectory).length !== 0 + ) { + _logger.info("7-Zip is already installed."); + + progress.report({ + message: "7-Zip already installed.", + increment: 100, + }); + + return true; + } + + const binName = `7z${CURRENT_7ZIP_VERSION}-x64.msi`; + const downloadURL = new URL(WINDOWS_X86_7ZIP_DOWNLOAD_URL); + const downloadDir = tmpdir().replaceAll("\\", "/"); + const result = await downloadFileGot( + downloadURL, + joinPosix(downloadDir, binName) ); - await window.withProgress( + if (!result) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return false; + } + + const installDir = build7ZipPathWin32(CURRENT_7ZIP_VERSION); + const installResult = await _runCommand( + `msiexec /i ${binName} INSTALLDIR="${installDir}" /quiet /norestart`, { - location: ProgressLocation.Notification, - title: "Set up Python", - cancellable: false, - }, - async progress2 => { - python3Path = await findPython(); - if (!python3Path) { - _logger.error("Failed to find Python3 executable."); - showPythonNotFoundError(); + cwd: join(homedir(), ".pico-sdk"), + } + ); - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - } + if (installResult !== 0) { + progress.report({ + message: "Failed", + increment: 100, + }); - progress2.report({ - message: "Successfully downloaded and installed Picotool.", - increment: 100, - }); + return false; + } + + // Clean up + rmSync(join(downloadDir, binName)); + + progress.report({ + message: "Seccess", + increment: 100, + }); + + return true; + } + ); +} - installedSuccessfully = true; +async function checkWindowsDeps(isWindows: boolean): Promise { + if (!isWindows) { + return true; + } + + let installedSuccessfully = await checkDtc(); + if (!installedSuccessfully) { + return false; + } + + installedSuccessfully = await checkGperf(); + if (!installedSuccessfully) { + return false; + } + + installedSuccessfully = await checkWget(); + if (!installedSuccessfully) { + return false; + } + + installedSuccessfully = await check7Zip(); + if (!installedSuccessfully) { + return false; + } + + return true; +} + +async function checkOpenOCD(): Promise { + let progLastState = 0; + + return window.withProgress( + { + location: ProgressLocation.Notification, + title: "Downloading and installing OpenOCD", + cancellable: false, + }, + async progress => { + const result = await downloadAndInstallOpenOCD( + OPENOCD_VERSION, + (prog: GotProgress) => { + const per = prog.percent * 100; + progress.report({ + increment: per - progLastState, + }); + progLastState = per; } ); - isWindows = process.platform === "win32"; + if (result) { + progress.report({ + message: "Success", + increment: 100, + }); - if (isWindows) { - await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Download and install DTC", - cancellable: false, - }, - async progress2 => { - if ( - // TODO: integrate into extension system for github api caching - await downloadAndInstallArchive( - "https://github.com/oss-winget/oss-winget-storage/raw/" + - "96ea1b934342f45628a488d3b50d0c37cf06012c/packages/dtc/" + - "1.6.1/dtc-msys2-1.6.1-x86_64.zip", - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc"), - "dtc-msys2-1.6.1-x86_64.zip", - "dtc" - ) - ) { - progress2.report({ - message: "Successfully downloaded and installed DTC.", - increment: 100, - }); + return true; + } else { + progress.report({ + message: "Failed", + increment: 100, + }); - installedSuccessfully = true; - } else { - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - } - } - ); + return false; + } + } + ); +} - await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Download and install gperf", - cancellable: false, - }, - async progress2 => { - if ( - // TODO: integrate into extension system for github api caching - await downloadAndInstallArchive( - "https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/" + - "gperf-3.0.1-bin.zip/download", - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "gperf" - ), - "gperf-3.0.1-bin.zip", - "gperf" - ) - ) { - progress2.report({ - message: "Successfully downloaded and installed DTC.", - increment: 100, - }); +export async function setupZephyr( + data: ZephyrSetupValue +): Promise { + _logger.info("Setting up Zephyr..."); - installedSuccessfully = true; - } else { - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - } - } - ); + const latestVb = await new VersionBundlesLoader(data.extUri).getLatest(); + if (latestVb === undefined) { + _logger.error("Failed to get latest version bundles."); + void window.showErrorMessage( + "Failed to get latest version bundles. Cannot continue Zephyr setup." + ); - await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Download and install wget", - cancellable: false, - }, - async progress2 => { - if ( - // TODO: integrate into extension system for github api caching - await downloadAndInstallArchive( - "https:///eternallybored.org/misc/wget/releases/" + - "wget-1.21.4-win64.zip", - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), - "wget-1.21.4-win64.zip", - "wget" - ) - ) { - progress2.report({ - message: "Successfully downloaded and installed wget.", - increment: 100, - }); + return; + } + const output: ZephyrSetupOutputs = { cmakeExecutable: "" }; - installedSuccessfully = true; - } else { - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - } - } - ); + let isWindows = false; + await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Setting up Zephyr Toolchain", + }, + async progress => { + let installedSuccessfully = true; - await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Download and install 7-zip", - cancellable: false, - }, - async progress2 => { - _logger.info("Installing 7zip"); - const szipTargetDirectory = joinPosix("C:\\Program Files\\7-Zip"); - if ( - existsSync(szipTargetDirectory) && - readdirSync(szipTargetDirectory).length !== 0 - ) { - _logger.info("7-Zip is already installed."); - progress2.report({ - message: "7-zip already installed.", - increment: 100, - }); - } else { - const szipURL = new URL("https://7-zip.org/a/7z2409-x64.exe"); - const szipResult = await downloadFileGot( - szipURL, - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "7zip-x64.exe" - ) - ); - - if (!szipResult) { - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - - return; - } - - const szipCommand: string = "7zip-x64.exe"; - const szipInstallResult = await _runCommand(szipCommand, { - cwd: joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk"), - }); + installedSuccessfully = await checkGit(); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); - if (szipInstallResult !== 0) { - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - - return; - } - - // Clean up - rmSync( - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "7zip-x64.exe" - ) - ); + return; + } - progress2.report({ - message: "Successfully downloaded and installed 7-zip.", - increment: 100, - }); + // Handle CMake install + const cmakePath = await checkCmake( + data.cmakeMode, + data.cmakeVersion, + data.cmakePath + ); + if (cmakePath === undefined) { + progress.report({ + message: "Failed", + increment: 100, + }); - installedSuccessfully = true; - } - } + return; + } + output.cmakeExecutable = cmakePath; + + installedSuccessfully = await checkNinja(latestVb[1]); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return; + } + + installedSuccessfully = await checkPicotool(latestVb[1]); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return; + } + + // install python (if necessary) + const python3Path = await findPython(); + if (!python3Path) { + progress.report({ + message: "Failed", + increment: 100, + }); + Logger.error( + LoggerSource.zephyrSetup, + "Failed to find Python3 executable." ); + showPythonNotFoundError(); + + return; } - await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Download and install OpenOCD", - cancellable: false, - }, - async progress2 => { - if ( - await downloadAndInstallOpenOCD( - OPENOCD_VERSION, - (prog: GotProgress) => { - const per = prog.percent * 100; - progress2.report({ - increment: per - prog2LastState, - }); - prog2LastState = per; - } - ) - ) { - progress2.report({ - message: "Successfully downloaded and installed Picotool.", - increment: 100, - }); + isWindows = process.platform === "win32"; - installedSuccessfully = true; - } else { - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - } - } - ); + installedSuccessfully = await checkWindowsDeps(isWindows); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return; + } + + installedSuccessfully = await checkOpenOCD(); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return; + } const pythonExe = python3Path?.replace( HOME_VAR, homedir().replaceAll("\\", "/") ); - - const customEnv = process.env; - - const customPath = [ - dirname(output.cmakeExecutable.replaceAll("\\", "/")), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "dtc", "bin"), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "git", "cmd"), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "gperf", "bin"), - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "ninja", - "v1.12.1" - ), - joinPosix( - homedir().replaceAll("\\", "/"), - ".pico-sdk", - "python", - "3.12.6" - ), - joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "wget"), - joinPosix("C:\\Program Files".replaceAll("\\", "/"), "7-Zip"), - "", // Need this to add separator to end - ].join(process.platform === "win32" ? ";" : ":"); - - _logger.info(`New path: ${customPath}`); - - customPath.replaceAll("/", "\\"); - customEnv[isWindows ? "Path" : "PATH"] = - customPath + customEnv[isWindows ? "Path" : "PATH"]; + const customEnv = generateCustomEnv( + isWindows, + latestVb[1], + output.cmakeExecutable, + pythonExe + ); const zephyrWorkspaceDirectory = buildZephyrWorkspacePath(); - const zephyrManifestDir: string = joinPosix( zephyrWorkspaceDirectory, "manifest" ); - const zephyrManifestFile: string = joinPosix( zephyrManifestDir, "west.yml" @@ -601,104 +730,123 @@ export async function setupZephyr( ].join(" "); // Create a Zephyr workspace, copy the west manifest in and initialise the workspace - workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); + await workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); // Generic result to get value from runCommand calls let result: number | null; + const venvPython: string = joinPosix( + zephyrWorkspaceDirectory, + "venv", + process.platform === "win32" ? "Scripts" : "bin", + process.platform === "win32" ? "python.exe" : "python" + ); - await window.withProgress( + installedSuccessfully = await window.withProgress( { location: ProgressLocation.Notification, title: "Setup Python virtual environment for Zephyr", cancellable: false, }, async progress2 => { - const venvPython: string = joinPosix( - zephyrWorkspaceDirectory, - "venv", - process.platform === "win32" ? "Scripts" : "bin", - process.platform === "win32" ? "python.exe" : "python" - ); if (existsSync(venvPython)) { - _logger.info("Existing Python venv found."); - } else { - result = await _runCommand(createVenvCommandVenv, { + _logger.info( + "Existing Python virtual environment for Zephyr found." + ); + + return true; + } + + result = await _runCommand(createVenvCommandVenv, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + if (result !== 0) { + _logger.warn( + "Could not create virtual environment with venv," + + "trying with virtualenv..." + ); + + result = await _runCommand(createVenvCommandVirtualenv, { cwd: zephyrWorkspaceDirectory, windowsHide: true, env: customEnv, }); + if (result !== 0) { - _logger.warn( - "Could not create virtual environment with venv," + - "trying with virtualenv..." - ); - - result = await _runCommand(createVenvCommandVirtualenv, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, + progress2.report({ + message: "Failed", + increment: 100, }); - if (result === 0) { - progress2.report({ - message: - "Successfully setup Python virtual environment for Zephyr.", - increment: 100, - }); - } else { - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - } + return false; } - - _logger.info("Venv created."); } + + progress2.report({ + message: "Success", + increment: 100, + }); + + _logger.info("Zephyr Python virtual environment created."); + + return true; } ); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return; + } const venvPythonCommand: string = joinPosix( process.env.ComSpec === "powershell.exe" ? "&" : "", - zephyrWorkspaceDirectory, - "venv", - process.platform === "win32" ? "Scripts" : "bin", - process.platform === "win32" ? "python.exe" : "python" + venvPython ); - await window.withProgress( + installedSuccessfully = await window.withProgress( { location: ProgressLocation.Notification, title: "Install Zephyr Python dependencies", cancellable: false, }, async progress2 => { - const installWestCommand: string = [ - venvPythonCommand, - "-m pip install west pyelftools", - ].join(" "); + const installWestCommand: string = + `${venvPythonCommand} -m pip install ` + "west pyelftools"; - if ( - (await _runCommand(installWestCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - })) === 0 - ) { + const installExitCode = await _runCommand(installWestCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + if (installExitCode === 0) { progress2.report({ - message: "Successfully installed Python dependencies for Zephyr.", + message: "Success", increment: 100, }); + + return true; } else { - installedSuccessfully = false; progress2.report({ message: "Failed", increment: 100, }); + + return false; } } ); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return; + } const westExe: string = joinPosix( process.env.ComSpec === "powershell.exe" ? "&" : "", @@ -708,7 +856,7 @@ export async function setupZephyr( process.platform === "win32" ? "west.exe" : "west" ); - await window.withProgress( + installedSuccessfully = await window.withProgress( { location: ProgressLocation.Notification, title: "Setting up West workspace", @@ -727,179 +875,208 @@ export async function setupZephyr( } else { _logger.info("No West workspace found. Initialising..."); - const westInitCommand: string = [westExe, "init -l manifest"].join( - " " - ); + const westInitCommand: string = `${westExe} init -l manifest`; result = await _runCommand(westInitCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, env: customEnv, }); - _logger.info(`${result}`); + _logger.info(`West workspace initialization ended with ${result}`); + + if (result !== 0) { + progress2.report({ + message: "Failed", + increment: 100, + }); + + return false; + } } - const westUpdateCommand: string = [westExe, "update"].join(" "); + const westUpdateCommand: string = `${westExe} update`; + result = await _runCommand(westUpdateCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); - if ( - (await _runCommand(westUpdateCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - })) === 0 - ) { + if (result === 0) { progress2.report({ - message: "Successfully setup West workspace.", + message: "Success", increment: 100, }); + + return true; } else { - installedSuccessfully = false; progress2.report({ message: "Failed", increment: 100, }); + + return false; } } ); - const zephyrExportCommand: string = [westExe, "zephyr-export"].join(" "); - _logger.info("Exporting Zephyr CMake Files"); + const zephyrExportCommand: string = `${westExe} zephyr-export`; + _logger.info("Exporting Zephyr CMake Files..."); + + // TODO: maybe progress result = await _runCommand(zephyrExportCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, env: customEnv, }); + if (result !== 0) { + _logger.error("Error exporting Zephyr CMake files."); + void window.showErrorMessage("Error exporting Zephyr CMake files."); + progress.report({ + message: "Failed", + increment: 100, + }); - await window.withProgress( + return; + } + + installedSuccessfully = await window.withProgress( { location: ProgressLocation.Notification, - title: "Install West Python dependencies", + title: "Installing West Python dependencies", cancellable: false, }, async progress2 => { - const westPipPackagesCommand: string = [ - westExe, - "packages pip --install", - ].join(" "); + const westPipPackagesCommand: string = + `${westExe} packages ` + "pip --install"; - if ( - (await _runCommand(westPipPackagesCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - })) === 0 - ) { + result = await _runCommand(westPipPackagesCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + + if (result === 0) { + _logger.debug("West Python dependencies installed."); progress2.report({ - message: "Successfully installed Python dependencies for West.", + message: "Success", increment: 100, }); + + return true; } else { - installedSuccessfully = false; + _logger.error("Error installing West Python dependencies."); progress2.report({ message: "Failed", increment: 100, }); + + return false; } } ); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); - await window.withProgress( + return; + } + + installedSuccessfully = await window.withProgress( { location: ProgressLocation.Notification, title: "Fetching Zephyr binary blobs", cancellable: false, }, async progress2 => { - const westBlobsFetchCommand: string = [ - westExe, - "blobs fetch hal_infineon", - ].join(" "); + const westBlobsFetchCommand: string = + `${westExe} blobs fetch ` + "hal_infineon"; - if ( - (await _runCommand(westBlobsFetchCommand, { - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - })) === 0 - ) { + result = await _runCommand(westBlobsFetchCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); + + if (result === 0) { progress2.report({ - message: "Successfully retrieved Zephyr binary blobs.", + message: "Success", increment: 100, }); + + return true; } else { - installedSuccessfully = false; progress2.report({ message: "Failed", increment: 100, }); + + return false; } } ); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return; + } - await window.withProgress( + installedSuccessfully = await window.withProgress( { location: ProgressLocation.Notification, title: "Installing Zephyr SDK", cancellable: false, }, - async progress2 => - new Promise(resolve => { - const westInstallSDKCommand: string = [ - westExe, - "sdk install -t arm-zephyr-eabi", - `-b ${zephyrWorkspaceDirectory}`, - ].join(" "); - - // This has to be a spawn due to the way the underlying SDK command calls - // subprocess and needs to inherit the Path variables set in customEnv - _logger.info("Installing Zephyr SDK"); - const child = spawnSync(westInstallSDKCommand, { - shell: true, - cwd: zephyrWorkspaceDirectory, - windowsHide: true, - env: customEnv, - }); - _logger.debug("stdout: ", child.stdout.toString()); - _logger.debug("stderr: ", child.stderr.toString()); - - if (child.status) { - _logger.debug("exit code: ", child.status); - if (child.status !== 0) { - void window.showErrorMessage( - "Error installing Zephyr SDK." + "Exiting Zephyr Setup." - ); - } - - installedSuccessfully = false; - progress2.report({ - message: "Failed", - increment: 100, - }); - resolve(); + async progress2 => { + const westInstallSDKCommand: string = + `${westExe} sdk install ` + + `-t arm-zephyr-eabi -b ${zephyrWorkspaceDirectory}`; + + result = await _runCommand(westInstallSDKCommand, { + cwd: zephyrWorkspaceDirectory, + windowsHide: true, + env: customEnv, + }); - return; - } + if (result === 0) { + progress2.report({ + message: "Success", + increment: 100, + }); + return true; + } else { progress2.report({ - message: "Successfully installed Zephyr SDK.", + message: "Failed", increment: 100, }); - resolve(); - }) - ); - if (installedSuccessfully) { - // TODO: duplicate signaling - void window.showInformationMessage("Zephyr setup complete"); + return false; + } + } + ); + if (!installedSuccessfully) { progress.report({ - message: "Zephyr setup complete.", + message: "Failed", increment: 100, }); + + return; } + + progress.report({ + message: "Complete", + increment: 100, + }); } ); - _logger.info("Complete"); + _logger.info("Zephyr setup complete."); + //void window.showInformationMessage("Zephyr setup complete"); return output; } diff --git a/src/utils/sharedConstants.mts b/src/utils/sharedConstants.mts index eddd5f2e..05a625e7 100644 --- a/src/utils/sharedConstants.mts +++ b/src/utils/sharedConstants.mts @@ -6,3 +6,20 @@ export const CURRENT_PYTHON_VERSION = "3.12.6"; export const CURRENT_DATA_VERSION = "0.18.0"; export const OPENOCD_VERSION = "0.12.0+dev"; + +export const WINDOWS_X86_DTC_DOWNLOAD_URL = + "https://github.com/oss-winget/oss-winget-storage/raw/" + + "96ea1b934342f45628a488d3b50d0c37cf06012c/packages/dtc/" + + "1.6.1/dtc-msys2-1.6.1-x86_64.zip"; +export const CURRENT_DTC_VERSION = "1.6.1"; +export const WINDOWS_X86_GPERF_DOWNLOAD_URL = + "https://github.com/oss-winget/oss-winget-storage/tree/" + + "d033d1c0fb054de32043af1d4d3be71b91c38221/packages/" + + "gperf/3.1/gperf-3.1-win64_x64.zip"; +export const CURRENT_GPERF_VERSION = "3.1"; +export const WINDOWS_X86_WGET_DOWNLOAD_URL = + "https:///eternallybored.org/misc/wget/releases/wget-1.21.4-win64.zip"; +export const CURRENT_WGET_VERSION = "1.21.4"; +export const WINDOWS_X86_7ZIP_DOWNLOAD_URL = + "https://7-zip.org/a/7z2501-x64.msi"; +export const CURRENT_7ZIP_VERSION = "25.01"; From f8d40e97058b8498acfef97a8ef5aa16754f2da0 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 18 Sep 2025 14:08:09 +0100 Subject: [PATCH 55/62] Fix zephyr integration (part 4) Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/pico_project.py | 1 + src/commands/getPaths.mts | 29 +- src/logger.mts | 1 + src/utils/projectGeneration/projectRust.mts | 2 +- src/utils/projectGeneration/projectZephyr.mts | 1172 +++++++++++++++++ src/utils/projectGeneration/zephyrFiles.mts | 463 +++++++ src/utils/setupZephyr.mts | 192 ++- src/webview/newProjectPanel.mts | 11 +- src/webview/newZephyrProjectPanel.mts | 296 +---- src/webview/sharedEnums.mts | 31 + web/zephyr/main.js | 30 + 11 files changed, 1911 insertions(+), 317 deletions(-) create mode 100644 src/utils/projectGeneration/projectZephyr.mts create mode 100644 src/utils/projectGeneration/zephyrFiles.mts create mode 100644 src/webview/sharedEnums.mts diff --git a/scripts/pico_project.py b/scripts/pico_project.py index a113dd3d..e0f2432c 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -1159,6 +1159,7 @@ def generateProjectFiles( ] } + # TODO: use get picotool path command! tasks = f"""{{ "version": "2.0.0", "tasks": [ diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index b7bac972..e474dc6c 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -28,11 +28,12 @@ import { getSupportedToolchains } from "../utils/toolchainUtil.mjs"; import Logger from "../logger.mjs"; import { rustProjectGetSelectedChip } from "../utils/rustUtil.mjs"; import { OPENOCD_VERSION } from "../utils/sharedConstants.mjs"; -import { setupZephyr } from "../utils/setupZephyr.mjs"; export class GetPythonPathCommand extends CommandWithResult { + public static readonly id = "getPythonPath"; + constructor() { - super("getPythonPath"); + super(GetPythonPathCommand.id); } async execute(): Promise { @@ -50,8 +51,10 @@ export class GetPythonPathCommand extends CommandWithResult { } export class GetEnvPathCommand extends CommandWithResult { + public static readonly id = "getEnvPath"; + constructor() { - super("getEnvPath"); + super(GetEnvPathCommand.id); } async execute(): Promise { @@ -69,8 +72,10 @@ export class GetEnvPathCommand extends CommandWithResult { } export class GetGDBPathCommand extends CommandWithResult { + public static readonly id = "getGDBPath"; + constructor(private readonly _extensionUri: Uri) { - super("getGDBPath"); + super(GetGDBPathCommand.id); } async execute(): Promise { @@ -160,8 +165,10 @@ export class GetGDBPathCommand extends CommandWithResult { } export class GetCompilerPathCommand extends CommandWithResult { + public static readonly id = "getCompilerPath"; + constructor() { - super("getCompilerPath"); + super(GetCompilerPathCommand.id); } async execute(): Promise { @@ -199,8 +206,10 @@ export class GetCompilerPathCommand extends CommandWithResult { } export class GetCxxCompilerPathCommand extends CommandWithResult { + public static readonly id = "getCxxCompilerPath"; + constructor() { - super("getCxxCompilerPath"); + super(GetCxxCompilerPathCommand.id); } async execute(): Promise { @@ -310,8 +319,10 @@ export class GetChipCommand extends CommandWithResult { } export class GetChipUppercaseCommand extends CommandWithResult { + public static readonly id = "getChipUppercase"; + constructor() { - super("getChipUppercase"); + super(GetChipUppercaseCommand.id); } async execute(): Promise { @@ -323,8 +334,10 @@ export class GetChipUppercaseCommand extends CommandWithResult { } export class GetTargetCommand extends CommandWithResult { + public static readonly id = "getTarget"; + constructor() { - super("getTarget"); + super(GetTargetCommand.id); } async execute(): Promise { diff --git a/src/logger.mts b/src/logger.mts index 93d9abe0..83796e16 100644 --- a/src/logger.mts +++ b/src/logger.mts @@ -45,6 +45,7 @@ export enum LoggerSource { rustUtil = "rustUtil", projectRust = "projectRust", zephyrSetup = "setupZephyr", + projectZephyr = "projectZephyr", } /** diff --git a/src/utils/projectGeneration/projectRust.mts b/src/utils/projectGeneration/projectRust.mts index 1e4f4719..10dd646e 100644 --- a/src/utils/projectGeneration/projectRust.mts +++ b/src/utils/projectGeneration/projectRust.mts @@ -224,7 +224,7 @@ async function generateVSCodeConfig(projectRoot: string): Promise { } catch (error) { Logger.error( LoggerSource.projectRust, - "Failed to write extensions.json file", + "Failed to write vscode configuration files:", unknownErrorToString(error) ); diff --git a/src/utils/projectGeneration/projectZephyr.mts b/src/utils/projectGeneration/projectZephyr.mts new file mode 100644 index 00000000..b70944c4 --- /dev/null +++ b/src/utils/projectGeneration/projectZephyr.mts @@ -0,0 +1,1172 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/naming-convention */ +import { join } from "path"; +import Logger, { LoggerSource } from "../../logger.mjs"; +import { unknownErrorToString } from "../errorHelper.mjs"; +import { + GetChipUppercaseCommand, + GetOpenOCDRootCommand, + GetPicotoolPathCommand, + GetTargetCommand, + GetWestPathCommand, +} from "../../commands/getPaths.mjs"; +import { extensionName } from "../../commands/command.mjs"; +import { commands, Uri, window, workspace } from "vscode"; +import { + CURRENT_7ZIP_VERSION, + CURRENT_DTC_VERSION, + CURRENT_GPERF_VERSION, + CURRENT_WGET_VERSION, +} from "../sharedConstants.mjs"; +import type { VersionBundle } from "../versionBundles.mjs"; +import { HOME_VAR } from "../../settings.mjs"; +import { TextEncoder } from "util"; +import { + BoardType, + ZephyrProjectBase, + type ZephyrSubmitMessageValue, +} from "../../webview/sharedEnums.mjs"; +import { + WIFI_HTTP_C, + WIFI_HTTP_H, + WIFI_JSON_DEFINITIONS_H, + WIFI_PING_C, + WIFI_PING_H, + WIFI_WIFI_C, + WIFI_WIFI_H, +} from "./zephyrFiles.mjs"; + +// Kconfig snippets +const spiKconfig: string = "CONFIG_SPI=y"; +const i2cKconfig: string = "CONFIG_I2C=y"; +const gpioKconfig: string = "CONFIG_GPIO=y"; +const sensorKconfig: string = "CONFIG_SENSOR=y"; +const shellKconfig: string = "CONFIG_SHELL=y"; +const wifiKconfig: string = `CONFIG_NETWORKING=y +CONFIG_TEST_RANDOM_GENERATOR=y + +CONFIG_MAIN_STACK_SIZE=5200 +CONFIG_SHELL_STACK_SIZE=5200 +CONFIG_NET_TX_STACK_SIZE=2048 +CONFIG_NET_RX_STACK_SIZE=2048 +CONFIG_LOG_BUFFER_SIZE=4096 + +CONFIG_NET_PKT_RX_COUNT=10 +CONFIG_NET_PKT_TX_COUNT=10 +CONFIG_NET_BUF_RX_COUNT=20 +CONFIG_NET_BUF_TX_COUNT=20 +CONFIG_NET_MAX_CONN=10 +CONFIG_NET_MAX_CONTEXTS=10 +CONFIG_NET_DHCPV4=y + +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=n + +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y + +CONFIG_DNS_RESOLVER=y +CONFIG_DNS_SERVER_IP_ADDRESSES=y +CONFIG_DNS_SERVER1="192.0.2.2" +CONFIG_DNS_RESOLVER_AI_MAX_ENTRIES=10 + +# Network address config +CONFIG_NET_CONFIG_AUTO_INIT=n +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_NEED_IPV4=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" + +CONFIG_NET_LOG=y +CONFIG_INIT_STACKS=y + +CONFIG_NET_STATISTICS=y +CONFIG_NET_STATISTICS_PERIODIC_OUTPUT=n + +CONFIG_HTTP_CLIENT=y + +CONFIG_WIFI=y +CONFIG_WIFI_LOG_LEVEL_ERR=y +# printing of scan results puts pressure on queues in new locking +# design in net_mgmt. So, use a higher timeout for a crowded +# environment. +CONFIG_NET_MGMT_EVENT_QUEUE_TIMEOUT=5000 +CONFIG_NET_MGMT_EVENT_QUEUE_SIZE=16`; + +// Shell Kconfig values +const shellSpiKconfig: string = "CONFIG_SPI_SHELL=y"; +const shellI2CKconfig: string = "CONFIG_I2C_SHELL=y"; +const shellGPIOKconfig: string = "CONFIG_GPIO_SHELL=y"; +const shellSensorKconfig: string = "CONFIG_SENSOR_SHELL=y"; +const shellWifiKconfig: string = `CONFIG_WIFI_LOG_LEVEL_ERR=y +CONFIG_NET_L2_WIFI_SHELL=y +CONFIG_NET_SHELL=y`; + +/** + * Convert the enum to the Zephyr board name + * + * @param e BoardType enum + * @returns string Zephyr board name + * @throws Error if unknown board type + */ +function enumToBoard(e: BoardType): string { + switch (e) { + case BoardType.pico: + return "rpi_pico"; + case BoardType.picoW: + return "rpi_pico/rp2040/w"; + case BoardType.pico2: + return "rpi_pico2/rp2350a/m33"; + case BoardType.pico2W: + return "rpi_pico2/rp2350a/m33/w"; + default: + throw new Error(`Unknown Board Type: ${e as string}`); + } +} + +async function generateVSCodeConfig( + projectRoot: string, + latestVb: [string, VersionBundle], + cmakePath: string, + te: TextEncoder = new TextEncoder(), + data: ZephyrSubmitMessageValue +): Promise { + const vsc = join(projectRoot, ".vscode"); + + // create extensions.json + const extensions = { + recommendations: [ + "marus25.cortex-debug", + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "ms-vscode.vscode-serial-monitor", + "raspberry-pi.raspberry-pi-pico", + ], + }; + + // TODO: why run, maybe to make sure installed but that should be done before! + const openOCDPath: string | undefined = await commands.executeCommand( + `${extensionName}.${GetOpenOCDRootCommand.id}` + ); + if (!openOCDPath) { + Logger.error(LoggerSource.projectRust, "Failed to get OpenOCD path"); + + void window.showErrorMessage("Failed to get OpenOCD path"); + + return false; + } + + const cppProperties = { + version: 4, + configurations: [ + { + name: "Zephyr", + intelliSenseMode: "linux-gcc-arm", + compilerPath: + // TODO: maybe move into command (the part before the executable) / test if not .exe works on win32 + "${userHome}/.pico-sdk/zephyr_workspace/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc", + includePath: [ + "${workspaceFolder}/**", + "${workspaceFolder}/build/zephyr/include", + "${userHome}/.pico-sdk/zephyr_workspace/zephyr/include", + "${userHome}/.pico-sdk/zephyr_workspace/zephyr/modules/cmsis_6", + "${userHome}/.pico-sdk/zephyr_workspace/zephyr/modules/hal_infineon", + "${userHome}/.pico-sdk/zephyr_workspace/zephyr/modules/hal_rpi_pico", + ], + compileCommands: "${workspaceFolder}/build/compile_commands.json", + cppStandard: "gnu++20", + cStandard: "gnu17", + forcedInclude: [ + "${userHome}/.pico-sdk/zephyr_workspace/zephyr/include/zephyr/devicetree.h", + "${workspaceFolder}/build/zephyr/include/generated/zephyr/autoconf.h", + "${workspaceFolder}/build/zephyr/include/generated/zephyr/version.h", + ], + }, + ], + }; + + const launch = { + version: "0.2.0", + configurations: [ + { + name: "Pico Debug (Zephyr)", + cwd: "${workspaceFolder}", + // TODO: command launch target path? + executable: "${workspaceFolder}/build/zephyr/zephyr.elf", + request: "launch", + type: "cortex-debug", + servertype: "openocd", + // TODO: maybe svd file + serverpath: `\${command:${extensionName}.${GetOpenOCDRootCommand.id}}/openocd`, + searchDir: [ + `\${command:${extensionName}.${GetOpenOCDRootCommand.id}}/scripts`, + ], + toolchainPrefix: "arm-zephyr-eabi", + armToolchainPath: + // TODO: maybe just full get zephyr compiler path command + "${command:${extensionName}.getZephyrWorkspacePath}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin", + // TODO: get chip dynamically maybe: chip: `\${command:${extensionName}.${GetChipCommand.id}}`, + // meaning only one cfg required + device: `\${command:${extensionName}.${GetChipUppercaseCommand.id}}`, + svdFile: `\${userHome}/.pico-sdk/sdk/${latestVb[0]}/src/\${command:${extensionName}.getChip}/hardware_regs/\${command:${extensionName}.${GetChipUppercaseCommand.id}}.svd`, + configFiles: [ + "interface/cmsis-dap.cfg", + `target/\${command:${extensionName}.${GetTargetCommand.id}}.cfg`, + ], + runToEntryPoint: "main", + // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected + // Also works fine for flash binaries + openOCDLaunchCommands: ["adapter speed 5000"], + }, + ], + }; + + const settings = { + "cmake.options.statusBarVisibility": "hidden", + "cmake.options.advanced": { + build: { + statusBarVisibility: "hidden", + }, + launch: { + statusBarVisibility: "hidden", + }, + debug: { + statusBarVisibility: "hidden", + }, + }, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.configureOnOpen": false, + "cmake.generator": "Ninja", + "cmake.cmakePath": "", + "C_Cpp.debugShortcut": false, + "terminal.integrated.env.windows": { + // TODO: contitionally cmake: \${env:USERPROFILE}/.pico-sdk/cmake/${latestVb.cmake}/bin + Path: `\${env:USERPROFILE}/.pico-sdk/dtc/${CURRENT_DTC_VERSION}/bin;\${env:USERPROFILE}/.pico-sdk/gperf/${CURRENT_GPERF_VERSION};\${env:USERPROFILE}/.pico-sdk/ninja/${latestVb[1].ninja};\${env:USERPROFILE}/.pico-sdk/wget/${CURRENT_WGET_VERSION};\${env:USERPROFILE}/.pico-sdk/7zip/${CURRENT_7ZIP_VERSION};\${env:PATH}`, + }, + "terminal.integrated.env.osx": { + PATH: `\${env:HOME}/.pico-sdk/dtc/${CURRENT_DTC_VERSION}/bin:\${env:HOME}/.pico-sdk/gperf/${CURRENT_GPERF_VERSION}:\${env:HOME}/.pico-sdk/ninja/${latestVb[1].ninja}:\${env:HOME}/.pico-sdk/wget:\${env:PATH}`, + }, + "terminal.integrated.env.linux": { + PATH: `\${env:HOME}/.pico-sdk/dtc/${CURRENT_DTC_VERSION}/bin:\${env:HOME}/.pico-sdk/gperf/${CURRENT_GPERF_VERSION}:\${env:HOME}/.pico-sdk/ninja/${latestVb[1].ninja}:\${env:HOME}/.pico-sdk/wget:\${env:PATH}`, + }, + "raspberry-pi-pico.cmakeAutoConfigure": true, + "raspberry-pi-pico.useCmakeTools": false, + "raspberry-pi-pico.cmakePath": "", + "raspberry-pi-pico.ninjaPath": `\${HOME}/.pico-sdk/ninja/${latestVb[1].ninja}/ninja`, + "editor.formatOnSave": true, + "search.exclude": { + "build/": true, + }, + }; + + if (cmakePath.length > 0) { + const pathWindows = cmakePath.replace(HOME_VAR, "${env:USERPROFILE}"); + const pathPosix = cmakePath.replace(HOME_VAR, "${env:HOME}"); + settings["cmake.cmakePath"] = cmakePath; + settings["raspberry-pi-pico.cmakePath"] = cmakePath; + // add to path + settings[ + "terminal.integrated.env.windows" + ].Path = `${pathWindows};${settings["terminal.integrated.env.windows"].Path}`; + settings[ + "terminal.integrated.env.osx" + ].PATH = `${pathPosix}:${settings["terminal.integrated.env.osx"].PATH}`; + settings[ + "terminal.integrated.env.linux" + ].PATH = `${pathPosix}:${settings["terminal.integrated.env.linux"].PATH}`; + } else { + // assume in PATH + settings["cmake.cmakePath"] = "cmake"; + settings["raspberry-pi-pico.cmakePath"] = "cmake"; + } + + const westArgs = [ + "build", + "-p", + "auto", + "-b", + enumToBoard(data.boardType), + "-d", + // TODO: check! "" + '"${workspaceFolder}"/build', + '"${workspaceFolder}"', + "--", + `-DOPENOCD=\${command:${extensionName}.${GetOpenOCDRootCommand.id}}/openocd`, + `-DOPENOCD_DEFAULT_PATH=\${command:${extensionName}.${GetOpenOCDRootCommand.id}}/scripts`, + ]; + + // If console is USB, use the local snippet + if (data.console === "USB") { + westArgs.unshift("-S", "usb_serial_port"); + } + + const tasks = { + version: "2.0.0", + tasks: [ + { + label: "Compile Project", + type: "shell", + isBuildCommand: true, + command: `\${command:${extensionName}.${GetWestPathCommand.id}}`, + args: westArgs, + group: { + kind: "build", + isDefault: true, + }, + presentation: { + reveal: "always", + panel: "dedicated", + }, + problemMatcher: "$gcc", + options: { + cwd: "${command:raspberry-pi-pico.getZephyrWorkspacePath}", + }, + windows: { + options: { + shell: { + executable: "cmd.exe", + args: ["/d", "/c"], + }, + }, + }, + }, + // TODO: test + { + label: "Flash", + type: "shell", + group: { + kind: "build", + }, + command: "${command:raspberry-pi-pico.getWestPath}", + args: ["flash", "--build-dir", '"${workspaceFolder}"/build'], + options: { + cwd: "${command:raspberry-pi-pico.getZephyrWorkspacePath}", + }, + }, + { + label: "Run Project", + type: "shell", + command: `\${command:${extensionName}.${GetPicotoolPathCommand.id}}`, + // TODO: support for launch target path command + args: ["load", '"${workspaceFolder}"/build/zephyr/zephyr.elf', "-fx"], + presentation: { + reveal: "always", + panel: "dedicated", + }, + problemMatcher: [], + }, + ], + }; + + try { + await workspace.fs.createDirectory(Uri.file(vsc)); + await workspace.fs.writeFile( + Uri.file(join(vsc, "extensions.json")), + te.encode(JSON.stringify(extensions, null, 2)) + ); + await workspace.fs.writeFile( + Uri.file(join(vsc, "launch.json")), + te.encode(JSON.stringify(launch, null, 2)) + ); + await workspace.fs.writeFile( + Uri.file(join(vsc, "settings.json")), + te.encode(JSON.stringify(settings, null, 2)) + ); + await workspace.fs.writeFile( + Uri.file(join(vsc, "tasks.json")), + te.encode(JSON.stringify(tasks, null, 2)) + ); + await workspace.fs.writeFile( + Uri.file(join(vsc, "c_cpp_properties.json")), + te.encode(JSON.stringify(cppProperties, null, 2)) + ); + + return true; + } catch (error) { + Logger.error( + LoggerSource.projectRust, + "Failed to write vscode configuration files:", + unknownErrorToString(error) + ); + + return false; + } +} + +async function generateWifiMainC( + projectRoot: string, + prjBase: ZephyrProjectBase, + te: TextEncoder = new TextEncoder() +): Promise { + const mainC = `/* + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +// Local includes +#include "http.h" +#include "json_definitions.h" +#include "ping.h" +#include "wifi.h" +#include "wifi_info.h" + +LOG_MODULE_REGISTER(wifi_example); + +/* HTTP server to connect to */ +const char HTTP_HOSTNAME[] = "google.com"; +const char HTTP_PATH[] = "/"; +const char JSON_HOSTNAME[] = "jsonplaceholder.typicode.com"; +const char JSON_GET_PATH[] = "/posts/1"; +const char JSON_POST_PATH[] = "/posts"; + +int main(void) +{ + printk("Starting wifi example on %s\n", CONFIG_BOARD_TARGET); + + wifi_connect(WIFI_SSID, WIFI_PSK); + + // Ping Google DNS 4 times + printk("Pinging 8.8.8.8 to demonstrate connection:\n"); + ping("8.8.8.8", 4); + + printk("Now performing http GET request to google.com...\n"); + http_get_example(HTTP_HOSTNAME, HTTP_PATH); + k_sleep(K_SECONDS(1)); + + // Using https://jsonplaceholder.typicode.com/ to demonstrate GET and POST requests with JSON + struct json_example_object get_post_result; + int json_get_status = json_get_example(JSON_HOSTNAME, JSON_GET_PATH, &get_post_result); + if (json_get_status < 0) + { + LOG_ERR("Error in json_get_example"); + } else { + printk("Got JSON result:\n"); + printk("Title: %s\n", get_post_result.title); + printk("Body: %s\n", get_post_result.body); + printk("User ID: %d\n", get_post_result.userId); + printk("ID: %d\n", get_post_result.id); + } + k_sleep(K_SECONDS(1)); + + struct json_example_object new_post_result; + struct json_example_payload new_post = { + .body = "RPi", + .title = "Pico", + .userId = 199 + }; + + json_get_status = json_post_example(JSON_HOSTNAME, JSON_POST_PATH, &new_post, &new_post_result); + if (json_get_status < 0) + { + LOG_ERR("Error in json_post_example"); + } else { + printk("Got JSON result:\n"); + printk("Title: %s\n", new_post_result.title); + printk("Body: %s\n", new_post_result.body); + printk("User ID: %d\n", new_post_result.userId); + printk("ID: %d\n", new_post_result.id); + } + k_sleep(K_SECONDS(1)); + + return 0; +} +`; + + // write the file + try { + await workspace.fs.createDirectory(Uri.file(join(projectRoot, "src"))); + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "src", "main.c")), + te.encode(mainC) + ); + + return true; + } catch (error) { + Logger.error( + LoggerSource.projectZephyr, + "Failed to write main.c file", + unknownErrorToString(error) + ); + + return false; + } +} + +async function generateMainC( + projectRoot: string, + prjBase: ZephyrProjectBase, + te: TextEncoder = new TextEncoder() +): Promise { + if (prjBase === ZephyrProjectBase.wifi) { + return generateWifiMainC(projectRoot, prjBase, te); + } + + const isBlinky = prjBase === ZephyrProjectBase.blinky; + + let mainC = `#include +#include +`; + + if (isBlinky) { + mainC = mainC.concat("#include \n"); + } + mainC = mainC.concat("\n"); + + mainC = mainC.concat( + "LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL);\n\n" + ); + + if (isBlinky) { + mainC = mainC.concat("#define LED0_NODE DT_ALIAS(led0)\n\n"); + mainC = mainC.concat( + "static const struct gpio_dt_spec led = " + + "GPIO_DT_SPEC_GET(LED0_NODE, gpios);\n\n" + ); + } + + mainC = mainC.concat("void main(void) {\n"); + + if (isBlinky) { + mainC = mainC.concat(' printk("Hello World! Blinky sample\\n");\n\n'); + } else { + mainC = mainC.concat(' printk("Hello World from Pico!\\n");\n\n'); + } + + if (isBlinky) { + const blinkySetup = ` bool led_state = false; + + if (!gpio_is_ready_dt(&led)) { + return 0; + } + + if (gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE) < 0) { + return 0; + } + +`; + mainC = mainC.concat(blinkySetup); + } + + // main loop + mainC = mainC.concat(" while (1) {\n"); + + if (isBlinky) { + const blinkyLoop = ` if (gpio_pin_toggle_dt(&led) < 0) { + return 0; + } + + led_state = !led_state; + printk("LED state: %s\n", led_state ? "ON" : "OFF"); + +`; + mainC = mainC.concat(blinkyLoop); + } else { + mainC = mainC.concat( + ' printk("Running on %s...\n", CONFIG_BOARD);\n\n' + ); + } + + mainC = mainC.concat(" k_sleep(K_MSEC(1000));\n}\n\n"); + mainC = mainC.concat(" return 0;\n}\n"); + + // write the file + try { + await workspace.fs.createDirectory(Uri.file(join(projectRoot, "src"))); + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "src", "main.c")), + te.encode(mainC) + ); + + return true; + } catch (error) { + Logger.error( + LoggerSource.projectZephyr, + "Failed to write main.c file", + unknownErrorToString(error) + ); + + return false; + } +} + +async function generateAdditionalCodeFiles( + projectRoot: string, + prjBase: ZephyrProjectBase, + te: TextEncoder = new TextEncoder() +): Promise { + if (prjBase !== ZephyrProjectBase.wifi) { + return true; + } + + const wifiInfoTemplate = `// Fill in your WiFi information here +#define WIFI_SSID "" +#define WIFI_PSK "" +`; + + try { + await workspace.fs.createDirectory(Uri.file(join(projectRoot, "src"))); + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "src", "http.c")), + te.encode(WIFI_HTTP_C) + ); + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "src", "http.h")), + te.encode(WIFI_HTTP_H) + ); + + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "src", "json_definitions.h")), + te.encode(WIFI_JSON_DEFINITIONS_H) + ); + + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "src", "ping.c")), + te.encode(WIFI_PING_C) + ); + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "src", "ping.h")), + te.encode(WIFI_PING_H) + ); + + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "src", "wifi.c")), + te.encode(WIFI_WIFI_C) + ); + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "src", "wifi.h")), + te.encode(WIFI_WIFI_H) + ); + + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "src", "wifi_info.h")), + te.encode(wifiInfoTemplate) + ); + + return true; + } catch (error) { + Logger.error( + LoggerSource.projectZephyr, + "Failed to write additional code files", + unknownErrorToString(error) + ); + + return false; + } +} + +async function generateGitIgnore( + projectRoot: string, + te: TextEncoder = new TextEncoder(), + projBase: ZephyrProjectBase +): Promise { + let gitIgnore = `# Created by https://www.toptal.com/developers/gitignore/api/c,cmake,visualstudiocode,ninja,windows,macos,linux +# Edit at https://www.toptal.com/developers/gitignore?templates=c,cmake,visualstudiocode,ninja,windows,macos,linux + +### C ### +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +### CMake Patch ### +CMakeUserPresets.json + +# External projects +*-prefix/ + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Ninja ### +.ninja_deps +.ninja_log + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/c,cmake,visualstudiocode,ninja,windows,macos,linux +`; + + if (projBase === ZephyrProjectBase.wifi) { + gitIgnore = gitIgnore.concat("\r\n", "wifi_info.h\r\n"); + } + + try { + await workspace.fs.writeFile( + Uri.file(join(projectRoot, ".gitignore")), + te.encode(gitIgnore) + ); + + return true; + } catch (error) { + Logger.error( + LoggerSource.projectRust, + "Failed to write .gitignore file", + unknownErrorToString(error) + ); + + return false; + } +} + +async function generateCMakeList( + projectRoot: string, + te: TextEncoder = new TextEncoder(), + data: ZephyrSubmitMessageValue +): Promise { + // TODO: maybe dynamic check cmake minimum cmake version on cmake selection + // TODO: license notice required anymore? + let cmakeList = `#------------------------------------------------------------------------------- +# Zephyr Example Application +# +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +#------------------------------------------------------------------------------- + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(${data.projectName} LANGUAGES C) +`; + + const appSources = ["src/main.c"]; + + if (data.projectBase === ZephyrProjectBase.wifi) { + cmakeList = cmakeList.concat("\nFILE(GLOB app_sources src/*.c)\n"); + appSources.push("src/http.c"); + appSources.push("src/ping.c"); + appSources.push("src/wifi.c"); + } + + cmakeList = cmakeList.concat( + `\ntarget_sources(app PRIVATE ${appSources.join(" ")})\n` + ); + + try { + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "CMakeLists.txt")), + te.encode(cmakeList) + ); + + return true; + } catch (error) { + Logger.error( + LoggerSource.projectZephyr, + "Failed to write CMakeLists.txt file", + unknownErrorToString(error) + ); + + return false; + } +} + +async function generateConf( + projectRoot: string, + prjBase: ZephyrProjectBase, + te: TextEncoder = new TextEncoder(), + data: ZephyrSubmitMessageValue +): Promise { + let conf = `CONFIG_LOG=y +CONFIG_LOG_PRINTK=y +CONFIG_LOG_DEFAULT_LEVEL=3 +`; + + // TODO: already do in UI + if (prjBase === ZephyrProjectBase.blinky) { + data.gpioFeature = true; + } + + conf = conf.concat( + "\r\n", + "\r\n", + "# Enable Modules:", + "\r\n", + data.gpioFeature ? gpioKconfig + "\r\n" : "", + data.i2cFeature ? i2cKconfig + "\r\n" : "", + data.spiFeature ? spiKconfig + "\r\n" : "", + data.sensorFeature ? sensorKconfig + "\r\n" : "", + data.wifiFeature ? wifiKconfig + "\r\n" : "", + data.shellFeature ? "\r\n" + "# Enabling shells:" + "\r\n" : "", + data.shellFeature ? shellKconfig + "\r\n" : "", + data.shellFeature && data.gpioFeature ? shellGPIOKconfig + "\r\n" : "", + data.shellFeature && data.i2cFeature ? shellI2CKconfig + "\r\n" : "", + data.shellFeature && data.spiFeature ? shellSpiKconfig + "\r\n" : "", + data.shellFeature && data.sensorFeature ? shellSensorKconfig + "\r\n" : "", + data.shellFeature && data.wifiFeature ? shellWifiKconfig + "\r\n" : "" + ); + + try { + await workspace.fs.writeFile( + Uri.file(join(projectRoot, "prj.conf")), + te.encode(conf) + ); + + return true; + } catch (error) { + Logger.error( + LoggerSource.projectZephyr, + "Failed to write prj.conf file", + unknownErrorToString(error) + ); + + return false; + } +} + +async function addSnippets( + projectRoot: string, + te: TextEncoder = new TextEncoder(), + data: ZephyrSubmitMessageValue +): Promise { + // check for all snippet options if we should even continue + if (data.console !== "USB") { + return true; + } + + const usbSerialSnippetYml = `name: usb_serial_port +append: + EXTRA_CONF_FILE: usb_serial_port.conf + EXTRA_DTC_OVERLAY_FILE: usb_serial_port.overlay +`; + + const usbSerialPortConf = `CONFIG_USB_DEVICE_STACK=y +CONFIG_USB_DEVICE_PRODUCT="Raspberry Pi Pico Example for Zephyr" +CONFIG_USB_DEVICE_VID=0x2E8A +CONFIG_USB_DEVICE_PID=0x0001 + +CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y + +CONFIG_SERIAL=y +CONFIG_UART_LINE_CTRL=y + +CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y +CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y +CONFIG_USB_CDC_ACM_LOG_LEVEL_ERR=y +`; + + const usbSerialPortOverlay = `/ { + chosen { + zephyr,console = &usb_serial_port; + zephyr,shell-uart = &usb_serial_port; + }; +}; + +&zephyr_udc0 { + usb_serial_port: usb_serial_port { + compatible = "zephyr,cdc-acm-uart"; + }; +}; +`; + + try { + await workspace.fs.createDirectory(Uri.file(join(projectRoot, "snippets"))); + + if (data.console === "USB") { + const usbSerialFolder = join(projectRoot, "snippets", "usb_serial_port"); + await workspace.fs.createDirectory(Uri.file(usbSerialFolder)); + + await workspace.fs.writeFile( + Uri.file(join(usbSerialFolder, "snippet.yml")), + te.encode(usbSerialSnippetYml) + ); + await workspace.fs.writeFile( + Uri.file(join(usbSerialFolder, "usb_serial_port.conf")), + te.encode(usbSerialPortConf) + ); + await workspace.fs.writeFile( + Uri.file(join(usbSerialFolder, "usb_serial_port.overlay")), + te.encode(usbSerialPortOverlay) + ); + } + + return true; + } catch (error) { + Logger.error( + LoggerSource.projectZephyr, + "Failed to write code snippets:", + unknownErrorToString(error) + ); + + return false; + } +} + +/** + * Generates a new Zephyr project in the given folder. + * + * @param projectFolder The path where the project folder should be created. + * @param projectName The name of the project folder to create. + * @param prjBase The base template to use for the project. + * @returns True if the project was created successfully, false otherwise. + */ +export async function generateZephyrProject( + projectFolder: string, + projectName: string, + latestVb: [string, VersionBundle], + cmakePath: string, + data: ZephyrSubmitMessageValue +): Promise { + const projectRoot = join(projectFolder, projectName); + + try { + await workspace.fs.createDirectory(Uri.file(projectRoot)); + } catch (error) { + const msg = unknownErrorToString(error); + if ( + msg.includes("EPERM") || + msg.includes("EACCES") || + msg.includes("access denied") + ) { + Logger.error( + LoggerSource.projectRust, + "Failed to create project folder", + "Permission denied. Please check your permissions." + ); + + void window.showErrorMessage( + "Failed to create project folder. " + + "Permission denied - Please check your permissions." + ); + } else { + Logger.error( + LoggerSource.projectRust, + "Failed to create project folder", + unknownErrorToString(error) + ); + + void window.showErrorMessage( + "Failed to create project folder. " + + "See the output panel for more details." + ); + } + + return false; + } + + const te = new TextEncoder(); + + let result = await generateGitIgnore(projectRoot, te, data.projectBase); + if (!result) { + Logger.debug( + LoggerSource.projectRust, + "Failed to generate .gitignore file" + ); + + return false; + } + + result = await generateMainC(projectRoot, data.projectBase, te); + if (!result) { + Logger.debug(LoggerSource.projectRust, "Failed to generate main.c file"); + + return false; + } + + result = await generateAdditionalCodeFiles(projectRoot, data.projectBase, te); + if (!result) { + Logger.debug( + LoggerSource.projectRust, + "Failed to generate additional code files" + ); + + return false; + } + + result = await generateVSCodeConfig( + projectRoot, + latestVb, + cmakePath, + te, + data + ); + if (!result) { + Logger.debug( + LoggerSource.projectRust, + "Failed to generate .vscode configuration files." + ); + + return false; + } + + result = await generateCMakeList(projectRoot, te, data); + if (!result) { + Logger.debug( + LoggerSource.projectRust, + "Failed to generate CMakeLists.txt file" + ); + + return false; + } + + result = await generateConf(projectRoot, data.projectBase, te, data); + if (!result) { + Logger.debug(LoggerSource.projectRust, "Failed to generate prj.conf file"); + + return false; + } + + result = await addSnippets(projectRoot, te, data); + if (!result) { + Logger.debug( + LoggerSource.projectRust, + "Failed to generate code snippets file" + ); + + return false; + } + + return true; +} diff --git a/src/utils/projectGeneration/zephyrFiles.mts b/src/utils/projectGeneration/zephyrFiles.mts new file mode 100644 index 00000000..eda243ac --- /dev/null +++ b/src/utils/projectGeneration/zephyrFiles.mts @@ -0,0 +1,463 @@ +/* eslint-disable max-len */ + +export const WIFI_HTTP_C = `#include "http.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "json_definitions.h" + +LOG_MODULE_REGISTER(http); + +#define HTTP_PORT "80" + +static K_SEM_DEFINE(json_response_complete, 0, 1); +static K_SEM_DEFINE(http_response_complete, 0, 1); +static const char * json_post_headers[] = { "Content-Type: application/json\r\n", NULL }; + +// Holds the HTTP response +static char response_buffer[2048]; + +// Holds the JSON payload +char json_payload_buffer[128]; + +// Keeps track of JSON parsing result +static struct json_example_object * returned_placeholder_post = NULL; +static int json_parse_result = -1; + +// void http_get(const char * hostname, const char * path); + +int connect_socket(const char * hostname) +{ + static struct addrinfo hints; + struct addrinfo *res; + int st, sock; + + LOG_DBG("Looking up IP addresses:"); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + st = getaddrinfo(hostname, HTTP_PORT, &hints, &res); + if (st != 0) { + LOG_ERR("Unable to resolve address, quitting"); + return -1; + } + LOG_DBG("getaddrinfo status: %d", st); + + dump_addrinfo(res); + + sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock < 0) + { + LOG_ERR("Issue setting up socket: %d", sock); + return -1; + } + LOG_DBG("sock = %d", sock); + + LOG_INF("Connecting to server..."); + int connect_result = connect(sock, res->ai_addr, res->ai_addrlen); + if (connect_result != 0) + { + LOG_ERR("Issue during connect: %d", sock); + return -1; + } + + return sock; +} + +static void http_response_cb(struct http_response *rsp, + enum http_final_call final_data, + void *user_data) +{ + printk("HTTP Callback: %.*s", rsp->data_len, rsp->recv_buf); + + if (HTTP_DATA_FINAL == final_data){ + printk("\n"); + k_sem_give(&http_response_complete); + } +} + +void http_get_example(const char * hostname, const char * path) +{ + int sock = connect_socket(hostname); + if (sock < 0) + { + LOG_ERR("Issue setting up socket: %d", sock); + return; + } + + LOG_INF("Connected. Making HTTP request..."); + + struct http_request req = { 0 }; + int ret; + + req.method = HTTP_GET; + req.host = hostname; + req.url = path; + req.protocol = "HTTP/1.1"; + req.response = http_response_cb; + req.recv_buf = response_buffer; + req.recv_buf_len = sizeof(response_buffer); + + /* sock is a file descriptor referencing a socket that has been connected + * to the HTTP server. + */ + ret = http_client_req(sock, &req, 5000, NULL); + LOG_INF("HTTP Client Request returned: %d", ret); + if (ret < 0) + { + LOG_ERR("Error sending HTTP Client Request"); + return; + } + + k_sem_take(&http_response_complete, K_FOREVER); + + LOG_INF("HTTP GET complete"); + + LOG_INF("Close socket"); + + (void)close(sock); +} + +static void json_response_cb(struct http_response *rsp, + enum http_final_call final_data, + void *user_data) +{ + LOG_DBG("JSON Callback: %.*s", rsp->data_len, rsp->recv_buf); + + if (rsp->body_found) + { + LOG_DBG("Body:"); + printk("%.*s\n", rsp->body_frag_len, rsp->body_frag_start); + + if (returned_placeholder_post != NULL) + { + json_parse_result = json_obj_parse( + rsp->body_frag_start, + rsp->body_frag_len, + json_example_object_descr, + ARRAY_SIZE(json_example_object_descr), + returned_placeholder_post + ); + + if (json_parse_result < 0) + { + LOG_ERR("JSON Parse Error: %d", json_parse_result); + } + else + { + LOG_DBG("json_obj_parse return code: %d", json_parse_result); + LOG_DBG("Title: %s", returned_placeholder_post->title); + LOG_DBG("Body: %s", returned_placeholder_post->body); + LOG_DBG("User ID: %d", returned_placeholder_post->id); + LOG_DBG("ID: %d", returned_placeholder_post->userId); + } + } else { + LOG_ERR("No pointer passed to copy JSON GET result to"); + } + } + + if (HTTP_DATA_FINAL == final_data){ + k_sem_give(&json_response_complete); + } +} + +int json_get_example(const char * hostname, const char * path, struct json_example_object * result) +{ + json_parse_result = -1; + returned_placeholder_post = result; + + int sock = connect_socket(hostname); + if (sock < 0) + { + LOG_ERR("Issue setting up socket: %d", sock); + return -1; + } + + LOG_INF("Connected. Get JSON Payload..."); + + struct http_request req = { 0 }; + int ret; + + req.method = HTTP_GET; + req.host = hostname; + req.url = path; + req.protocol = "HTTP/1.1"; + req.response = json_response_cb; + req.recv_buf = response_buffer; + req.recv_buf_len = sizeof(response_buffer); + + /* sock is a file descriptor referencing a socket that has been connected + * to the HTTP server. + */ + ret = http_client_req(sock, &req, 5000, NULL); + LOG_INF("HTTP Client Request returned: %d", ret); + if (ret < 0) + { + LOG_ERR("Error sending HTTP Client Request"); + return -1; + } + + k_sem_take(&json_response_complete, K_FOREVER); + + LOG_INF("JSON Response complete"); + + LOG_INF("Close socket"); + + (void)close(sock); + + return json_parse_result; +} + +int json_post_example(const char * hostname, const char * path, struct json_example_payload * payload, struct json_example_object * result) +{ + json_parse_result = -1; + returned_placeholder_post = result; + + int sock = connect_socket(hostname); + if (sock < 0) + { + LOG_ERR("Issue setting up socket: %d", sock); + return -1; + } + + LOG_INF("Connected. Post JSON Payload..."); + + // Parse the JSON object into a buffer + int required_buffer_len = json_calc_encoded_len( + json_example_payload_descr, + ARRAY_SIZE(json_example_payload_descr), + payload + ); + if (required_buffer_len > sizeof(json_payload_buffer)) + { + LOG_ERR("Payload is too large. Increase size of json_payload_buffer in http.c"); + return -1; + } + + int encode_status = json_obj_encode_buf( + json_example_payload_descr, + ARRAY_SIZE(json_example_payload_descr), + payload, + json_payload_buffer, + sizeof(json_payload_buffer) + ); + if (encode_status < 0) + { + LOG_ERR("Error encoding JSON payload: %d", encode_status); + return -1; + } + + LOG_DBG("%s", json_payload_buffer); + + struct http_request req = { 0 }; + int ret; + + req.method = HTTP_POST; + req.host = hostname; + req.url = path; + req.header_fields = json_post_headers; + req.protocol = "HTTP/1.1"; + req.response = json_response_cb; + req.payload = json_payload_buffer; + req.payload_len = strlen(json_payload_buffer); + req.recv_buf = response_buffer; + req.recv_buf_len = sizeof(response_buffer); + + ret = http_client_req(sock, &req, 5000, NULL); + LOG_INF("HTTP Client Request returned: %d", ret); + if (ret < 0) + { + LOG_ERR("Error sending HTTP Client Request"); + return -1; + } + + k_sem_take(&json_response_complete, K_FOREVER); + + LOG_INF("JSON POST complete"); + + LOG_INF("Close socket"); + + (void)close(sock); + + return json_parse_result; +} + +void dump_addrinfo(const struct addrinfo *ai) +{ + LOG_INF("addrinfo @%p: ai_family=%d, ai_socktype=%d, ai_protocol=%d, " + "sa_family=%d, sin_port=%x", + ai, ai->ai_family, ai->ai_socktype, ai->ai_protocol, ai->ai_addr->sa_family, + ntohs(((struct sockaddr_in *)ai->ai_addr)->sin_port)); +} +`; + +export const WIFI_HTTP_H = `#pragma once + +#include +#include + +#include "json_definitions.h" + +void dump_addrinfo(const struct addrinfo *ai); + +void http_get_example(const char * hostname, const char * path); + +int json_get_example(const char * hostname, const char * path, struct json_example_object * result); + +int json_post_example(const char * hostname, const char * path, struct json_example_payload * payload, struct json_example_object * result); +`; + +export const WIFI_JSON_DEFINITIONS_H = `#pragma once + +#include + +struct json_example_object { + const char *title; + const char *body; + int id; + int userId; +}; + +static const struct json_obj_descr json_example_object_descr[] = { + JSON_OBJ_DESCR_PRIM(struct json_example_object, title, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct json_example_object, body, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct json_example_object, id, JSON_TOK_NUMBER), + JSON_OBJ_DESCR_PRIM(struct json_example_object, userId, JSON_TOK_NUMBER), +}; + +struct json_example_payload { + const char *title; + const char *body; + int userId; +}; + +static const struct json_obj_descr json_example_payload_descr[] = { + JSON_OBJ_DESCR_PRIM(struct json_example_payload, title, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct json_example_payload, body, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct json_example_payload, userId, JSON_TOK_NUMBER), +}; +`; + +export const WIFI_PING_C = `#include "ping.h" + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ping); + +int icmp_echo_reply_handler(struct net_icmp_ctx *ctx, + struct net_pkt *pkt, + struct net_icmp_ip_hdr *hdr, + struct net_icmp_hdr *icmp_hdr, + void *user_data) +{ + uint32_t cycles; + char ipv4[INET_ADDRSTRLEN]; + zsock_inet_ntop(AF_INET, &hdr->ipv4->src, ipv4, INET_ADDRSTRLEN); + + uint32_t *start_cycles = user_data; + + cycles = k_cycle_get_32() - *start_cycles; + + LOG_INF("Reply from %s: bytes=%d time=%dms TTL=%d", + ipv4, + ntohs(hdr->ipv4->len), + ((uint32_t)k_cyc_to_ns_floor64(cycles) / 1000000), + hdr->ipv4->ttl); + + return 0; +} + +void ping(char* ipv4_addr, uint8_t count) +{ + uint32_t cycles; + int ret; + struct net_icmp_ctx icmp_context; + + // Register handler for echo reply + ret = net_icmp_init_ctx(&icmp_context, NET_ICMPV4_ECHO_REPLY, 0, icmp_echo_reply_handler); + if (ret != 0) { + LOG_ERR("Failed to init ping, err: %d", ret); + } + + struct net_if *iface = net_if_get_default(); + struct sockaddr_in dst_addr; + net_addr_pton(AF_INET, ipv4_addr, &dst_addr.sin_addr); + dst_addr.sin_family = AF_INET; + + for (int i = 0; i < count; i++) + { + cycles = k_cycle_get_32(); + ret = net_icmp_send_echo_request(&icmp_context, iface, (struct sockaddr *)&dst_addr, NULL, &cycles); + if (ret != 0) { + LOG_ERR("Failed to send ping, err: %d", ret); + } + k_sleep(K_SECONDS(1)); + } + + net_icmp_cleanup_ctx(&icmp_context); +} +`; + +export const WIFI_PING_H = `#pragma once + +#include + +void ping(char* ipv4_addr, uint8_t count); +`; + +export const WIFI_WIFI_C = `#include "wifi.h" + +#include +#include + +LOG_MODULE_REGISTER(wifi); + +void wifi_connect(const char * ssid, const char * psk) +{ + struct net_if *iface = net_if_get_default(); + struct wifi_connect_req_params cnx_params = { 0 }; + + cnx_params.ssid = ssid; + cnx_params.ssid_length = strlen(cnx_params.ssid); + cnx_params.psk = psk; + cnx_params.psk_length = strlen(cnx_params.psk); + cnx_params.security = WIFI_SECURITY_TYPE_NONE; + cnx_params.band = WIFI_FREQ_BAND_UNKNOWN; + cnx_params.channel = WIFI_CHANNEL_ANY; + cnx_params.mfp = WIFI_MFP_OPTIONAL; + cnx_params.wpa3_ent_mode = WIFI_WPA3_ENTERPRISE_NA; + cnx_params.eap_ver = 1; + cnx_params.bandwidth = WIFI_FREQ_BANDWIDTH_20MHZ; + cnx_params.verify_peer_cert = false; + + int connection_result = 1; + + while (connection_result != 0){ + LOG_INF("Attempting to connect to network %s", ssid); + connection_result = net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, + &cnx_params, sizeof(struct wifi_connect_req_params)); + if (connection_result) { + LOG_ERR("Connection request failed with error: %d\n", connection_result); + } + k_sleep(K_MSEC(1000)); + } + + LOG_INF("Connection succeeded."); +} +`; + +export const WIFI_WIFI_H = `#pragma once + +void wifi_connect(const char * ssid, const char * psk); +`; diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts index 18373b8e..56955b48 100644 --- a/src/utils/setupZephyr.mts +++ b/src/utils/setupZephyr.mts @@ -1,6 +1,6 @@ import { existsSync, readdirSync, rmSync } from "fs"; import { window, workspace, ProgressLocation, Uri } from "vscode"; -import { type ExecOptions, exec, spawnSync } from "child_process"; +import { type ExecOptions, exec } from "child_process"; import { dirname, join } from "path"; import { join as joinPosix } from "path/posix"; import { homedir, tmpdir } from "os"; @@ -15,6 +15,7 @@ import { downloadAndInstallNinja, downloadAndInstallOpenOCD, downloadAndInstallPicotool, + downloadAndInstallSDK, downloadFileGot, } from "../utils/download.mjs"; import Settings, { HOME_VAR } from "../settings.mjs"; @@ -32,8 +33,23 @@ import { WINDOWS_X86_GPERF_DOWNLOAD_URL, WINDOWS_X86_WGET_DOWNLOAD_URL, } from "./sharedConstants.mjs"; +import { SDK_REPOSITORY_URL } from "./githubREST.mjs"; -const _logger = new Logger("zephyrSetup"); +interface ZephyrSetupValue { + cmakeMode: number; + cmakePath: string; + cmakeVersion: string; + extUri: Uri; +} + +interface ZephyrSetupOutputs { + cmakeExecutable: string; + gitPath: string; + latestVb: [string, VersionBundle]; +} + +// Compute and cache the home directory +const homeDirectory: string = homedir(); const zephyrManifestContent: string = ` manifest: @@ -42,12 +58,11 @@ manifest: remotes: - name: zephyrproject-rtos - url-base: https://github.com/magpieembedded + url-base: https://github.com/zephyrproject-rtos projects: - name: zephyr remote: zephyrproject-rtos - revision: pico2w import: # By using name-allowlist we can clone only the modules that are # strictly needed by the application. @@ -57,20 +72,6 @@ manifest: - hal_infineon # required for Wifi chip support `; -interface ZephyrSetupValue { - cmakeMode: number; - cmakePath: string; - cmakeVersion: string; - extUri: Uri; -} - -interface ZephyrSetupOutputs { - cmakeExecutable: string; -} - -// Compute and cache the home directory -const homeDirectory: string = homedir(); - // TODO: maybe move into download.mts function buildDtcPath(version: string): string { return joinPosix( @@ -114,11 +115,12 @@ function generateCustomEnv( const customPath = [ dirname(cmakeExe), join(homedir(), ".pico-sdk", "dtc", CURRENT_DTC_VERSION, "bin"), + // TODO: better git path join(homedir(), ".pico-sdk", "git", "cmd"), join(homedir(), ".pico-sdk", "gperf", CURRENT_GPERF_VERSION, "bin"), join(homedir(), ".pico-sdk", "ninja", latestVb.ninja), dirname(pythonExe), - join(homedir(), ".pico-sdk", "wget"), + join(homedir(), ".pico-sdk", "wget", CURRENT_WGET_VERSION), join(homedir(), ".pico-sdk", "7zip", CURRENT_7ZIP_VERSION), "", // Need this to add separator to end ].join(process.platform === "win32" ? ";" : ":"); @@ -134,14 +136,17 @@ function _runCommand( command: string, options: ExecOptions ): Promise { - _logger.debug(`Running: ${command}`); + Logger.debug(LoggerSource.zephyrSetup, `Running: ${command}`); return new Promise(resolve => { const proc = exec(command, options, (error, stdout, stderr) => { - _logger.debug(stdout); - _logger.error(stderr); + Logger.debug(LoggerSource.zephyrSetup, stdout); + Logger.debug(LoggerSource.zephyrSetup, stderr); if (error) { - _logger.error(`Setup venv error: ${error.message}`); + Logger.error( + LoggerSource.zephyrSetup, + `Setup venv error: ${error.message}` + ); resolve(null); // indicate error } }); @@ -153,12 +158,12 @@ function _runCommand( }); } -async function checkGit(): Promise { +async function checkGit(): Promise { const settings = Settings.getInstance(); if (settings === undefined) { - _logger.error("Settings not initialized."); + Logger.error(LoggerSource.zephyrSetup, "Settings not initialized."); - return false; + return; } return window.withProgress( @@ -176,7 +181,7 @@ async function checkGit(): Promise { increment: 100, }); - return false; + return; } progress2.report({ @@ -184,7 +189,7 @@ async function checkGit(): Promise { increment: 100, }); - return true; + return gitPath; } ); } @@ -338,6 +343,47 @@ async function checkPicotool(latestVb: VersionBundle): Promise { ); } +async function checkSdk( + latestVb: [string, VersionBundle], + python3Path: string +): Promise { + return window.withProgress( + { + location: ProgressLocation.Notification, + title: "Downloading and installing Pico SDK", + cancellable: false, + }, + async progress => { + const result = await downloadAndInstallSDK( + latestVb[0], + SDK_REPOSITORY_URL, + python3Path + ); + + if (!result) { + Logger.error( + LoggerSource.zephyrSetup, + "Failed to download and install the Pico SDK." + ); + + progress.report({ + message: "Failed", + increment: 100, + }); + + return false; + } else { + progress.report({ + message: "Success", + increment: 100, + }); + + return true; + } + } + ); +} + async function checkDtc(): Promise { return window.withProgress( { @@ -453,14 +499,11 @@ async function check7Zip(): Promise { cancellable: false, }, async progress => { - _logger.info("Installing 7-Zip"); - const targetDirectory = joinPosix("C:\\Program Files\\7-Zip"); + Logger.info(LoggerSource.zephyrSetup, "Installing 7-Zip"); + const installDir = build7ZipPathWin32(CURRENT_7ZIP_VERSION); - if ( - existsSync(targetDirectory) && - readdirSync(targetDirectory).length !== 0 - ) { - _logger.info("7-Zip is already installed."); + if (existsSync(installDir) && readdirSync(installDir).length !== 0) { + Logger.info(LoggerSource.zephyrSetup, "7-Zip is already installed."); progress.report({ message: "7-Zip already installed.", @@ -468,6 +511,9 @@ async function check7Zip(): Promise { }); return true; + } else if (existsSync(installDir)) { + // Remove existing empty directory + rmSync(installDir, { recursive: true, force: true }); } const binName = `7z${CURRENT_7ZIP_VERSION}-x64.msi`; @@ -487,7 +533,6 @@ async function check7Zip(): Promise { return false; } - const installDir = build7ZipPathWin32(CURRENT_7ZIP_VERSION); const installResult = await _runCommand( `msiexec /i ${binName} INSTALLDIR="${installDir}" /quiet /norestart`, { @@ -588,18 +633,26 @@ async function checkOpenOCD(): Promise { export async function setupZephyr( data: ZephyrSetupValue ): Promise { - _logger.info("Setting up Zephyr..."); + Logger.info(LoggerSource.zephyrSetup, "Setting up Zephyr..."); const latestVb = await new VersionBundlesLoader(data.extUri).getLatest(); if (latestVb === undefined) { - _logger.error("Failed to get latest version bundles."); + Logger.error( + LoggerSource.zephyrSetup, + "Failed to get latest version bundles." + ); void window.showErrorMessage( "Failed to get latest version bundles. Cannot continue Zephyr setup." ); return; } - const output: ZephyrSetupOutputs = { cmakeExecutable: "" }; + + const output: ZephyrSetupOutputs = { + cmakeExecutable: "", + gitPath: "", + latestVb, + }; let isWindows = false; await window.withProgress( @@ -610,8 +663,8 @@ export async function setupZephyr( async progress => { let installedSuccessfully = true; - installedSuccessfully = await checkGit(); - if (!installedSuccessfully) { + const gitPath = await checkGit(); + if (gitPath === undefined) { progress.report({ message: "Failed", increment: 100, @@ -619,6 +672,7 @@ export async function setupZephyr( return; } + output.gitPath = gitPath; // Handle CMake install const cmakePath = await checkCmake( @@ -672,6 +726,17 @@ export async function setupZephyr( return; } + // required for svd files + const sdk = await checkSdk(latestVb, python3Path); + if (!sdk) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return; + } + isWindows = process.platform === "win32"; installedSuccessfully = await checkWindowsDeps(isWindows); @@ -749,7 +814,8 @@ export async function setupZephyr( }, async progress2 => { if (existsSync(venvPython)) { - _logger.info( + Logger.info( + LoggerSource.zephyrSetup, "Existing Python virtual environment for Zephyr found." ); @@ -762,7 +828,8 @@ export async function setupZephyr( env: customEnv, }); if (result !== 0) { - _logger.warn( + Logger.warn( + LoggerSource.zephyrSetup, "Could not create virtual environment with venv," + "trying with virtualenv..." ); @@ -788,7 +855,10 @@ export async function setupZephyr( increment: 100, }); - _logger.info("Zephyr Python virtual environment created."); + Logger.info( + LoggerSource.zephyrSetup, + "Zephyr Python virtual environment created." + ); return true; } @@ -871,9 +941,15 @@ export async function setupZephyr( x => x[0] === ".west" ); if (westAlreadyExists) { - _logger.info("West workspace already initialised."); + Logger.info( + LoggerSource.zephyrSetup, + "West workspace already initialised." + ); } else { - _logger.info("No West workspace found. Initialising..."); + Logger.info( + LoggerSource.zephyrSetup, + "No West workspace found. Initialising..." + ); const westInitCommand: string = `${westExe} init -l manifest`; result = await _runCommand(westInitCommand, { @@ -882,7 +958,10 @@ export async function setupZephyr( env: customEnv, }); - _logger.info(`West workspace initialization ended with ${result}`); + Logger.info( + LoggerSource.zephyrSetup, + `West workspace initialization ended with ${result}` + ); if (result !== 0) { progress2.report({ @@ -920,7 +999,7 @@ export async function setupZephyr( ); const zephyrExportCommand: string = `${westExe} zephyr-export`; - _logger.info("Exporting Zephyr CMake Files..."); + Logger.info(LoggerSource.zephyrSetup, "Exporting Zephyr CMake Files..."); // TODO: maybe progress result = await _runCommand(zephyrExportCommand, { @@ -929,7 +1008,10 @@ export async function setupZephyr( env: customEnv, }); if (result !== 0) { - _logger.error("Error exporting Zephyr CMake files."); + Logger.error( + LoggerSource.zephyrSetup, + "Error exporting Zephyr CMake files." + ); void window.showErrorMessage("Error exporting Zephyr CMake files."); progress.report({ message: "Failed", @@ -956,7 +1038,10 @@ export async function setupZephyr( }); if (result === 0) { - _logger.debug("West Python dependencies installed."); + Logger.debug( + LoggerSource.zephyrSetup, + "West Python dependencies installed." + ); progress2.report({ message: "Success", increment: 100, @@ -964,7 +1049,10 @@ export async function setupZephyr( return true; } else { - _logger.error("Error installing West Python dependencies."); + Logger.error( + LoggerSource.zephyrSetup, + "Error installing West Python dependencies." + ); progress2.report({ message: "Failed", increment: 100, @@ -1075,7 +1163,7 @@ export async function setupZephyr( } ); - _logger.info("Zephyr setup complete."); + Logger.info(LoggerSource.zephyrSetup, "Zephyr setup complete."); //void window.showInformationMessage("Zephyr setup complete"); return output; diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index dc14cff7..bbf47484 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -62,6 +62,7 @@ import { unknownErrorToString } from "../utils/errorHelper.mjs"; import type { Progress as GotProgress } from "got"; import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; import { OPENOCD_VERSION } from "../utils/sharedConstants.mjs"; +import { BoardType } from "./sharedEnums.mjs"; export const NINJA_AUTO_INSTALL_DISABLED = false; // process.platform === "linux" && process.arch === "arm64"; @@ -123,14 +124,6 @@ export interface WebviewMessage { key?: string; } -enum BoardType { - pico = "pico", - picoW = "pico_w", - pico2 = "pico2", - pico2W = "pico2_w", - other = "other", -} - enum ConsoleOption { consoleOverUART = "Console over UART", consoleOverUSB = "Console over USB (disables other USB use)", @@ -184,7 +177,7 @@ async function enumToBoard(e: BoardType, sdkPath: string): Promise { readdirSync(`${sdkPath}/src/boards/include/boards`) .filter((file: string) => file.endsWith(".h")) .forEach((file: string) => { - quickPickItems.push(file.slice(0, -2)); // remove .h + quickPickItems.push(file.slice(0, -2)); // remove .h }); // show quick pick for board type diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index c92c5d6f..7c2ee579 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -11,7 +11,6 @@ import { ProgressLocation, commands, } from "vscode"; -import { homedir } from "os"; import { join as joinPosix } from "path/posix"; import Settings from "../settings.mjs"; import Logger from "../logger.mjs"; @@ -25,107 +24,15 @@ import { existsSync } from "fs"; import { join, dirname } from "path"; import { PythonExtension } from "@vscode/python-extension"; import { unknownErrorToString } from "../utils/errorHelper.mjs"; -import { buildZephyrWorkspacePath } from "../utils/download.mjs"; import { setupZephyr } from "../utils/setupZephyr.mjs"; import { getCmakeReleases } from "../utils/githubREST.mjs"; import { getSystemCmakeVersion } from "../utils/cmakeUtil.mjs"; - -enum BoardType { - pico = "pico", - picoW = "pico_w", - pico2 = "pico2", - pico2W = "pico2_w", -} - -interface SubmitMessageValue { - projectName: string; - pythonMode: number; - pythonPath: string; - console: string; - boardType: BoardType; - spiFeature: boolean; - i2cFeature: boolean; - gpioFeature: boolean; - wifiFeature: boolean; - sensorFeature: boolean; - shellFeature: boolean; - cmakeMode: number; - cmakePath: string; - cmakeVersion: string; -} - -// Kconfig snippets -const spiKconfig: string = "CONFIG_SPI=y"; -const i2cKconfig: string = "CONFIG_I2C=y"; -const gpioKconfig: string = "CONFIG_GPIO=y"; -const sensorKconfig: string = "CONFIG_SENSOR=y"; -const shellKconfig: string = "CONFIG_SHELL=y"; -const wifiKconfig: string = `CONFIG_NETWORKING=y -CONFIG_TEST_RANDOM_GENERATOR=y - -CONFIG_MAIN_STACK_SIZE=5200 -CONFIG_SHELL_STACK_SIZE=5200 -CONFIG_NET_TX_STACK_SIZE=2048 -CONFIG_NET_RX_STACK_SIZE=2048 -CONFIG_LOG_BUFFER_SIZE=4096 - -CONFIG_NET_PKT_RX_COUNT=10 -CONFIG_NET_PKT_TX_COUNT=10 -CONFIG_NET_BUF_RX_COUNT=20 -CONFIG_NET_BUF_TX_COUNT=20 -CONFIG_NET_MAX_CONN=10 -CONFIG_NET_MAX_CONTEXTS=10 -CONFIG_NET_DHCPV4=y - -CONFIG_NET_IPV4=y -CONFIG_NET_IPV6=n - -CONFIG_NET_TCP=y -CONFIG_NET_SOCKETS=y - -CONFIG_DNS_RESOLVER=y -CONFIG_DNS_SERVER_IP_ADDRESSES=y -CONFIG_DNS_SERVER1="192.0.2.2" -CONFIG_DNS_RESOLVER_AI_MAX_ENTRIES=10 - -# Network address config -CONFIG_NET_CONFIG_AUTO_INIT=n -CONFIG_NET_CONFIG_SETTINGS=y -CONFIG_NET_CONFIG_NEED_IPV4=y -CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" -CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2" -CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" - -CONFIG_NET_LOG=y -CONFIG_INIT_STACKS=y - -CONFIG_NET_STATISTICS=y -CONFIG_NET_STATISTICS_PERIODIC_OUTPUT=n - -CONFIG_HTTP_CLIENT=y - -CONFIG_WIFI=y -CONFIG_WIFI_LOG_LEVEL_ERR=y -# printing of scan results puts pressure on queues in new locking -# design in net_mgmt. So, use a higher timeout for a crowded -# environment. -CONFIG_NET_MGMT_EVENT_QUEUE_TIMEOUT=5000 -CONFIG_NET_MGMT_EVENT_QUEUE_SIZE=16`; - -// Shell Kconfig values -const shellSpiKconfig: string = "CONFIG_SPI_SHELL=y"; -const shellI2CKconfig: string = "CONFIG_I2C_SHELL=y"; -const shellGPIOKconfig: string = "CONFIG_GPIO_SHELL=y"; -const shellSensorKconfig: string = "CONFIG_SENSOR_SHELL=y"; -const shellWifiKconfig: string = `CONFIG_WIFI_LOG_LEVEL_ERR=y -CONFIG_NET_L2_WIFI_SHELL=y -CONFIG_NET_SHELL=y`; - -interface TasksJson { - tasks: Array<{ - args: string[]; - }>; -} +import { generateZephyrProject } from "../utils/projectGeneration/projectZephyr.mjs"; +import { + BoardType, + ZephyrProjectBase, + type ZephyrSubmitMessageValue, +} from "./sharedEnums.mjs"; export class NewZephyrProjectPanel { public static currentPanel: NewZephyrProjectPanel | undefined; @@ -348,7 +255,7 @@ export class NewZephyrProjectPanel { break; case "submit": { - const data = message.value as SubmitMessageValue; + const data = message.value as ZephyrSubmitMessageValue; if ( this._projectRoot === undefined || @@ -423,51 +330,9 @@ export class NewZephyrProjectPanel { } } - /** - * Convert the enum to the Zephyr board name - * - * @param e BoardType enum - * @returns string Zephyr board name - * @throws Error if unknown board type - */ - private enumToBoard(e: BoardType): string { - switch (e) { - case BoardType.pico: - return "rpi_pico"; - case BoardType.picoW: - return "rpi_pico/rp2040/w"; - case BoardType.pico2: - return "rpi_pico2/rp2350a/m33"; - case BoardType.pico2W: - return "rpi_pico2/rp2350a/m33/w"; - default: - throw new Error(`Unknown Board Type: ${e as string}`); - } - } - - private generateZephyrBuildArgs( - boardType: BoardType, - usbSerialPort: boolean - ): string[] { - return [ - "build", - "-p", - "auto", - "-b", - this.enumToBoard(boardType), - "-d", - '"${workspaceFolder}"/build', - '"${workspaceFolder}"', - usbSerialPort ? "-S usb_serial_port" : "", - "--", - "-DOPENOCD=${command:raspberry-pi-pico.getOpenOCDRoot}/openocd.exe", - "-DOPENOCD_DEFAULT_PATH=${command:raspberry-pi-pico.getOpenOCDRoot}/scripts", - ]; - } - private async _generateProjectOperation( progress: Progress<{ message?: string; increment?: number }>, - data: SubmitMessageValue, + data: ZephyrSubmitMessageValue, message: WebviewMessage ): Promise { const projectPath = this._projectRoot?.fsPath ?? ""; @@ -503,120 +368,49 @@ export class NewZephyrProjectPanel { extUri: this._extensionUri, }); - if (zephyrSetupOutputs === null) { + if (zephyrSetupOutputs === undefined) { + progress.report({ + message: "Failed", + increment: 100, + }); + return; } - this._logger.info(JSON.stringify(zephyrSetupOutputs)); - - this._logger.info("Generating new Zephyr Project"); - - // Create a new directory to put the project in - const homeDirectory: string = homedir(); - const newProjectDir = joinPosix( - homeDirectory, - "zephyr_test", - data.projectName - ); - - await workspace.fs.createDirectory(Uri.file(newProjectDir)); + this._logger.info("Generating new Zephyr Project..."); - // Copy the Zephyr App into the new directory - const zephyrAppDir = joinPosix( - buildZephyrWorkspacePath(), - "pico-zephyr", - "app" + const result = await generateZephyrProject( + projectPath, + data.projectName, + zephyrSetupOutputs.latestVb, + zephyrSetupOutputs.cmakeExecutable, + data ); - await workspace.fs.copy(Uri.file(zephyrAppDir), Uri.file(newProjectDir), { - overwrite: true, - }); - - const result = true; + if (!result) { + progress.report({ + message: "Failed", + increment: 100, + }); + void window.showErrorMessage( + "Failed to generate Zephyrproject. " + + "Please try again and check your settings." + ); - if (result === null || !result) { - return undefined; + return; } - this._logger.info(`Console: ${data.console}`); - const usbSerialPort = data.console === "USB"; - - const buildArgs = this.generateZephyrBuildArgs( - data.boardType, - usbSerialPort - ); - - const taskJsonFile = joinPosix(newProjectDir, ".vscode", "tasks.json"); - - const jsonString = ( - await workspace.fs.readFile(Uri.file(taskJsonFile)) - ).toString(); - - const tasksJson = JSON.parse(jsonString) as TasksJson; - - // Modify task.json - tasksJson.tasks[0].args = buildArgs; - - // Write modified tasks.json to folder - const newTasksJsonString = JSON.stringify(tasksJson, null, 4); - await workspace.fs.writeFile( - Uri.file(taskJsonFile), - Buffer.from(newTasksJsonString) + this._logger.info( + `Zephyr Project generated at ${projectPath}/${data.projectName}` ); - // Enable modules with Kconfig - const kconfigFile = joinPosix(newProjectDir, "prj.conf"); - const kconfigString = ( - await workspace.fs.readFile(Uri.file(kconfigFile)) - ).toString(); - - const newKconfigString = kconfigString.concat( - "\r\n", - "\r\n", - "# Enable Modules:", - "\r\n", - data.gpioFeature ? gpioKconfig + "\r\n" : "", - data.i2cFeature ? i2cKconfig + "\r\n" : "", - data.spiFeature ? spiKconfig + "\r\n" : "", - data.sensorFeature ? sensorKconfig + "\r\n" : "", - data.wifiFeature ? wifiKconfig + "\r\n" : "", - data.shellFeature ? "\r\n" + "# Enabling shells:" + "\r\n" : "", - data.shellFeature ? shellKconfig + "\r\n" : "", - data.shellFeature && data.gpioFeature ? shellGPIOKconfig + "\r\n" : "", - data.shellFeature && data.i2cFeature ? shellI2CKconfig + "\r\n" : "", - data.shellFeature && data.spiFeature ? shellSpiKconfig + "\r\n" : "", - data.shellFeature && data.sensorFeature - ? shellSensorKconfig + "\r\n" - : "", - data.shellFeature && data.wifiFeature ? shellWifiKconfig + "\r\n" : "" - ); - - await workspace.fs.writeFile( - Uri.file(kconfigFile), - Buffer.from(newKconfigString) - ); - - const settingJsonFile = joinPosix( - newProjectDir, - ".vscode", - "settings.json" - ); - - // Create settings JSON and write to new folder - const settingsJson = NewZephyrProjectPanel.createSettingsJson( - homeDirectory.replaceAll("\\", "/"), - zephyrSetupOutputs?.cmakeExecutable || "" - ); - await workspace.fs.writeFile( - Uri.file(settingJsonFile), - Buffer.from(settingsJson) - ); - - this._logger.info(`Zephyr Project generated at ${newProjectDir}`); - // Open the folder - void commands.executeCommand(`vscode.openFolder`, Uri.file(newProjectDir), { - forceNewWindow: (workspace.workspaceFolders?.length ?? 0) > 0, - }); + void commands.executeCommand( + `vscode.openFolder`, + Uri.file(join(projectPath, data.projectName)), + { + forceNewWindow: (workspace.workspaceFolders?.length ?? 0) > 0, + } + ); return; } @@ -817,7 +611,7 @@ export class NewZephyrProjectPanel {

-
+
+
+ + +
diff --git a/src/webview/sharedEnums.mts b/src/webview/sharedEnums.mts new file mode 100644 index 00000000..d2382add --- /dev/null +++ b/src/webview/sharedEnums.mts @@ -0,0 +1,31 @@ +export enum BoardType { + pico = "pico", + picoW = "pico_w", + pico2 = "pico2", + pico2W = "pico2_w", + other = "other", +} + +export enum ZephyrProjectBase { + simple = "simple", + blinky = "blinky", + wifi = "wifi", +} + +export interface ZephyrSubmitMessageValue { + projectName: string; + pythonMode: number; + pythonPath: string; + console: string; + boardType: BoardType; + spiFeature: boolean; + i2cFeature: boolean; + gpioFeature: boolean; + wifiFeature: boolean; + sensorFeature: boolean; + shellFeature: boolean; + cmakeMode: number; + cmakePath: string; + cmakeVersion: string; + projectBase: ZephyrProjectBase; +} diff --git a/web/zephyr/main.js b/web/zephyr/main.js index 864fe303..f1e9654c 100644 --- a/web/zephyr/main.js +++ b/web/zephyr/main.js @@ -8,6 +8,8 @@ const CMD_ERROR = "error"; const CMD_SUBMIT_DENIED = "submitDenied"; var submitted = false; +var previousTemplate = "simple"; +var previousGpioState = false; (function () { const vscode = acquireVsCodeApi(); @@ -75,6 +77,33 @@ var submitted = false; setMode(modeEl.value); } + { + const templateSelector = document.getElementById("sel-template"); + const gpioCheckbox = document.getElementById("gpio-features-cblist"); + if (templateSelector) { + templateSelector.addEventListener("change", function (event) { + try { + const template = templateSelector.value; + + if (gpioCheckbox) { + if (template === "blinky") { + previousGpioState = gpioCheckbox.checked; + gpioCheckbox.checked = true; + gpioCheckbox.disabled = true; + } else if (previousTemplate === "blinky") { + gpioCheckbox.checked = previousGpioState; + gpioCheckbox.disabled = false; + } + } + + previousTemplate = template; + } catch (error) { + console.error("[raspberry-pi-pico - new zephyr pico project] Error handling template change:", error); + } + }); + } + } + // needed so a element isn't hidden behind the navbar on scroll const navbarOffsetHeight = document.getElementById("top-navbar").offsetHeight; @@ -331,6 +360,7 @@ var submitted = false; cmakeMode: Number(cmakeMode), cmakePath: cmakePath, cmakeVersion: cmakeVersion, + projectBase: document.getElementById("sel-template").value, }, }); }; From e1ace0b6d32ce70f49a987f89b8cee1e9673f70d Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 18 Sep 2025 17:48:21 +0100 Subject: [PATCH 56/62] Fix zephyr integration (part 5) Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/extension.mts | 57 ++++- src/utils/projectGeneration/projectZephyr.mts | 76 +++--- src/utils/projectGeneration/zephyrFiles.mts | 8 +- src/utils/setupZephyr.mts | 236 +++++++++++------- src/utils/sharedConstants.mts | 9 +- 5 files changed, 254 insertions(+), 132 deletions(-) diff --git a/src/extension.mts b/src/extension.mts index 727586cd..5d502716 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -5,6 +5,8 @@ import { type WebviewPanel, commands, ProgressLocation, + Uri, + FileSystemError, } from "vscode"; import { extensionName, @@ -101,9 +103,13 @@ import { import State from "./state.mjs"; import { cmakeToolsForcePicoKit } from "./utils/cmakeToolsUtil.mjs"; import { NewRustProjectPanel } from "./webview/newRustProjectPanel.mjs"; -import { OPENOCD_VERSION } from "./utils/sharedConstants.mjs"; +import { + CMAKELISTS_ZEPHYR_HEADER, + OPENOCD_VERSION, +} from "./utils/sharedConstants.mjs"; import VersionBundlesLoader from "./utils/versionBundles.mjs"; import { setupZephyr } from "./utils/setupZephyr.mjs"; +import { unknownErrorToString } from "./utils/errorHelper.mjs"; export async function activate(context: ExtensionContext): Promise { Logger.info(LoggerSource.extension, "Extension activation triggered"); @@ -268,10 +274,12 @@ export async function activate(context: ExtensionContext): Promise { false ); + const cmakeListsContents = new TextDecoder().decode( + await workspace.fs.readFile(Uri.file(cmakeListsFilePath)) + ); + // Check for pico_zephyr in CMakeLists.txt - if ( - readFileSync(cmakeListsFilePath).toString("utf-8").includes("pico_zephyr") - ) { + if (cmakeListsContents.startsWith(CMAKELISTS_ZEPHYR_HEADER)) { Logger.info(LoggerSource.extension, "Project is of type: Zephyr"); await commands.executeCommand( "setContext", @@ -307,13 +315,44 @@ export async function activate(context: ExtensionContext): Promise { ui.updateBoard("Other"); } } + + // check if build dir is empty and recommend to run a build to + // get the intellisense working + // TODO: maybe run cmake configure automatically if build folder empty + try { + const buildDirContents = await workspace.fs.readDirectory( + Uri.file(join(workspaceFolder.uri.fsPath, "build")) + ); + + if (buildDirContents.length === 0) { + void window.showWarningMessage( + "To get full intellisense support please build the project once." + ); + } + } catch (error) { + if (error instanceof FileSystemError && error.code === "FileNotFound") { + void window.showWarningMessage( + "To get full intellisense support please build the project once." + ); + + Logger.debug( + LoggerSource.extension, + 'No "build" folder found. Intellisense might not work ' + + "properly until a build has been done." + ); + } else { + Logger.error( + LoggerSource.extension, + "Error when reading build folder:", + unknownErrorToString(error) + ); + } + } + + return; } // check for pico_sdk_init() in CMakeLists.txt - else if ( - !readFileSync(cmakeListsFilePath) - .toString("utf-8") - .includes("pico_sdk_init()") - ) { + else if (!cmakeListsContents.includes("pico_sdk_init()")) { Logger.warn( LoggerSource.extension, "No pico_sdk_init() in CMakeLists.txt found." diff --git a/src/utils/projectGeneration/projectZephyr.mts b/src/utils/projectGeneration/projectZephyr.mts index b70944c4..e9beae3f 100644 --- a/src/utils/projectGeneration/projectZephyr.mts +++ b/src/utils/projectGeneration/projectZephyr.mts @@ -13,7 +13,6 @@ import { import { extensionName } from "../../commands/command.mjs"; import { commands, Uri, window, workspace } from "vscode"; import { - CURRENT_7ZIP_VERSION, CURRENT_DTC_VERSION, CURRENT_GPERF_VERSION, CURRENT_WGET_VERSION, @@ -35,6 +34,7 @@ import { WIFI_WIFI_C, WIFI_WIFI_H, } from "./zephyrFiles.mjs"; +import { homedir } from "os"; // Kconfig snippets const spiKconfig: string = "CONFIG_SPI=y"; @@ -165,7 +165,7 @@ async function generateVSCodeConfig( intelliSenseMode: "linux-gcc-arm", compilerPath: // TODO: maybe move into command (the part before the executable) / test if not .exe works on win32 - "${userHome}/.pico-sdk/zephyr_workspace/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc", + "${userHome}/.pico-sdk/zephyr_workspace/zephyr-sdk/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc", includePath: [ "${workspaceFolder}/**", "${workspaceFolder}/build/zephyr/include", @@ -205,7 +205,7 @@ async function generateVSCodeConfig( toolchainPrefix: "arm-zephyr-eabi", armToolchainPath: // TODO: maybe just full get zephyr compiler path command - "${command:${extensionName}.getZephyrWorkspacePath}/zephyr-sdk-0.17.1/arm-zephyr-eabi/bin", + "${command:${extensionName}.getZephyrWorkspacePath}/zephyr-sdk/arm-zephyr-eabi/bin", // TODO: get chip dynamically maybe: chip: `\${command:${extensionName}.${GetChipCommand.id}}`, // meaning only one cfg required device: `\${command:${extensionName}.${GetChipUppercaseCommand.id}}`, @@ -243,7 +243,7 @@ async function generateVSCodeConfig( "C_Cpp.debugShortcut": false, "terminal.integrated.env.windows": { // TODO: contitionally cmake: \${env:USERPROFILE}/.pico-sdk/cmake/${latestVb.cmake}/bin - Path: `\${env:USERPROFILE}/.pico-sdk/dtc/${CURRENT_DTC_VERSION}/bin;\${env:USERPROFILE}/.pico-sdk/gperf/${CURRENT_GPERF_VERSION};\${env:USERPROFILE}/.pico-sdk/ninja/${latestVb[1].ninja};\${env:USERPROFILE}/.pico-sdk/wget/${CURRENT_WGET_VERSION};\${env:USERPROFILE}/.pico-sdk/7zip/${CURRENT_7ZIP_VERSION};\${env:PATH}`, + Path: `\${env:USERPROFILE}/.pico-sdk/dtc/${CURRENT_DTC_VERSION}/bin;\${env:USERPROFILE}/.pico-sdk/gperf/${CURRENT_GPERF_VERSION};\${env:USERPROFILE}/.pico-sdk/ninja/${latestVb[1].ninja};\${env:USERPROFILE}/.pico-sdk/wget/${CURRENT_WGET_VERSION};\${env:USERPROFILE}/.pico-sdk/7zip;\${env:PATH}`, }, "terminal.integrated.env.osx": { PATH: `\${env:HOME}/.pico-sdk/dtc/${CURRENT_DTC_VERSION}/bin:\${env:HOME}/.pico-sdk/gperf/${CURRENT_GPERF_VERSION}:\${env:HOME}/.pico-sdk/ninja/${latestVb[1].ninja}:\${env:HOME}/.pico-sdk/wget:\${env:PATH}`, @@ -262,10 +262,20 @@ async function generateVSCodeConfig( }; if (cmakePath.length > 0) { - const pathWindows = cmakePath.replace(HOME_VAR, "${env:USERPROFILE}"); - const pathPosix = cmakePath.replace(HOME_VAR, "${env:HOME}"); - settings["cmake.cmakePath"] = cmakePath; - settings["raspberry-pi-pico.cmakePath"] = cmakePath; + const pathWindows = cmakePath + .replace(HOME_VAR, "${env:USERPROFILE}") + .replace(homedir().replace("\\", "/"), "${env:USERPROFILE}"); + const pathPosix = cmakePath + .replace(HOME_VAR, "${env:HOME}") + .replace(homedir().replace("\\", "/"), "${env:HOME}"); + settings["cmake.cmakePath"] = cmakePath.replace( + homedir().replace("\\", "/"), + "${userHome}" + ); + settings["raspberry-pi-pico.cmakePath"] = cmakePath.replace( + homedir().replace("\\", "/"), + HOME_VAR + ); // add to path settings[ "terminal.integrated.env.windows" @@ -292,23 +302,25 @@ async function generateVSCodeConfig( // TODO: check! "" '"${workspaceFolder}"/build', '"${workspaceFolder}"', - "--", - `-DOPENOCD=\${command:${extensionName}.${GetOpenOCDRootCommand.id}}/openocd`, - `-DOPENOCD_DEFAULT_PATH=\${command:${extensionName}.${GetOpenOCDRootCommand.id}}/scripts`, ]; // If console is USB, use the local snippet if (data.console === "USB") { - westArgs.unshift("-S", "usb_serial_port"); + westArgs.push("-S", "usb_serial_port"); } + westArgs.push( + "--", + `-DOPENOCD=\${command:${extensionName}.${GetOpenOCDRootCommand.id}}/openocd`, + `-DOPENOCD_DEFAULT_PATH=\${command:${extensionName}.${GetOpenOCDRootCommand.id}}/scripts` + ); + const tasks = { version: "2.0.0", tasks: [ { label: "Compile Project", type: "shell", - isBuildCommand: true, command: `\${command:${extensionName}.${GetWestPathCommand.id}}`, args: westArgs, group: { @@ -426,15 +438,15 @@ const char JSON_POST_PATH[] = "/posts"; int main(void) { - printk("Starting wifi example on %s\n", CONFIG_BOARD_TARGET); + printk("Starting wifi example on %s\\n", CONFIG_BOARD_TARGET); wifi_connect(WIFI_SSID, WIFI_PSK); // Ping Google DNS 4 times - printk("Pinging 8.8.8.8 to demonstrate connection:\n"); + printk("Pinging 8.8.8.8 to demonstrate connection:\\n"); ping("8.8.8.8", 4); - printk("Now performing http GET request to google.com...\n"); + printk("Now performing http GET request to google.com...\\n"); http_get_example(HTTP_HOSTNAME, HTTP_PATH); k_sleep(K_SECONDS(1)); @@ -445,11 +457,11 @@ int main(void) { LOG_ERR("Error in json_get_example"); } else { - printk("Got JSON result:\n"); - printk("Title: %s\n", get_post_result.title); - printk("Body: %s\n", get_post_result.body); - printk("User ID: %d\n", get_post_result.userId); - printk("ID: %d\n", get_post_result.id); + printk("Got JSON result:\\n"); + printk("Title: %s\\n", get_post_result.title); + printk("Body: %s\\n", get_post_result.body); + printk("User ID: %d\\n", get_post_result.userId); + printk("ID: %d\\n", get_post_result.id); } k_sleep(K_SECONDS(1)); @@ -465,11 +477,11 @@ int main(void) { LOG_ERR("Error in json_post_example"); } else { - printk("Got JSON result:\n"); - printk("Title: %s\n", new_post_result.title); - printk("Body: %s\n", new_post_result.body); - printk("User ID: %d\n", new_post_result.userId); - printk("ID: %d\n", new_post_result.id); + printk("Got JSON result:\\n"); + printk("Title: %s\\n", new_post_result.title); + printk("Body: %s\\n", new_post_result.body); + printk("User ID: %d\\n", new_post_result.userId); + printk("ID: %d\\n", new_post_result.id); } k_sleep(K_SECONDS(1)); @@ -529,7 +541,7 @@ async function generateMainC( ); } - mainC = mainC.concat("void main(void) {\n"); + mainC = mainC.concat("int main(void) {\n"); if (isBlinky) { mainC = mainC.concat(' printk("Hello World! Blinky sample\\n");\n\n'); @@ -561,13 +573,13 @@ async function generateMainC( } led_state = !led_state; - printk("LED state: %s\n", led_state ? "ON" : "OFF"); + printk("LED state: %s\\n", led_state ? "ON" : "OFF"); `; mainC = mainC.concat(blinkyLoop); } else { mainC = mainC.concat( - ' printk("Running on %s...\n", CONFIG_BOARD);\n\n' + ' printk("Running on %s...\\n", CONFIG_BOARD);\n\n' ); } @@ -869,12 +881,16 @@ async function generateCMakeList( ): Promise { // TODO: maybe dynamic check cmake minimum cmake version on cmake selection // TODO: license notice required anymore? - let cmakeList = `#------------------------------------------------------------------------------- + let cmakeList = `#pico-zephyr-project +#------------------------------------------------------------------------------- # Zephyr Example Application # # Copyright (c) 2021 Nordic Semiconductor ASA # SPDX-License-Identifier: Apache-2.0 #------------------------------------------------------------------------------- +# NOTE: Please do not remove the #pico-zephyr-project header, it is used by +# the Raspberry Pi Pico SDK extension to identify the project type. +#------------------------------------------------------------------------------- cmake_minimum_required(VERSION 3.20.0) diff --git a/src/utils/projectGeneration/zephyrFiles.mts b/src/utils/projectGeneration/zephyrFiles.mts index eda243ac..2eb40245 100644 --- a/src/utils/projectGeneration/zephyrFiles.mts +++ b/src/utils/projectGeneration/zephyrFiles.mts @@ -21,7 +21,7 @@ LOG_MODULE_REGISTER(http); static K_SEM_DEFINE(json_response_complete, 0, 1); static K_SEM_DEFINE(http_response_complete, 0, 1); -static const char * json_post_headers[] = { "Content-Type: application/json\r\n", NULL }; +static const char * json_post_headers[] = { "Content-Type: application/json\\r\\n", NULL }; // Holds the HTTP response static char response_buffer[2048]; @@ -79,7 +79,7 @@ static void http_response_cb(struct http_response *rsp, printk("HTTP Callback: %.*s", rsp->data_len, rsp->recv_buf); if (HTTP_DATA_FINAL == final_data){ - printk("\n"); + printk("\\n"); k_sem_give(&http_response_complete); } } @@ -135,7 +135,7 @@ static void json_response_cb(struct http_response *rsp, if (rsp->body_found) { LOG_DBG("Body:"); - printk("%.*s\n", rsp->body_frag_len, rsp->body_frag_start); + printk("%.*s\\n", rsp->body_frag_len, rsp->body_frag_start); if (returned_placeholder_post != NULL) { @@ -448,7 +448,7 @@ void wifi_connect(const char * ssid, const char * psk) connection_result = net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, &cnx_params, sizeof(struct wifi_connect_req_params)); if (connection_result) { - LOG_ERR("Connection request failed with error: %d\n", connection_result); + LOG_ERR("Connection request failed with error: %d\\n", connection_result); } k_sleep(K_MSEC(1000)); } diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts index 56955b48..16cbcbc6 100644 --- a/src/utils/setupZephyr.mts +++ b/src/utils/setupZephyr.mts @@ -1,9 +1,15 @@ -import { existsSync, readdirSync, rmSync } from "fs"; -import { window, workspace, ProgressLocation, Uri } from "vscode"; +import { existsSync } from "fs"; +import { + window, + workspace, + ProgressLocation, + Uri, + FileSystemError, +} from "vscode"; import { type ExecOptions, exec } from "child_process"; import { dirname, join } from "path"; import { join as joinPosix } from "path/posix"; -import { homedir, tmpdir } from "os"; +import { homedir } from "os"; import Logger, { LoggerSource } from "../logger.mjs"; import type { Progress as GotProgress } from "got"; @@ -23,10 +29,10 @@ import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; import { ensureGit } from "../utils/gitUtil.mjs"; import VersionBundlesLoader, { type VersionBundle } from "./versionBundles.mjs"; import { - CURRENT_7ZIP_VERSION, CURRENT_DTC_VERSION, CURRENT_GPERF_VERSION, CURRENT_WGET_VERSION, + LICENSE_URL_7ZIP, OPENOCD_VERSION, WINDOWS_X86_7ZIP_DOWNLOAD_URL, WINDOWS_X86_DTC_DOWNLOAD_URL, @@ -63,6 +69,7 @@ manifest: projects: - name: zephyr remote: zephyrproject-rtos + revision: main import: # By using name-allowlist we can clone only the modules that are # strictly needed by the application. @@ -100,8 +107,8 @@ function buildWgetPath(version: string): string { ); } -function build7ZipPathWin32(version: string): string { - return join(homeDirectory, ".pico-sdk", "7zip", version); +function build7ZipPathWin32(): string { + return join(homeDirectory, ".pico-sdk", "7zip"); } function generateCustomEnv( @@ -121,7 +128,7 @@ function generateCustomEnv( join(homedir(), ".pico-sdk", "ninja", latestVb.ninja), dirname(pythonExe), join(homedir(), ".pico-sdk", "wget", CURRENT_WGET_VERSION), - join(homedir(), ".pico-sdk", "7zip", CURRENT_7ZIP_VERSION), + join(homedir(), ".pico-sdk", "7zip"), "", // Need this to add separator to end ].join(process.platform === "win32" ? ";" : ":"); @@ -139,17 +146,26 @@ function _runCommand( Logger.debug(LoggerSource.zephyrSetup, `Running: ${command}`); return new Promise(resolve => { - const proc = exec(command, options, (error, stdout, stderr) => { - Logger.debug(LoggerSource.zephyrSetup, stdout); - Logger.debug(LoggerSource.zephyrSetup, stderr); - if (error) { - Logger.error( - LoggerSource.zephyrSetup, - `Setup venv error: ${error.message}` - ); - resolve(null); // indicate error + const proc = exec( + ((process.env.ComSpec === "powershell.exe" || + process.env.ComSpec === + "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") && + command.startsWith('"') + ? "&" + : "") + command, + options, + (error, stdout, stderr) => { + Logger.debug(LoggerSource.zephyrSetup, stdout); + Logger.debug(LoggerSource.zephyrSetup, stderr); + if (error) { + Logger.error( + LoggerSource.zephyrSetup, + `An error occurred executing a command: ${error.message}` + ); + resolve(null); // indicate error + } } - }); + ); proc.on("exit", code => { // Resolve with exit code or -1 if code is undefined @@ -491,6 +507,20 @@ async function checkWget(): Promise { ); } +async function vsExists(path: string): Promise { + try { + await workspace.fs.stat(Uri.file(path)); + + return true; + } catch (err) { + if (err instanceof FileSystemError && err.code === "FileNotFound") { + return false; + } + // rethrow unexpected errors + throw err; + } +} + async function check7Zip(): Promise { return window.withProgress( { @@ -499,32 +529,32 @@ async function check7Zip(): Promise { cancellable: false, }, async progress => { - Logger.info(LoggerSource.zephyrSetup, "Installing 7-Zip"); - const installDir = build7ZipPathWin32(CURRENT_7ZIP_VERSION); + Logger.info(LoggerSource.zephyrSetup, "Installing 7-Zip..."); + const installDir = build7ZipPathWin32(); - if (existsSync(installDir) && readdirSync(installDir).length !== 0) { - Logger.info(LoggerSource.zephyrSetup, "7-Zip is already installed."); + if (await vsExists(installDir)) { + const installDirContents = await workspace.fs.readDirectory( + Uri.file(installDir) + ); - progress.report({ - message: "7-Zip already installed.", - increment: 100, - }); + if (installDirContents.length !== 0) { + Logger.info(LoggerSource.zephyrSetup, "7-Zip is already installed."); - return true; - } else if (existsSync(installDir)) { - // Remove existing empty directory - rmSync(installDir, { recursive: true, force: true }); - } + progress.report({ + message: "7-Zip already installed.", + increment: 100, + }); - const binName = `7z${CURRENT_7ZIP_VERSION}-x64.msi`; - const downloadURL = new URL(WINDOWS_X86_7ZIP_DOWNLOAD_URL); - const downloadDir = tmpdir().replaceAll("\\", "/"); - const result = await downloadFileGot( - downloadURL, - joinPosix(downloadDir, binName) - ); + return true; + } + } else { + await workspace.fs.createDirectory(Uri.file(installDir)); + } - if (!result) { + const licenseURL = new URL(LICENSE_URL_7ZIP); + const licenseTarget = join(installDir, "License.txt"); + const licenseResult = await downloadFileGot(licenseURL, licenseTarget); + if (!licenseResult) { progress.report({ message: "Failed", increment: 100, @@ -533,14 +563,12 @@ async function check7Zip(): Promise { return false; } - const installResult = await _runCommand( - `msiexec /i ${binName} INSTALLDIR="${installDir}" /quiet /norestart`, - { - cwd: join(homedir(), ".pico-sdk"), - } - ); + const downloadURL = new URL(WINDOWS_X86_7ZIP_DOWNLOAD_URL); + // rename latest 7zr.exe to 7z.exe for compaibility with Zephyr installer script + const downloadTarget = join(installDir, "7z.exe"); + const result = await downloadFileGot(downloadURL, downloadTarget); - if (installResult !== 0) { + if (!result) { progress.report({ message: "Failed", increment: 100, @@ -549,11 +577,8 @@ async function check7Zip(): Promise { return false; } - // Clean up - rmSync(join(downloadDir, binName)); - progress.report({ - message: "Seccess", + message: "Success", increment: 100, }); @@ -569,21 +594,37 @@ async function checkWindowsDeps(isWindows: boolean): Promise { let installedSuccessfully = await checkDtc(); if (!installedSuccessfully) { + void window.showErrorMessage( + "Failed to install DTC. Cannot continue Zephyr setup." + ); + return false; } installedSuccessfully = await checkGperf(); if (!installedSuccessfully) { + void window.showErrorMessage( + "Failed to install gperf. Cannot continue Zephyr setup." + ); + return false; } installedSuccessfully = await checkWget(); if (!installedSuccessfully) { + void window.showErrorMessage( + "Failed to install wget. Cannot continue Zephyr setup." + ); + return false; } installedSuccessfully = await check7Zip(); if (!installedSuccessfully) { + void window.showErrorMessage( + "Failed to install 7-Zip. Cannot continue Zephyr setup." + ); + return false; } @@ -655,7 +696,7 @@ export async function setupZephyr( }; let isWindows = false; - await window.withProgress( + const endResult: boolean = await window.withProgress( { location: ProgressLocation.Notification, title: "Setting up Zephyr Toolchain", @@ -670,7 +711,7 @@ export async function setupZephyr( increment: 100, }); - return; + return false; } output.gitPath = gitPath; @@ -685,8 +726,11 @@ export async function setupZephyr( message: "Failed", increment: 100, }); + void window.showErrorMessage( + "Failed to install or find CMake. Cannot continue Zephyr setup." + ); - return; + return false; } output.cmakeExecutable = cmakePath; @@ -696,8 +740,11 @@ export async function setupZephyr( message: "Failed", increment: 100, }); + void window.showErrorMessage( + "Failed to install Ninja. Cannot continue Zephyr setup." + ); - return; + return false; } installedSuccessfully = await checkPicotool(latestVb[1]); @@ -706,8 +753,11 @@ export async function setupZephyr( message: "Failed", increment: 100, }); + void window.showErrorMessage( + "Failed to install Picotool. Cannot continue Zephyr setup." + ); - return; + return false; } // install python (if necessary) @@ -723,7 +773,7 @@ export async function setupZephyr( ); showPythonNotFoundError(); - return; + return false; } // required for svd files @@ -733,8 +783,11 @@ export async function setupZephyr( message: "Failed", increment: 100, }); + void window.showErrorMessage( + "Failed to install Pico SDK. Cannot continue Zephyr setup." + ); - return; + return false; } isWindows = process.platform === "win32"; @@ -746,7 +799,7 @@ export async function setupZephyr( increment: 100, }); - return; + return false; } installedSuccessfully = await checkOpenOCD(); @@ -755,8 +808,11 @@ export async function setupZephyr( message: "Failed", increment: 100, }); + void window.showErrorMessage( + "Failed to install OpenOCD. Cannot continue Zephyr setup." + ); - return; + return false; } const pythonExe = python3Path?.replace( @@ -785,14 +841,9 @@ export async function setupZephyr( Buffer.from(zephyrManifestContent) ); - const createVenvCommandVenv: string = [ - `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, - "-m venv venv", - ].join(" "); - const createVenvCommandVirtualenv: string = [ - `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${pythonExe}"`, - "-m virtualenv venv", - ].join(" "); + const createVenvCommandVenv: string = `"${pythonExe}" -m venv venv`; + const createVenvCommandVirtualenv: string = + `"${pythonExe}" -m ` + "virtualenv venv"; // Create a Zephyr workspace, copy the west manifest in and initialise the workspace await workspace.fs.createDirectory(Uri.file(zephyrWorkspaceDirectory)); @@ -869,7 +920,7 @@ export async function setupZephyr( increment: 100, }); - return; + return false; } const venvPythonCommand: string = joinPosix( @@ -915,11 +966,10 @@ export async function setupZephyr( increment: 100, }); - return; + return false; } const westExe: string = joinPosix( - process.env.ComSpec === "powershell.exe" ? "&" : "", zephyrWorkspaceDirectory, "venv", process.platform === "win32" ? "Scripts" : "bin", @@ -951,7 +1001,7 @@ export async function setupZephyr( "No West workspace found. Initialising..." ); - const westInitCommand: string = `${westExe} init -l manifest`; + const westInitCommand: string = `"${westExe}" init -l manifest`; result = await _runCommand(westInitCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, @@ -960,7 +1010,7 @@ export async function setupZephyr( Logger.info( LoggerSource.zephyrSetup, - `West workspace initialization ended with ${result}` + `West workspace initialization ended with exit code ${result}.` ); if (result !== 0) { @@ -973,7 +1023,7 @@ export async function setupZephyr( } } - const westUpdateCommand: string = `${westExe} update`; + const westUpdateCommand: string = `"${westExe}" update`; result = await _runCommand(westUpdateCommand, { cwd: zephyrWorkspaceDirectory, windowsHide: true, @@ -997,8 +1047,16 @@ export async function setupZephyr( } } ); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return false; + } - const zephyrExportCommand: string = `${westExe} zephyr-export`; + const zephyrExportCommand: string = `"${westExe}" zephyr-export`; Logger.info(LoggerSource.zephyrSetup, "Exporting Zephyr CMake Files..."); // TODO: maybe progress @@ -1012,13 +1070,13 @@ export async function setupZephyr( LoggerSource.zephyrSetup, "Error exporting Zephyr CMake files." ); - void window.showErrorMessage("Error exporting Zephyr CMake files."); progress.report({ message: "Failed", increment: 100, }); + void window.showErrorMessage("Error exporting Zephyr CMake files."); - return; + return false; } installedSuccessfully = await window.withProgress( @@ -1029,7 +1087,7 @@ export async function setupZephyr( }, async progress2 => { const westPipPackagesCommand: string = - `${westExe} packages ` + "pip --install"; + `"${westExe}" packages ` + "pip --install"; result = await _runCommand(westPipPackagesCommand, { cwd: zephyrWorkspaceDirectory, @@ -1068,7 +1126,7 @@ export async function setupZephyr( increment: 100, }); - return; + return false; } installedSuccessfully = await window.withProgress( @@ -1079,7 +1137,7 @@ export async function setupZephyr( }, async progress2 => { const westBlobsFetchCommand: string = - `${westExe} blobs fetch ` + "hal_infineon"; + `"${westExe}" blobs fetch ` + "hal_infineon"; result = await _runCommand(westBlobsFetchCommand, { cwd: zephyrWorkspaceDirectory, @@ -1110,7 +1168,7 @@ export async function setupZephyr( increment: 100, }); - return; + return false; } installedSuccessfully = await window.withProgress( @@ -1120,9 +1178,10 @@ export async function setupZephyr( cancellable: false, }, async progress2 => { + // was -b ${zephyrWorkspaceDirectory} which results in zephyr-sdk- in it const westInstallSDKCommand: string = - `${westExe} sdk install ` + - `-t arm-zephyr-eabi -b ${zephyrWorkspaceDirectory}`; + `"${westExe}" sdk install ` + + `-t arm-zephyr-eabi -d "${zephyrWorkspaceDirectory}/zephyr-sdk"`; result = await _runCommand(westInstallSDKCommand, { cwd: zephyrWorkspaceDirectory, @@ -1153,18 +1212,25 @@ export async function setupZephyr( increment: 100, }); - return; + return false; } progress.report({ message: "Complete", increment: 100, }); + + return true; } ); - Logger.info(LoggerSource.zephyrSetup, "Zephyr setup complete."); - //void window.showInformationMessage("Zephyr setup complete"); + if (endResult) { + Logger.info(LoggerSource.zephyrSetup, "Zephyr setup complete."); + + return output; + } else { + Logger.error(LoggerSource.zephyrSetup, "Zephyr setup failed."); - return output; + return undefined; + } } diff --git a/src/utils/sharedConstants.mts b/src/utils/sharedConstants.mts index 05a625e7..591b3d51 100644 --- a/src/utils/sharedConstants.mts +++ b/src/utils/sharedConstants.mts @@ -13,13 +13,14 @@ export const WINDOWS_X86_DTC_DOWNLOAD_URL = "1.6.1/dtc-msys2-1.6.1-x86_64.zip"; export const CURRENT_DTC_VERSION = "1.6.1"; export const WINDOWS_X86_GPERF_DOWNLOAD_URL = - "https://github.com/oss-winget/oss-winget-storage/tree/" + + "https://github.com/oss-winget/oss-winget-storage/raw/" + "d033d1c0fb054de32043af1d4d3be71b91c38221/packages/" + "gperf/3.1/gperf-3.1-win64_x64.zip"; export const CURRENT_GPERF_VERSION = "3.1"; export const WINDOWS_X86_WGET_DOWNLOAD_URL = "https:///eternallybored.org/misc/wget/releases/wget-1.21.4-win64.zip"; export const CURRENT_WGET_VERSION = "1.21.4"; -export const WINDOWS_X86_7ZIP_DOWNLOAD_URL = - "https://7-zip.org/a/7z2501-x64.msi"; -export const CURRENT_7ZIP_VERSION = "25.01"; +export const LICENSE_URL_7ZIP = "https://7-zip.org/license.txt"; +export const WINDOWS_X86_7ZIP_DOWNLOAD_URL = "https://www.7-zip.org/a/7zr.exe"; + +export const CMAKELISTS_ZEPHYR_HEADER = "#pico-zephyr-project"; From ac6a004ebc471ac39f0e60aacd1f5ddbf4904c76 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:53:49 +0100 Subject: [PATCH 57/62] Fix zephyr integration (part 6) Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- package.json | 32 +-- src/commands/configureCmake.mts | 28 +- src/commands/getPaths.mts | 81 ++++-- src/commands/switchBoard.mts | 243 ++++++++++++++---- src/commands/switchBoardZephyr.mts | 241 ----------------- src/contextKeys.mts | 6 +- src/extension.mts | 34 ++- src/state.mts | 1 + src/ui.mts | 14 +- src/utils/cmakeUtil.mts | 153 +++++++++-- src/utils/generateZephyrProject.mts | 63 ----- src/utils/projectGeneration/projectZephyr.mts | 5 +- src/utils/setupZephyr.mts | 63 ++++- src/utils/vsHelpers.mts | 15 ++ 14 files changed, 508 insertions(+), 471 deletions(-) delete mode 100644 src/commands/switchBoardZephyr.mts delete mode 100644 src/utils/generateZephyrProject.mts create mode 100644 src/utils/vsHelpers.mts diff --git a/package.json b/package.json index 001d8c30..3a15b895 100644 --- a/package.json +++ b/package.json @@ -81,19 +81,13 @@ "command": "raspberry-pi-pico.switchSDK", "title": "Switch Pico SDK", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isPicoZephyrProject" + "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isZephyrProject" }, { "command": "raspberry-pi-pico.switchBoard", "title": "Switch Board", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject" - }, - { - "command": "raspberry-pi-pico.switchBoardZephyr", - "title": "Switch Board for Zephyr", - "category": "Raspberry Pi Pico", - "enablement": "false" + "enablement": "raspberry-pi-pico.isPicoProject" }, { "command": "raspberry-pi-pico.launchTargetPath", @@ -223,13 +217,13 @@ "command": "raspberry-pi-pico.configureCmake", "title": "Configure CMake", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isPicoZephyrProject" + "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject" }, { "command": "raspberry-pi-pico.switchBuildType", "title": "Switch Build Type", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isPicoZephyrProject" + "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isZephyrProject" }, { "command": "raspberry-pi-pico.importProject", @@ -261,13 +255,7 @@ "command": "raspberry-pi-pico.cleanCmake", "title": "Clean CMake", "category": "Raspberry Pi Pico", - "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isPicoZephyrProject" - }, - { - "command": "raspberry-pi-pico.getRTTDecoderPath", - "title": "Get RTT Decoder module path", - "category": "Raspberry Pi Pico", - "enablement": "false" + "enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject" }, { "command": "raspberry-pi-pico.sbomTargetPathDebug", @@ -280,16 +268,6 @@ "title": "Get path of the project release SBOM (rust only)", "category": "Raspberry Pi Pico", "enablement": "false" - }, - { - "command": "raspberry-pi-pico.setupZephyr", - "title": "Setup Zephyr Toolchain", - "category": "Raspberry Pi Pico" - }, - { - "command": "raspberry-pi-pico.newZephyrProject", - "title": "New Zephyr Project", - "category": "Raspberry Pi Pico" } ], "configuration": { diff --git a/src/commands/configureCmake.mts b/src/commands/configureCmake.mts index e1b43dfc..9614cd0f 100644 --- a/src/commands/configureCmake.mts +++ b/src/commands/configureCmake.mts @@ -47,10 +47,10 @@ export default class ConfigureCmakeCommand extends Command { } else { void window.showWarningMessage( "CMake failed to configure your build. " + - "See the developer console for details " + - "(Help -> Toggle Developer Tools). " + - "You can also use the CMake Tools Extension Integration " + - "to get more information about the error." + "See the developer console for details " + + "(Help -> Toggle Developer Tools). " + + "You can also use the CMake Tools Extension Integration " + + "to get more information about the error." ); } } @@ -110,15 +110,15 @@ export class CleanCMakeCommand extends Command { } else { void window.showWarningMessage( "CMake could not be reconfigured. " + - "See the developer console for details " + - "(Help -> Toggle Developer Tools). " + - "You can also use the CMake Tools Extension Integration " + - "to get more information about the error." + "See the developer console for details " + + "(Help -> Toggle Developer Tools). " + + "You can also use the CMake Tools Extension Integration " + + "to get more information about the error." ); } const ws = workspaceFolder.uri.fsPath; - const cMakeCachePath = join(ws, "build","CMakeCache.txt"); + const cMakeCachePath = join(ws, "build", "CMakeCache.txt"); const newBuildType = cmakeGetPicoVar(cMakeCachePath, "CMAKE_BUILD_TYPE"); this._ui.updateBuildType(newBuildType ?? "unknown"); } @@ -159,7 +159,7 @@ export class SwitchBuildTypeCommand extends Command { } const ws = workspaceFolder.uri.fsPath; - const cMakeCachePath = join(ws, "build","CMakeCache.txt"); + const cMakeCachePath = join(ws, "build", "CMakeCache.txt"); const oldBuildType = cmakeGetPicoVar(cMakeCachePath, "CMAKE_BUILD_TYPE"); // QuickPick for the build type @@ -173,10 +173,10 @@ export class SwitchBuildTypeCommand extends Command { } else { void window.showWarningMessage( "CMake failed to configure your build. " + - "See the developer console for details " + - "(Help -> Toggle Developer Tools). " + - "You can also use the CMake Tools Extension Integration " + - "to get more information about the error." + "See the developer console for details " + + "(Help -> Toggle Developer Tools). " + + "You can also use the CMake Tools Extension Integration " + + "to get more information about the error." ); } diff --git a/src/commands/getPaths.mts b/src/commands/getPaths.mts index e474dc6c..acedf35f 100644 --- a/src/commands/getPaths.mts +++ b/src/commands/getPaths.mts @@ -28,6 +28,13 @@ import { getSupportedToolchains } from "../utils/toolchainUtil.mjs"; import Logger from "../logger.mjs"; import { rustProjectGetSelectedChip } from "../utils/rustUtil.mjs"; import { OPENOCD_VERSION } from "../utils/sharedConstants.mjs"; +import { + getBoardFromZephyrProject, + ZEPHYR_PICO, + ZEPHYR_PICO2, + ZEPHYR_PICO2_W, + ZEPHYR_PICO_W, +} from "./switchBoard.mjs"; export class GetPythonPathCommand extends CommandWithResult { public static readonly id = "getPythonPath"; @@ -265,6 +272,37 @@ export class GetChipCommand extends CommandWithResult { const workspaceFolder = workspace.workspaceFolders?.[0]; const isRustProject = State.getInstance().isRustProject; + const isZephyrProject = State.getInstance().isZephyrProject; + + if (isZephyrProject) { + const board = await getBoardFromZephyrProject( + join(workspaceFolder.uri.fsPath, ".vscode", "tasks.json") + ); + + if (board === undefined) { + this._logger.error("Failed to read Zephyr board from tasks.json"); + + return ""; + } + + switch (board) { + case ZEPHYR_PICO: + case ZEPHYR_PICO_W: + return "rp2040"; + case ZEPHYR_PICO2: + case ZEPHYR_PICO2_W: + return "rp2350"; + default: + this._logger.error(`Unsupported Zephyr board: ${board}`); + void window.showErrorMessage( + `Unsupported Zephyr board: ${board}. ` + + `Supported boards are: ${ZEPHYR_PICO}, ${ZEPHYR_PICO_W}, ` + + `${ZEPHYR_PICO2}, ${ZEPHYR_PICO2_W}` + ); + + return "rp2040"; + } + } if (isRustProject) { // read .pico-rs @@ -350,7 +388,28 @@ export class GetTargetCommand extends CommandWithResult { const workspaceFolder = workspace.workspaceFolders?.[0]; const isRustProject = State.getInstance().isRustProject; + const isZephyrProject = State.getInstance().isZephyrProject; + + if (isZephyrProject) { + const board = await getBoardFromZephyrProject( + join(workspaceFolder.uri.fsPath, ".vscode", "tasks.json") + ); + + if (board === undefined) { + return "rp2040"; + } + switch (board) { + case ZEPHYR_PICO: + case ZEPHYR_PICO_W: + return "rp2040"; + case ZEPHYR_PICO2: + case ZEPHYR_PICO2_W: + return "rp2350"; + default: + return "rp2040"; + } + } if (isRustProject) { const chip = rustProjectGetSelectedChip(workspaceFolder.uri.fsPath); @@ -523,8 +582,6 @@ export class GetSVDPathCommand extends CommandWithResult { } export class GetWestPathCommand extends CommandWithResult { - private running: boolean = false; - public static readonly id = "getWestPath"; constructor() { @@ -532,21 +589,12 @@ export class GetWestPathCommand extends CommandWithResult { } execute(): string | undefined { - if (this.running) { - return undefined; - } - this.running = true; - const result = buildWestPath(); if (result === null || !result) { - this.running = false; - return undefined; } - this.running = false; - return result; } } @@ -554,8 +602,6 @@ export class GetWestPathCommand extends CommandWithResult { export class GetZephyrWorkspacePathCommand extends CommandWithResult< string | undefined > { - private running: boolean = false; - public static readonly id = "getZephyrWorkspacePath"; constructor() { @@ -563,21 +609,12 @@ export class GetZephyrWorkspacePathCommand extends CommandWithResult< } execute(): string | undefined { - if (this.running) { - return undefined; - } - this.running = true; - const result = buildZephyrWorkspacePath(); if (result === null || !result) { - this.running = false; - return undefined; } - this.running = false; - return result; } } diff --git a/src/commands/switchBoard.mts b/src/commands/switchBoard.mts index 217f7ce3..2e21c599 100644 --- a/src/commands/switchBoard.mts +++ b/src/commands/switchBoard.mts @@ -1,11 +1,12 @@ -import { extensionName, Command } from "./command.mjs"; +import { Command } from "./command.mjs"; import Logger from "../logger.mjs"; import { commands, ProgressLocation, + Uri, window, workspace, - type Uri, + type WorkspaceFolder, } from "vscode"; import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs"; import { @@ -28,7 +29,131 @@ import { getSupportedToolchains } from "../utils/toolchainUtil.mjs"; import VersionBundlesLoader from "../utils/versionBundles.mjs"; import State from "../state.mjs"; import { unknownErrorToString } from "../utils/errorHelper.mjs"; -import SwitchZephyrBoardCommand from "./switchBoardZephyr.mjs"; + +interface IBoardFile { + [key: string]: string; +} + +interface ITask { + label: string; + args: string[]; +} + +const PICO_BOARD = "pico"; +const PICO_W_BOARD = "pico_w"; +const PICO2_BOARD = "pico2"; +const PICO2_W_BOARD = "pico2_w"; +export const ZEPHYR_PICO = "rpi_pico"; +export const ZEPHYR_PICO_W = "rpi_pico/rp2040/w"; +export const ZEPHYR_PICO2 = "rpi_pico2/rp2350a/m33"; +export const ZEPHYR_PICO2_W = "rpi_pico2/rp2350a/m33/w"; +const VALID_ZEPHYR_BOARDS = [ + ZEPHYR_PICO, + ZEPHYR_PICO_W, + ZEPHYR_PICO2, + ZEPHYR_PICO2_W, +]; + +function stringToZephyrBoard(e: string): string { + if (e === PICO_BOARD) { + return "rpi_pico"; + } else if (e === PICO_W_BOARD) { + return "rpi_pico/rp2040/w"; + } else if (e === PICO2_BOARD) { + return "rpi_pico2/rp2350a/m33"; + } else if (e === PICO2_W_BOARD) { + return "rpi_pico2/rp2350a/m33/w"; + } else { + throw new Error(`Unknown Board Type: ${e}`); + } +} + +/** + * Reads and parses the tasks.json file at the given path. + * If a overwriteBoard is provided, it will replace the board in the tasks.json file. + * + * @param tasksJsonPath The path to the tasks.json file. + * @param overwriteBoard The board to overwrite in the tasks.json file. + * @returns The current board if found, otherwise undefined. + */ +async function touchTasksJson( + tasksJsonPath: string, + overwriteBoard?: string +): Promise { + const tasksUri = Uri.file(tasksJsonPath); + + try { + await workspace.fs.stat(tasksUri); + + const td = new TextDecoder("utf-8"); + const tasksJson = JSON.parse( + td.decode(await workspace.fs.readFile(tasksUri)) + ) as { tasks: ITask[] }; + + const compileTask = tasksJson.tasks.find( + t => t.label === "Compile Project" + ); + if (compileTask === undefined) { + return undefined; + } + + // find index of -b in the args and then thing after it should match on of the board strings + const bIndex = compileTask.args.findIndex(a => a === "-b"); + if (bIndex === -1 || bIndex === compileTask.args.length - 1) { + return undefined; + } + + let currentBoard = compileTask.args[bIndex + 1]; + + if (overwriteBoard !== undefined) { + if (!VALID_ZEPHYR_BOARDS.includes(currentBoard)) { + const cont = await window.showWarningMessage( + `Current board "${currentBoard}" is not a known ` + + "Zephyr board. Do you want to continue?", + { + modal: true, + }, + "Continue", + "Cancel" + ); + + if (cont !== "Continue") { + return; + } + } + + compileTask.args[bIndex + 1] = overwriteBoard; + currentBoard = overwriteBoard; + const te = new TextEncoder(); + await workspace.fs.writeFile( + tasksUri, + te.encode(JSON.stringify(tasksJson, null, 2)) + ); + } + + if (VALID_ZEPHYR_BOARDS.includes(currentBoard)) { + return currentBoard; + } else { + return undefined; + } + } catch (error) { + Logger.log( + `Failed to read tasks.json file: ${unknownErrorToString(error)}` + ); + void window.showErrorMessage( + "Failed to read tasks.json file. " + + "Make sure the file exists and has a Compile Project task." + ); + + return undefined; + } +} + +export async function getBoardFromZephyrProject( + tasksJsonPath: string +): Promise { + return touchTasksJson(tasksJsonPath); +} export default class SwitchBoardCommand extends Command { private _logger: Logger = new Logger("SwitchBoardCommand"); @@ -42,22 +167,28 @@ export default class SwitchBoardCommand extends Command { } public static async askBoard( - sdkVersion: string + sdkVersion: string, + isZephyrProject = false ): Promise<[string, boolean] | undefined> { - const quickPickItems: string[] = ["pico", "pico_w"]; + const quickPickItems: string[] = [PICO_BOARD, PICO_W_BOARD]; const workspaceFolder = workspace.workspaceFolders?.[0]; + if (workspaceFolder === undefined) { + return; + } + if (!compareLt(sdkVersion, "2.0.0")) { - quickPickItems.push("pico2"); + quickPickItems.push(PICO2_BOARD); } if (!compareLt(sdkVersion, "2.1.0")) { - quickPickItems.push("pico2_w"); + quickPickItems.push(PICO2_W_BOARD); } - const sdkPath = buildSDKPath(sdkVersion); - const boardHeaderDirList = []; + const boardFiles: IBoardFile = {}; - if (workspaceFolder !== undefined) { + if (!isZephyrProject) { + const sdkPath = buildSDKPath(sdkVersion); + const boardHeaderDirList = []; const ws = workspaceFolder.uri.fsPath; const cMakeCachePath = join(ws, "build", "CMakeCache.txt"); @@ -87,38 +218,28 @@ export default class SwitchBoardCommand extends Command { } }); } - } - const systemBoardHeaderDir = join( - sdkPath, - "src", - "boards", - "include", - "boards" - ); - - boardHeaderDirList.push(systemBoardHeaderDir); + const systemBoardHeaderDir = join( + sdkPath, + "src", + "boards", + "include", + "boards" + ); - interface IBoardFile { - [key: string]: string; - } + boardHeaderDirList.push(systemBoardHeaderDir); - const boardFiles: IBoardFile = {}; - - boardHeaderDirList.forEach( - path =>{ - readdirSync(path).forEach( - file => { - const fullFilename = join(path, file); - if(fullFilename.endsWith(".h")) { - const boardName = file.slice(0, -2); // remove .h - boardFiles[boardName] = fullFilename; - quickPickItems.push(boardName); - } + boardHeaderDirList.forEach(path => { + readdirSync(path).forEach(file => { + const fullFilename = join(path, file); + if (fullFilename.endsWith(".h")) { + const boardName = file.slice(0, -2); // remove .h + boardFiles[boardName] = fullFilename; + quickPickItems.push(boardName); } - ) - } - ); + }); + }); + } // show quick pick for board type const board = await window.showQuickPick(quickPickItems, { @@ -126,7 +247,9 @@ export default class SwitchBoardCommand extends Command { }); if (board === undefined) { - return board; + return; + } else if (isZephyrProject) { + return [board, false]; } // Check that board doesn't have an RP2040 on it @@ -137,11 +260,11 @@ export default class SwitchBoardCommand extends Command { } const useRiscV = await window.showQuickPick(["No", "Yes"], { - placeHolder: "Use Risc-V?", + placeHolder: "Use RISC-V?", }); if (useRiscV === undefined) { - return undefined; + return; } return [board, useRiscV === "Yes"]; @@ -150,6 +273,7 @@ export default class SwitchBoardCommand extends Command { async execute(): Promise { const workspaceFolder = workspace.workspaceFolders?.[0]; const isRustProject = State.getInstance().isRustProject; + const isZephyrProject = State.getInstance().isZephyrProject; // check it has a CMakeLists.txt if ( @@ -209,19 +333,40 @@ export default class SwitchBoardCommand extends Command { return; } - // Check if Pico Zephyr project and execute switchBoardZephyr - if ( - readFileSync(join(workspaceFolder.uri.fsPath, "CMakeLists.txt")) - .toString("utf-8") - .includes("pico_zephyr") - ) { - commands.executeCommand( - `${extensionName}.${SwitchZephyrBoardCommand.id}` + if (isZephyrProject) { + await this._switchBoardZephyr(workspaceFolder); + } else { + await this._switchBoardPicoSDK(workspaceFolder); + } + } + + private async _switchBoardZephyr(wsf: WorkspaceFolder): Promise { + const latestSdkVersion = await this._versionBundlesLoader.getLatestSDK(); + if (latestSdkVersion === undefined) { + void window.showErrorMessage( + "Failed to get latest SDK version - cannot update board" ); return; } + const boardRes = await SwitchBoardCommand.askBoard(latestSdkVersion, true); + + if (boardRes === undefined) { + this._logger.info("User cancelled board type selection."); + + return; + } + + const board = stringToZephyrBoard(boardRes[0]); + const taskJsonFile = join(wsf.uri.fsPath, ".vscode", "tasks.json"); + await touchTasksJson(taskJsonFile, board); + + // TODO: maybe reload cmake + } + private async _switchBoardPicoSDK( + workspaceFolder: WorkspaceFolder + ): Promise { const versions = await cmakeGetSelectedToolchainAndSDKVersions( workspaceFolder.uri ); diff --git a/src/commands/switchBoardZephyr.mts b/src/commands/switchBoardZephyr.mts deleted file mode 100644 index 209581d7..00000000 --- a/src/commands/switchBoardZephyr.mts +++ /dev/null @@ -1,241 +0,0 @@ -import { existsSync, type PathOrFileDescriptor, readFileSync } from "fs"; -import { join } from "path"; -import { join as joinPosix } from "path/posix"; -import { window, workspace, Uri } from "vscode"; - -import { Command } from "./command.mjs"; -import type UI from "../ui.mjs"; -import { buildZephyrWorkspacePath } from "../utils/download.mjs"; - -enum BoardNames { - pico = "Pico", - picoW = "Pico W", - pico2 = "Pico 2", - pico2W = "Pico 2W", -} - -interface TasksJson { - tasks: Array<{ - label: string; - args: string[]; - }>; -} - -export function findZephyrBoardInTasksJson( - tasksJsonFilePath: PathOrFileDescriptor -): string | undefined { - const tasksJson = JSON.parse( - readFileSync(tasksJsonFilePath).toString("utf-8") - ) as TasksJson; - - // Check it has a tasks object with - if (tasksJson.tasks === undefined || !Array.isArray(tasksJson.tasks)) { - window.showErrorMessage( - "Could not find task to modify board on.\ - Please see the Pico Zephyr examples for a reference." - ); - - return; - } - - // Find the index of the task called "Compile Project" - const compileIndex = tasksJson.tasks.findIndex( - element => - element.label !== undefined && element.label === "Compile Project" - ); - - if (compileIndex < 0) { - window.showErrorMessage( - "Could not find Compile Project task to modify board on.\ - Please see the Pico Zephyr examples for a reference." - ); - - return; - } - - const args: string[] = tasksJson.tasks[compileIndex].args; - - if (args === undefined || !Array.isArray(args)) { - window.showErrorMessage( - "Could not find args within Compile Project task to modify board on.\ - Please see the Pico Zephyr examples for a reference." - ); - - return; - } - - // Get the args array - const buildIndex = args.findIndex(element => element === "-b"); - let board; - if (buildIndex >= 0 && buildIndex < args.length - 1) { - // Update UI with board description - board = args[buildIndex + 1]; - } else { - window.showErrorMessage( - "Could not find board arg within Compile Project task to modify board\ - on. Please see the Pico Zephyr examples for a reference." - ); - - return; - } - - return board; -} - -export default class SwitchZephyrBoardCommand extends Command { - public static readonly id = "switchBoardZephyr"; - - constructor(private readonly _ui: UI) { - super(SwitchZephyrBoardCommand.id); - } - - private stringToBoard(e: string): string { - if (e === (BoardNames.pico as string)) { - return "rpi_pico"; - } else if (e === (BoardNames.picoW as string)) { - return "rpi_pico/rp2040/w"; - } else if (e === (BoardNames.pico2 as string)) { - return "rpi_pico2/rp2350a/m33"; - } else if (e === (BoardNames.pico2W as string)) { - return "rpi_pico2/rp2350a/m33/w"; - } else { - throw new Error(`Unknown Board Type: ${e}`); - } - } - - async execute(): Promise { - const workspaceFolder = workspace.workspaceFolders?.[0]; - - // check it has a CMakeLists.txt - if ( - workspaceFolder === undefined || - !existsSync(join(workspaceFolder.uri.fsPath, "CMakeLists.txt")) - ) { - return; - } - - // check if pico_zephyr is in CMakeLists.txt - Then update using Zephyr tooling - if ( - readFileSync(join(workspaceFolder.uri.fsPath, "CMakeLists.txt")) - .toString("utf-8") - .includes("pico_zephyr") - ) { - // Get tasks.json, find the task called "Compile Project" - // then edit the build arguments - const taskJsonFile = joinPosix( - workspaceFolder.uri.fsPath, - ".vscode", - "tasks.json" - ); - - if (!existsSync(taskJsonFile)) { - window.showInformationMessage( - "Creating tasks.json file for Zephyr project" - ); - - const zephyrTasksJsonFile = joinPosix( - buildZephyrWorkspacePath(), - "pico-zephyr", - "app", - ".vscode", - "tasks.json" - ); - - await workspace.fs.copy( - Uri.file(zephyrTasksJsonFile), - Uri.file(taskJsonFile) - ); - } - - // Get the board name - const quickPickItems = [ - BoardNames.pico, - BoardNames.picoW, - BoardNames.pico2, - BoardNames.pico2W, - ]; - - const selectedBoard = await window.showQuickPick(quickPickItems, { - placeHolder: "Select Board", - }); - - if (selectedBoard === undefined) { - window.showErrorMessage( - "Error getting board definition from quck pick.\ - Please try again." - ); - - return; - } - - const boardArg = this.stringToBoard(selectedBoard); - - // Now that we know there is a tasks.json file, we can read it - // and set the board in the "Compile Project" task - const jsonString = ( - await workspace.fs.readFile(Uri.file(taskJsonFile)) - ).toString(); - const tasksJson = JSON.parse(jsonString) as TasksJson; - - // Check it has a tasks object with - if (tasksJson.tasks === undefined || !Array.isArray(tasksJson.tasks)) { - window.showErrorMessage( - "Could not find task to modify board on.\ - Please see the Pico Zephyr examples for a reference." - ); - - return; - } - - // Find the index of the task called "Compile Project" - const compileIndex = tasksJson.tasks.findIndex( - element => - element.label !== undefined && element.label === "Compile Project" - ); - - if (compileIndex < 0) { - window.showErrorMessage( - "Could not find Compile Project task to modify board on.\ - Please see the Pico Zephyr examples for a reference." - ); - - return; - } - - const args: string[] = tasksJson.tasks[compileIndex].args; - - if (args === undefined || !Array.isArray(args)) { - window.showErrorMessage( - "Could not find args within Compile Project task to modify board on.\ - Please see the Pico Zephyr examples for a reference." - ); - - return; - } - - // Get the args array - const buildIndex = args.findIndex(element => element === "-b"); - if (buildIndex >= 0 && buildIndex < args.length - 1) { - args[buildIndex + 1] = boardArg; - } else { - window.showErrorMessage( - "Could not find board arg within Compile Project task to modify board\ - on. Please see the Pico Zephyr examples for a reference." - ); - - return; - } - - // Write JSON back into file - const newTasksJsonString = JSON.stringify(tasksJson, null, 4); - await workspace.fs.writeFile( - Uri.file(taskJsonFile), - Buffer.from(newTasksJsonString) - ); - - this._ui.updateBoard(selectedBoard); - - window.showInformationMessage(`Board Updated to ${selectedBoard}`); - } - } -} diff --git a/src/contextKeys.mts b/src/contextKeys.mts index 300fc722..4722bcff 100644 --- a/src/contextKeys.mts +++ b/src/contextKeys.mts @@ -1,7 +1,11 @@ import { extensionName } from "./commands/command.mjs"; export enum ContextKeys { + // General key to check if the current project is a pico project + // that is supported by the extension (C/C++, Rust or Zpephyr) isPicoProject = `${extensionName}.isPicoProject`, + // Key to check if the current project is a rust pico project isRustProject = `${extensionName}.isRustProject`, - isPicoZephyrProject = `${extensionName}.isPicoZephyrProject`, + // Key to check if the current project is a zephyr pico project + isZephyrProject = `${extensionName}.isZephyrProject`, } diff --git a/src/extension.mts b/src/extension.mts index 5d502716..aaea4f5f 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -65,7 +65,6 @@ import { downloadAndInstallOpenOCD, installLatestRustRequirements, } from "./utils/download.mjs"; -import { NewZephyrProjectCommand } from "./utils/generateZephyrProject.mjs"; import { SDK_REPOSITORY_URL } from "./utils/githubREST.mjs"; import { getSupportedToolchains } from "./utils/toolchainUtil.mjs"; import { @@ -86,12 +85,16 @@ import ConfigureCmakeCommand, { import ImportProjectCommand from "./commands/importProject.mjs"; import { homedir } from "os"; import NewExampleProjectCommand from "./commands/newExampleProject.mjs"; -import SwitchBoardCommand from "./commands/switchBoard.mjs"; -import SwitchBoardZephyrCommand from "./commands/switchBoardZephyr.mjs"; +import SwitchBoardCommand, { + getBoardFromZephyrProject, + ZEPHYR_PICO, + ZEPHYR_PICO2, + ZEPHYR_PICO2_W, + ZEPHYR_PICO_W, +} from "./commands/switchBoard.mjs"; import UninstallPicoSDKCommand from "./commands/uninstallPicoSDK.mjs"; import UpdateOpenOCDCommand from "./commands/updateOpenOCD.mjs"; import FlashProjectSWDCommand from "./commands/flashProjectSwd.mjs"; -import { findZephyrBoardInTasksJson } from "./commands/switchBoardZephyr.mjs"; // eslint-disable-next-line max-len import { NewMicroPythonProjectPanel } from "./webview/newMicroPythonProjectPanel.mjs"; import type { Progress as GotProgress } from "got"; @@ -108,7 +111,6 @@ import { OPENOCD_VERSION, } from "./utils/sharedConstants.mjs"; import VersionBundlesLoader from "./utils/versionBundles.mjs"; -import { setupZephyr } from "./utils/setupZephyr.mjs"; import { unknownErrorToString } from "./utils/errorHelper.mjs"; export async function activate(context: ExtensionContext): Promise { @@ -135,7 +137,6 @@ export async function activate(context: ExtensionContext): Promise { new NewProjectCommand(context.extensionUri), new SwitchSDKCommand(ui, context.extensionUri), new SwitchBoardCommand(ui, context.extensionUri), - new SwitchBoardZephyrCommand(ui), new LaunchTargetPathCommand(), new LaunchTargetPathReleaseCommand(), new GetPythonPathCommand(), @@ -151,7 +152,6 @@ export async function activate(context: ExtensionContext): Promise { new GetSVDPathCommand(context.extensionUri), new GetWestPathCommand(), new GetZephyrWorkspacePathCommand(), - new NewZephyrProjectCommand(), new CompileProjectCommand(), new RunProjectCommand(), new FlashProjectSWDCommand(), @@ -270,7 +270,7 @@ export async function activate(context: ExtensionContext): Promise { // Set Pico Zephyr Project false by default await commands.executeCommand( "setContext", - ContextKeys.isPicoZephyrProject, + ContextKeys.isZephyrProject, false ); @@ -283,9 +283,15 @@ export async function activate(context: ExtensionContext): Promise { Logger.info(LoggerSource.extension, "Project is of type: Zephyr"); await commands.executeCommand( "setContext", - ContextKeys.isPicoZephyrProject, + ContextKeys.isPicoProject, + true + ); + await commands.executeCommand( + "setContext", + ContextKeys.isZephyrProject, true ); + State.getInstance().isZephyrProject = true; // TODO: !!!!!!!!! IMPORTANT !!!!!!!!! // TODO: make sure zephy dependencies are installed @@ -300,16 +306,16 @@ export async function activate(context: ExtensionContext): Promise { ); // Update UI with board description - const board = findZephyrBoardInTasksJson(tasksJsonFilePath); + const board = await getBoardFromZephyrProject(tasksJsonFilePath); if (board !== undefined) { - if (board === "rpi_pico2/rp2350a/m33/w") { + if (board === ZEPHYR_PICO2_W) { ui.updateBoard("Pico 2W"); - } else if (board === "rpi_pico2/rp2350a/m33") { + } else if (board === ZEPHYR_PICO2) { ui.updateBoard("Pico 2"); - } else if (board === "rpi_pico/rp2040/w") { + } else if (board === ZEPHYR_PICO_W) { ui.updateBoard("Pico W"); - } else if (board.includes("rpi_pico")) { + } else if (board === ZEPHYR_PICO) { ui.updateBoard("Pico"); } else { ui.updateBoard("Other"); diff --git a/src/state.mts b/src/state.mts index 054c97ce..c555de4c 100644 --- a/src/state.mts +++ b/src/state.mts @@ -1,6 +1,7 @@ export default class State { private static instance?: State; public isRustProject = false; + public isZephyrProject = false; public constructor() {} diff --git a/src/ui.mts b/src/ui.mts index 93835b64..34aa2f50 100644 --- a/src/ui.mts +++ b/src/ui.mts @@ -2,6 +2,11 @@ import { window, type StatusBarItem, StatusBarAlignment } from "vscode"; import Logger from "./logger.mjs"; import type { PicoProjectActivityBar } from "./webview/activityBar.mjs"; import State from "./state.mjs"; +import { extensionName } from "./commands/command.mjs"; +import CompileProjectCommand from "./commands/compileProject.mjs"; +import RunProjectCommand from "./commands/runProject.mjs"; +import SwitchSDKCommand from "./commands/switchSDK.mjs"; +import SwitchBoardCommand from "./commands/switchBoard.mjs"; enum StatusBarItemKey { compile = "raspberry-pi-pico.compileProject", @@ -23,7 +28,7 @@ const STATUS_BAR_ITEMS: { [StatusBarItemKey.compile]: { // alt. "$(gear) Compile" text: "$(file-binary) Compile", - command: "raspberry-pi-pico.compileProject", + command: `${extensionName}.${CompileProjectCommand.id}`, tooltip: "Compile Project", rustSupport: true, zephyrSupport: true, @@ -31,14 +36,14 @@ const STATUS_BAR_ITEMS: { [StatusBarItemKey.run]: { // alt. "$(gear) Compile" text: "$(run) Run", - command: "raspberry-pi-pico.runProject", + command: `${extensionName}.${RunProjectCommand.id}`, tooltip: "Run Project", rustSupport: true, zephyrSupport: true, }, [StatusBarItemKey.picoSDKQuickPick]: { text: "Pico SDK: ", - command: "raspberry-pi-pico.switchSDK", + command: `${extensionName}.${SwitchSDKCommand.id}`, tooltip: "Select Pico SDK", rustSupport: false, zephyrSupport: false, @@ -46,7 +51,8 @@ const STATUS_BAR_ITEMS: { [StatusBarItemKey.picoBoardQuickPick]: { text: "Board: ", rustText: "Chip: ", - command: "raspberry-pi-pico.switchBoard", + // TODO: zephyrCommand option to zwphyr switch borad command or merge them that better + command: `${extensionName}.${SwitchBoardCommand.id}`, tooltip: "Select Chip", rustSupport: true, zephyrSupport: true, diff --git a/src/utils/cmakeUtil.mts b/src/utils/cmakeUtil.mts index c68199ca..20f917df 100644 --- a/src/utils/cmakeUtil.mts +++ b/src/utils/cmakeUtil.mts @@ -1,5 +1,5 @@ import { exec, execFile } from "child_process"; -import { workspace, type Uri, window, ProgressLocation } from "vscode"; +import { Uri, workspace, window, ProgressLocation, commands } from "vscode"; import { showRequirementsNotMetErrorMessage } from "./requirementsUtil.mjs"; import { dirname, join, resolve } from "path"; import Settings from "../settings.mjs"; @@ -12,7 +12,17 @@ import { homedir } from "os"; import which from "which"; import { compareLt } from "./semverUtil.mjs"; import { buildCMakeIncPath } from "./download.mjs"; -import {EOL} from "os"; +import { EOL } from "os"; +import { vsExists } from "./vsHelpers.mjs"; +import State from "../state.mjs"; +import { + CURRENT_DTC_VERSION, + CURRENT_GPERF_VERSION, + CURRENT_WGET_VERSION, +} from "./sharedConstants.mjs"; +import { extensionName } from "../commands/command.mjs"; +import { GetZephyrWorkspacePathCommand } from "../commands/getPaths.mjs"; +import { getBoardFromZephyrProject } from "../commands/switchBoard.mjs"; export const CMAKE_DO_NOT_EDIT_HEADER_PREFIX = // eslint-disable-next-line max-len @@ -42,6 +52,54 @@ export async function getPythonPath(): Promise { return `${pythonPath.replaceAll("\\", "/")}`; } +async function findZephyrBinaries(): Promise { + const isWindows = process.platform === "win32"; + + if (isWindows) { + // get paths to the latest installed gperf and dtc and wget + const wgetPath = join(homedir(), ".pico-sdk", "wget", CURRENT_WGET_VERSION); + const gperfPath = join( + homedir(), + ".pico-sdk", + "gperf", + CURRENT_GPERF_VERSION + ); + const zipPath = join(homedir(), ".pico-sdk", "7zip"); + const dtcPath = join( + homedir(), + ".pico-sdk", + "dtc", + CURRENT_DTC_VERSION, + "bin" + ); + + const missingTools = []; + if (!(await vsExists(wgetPath))) { + missingTools.push("wget"); + } + if (!(await vsExists(gperfPath))) { + missingTools.push("gperf"); + } + if (!(await vsExists(zipPath))) { + missingTools.push("7zip"); + } + if (!(await vsExists(dtcPath))) { + missingTools.push("dtc"); + } + if (missingTools.length > 0) { + void showRequirementsNotMetErrorMessage(missingTools); + + return []; + } + + return [wgetPath, gperfPath, zipPath, dtcPath]; + } + + // TODO: macOS and linux stuff + + return []; +} + export async function getPath(): Promise { const settings = Settings.getInstance(); if (settings === undefined) { @@ -49,6 +107,7 @@ export async function getPath(): Promise { return ""; } + const isZephyrProject = State.getInstance().isZephyrProject; const ninjaPath = ( (await which( @@ -64,6 +123,9 @@ export async function getPath(): Promise { { nothrow: true } )) || "" ).replaceAll("\\", "/"); + + const zephyrBinaries = isZephyrProject ? await findZephyrBinaries() : []; + Logger.debug( LoggerSource.cmake, "Using python:", @@ -91,7 +153,7 @@ export async function getPath(): Promise { const isWindows = process.platform === "win32"; - return `${ninjaPath.includes("/") ? dirname(ninjaPath) : ""}${ + let result = `${ninjaPath.includes("/") ? dirname(ninjaPath) : ""}${ cmakePath.includes("/") ? `${isWindows ? ";" : ":"}${dirname(cmakePath)}` : "" @@ -100,6 +162,15 @@ export async function getPath(): Promise { ? `${dirname(pythonPath)}${isWindows ? ";" : ":"}` : "" }`; + + if (zephyrBinaries.length > 0) { + result += + (isWindows ? ";" : ":") + zephyrBinaries.join(isWindows ? ";" : ":"); + } + + Logger.debug(LoggerSource.cmake, "Using PATH:", result); + + return result; } export async function configureCmakeNinja( @@ -126,7 +197,7 @@ export async function configureCmakeNinja( return false; } - if (existsSync(join(folder.fsPath, "build", "CMakeCache.txt"))) { + if (await vsExists(join(folder.fsPath, "build", "CMakeCache.txt"))) { // check if the build directory has been moved const buildDir = join(folder.fsPath, "build"); @@ -150,7 +221,7 @@ export async function configureCmakeNinja( ` - Deleting CMakeCache.txt and regenerating.` ); - rmSync(join(buildDir, "CMakeCache.txt")); + await workspace.fs.delete(Uri.file(join(buildDir, "CMakeCache.txt"))); } } } @@ -197,6 +268,8 @@ export async function configureCmakeNinja( customEnv[isWindows ? "Path" : "PATH"] = customPath + customEnv[isWindows ? "Path" : "PATH"]; const pythonPath = await getPythonPath(); + const isZephyrProject = State.getInstance().isZephyrProject; + const buildDir = join(folder.fsPath, "build"); const command = `${process.env.ComSpec === "powershell.exe" ? "&" : ""}"${cmake}" ${ @@ -204,16 +277,41 @@ export async function configureCmakeNinja( ? `-DPython3_EXECUTABLE="${pythonPath.replaceAll("\\", "/")}" ` : "" }` + - `-G Ninja -B ./build "${folder.fsPath}"` + + `-G Ninja -B "${buildDir}" "${folder.fsPath}"` + (buildType ? ` -DCMAKE_BUILD_TYPE=${buildType}` : ""); + const zephyrWorkspace = await commands.executeCommand( + `${extensionName}.${GetZephyrWorkspacePathCommand.id}` + ); + const westExe = isWindows ? "west.exe" : "west"; + const westPath = join(zephyrWorkspace, "venv", "Scripts", westExe); + const zephyrBoard = await getBoardFromZephyrProject( + join(folder.fsPath, ".vscode", "tasks.json") + ); + if (isZephyrProject && zephyrBoard === undefined) { + void window.showErrorMessage( + "Failed to configure CMake for the current Zephyr project. " + + "Could not determine the board from .vscode/tasks.json." + ); + + return false; + } + const zephyrCommand = `${ + process.env.ComSpec === "powershell.exe" ? "&" : "" + }"${westPath}" build --cmake-only -b ${zephyrBoard} -d "${buildDir}" "${ + folder.fsPath + }"`; + await new Promise((resolve, reject) => { // use exec to be able to cancel the process const child = exec( - command, + isZephyrProject ? zephyrCommand : command, { env: customEnv, - cwd: folder.fsPath, + cwd: isZephyrProject + ? zephyrWorkspace || folder.fsPath + : folder.fsPath, + windowsHide: false, }, error => { progress.report({ increment: 100 }); @@ -351,19 +449,32 @@ export async function cmakeUpdateSDK( let modifiedContent = content.replace( updateSectionRegex, - `# ${CMAKE_DO_NOT_EDIT_HEADER_PREFIX}` + EOL + - "if(WIN32)" + EOL + - " set(USERHOME $ENV{USERPROFILE})" + EOL + - "else()" + EOL + - " set(USERHOME $ENV{HOME})" + EOL + - "endif()" + EOL + - `set(sdkVersion ${newSDKVersion})` + EOL + - `set(toolchainVersion ${newToolchainVersion})` + EOL + - `set(picotoolVersion ${newPicotoolVersion})` + EOL + - `set(picoVscode ${buildCMakeIncPath(false)}/pico-vscode.cmake)` + EOL + - "if (EXISTS ${picoVscode})" + EOL + - " include(${picoVscode})" + EOL + - "endif()" + EOL + + `# ${CMAKE_DO_NOT_EDIT_HEADER_PREFIX}` + + EOL + + "if(WIN32)" + + EOL + + " set(USERHOME $ENV{USERPROFILE})" + + EOL + + "else()" + + EOL + + " set(USERHOME $ENV{HOME})" + + EOL + + "endif()" + + EOL + + `set(sdkVersion ${newSDKVersion})` + + EOL + + `set(toolchainVersion ${newToolchainVersion})` + + EOL + + `set(picotoolVersion ${newPicotoolVersion})` + + EOL + + `set(picoVscode ${buildCMakeIncPath(false)}/pico-vscode.cmake)` + + EOL + + "if (EXISTS ${picoVscode})" + + EOL + + " include(${picoVscode})" + + EOL + + "endif()" + + EOL + // eslint-disable-next-line max-len "# ====================================================================================" ); diff --git a/src/utils/generateZephyrProject.mts b/src/utils/generateZephyrProject.mts deleted file mode 100644 index 13e8edb0..00000000 --- a/src/utils/generateZephyrProject.mts +++ /dev/null @@ -1,63 +0,0 @@ -import { homedir } from "os"; -import { join as joinPosix } from "path/posix"; -import { workspace, Uri } from "vscode"; - -import { CommandWithResult } from "../commands/command.mjs"; -import { buildZephyrWorkspacePath } from "./download.mjs"; -import Logger from "../logger.mjs"; - -export class NewZephyrProjectCommand extends CommandWithResult< - string | undefined -> { - private running: boolean = false; - - public static readonly id = "newZephyrProject"; - - private readonly _logger: Logger = new Logger("newZephyrProject"); - - constructor() { - super(NewZephyrProjectCommand.id); - } - - async execute(): Promise { - if (this.running) { - return undefined; - } - this.running = true; - - this._logger.info("Generating new Zephyr Project"); - - // Create a new directory to put the project in - const homeDirectory: string = homedir(); - const newProjectDir = joinPosix( - homeDirectory, - "zephyr_test", - "zephyr_project" - ); - await workspace.fs.createDirectory(Uri.file(newProjectDir)); - - // Copy the Zephyr App into the new directory - const zephyrAppDir = joinPosix( - buildZephyrWorkspacePath(), - "pico-zephyr", - "app" - ); - await workspace.fs.copy(Uri.file(zephyrAppDir), Uri.file(newProjectDir), { - overwrite: true, - }); - - const result = true; - - if (result === null || !result) { - this.running = false; - - return undefined; - } - - this._logger.info(`Zephyr Project generated at ${newProjectDir}`); - - this.running = false; - - return "hello"; - } -} diff --git a/src/utils/projectGeneration/projectZephyr.mts b/src/utils/projectGeneration/projectZephyr.mts index e9beae3f..87d70b24 100644 --- a/src/utils/projectGeneration/projectZephyr.mts +++ b/src/utils/projectGeneration/projectZephyr.mts @@ -9,6 +9,7 @@ import { GetPicotoolPathCommand, GetTargetCommand, GetWestPathCommand, + GetZephyrWorkspacePathCommand, } from "../../commands/getPaths.mjs"; import { extensionName } from "../../commands/command.mjs"; import { commands, Uri, window, workspace } from "vscode"; @@ -205,7 +206,7 @@ async function generateVSCodeConfig( toolchainPrefix: "arm-zephyr-eabi", armToolchainPath: // TODO: maybe just full get zephyr compiler path command - "${command:${extensionName}.getZephyrWorkspacePath}/zephyr-sdk/arm-zephyr-eabi/bin", + `\${command:${extensionName}.${GetZephyrWorkspacePathCommand.id}}/zephyr-sdk/arm-zephyr-eabi/bin`, // TODO: get chip dynamically maybe: chip: `\${command:${extensionName}.${GetChipCommand.id}}`, // meaning only one cfg required device: `\${command:${extensionName}.${GetChipUppercaseCommand.id}}`, @@ -218,6 +219,8 @@ async function generateVSCodeConfig( // Fix for no_flash binaries, where monitor reset halt doesn't do what is expected // Also works fine for flash binaries openOCDLaunchCommands: ["adapter speed 5000"], + // TODO: add zephyr build support to support this. + rtos: "Zephyr", }, ], }; diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts index 16cbcbc6..906863b9 100644 --- a/src/utils/setupZephyr.mts +++ b/src/utils/setupZephyr.mts @@ -40,6 +40,8 @@ import { WINDOWS_X86_WGET_DOWNLOAD_URL, } from "./sharedConstants.mjs"; import { SDK_REPOSITORY_URL } from "./githubREST.mjs"; +import { vsExists } from "./vsHelpers.mjs"; +import which from "which"; interface ZephyrSetupValue { cmakeMode: number; @@ -77,6 +79,7 @@ manifest: - cmsis_6 # required by the ARM Cortex-M port - hal_rpi_pico # required for Pico board support - hal_infineon # required for Wifi chip support + - segger # required for Segger RTT support `; // TODO: maybe move into download.mts @@ -507,20 +510,6 @@ async function checkWget(): Promise { ); } -async function vsExists(path: string): Promise { - try { - await workspace.fs.stat(Uri.file(path)); - - return true; - } catch (err) { - if (err instanceof FileSystemError && err.code === "FileNotFound") { - return false; - } - // rethrow unexpected errors - throw err; - } -} - async function check7Zip(): Promise { return window.withProgress( { @@ -587,6 +576,42 @@ async function check7Zip(): Promise { ); } +async function checkMacosLinuxDeps(isWindows: boolean): Promise { + if (isWindows) { + return true; + } + + const wget = await which("wget", { nothrow: true }); + if (!wget) { + void window.showErrorMessage( + "wget not found in PATH. Please install wget " + + "and make sure it is available in PATH." + ); + + return false; + } + const dtc = await which("dtc", { nothrow: true }); + if (!dtc) { + void window.showErrorMessage( + "dtc (Device Tree Compiler) not found in PATH. Please install dtc " + + "and make sure it is available in PATH." + ); + + return false; + } + const gperf = await which("gperf", { nothrow: true }); + if (!gperf) { + void window.showErrorMessage( + "gperf not found in PATH. Please install gperf " + + "and make sure it is available in PATH." + ); + + return false; + } + + return true; +} + async function checkWindowsDeps(isWindows: boolean): Promise { if (!isWindows) { return true; @@ -802,6 +827,16 @@ export async function setupZephyr( return false; } + installedSuccessfully = await checkMacosLinuxDeps(isWindows); + if (!installedSuccessfully) { + progress.report({ + message: "Failed", + increment: 100, + }); + + return false; + } + installedSuccessfully = await checkOpenOCD(); if (!installedSuccessfully) { progress.report({ diff --git a/src/utils/vsHelpers.mts b/src/utils/vsHelpers.mts new file mode 100644 index 00000000..014bc2c4 --- /dev/null +++ b/src/utils/vsHelpers.mts @@ -0,0 +1,15 @@ +import { FileSystemError, Uri, workspace } from "vscode"; + +export async function vsExists(path: string): Promise { + try { + await workspace.fs.stat(Uri.file(path)); + + return true; + } catch (err) { + if (err instanceof FileSystemError && err.code === "FileNotFound") { + return false; + } + // rethrow unexpected errors + throw err; + } +} From d8468bfeca1593a5cec461f521057757a1598b75 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:59:17 +0100 Subject: [PATCH 58/62] Add zephyr setup on opening of zephyr pico projects Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/extension.mts | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/extension.mts b/src/extension.mts index aaea4f5f..655d5030 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -112,6 +112,7 @@ import { } from "./utils/sharedConstants.mjs"; import VersionBundlesLoader from "./utils/versionBundles.mjs"; import { unknownErrorToString } from "./utils/errorHelper.mjs"; +import { setupZephyr } from "./utils/setupZephyr.mjs"; export async function activate(context: ExtensionContext): Promise { Logger.info(LoggerSource.extension, "Extension activation triggered"); @@ -281,6 +282,36 @@ export async function activate(context: ExtensionContext): Promise { // Check for pico_zephyr in CMakeLists.txt if (cmakeListsContents.startsWith(CMAKELISTS_ZEPHYR_HEADER)) { Logger.info(LoggerSource.extension, "Project is of type: Zephyr"); + + const vb = new VersionBundlesLoader(context.extensionUri); + const latest = await vb.getLatest(); + if (latest === undefined) { + Logger.error( + LoggerSource.extension, + "Failed to get latest version bundle for Zephyr project." + ); + + void window.showErrorMessage( + "Failed to get latest version bundle for Zephyr project." + ); + + return; + } + + const result = await setupZephyr({ + extUri: context.extensionUri, + cmakeMode: 4, + cmakePath: latest[1].cmake, + cmakeVersion: "", + }); + if (result === undefined) { + void window.showErrorMessage( + "Failed to setup Zephyr Toolchain. See logs for details." + ); + + return; + } + await commands.executeCommand( "setContext", ContextKeys.isPicoProject, @@ -293,9 +324,6 @@ export async function activate(context: ExtensionContext): Promise { ); State.getInstance().isZephyrProject = true; - // TODO: !!!!!!!!! IMPORTANT !!!!!!!!! - // TODO: make sure zephy dependencies are installed - ui.showStatusBarItems(false, true); // Update the board info if it can be found in tasks.json From f80c4c2ad22a90653d80291dbf128e6f488c910a Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Fri, 19 Sep 2025 16:02:33 +0100 Subject: [PATCH 59/62] Add zephyr setup on opening of zephyr pico projects Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/extension.mts | 4 ++-- web/zephyr/main.js | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/extension.mts b/src/extension.mts index 655d5030..672bb73a 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -301,8 +301,8 @@ export async function activate(context: ExtensionContext): Promise { const result = await setupZephyr({ extUri: context.extensionUri, cmakeMode: 4, - cmakePath: latest[1].cmake, - cmakeVersion: "", + cmakePath: "", + cmakeVersion: latest[1].cmake, }); if (result === undefined) { void window.showErrorMessage( diff --git a/web/zephyr/main.js b/web/zephyr/main.js index f1e9654c..cdb37f64 100644 --- a/web/zephyr/main.js +++ b/web/zephyr/main.js @@ -293,7 +293,13 @@ var previousGpioState = false; // Validate + collect per-mode extras if (cmakeMode === 4) { if (!latestCmakeVersion) { - + console.error('Latest CMake version element not found'); + vscode.postMessage({ + command: CMD_ERROR, + value: 'Internal error: latest CMake version not found.' + }); + submitted = false; + return; } cmakeVersion = latestCmakeVersion.textContent.trim(); } else if (cmakeMode === 2) { From b693928ee813f9a3a466c333242559f5627350c8 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:25:29 +0100 Subject: [PATCH 60/62] Update Windows Python download to 3.13.7 Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/extension.mts | 3 +++ src/utils/sharedConstants.mts | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/extension.mts b/src/extension.mts index 672bb73a..9fe4edea 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -311,6 +311,9 @@ export async function activate(context: ExtensionContext): Promise { return; } + void window.showInformationMessage( + "Zephyr Toolchain setup done. You can now build your project." + ); await commands.executeCommand( "setContext", diff --git a/src/utils/sharedConstants.mts b/src/utils/sharedConstants.mts index 591b3d51..0ab60d2d 100644 --- a/src/utils/sharedConstants.mts +++ b/src/utils/sharedConstants.mts @@ -1,8 +1,8 @@ export const WINDOWS_X86_PYTHON_DOWNLOAD_URL = - "https://www.python.org/ftp/python/3.12.6/python-3.12.6-embed-amd64.zip"; + "https://www.python.org/ftp/python/3.13.7/python-3.13.7-embed-amd64.zip"; export const WINDOWS_ARM64_PYTHON_DOWNLOAD_URL = - "https://www.python.org/ftp/python/3.12.6/python-3.12.6-embed-arm64.zip"; -export const CURRENT_PYTHON_VERSION = "3.12.6"; + "https://www.python.org/ftp/python/3.13.7/python-3.13.7-embed-arm64.zip"; +export const CURRENT_PYTHON_VERSION = "3.13.7"; export const CURRENT_DATA_VERSION = "0.18.0"; export const OPENOCD_VERSION = "0.12.0+dev"; From 0fec6be62a821f7278f5b7cf9b117de52c4899d8 Mon Sep 17 00:00:00 2001 From: Magpie Embedded Date: Mon, 22 Sep 2025 09:51:14 +0100 Subject: [PATCH 61/62] Add custom ninja support for Zephyr project generation Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/utils/setupZephyr.mts | 1 + src/webview/newZephyrProjectPanel.mts | 71 ++++++++++++++++++++++++--- web/zephyr/main.js | 3 ++ 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts index 906863b9..5692adec 100644 --- a/src/utils/setupZephyr.mts +++ b/src/utils/setupZephyr.mts @@ -15,6 +15,7 @@ import type { Progress as GotProgress } from "got"; import { buildCMakePath, + buildNinjaPath, buildZephyrWorkspacePath, downloadAndInstallArchive, downloadAndInstallCmake, diff --git a/src/webview/newZephyrProjectPanel.mts b/src/webview/newZephyrProjectPanel.mts index 7c2ee579..18073508 100644 --- a/src/webview/newZephyrProjectPanel.mts +++ b/src/webview/newZephyrProjectPanel.mts @@ -14,6 +14,7 @@ import { import { join as joinPosix } from "path/posix"; import Settings from "../settings.mjs"; import Logger from "../logger.mjs"; +import { compare } from "../utils/semverUtil.mjs"; import type { WebviewMessage } from "./newProjectPanel.mjs"; import { getNonce, @@ -25,7 +26,7 @@ import { join, dirname } from "path"; import { PythonExtension } from "@vscode/python-extension"; import { unknownErrorToString } from "../utils/errorHelper.mjs"; import { setupZephyr } from "../utils/setupZephyr.mjs"; -import { getCmakeReleases } from "../utils/githubREST.mjs"; +import { getCmakeReleases, getNinjaReleases } from "../utils/githubREST.mjs"; import { getSystemCmakeVersion } from "../utils/cmakeUtil.mjs"; import { generateZephyrProject } from "../utils/projectGeneration/projectZephyr.mjs"; import { @@ -53,7 +54,8 @@ export class NewZephyrProjectPanel { // CMake, Ninja, Python, etc private static createSettingsJson( homePath: string, - cmakePath: string + cmakePath: string, + ninjaPath: string ): string { // Helper functions const getDirName = (s: string): string => dirname(joinPosix(s)); @@ -63,6 +65,11 @@ export class NewZephyrProjectPanel { ); console.log(subbedCmakePath); + const subbedNinjaPath = getDirName( + ninjaPath.replace(homePath, "${userHome}") + ); + console.log(subbedNinjaPath); + const settingsJson = { /* eslint-disable @typescript-eslint/naming-convention */ "cmake.options.statusBarVisibility": "hidden", @@ -89,7 +96,8 @@ export class NewZephyrProjectPanel { Path: "${env:USERPROFILE}/.pico-sdk/toolchain/14_2_Rel1/bin;${env:USERPROFILE}/.pico-sdk/picotool/2.1.1/picotool;" + `${getDirName(cmakePath.replace(homePath, "${env:USERPROFILE}"))};` + - "${env:USERPROFILE}/.pico-sdk/ninja/v1.12.1;${env:PATH}", + `${getDirName(ninjaPath.replace(homePath, "${env:USERPROFILE}"))};` + + "${env:PATH}", }, "terminal.integrated.env.osx": { PICO_SDK_PATH: "${env:HOME}/.pico-sdk/sdk/2.1.1", @@ -97,7 +105,8 @@ export class NewZephyrProjectPanel { PATH: "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.1.1/picotool:" + `${getDirName(cmakePath.replace(homePath, "{env:HOME}"))}:` + - "${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}", + `${getDirName(ninjaPath.replace(homePath, "{env:HOME}"))}:` + + "${env:PATH}", }, "terminal.integrated.env.linux": { PICO_SDK_PATH: "${env:HOME}/.pico-sdk/sdk/2.1.1", @@ -105,14 +114,17 @@ export class NewZephyrProjectPanel { PATH: "${env:HOME}/.pico-sdk/toolchain/14_2_Rel1/bin:${env:HOME}/.pico-sdk/picotool/2.1.1/picotool:" + `${getDirName(cmakePath.replace(homePath, "{env:HOME}"))}:` + - "${env:HOME}/.pico-sdk/ninja/v1.12.1:${env:PATH}", + `${getDirName(ninjaPath.replace(homePath, "{env:HOME}"))}:` + + "${env:PATH}", }, "raspberry-pi-pico.cmakeAutoConfigure": true, "raspberry-pi-pico.useCmakeTools": false, "raspberry-pi-pico.cmakePath": `${getDirName( cmakePath.replace(homePath, "${HOME}") )};`, - "raspberry-pi-pico.ninjaPath": "${HOME}/.pico-sdk/ninja/v1.12.1/ninja", + "raspberry-pi-pico.ninjaPath": `${getDirName( + ninjaPath.replace(homePath, "${HOME}") + )};`, }; /* eslint-enable @typescript-eslint/naming-convention */ @@ -512,6 +524,18 @@ export class NewZephyrProjectPanel { //const knownEnvironments = environments?.known; //const activeEnv = environments?.getActiveEnvironmentPath(); + let ninjasHtml = ""; + const ninjaReleases = await getNinjaReleases(); + console.debug(ninjaReleases); + const latestNinjaRelease = ninjaReleases[0]; + ninjaReleases + .sort((a, b) => compare(b.replace("v", ""), a.replace("v", ""))) + .forEach(ninja => { + ninjasHtml += ``; + }); + let cmakesHtml = ""; const cmakeReleases = await getCmakeReleases(); cmakeReleases.forEach(cmake => { @@ -841,6 +865,41 @@ export class NewZephyrProjectPanel {
+
+ + ${ + latestNinjaRelease !== undefined + ? `
+ + +
` + : "" + } + + ${ + isNinjaSystemAvailable + ? `
+ + +
` + : "" + } + +
+ + + +
+ +
+ + + +
+
+
+
-
+

CMake Version:

@@ -807,7 +735,7 @@ export class NewZephyrProjectPanel { ? `` : "" } - + @@ -863,44 +791,84 @@ export class NewZephyrProjectPanel {
-
-
- - ${ - latestNinjaRelease !== undefined - ? `
- - -
` - : "" - } + +
+

+ Ninja Version: +

- ${ - isNinjaSystemAvailable - ? `
- - -
` - : "" - } +
+ + -
- - - -
+ -
- - - -
+ + + + + + + + + + + +
+
+
-
+