diff --git a/package.json b/package.json index a25368ce..ac551024 100644 --- a/package.json +++ b/package.json @@ -285,6 +285,11 @@ "title": "Get git path", "category": "Raspberry Pi Pico", "enablement": "false" + }, + { + "command": "raspberry-pi-pico.cleanZephyr", + "title": "Remove Zephyr workspace", + "category": "Raspberry Pi Pico" } ], "configuration": { diff --git a/src/commands/cleanZephyr.mts b/src/commands/cleanZephyr.mts new file mode 100644 index 00000000..0acb7efa --- /dev/null +++ b/src/commands/cleanZephyr.mts @@ -0,0 +1,55 @@ +import { commands, Uri, window, workspace } from "vscode"; +import { CLEAN_ZEPHYR } from "./cmdIds.mjs"; +import { Command } from "./command.mjs"; +import Logger from "../logger.mjs"; +import { homedir } from "os"; +import { unknownErrorToString } from "../utils/errorHelper.mjs"; +import State from "../state.mjs"; + +export class CleanZephyrCommand extends Command { + private _logger: Logger = new Logger("CleanZephyrCommand"); + + public constructor() { + super(CLEAN_ZEPHYR); + } + + public override async execute(): Promise { + const zephyrWorkspaceUri = Uri.joinPath( + Uri.file(homedir()), + ".pico-sdk", + "zephyr_workspace" + ); + + try { + await workspace.fs.stat(zephyrWorkspaceUri); + + await workspace.fs.delete(zephyrWorkspaceUri, { recursive: true }); + this._logger.info( + `Zephyr workspace at ${zephyrWorkspaceUri.fsPath} has been removed.` + ); + if (State.getInstance().isZephyrProject) { + const answer = await window.showInformationMessage( + "Zephyr workspace has been removed. " + + "Reload the window to reinitialize the Zephyr workspace.", + "Reload Window" + ); + + if (answer === "Reload Window") { + await commands.executeCommand("workbench.action.reloadWindow"); + } + } else { + void window.showInformationMessage( + "Zephyr workspace has been removed." + ); + } + } catch (e) { + this._logger.warn( + `Zephyr workspace at ${zephyrWorkspaceUri.fsPath} ` + + `does not exist or could not be accessed: ${unknownErrorToString(e)}` + ); + void window.showInformationMessage( + "A Zephyr workspace does not exist or could not be accessed." + ); + } + } +} diff --git a/src/commands/cmdIds.mts b/src/commands/cmdIds.mts index 114b8b93..6b6d6c16 100644 --- a/src/commands/cmdIds.mts +++ b/src/commands/cmdIds.mts @@ -47,3 +47,5 @@ export const UNINSTALL_PICO_SDK = "uninstallPicoSDK"; export const UPDATE_OPENOCD = "updateOpenOCD"; export const OPEN_UNINSTALLER = "openUninstaller"; + +export const CLEAN_ZEPHYR = "cleanZephyr"; diff --git a/src/extension.mts b/src/extension.mts index 0fe0fbf8..000a5e68 100644 --- a/src/extension.mts +++ b/src/extension.mts @@ -126,6 +126,7 @@ import LastUsedDepsStore from "./utils/lastUsedDeps.mjs"; import { getWebviewOptions } from "./webview/sharedFunctions.mjs"; import { UninstallerPanel } from "./webview/uninstallerPanel.mjs"; import OpenUninstallerCommand from "./commands/openUninstaller.mjs"; +import { CleanZephyrCommand } from "./commands/cleanZephyr.mjs"; export async function activate(context: ExtensionContext): Promise { Logger.info(LoggerSource.extension, "Extension activation triggered"); @@ -186,6 +187,7 @@ export async function activate(context: ExtensionContext): Promise { new SbomTargetPathReleaseCommand(), new OpenUninstallerCommand(context.extensionUri), new GetGitPathCommand(settings), + new CleanZephyrCommand(), ]; // register all command handlers diff --git a/src/utils/setupZephyr.mts b/src/utils/setupZephyr.mts index c38109e6..30ff5096 100644 --- a/src/utils/setupZephyr.mts +++ b/src/utils/setupZephyr.mts @@ -26,6 +26,7 @@ import VersionBundlesLoader, { type VersionBundle } from "./versionBundles.mjs"; import { CURRENT_DTC_VERSION, CURRENT_GPERF_VERSION, + GET_PIP_URL, LICENSE_URL_7ZIP, OPENOCD_VERSION, SDK_REPOSITORY_URL, @@ -355,6 +356,84 @@ async function checkPicotool(latestVb: VersionBundle): Promise { ); } +async function preparePython(python3Path: string): Promise { + if (!python3Path.includes(".pico-sdk") || process.platform !== "win32") { + return true; + } + + // download get-pip.py and run it + const getPipUrl = new URL(GET_PIP_URL); + const getPipTarget = join(dirname(python3Path), "get-pip.py"); + + const downloadResult = await downloadFileGot(getPipUrl, getPipTarget); + if (!downloadResult) { + Logger.error( + LoggerSource.zephyrSetup, + "Failed to download get-pip.py for Python setup." + ); + + return false; + } + + const pipInstallCommand: string = `"${python3Path}" "${getPipTarget}"`; + + const result = await _runCommand(pipInstallCommand, { + windowsHide: true, + }); + if (result !== 0) { + Logger.error( + LoggerSource.zephyrSetup, + "Failed to install pip for Python setup." + ); + + return false; + } + + // add pip to python path by modifying the ._pth file + const dirContents = await workspace.fs.readDirectory( + Uri.file(dirname(python3Path)) + ); + + const pthFile = dirContents.find(item => item[0].endsWith("._pth")); + if (pthFile === undefined) { + Logger.error( + LoggerSource.zephyrSetup, + "Failed to find ._pth file for Python setup." + ); + + return false; + } + + const pthFileUri = Uri.file(join(dirname(python3Path), pthFile[0])); + let pthFileContents = await workspace.fs.readFile(pthFileUri); + let pthFileString = pthFileContents.toString(); + pthFileString = pthFileString.replace( + "#import site", + "Lib\\site-packages\nScripts\n.\n\n#import site" + ); + pthFileContents = Buffer.from(pthFileString, "utf-8"); + await workspace.fs.writeFile(pthFileUri, pthFileContents); + + const pipPathCommand: string = + `"${python3Path}" -m pip install --upgrade ` + + "pip setuptools wheel virtualenv"; + + const pipResult = await _runCommand(pipPathCommand, { + windowsHide: true, + }); + + if (pipResult !== 0) { + Logger.error( + LoggerSource.zephyrSetup, + "Failed to upgrade pip, setuptools, wheel and virtualenv." + ); + + return false; + } + + return true; +} + async function checkSdk( extensionUri: Uri, latestVb: [string, VersionBundle], @@ -926,6 +1005,18 @@ export async function setupZephyr( return false; } + if (!(await preparePython(python3Path))) { + progress.report({ + message: "Failed", + increment: 100, + }); + void window.showErrorMessage( + "Failed to prepare Python for Zephyr setup. " + + "Cannot continue Zephyr setup." + ); + + return false; + } // required for svd files const sdk = await checkSdk(data.extUri, latestVb, python3Path); diff --git a/src/utils/sharedConstants.mts b/src/utils/sharedConstants.mts index 2b118c9e..353c0e10 100644 --- a/src/utils/sharedConstants.mts +++ b/src/utils/sharedConstants.mts @@ -3,6 +3,7 @@ export const WINDOWS_X86_PYTHON_DOWNLOAD_URL = export const WINDOWS_ARM64_PYTHON_DOWNLOAD_URL = "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 GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"; export const CURRENT_DATA_VERSION = "0.18.0"; export const OPENOCD_VERSION = "0.12.0+dev";