13 type CommandWithResult,
14 } from "./commands/command.mjs";
15 import NewProjectCommand from "./commands/newProject.mjs";
16 import Logger, { LoggerSource } from "./logger.mjs";
18 CMAKE_DO_NOT_EDIT_HEADER_PREFIX,
19 cmakeGetSelectedBoard,
20 cmakeGetSelectedToolchainAndSDKVersions,
22 } from "./utils/cmakeUtil.mjs";
27 } from "./settings.mjs";
28 import UI from "./ui.mjs";
29 import SwitchSDKCommand from "./commands/switchSDK.mjs";
30 import { existsSync, readFileSync } from "fs";
31 import { basename, join } from "path";
32 import CompileProjectCommand from "./commands/compileProject.mjs";
33 import RunProjectCommand from "./commands/runProject.mjs";
34 import LaunchTargetPathCommand from "./commands/launchTargetPath.mjs";
41 GetChipUppercaseCommand,
42 } from "./commands/getPaths.mjs";
44 downloadAndInstallCmake,
45 downloadAndInstallNinja,
46 downloadAndInstallSDK,
47 downloadAndInstallToolchain,
48 downloadAndInstallTools,
49 downloadAndInstallPicotool,
50 downloadAndInstallOpenOCD,
52 } from "./utils/download.mjs";
53 import { SDK_REPOSITORY_URL } from "./utils/githubREST.mjs";
54 import { getSupportedToolchains } from "./utils/toolchainUtil.mjs";
59 } from "./webview/newProjectPanel.mjs";
60 import GithubApiCache from "./utils/githubApiCache.mjs";
61 import ClearGithubApiCacheCommand from "./commands/clearGithubApiCache.mjs";
62 import { ContextKeys } from "./contextKeys.mjs";
63 import { PicoProjectActivityBar } from "./webview/activityBar.mjs";
64 import ConditionalDebuggingCommand from "./commands/conditionalDebugging.mjs";
65 import DebugLayoutCommand from "./commands/debugLayout.mjs";
66 import OpenSdkDocumentationCommand from "./commands/openSdkDocumentation.mjs";
67 import ConfigureCmakeCommand from "./commands/configureCmake.mjs";
68 import ImportProjectCommand from "./commands/importProject.mjs";
69 import { homedir } from "os";
70 import VersionBundlesLoader from "./utils/versionBundles.mjs";
71 import { pyenvInstallPython, setupPyenv } from "./utils/pyenvUtil.mjs";
72 import NewExampleProjectCommand from "./commands/newExampleProject.mjs";
73 import SwitchBoardCommand from "./commands/switchBoard.mjs";
74 import UninstallPicoSDKCommand from "./commands/uninstallPicoSDK.mjs";
75 import FlashProjectSWDCommand from "./commands/flashProjectSwd.mjs";
77 export async function activate(context: ExtensionContext): Promise<void> {
78 Logger.info(LoggerSource.extension, "Extension activation triggered");
80 const settings = Settings.createInstance(
81 context.workspaceState,
83 context.extension.packageJSON as PackageJSON
85 GithubApiCache.createInstance(context);
87 const picoProjectActivityBarProvider = new PicoProjectActivityBar();
88 const ui = new UI(picoProjectActivityBarProvider);
91 const COMMANDS: Array<
93 | CommandWithResult<string>
94 | CommandWithResult<boolean>
97 new NewProjectCommand(context.extensionUri),
98 new SwitchSDKCommand(ui, context.extensionUri),
99 new SwitchBoardCommand(ui, context.extensionUri),
100 new LaunchTargetPathCommand(),
101 new GetPythonPathCommand(),
102 new GetEnvPathCommand(),
103 new GetGDBPathCommand(),
104 new GetChipCommand(),
105 new GetChipUppercaseCommand(),
106 new GetTargetCommand(),
107 new CompileProjectCommand(),
108 new RunProjectCommand(),
109 new FlashProjectSWDCommand(),
110 new ClearGithubApiCacheCommand(),
111 new ConditionalDebuggingCommand(),
112 new DebugLayoutCommand(),
113 new OpenSdkDocumentationCommand(context.extensionUri),
114 new ConfigureCmakeCommand(),
115 new ImportProjectCommand(context.extensionUri),
116 new NewExampleProjectCommand(context.extensionUri),
117 new UninstallPicoSDKCommand(),
120 // register all command handlers
121 COMMANDS.forEach(command => {
122 context.subscriptions.push(command.register());
125 context.subscriptions.push(
126 window.registerWebviewPanelSerializer(NewProjectPanel.viewType, {
127 // eslint-disable-next-line @typescript-eslint/require-await
128 async deserializeWebviewPanel(
129 webviewPanel: WebviewPanel,
130 state: { isImportProject: boolean; forceFromExample: boolean }
132 // Reset the webview options so we use latest uri for `localResourceRoots`.
133 webviewPanel.webview.options = getWebviewOptions(context.extensionUri);
134 NewProjectPanel.revive(
136 context.extensionUri,
137 state && state.isImportProject,
138 state && state.forceFromExample
144 context.subscriptions.push(
145 window.registerTreeDataProvider(
146 PicoProjectActivityBar.viewType,
147 picoProjectActivityBarProvider
151 const workspaceFolder = workspace.workspaceFolders?.[0];
153 // check if is a pico project
155 workspaceFolder === undefined ||
156 !existsSync(join(workspaceFolder.uri.fsPath, "pico_sdk_import.cmake"))
160 LoggerSource.extension,
161 "No workspace folder or Pico project found."
163 await commands.executeCommand(
165 ContextKeys.isPicoProject,
172 const cmakeListsFilePath = join(workspaceFolder.uri.fsPath, "CMakeLists.txt");
173 if (!existsSync(cmakeListsFilePath)) {
175 LoggerSource.extension,
176 "No CMakeLists.txt in workspace folder has been found."
178 await commands.executeCommand(
180 ContextKeys.isPicoProject,
187 // check if it has .vscode folder and cmake donotedit header in CMakelists.txt
189 !existsSync(join(workspaceFolder.uri.fsPath, ".vscode")) ||
190 !readFileSync(cmakeListsFilePath)
192 .includes(CMAKE_DO_NOT_EDIT_HEADER_PREFIX)
195 LoggerSource.extension,
196 "No .vscode folder and/or cmake",
197 '"DO NOT EDIT"-header in CMakelists.txt found.'
199 await commands.executeCommand(
201 ContextKeys.isPicoProject,
204 const wantToImport = await window.showInformationMessage(
205 "Do you want to import this project as Raspberry Pi Pico project?",
209 if (wantToImport === "Yes") {
210 void commands.executeCommand(
211 `${extensionName}.${ImportProjectCommand.id}`,
219 await commands.executeCommand("setContext", ContextKeys.isPicoProject, true);
221 // get sdk selected in the project
222 const selectedToolchainAndSDKVersions =
223 await cmakeGetSelectedToolchainAndSDKVersions(workspaceFolder.uri);
224 if (selectedToolchainAndSDKVersions === null) {
228 // get all available toolchains for download link of current selected one
229 const toolchains = await getSupportedToolchains();
230 const selectedToolchain = toolchains.find(
231 toolchain => toolchain.version === selectedToolchainAndSDKVersions[1]
236 !(await downloadAndInstallSDK(
237 selectedToolchainAndSDKVersions[0],
240 !(await downloadAndInstallTools(selectedToolchainAndSDKVersions[0])) ||
241 !(await downloadAndInstallPicotool(selectedToolchainAndSDKVersions[2]))
244 LoggerSource.extension,
245 "Failed to install project SDK",
246 `version: ${selectedToolchainAndSDKVersions[0]}.`,
247 "Make sure all requirements are met."
250 void window.showErrorMessage("Failed to install project SDK version.");
255 LoggerSource.extension,
256 "Found/installed project SDK",
257 `version: ${selectedToolchainAndSDKVersions[0]}`
260 if (!(await downloadAndInstallOpenOCD(openOCDVersion))) {
262 LoggerSource.extension,
263 "Failed to download and install openocd."
267 LoggerSource.extension,
268 "Successfully downloaded and installed openocd."
275 selectedToolchain === undefined ||
276 !(await downloadAndInstallToolchain(selectedToolchain))
279 LoggerSource.extension,
280 "Failed to install project toolchain",
281 `version: ${selectedToolchainAndSDKVersions[1]}`
284 void window.showErrorMessage(
285 "Failed to install project toolchain version."
291 LoggerSource.extension,
292 "Found/installed project toolchain",
293 `version: ${selectedToolchainAndSDKVersions[1]}`
297 // TODO: move this ninja, cmake and python installs out of extension.mts
298 const ninjaPath = settings.getString(SettingsKey.ninjaPath);
299 if (ninjaPath && ninjaPath.includes("/.pico-sdk/ninja")) {
300 // check if ninja path exists
301 if (!existsSync(ninjaPath.replace(HOME_VAR, homedir()))) {
303 LoggerSource.extension,
304 "Ninja path in settings does not exist.",
305 "Installing ninja to default path."
307 const ninjaVersion = /\/\.pico-sdk\/ninja\/([v.0-9]+)\//.exec(
310 if (ninjaVersion === undefined) {
312 LoggerSource.extension,
313 "Failed to get ninja version from path in the settings."
315 await commands.executeCommand(
317 ContextKeys.isPicoProject,
325 await window.withProgress(
327 location: ProgressLocation.Notification,
328 title: "Downloading and installing Ninja. This may take a while...",
332 result = await downloadAndInstallNinja(ninjaVersion);
340 Logger.error(LoggerSource.extension, "Failed to install ninja.");
341 await commands.executeCommand(
343 ContextKeys.isPicoProject,
350 LoggerSource.extension,
351 "Installed selected ninja for project."
356 const cmakePath = settings.getString(SettingsKey.cmakePath);
357 if (cmakePath && cmakePath.includes("/.pico-sdk/cmake")) {
358 // check if cmake path exists
359 if (!existsSync(cmakePath.replace(HOME_VAR, homedir()))) {
361 LoggerSource.extension,
362 "CMake path in settings does not exist.",
363 "Installing CMake to default path."
366 const cmakeVersion = /\/\.pico-sdk\/cmake\/([v.0-9A-Za-z-]+)\//.exec(
369 if (cmakeVersion === undefined) {
371 LoggerSource.extension,
372 "Failed to get CMake version from path in the settings."
374 await commands.executeCommand(
376 ContextKeys.isPicoProject,
384 await window.withProgress(
386 location: ProgressLocation.Notification,
387 title: "Downloading and installing CMake. This may take a while...",
391 result = await downloadAndInstallCmake(cmakeVersion);
399 Logger.error(LoggerSource.extension, "Failed to install CMake.");
400 await commands.executeCommand(
402 ContextKeys.isPicoProject,
409 LoggerSource.extension,
410 "Installed selected cmake for project."
415 const pythonPath = settings.getString(SettingsKey.python3Path);
416 if (pythonPath && pythonPath.includes("/.pico-sdk/python")) {
417 // check if python path exists
418 if (!existsSync(pythonPath.replace(HOME_VAR, homedir()))) {
420 LoggerSource.extension,
421 "Python path in settings does not exist.",
422 "Installing Python3 to default path."
424 const pythonVersion = /\/\.pico-sdk\/python\/([.0-9]+)\//.exec(
427 if (pythonVersion === undefined) {
429 LoggerSource.extension,
430 "Failed to get Python version from path."
432 await commands.executeCommand(
434 ContextKeys.isPicoProject,
441 let result: string | undefined;
442 await window.withProgress(
444 location: ProgressLocation.Notification,
446 "Downloading and installing Python. This may take a long while...",
450 if (process.platform === "win32") {
451 const versionBundle = await new VersionBundlesLoader(
453 ).getPythonWindowsAmd64Url(pythonVersion);
455 if (versionBundle === undefined) {
457 LoggerSource.extension,
458 "Failed to get Python download url from version bundle."
460 await commands.executeCommand(
462 ContextKeys.isPicoProject,
469 // ! because data.pythonMode === 0 => versionBundle !== undefined
470 result = await downloadEmbedPython(versionBundle);
471 } else if (process.platform === "darwin") {
472 const result1 = await setupPyenv();
480 const result2 = await pyenvInstallPython(pythonVersion);
482 if (result2 !== null) {
487 LoggerSource.extension,
488 "Automatic Python installation is only",
489 "supported on Windows and macOS."
492 await window.showErrorMessage(
493 "Automatic Python installation is only " +
494 "supported on Windows and macOS."
503 if (result === undefined) {
504 Logger.error(LoggerSource.extension, "Failed to install Python3.");
505 await commands.executeCommand(
507 ContextKeys.isPicoProject,
516 ui.showStatusBarItems();
517 ui.updateSDKVersion(selectedToolchainAndSDKVersions[0]);
519 const selectedBoard = cmakeGetSelectedBoard(workspaceFolder.uri);
520 if (selectedBoard !== null) {
521 ui.updateBoard(selectedBoard);
523 ui.updateBoard("unknown");
526 // auto project configuration with cmake
527 if (settings.getBoolean(SettingsKey.cmakeAutoConfigure)) {
528 //run `cmake -G Ninja -B ./build ` in the root folder
529 await configureCmakeNinja(workspaceFolder.uri);
531 workspace.onDidChangeTextDocument(event => {
532 // Check if the changed document is the file you are interested in
533 if (basename(event.document.fileName) === "CMakeLists.txt") {
534 // File has changed, do something here
535 // TODO: rerun configure project
536 // TODO: maybe conflicts with cmake extension which also does this
537 console.log("File changed:", event.document.fileName);
542 LoggerSource.extension,
543 "No workspace folder for configuration found",
544 "or cmakeAutoConfigure disabled."
549 export function deactivate(): void {
551 void commands.executeCommand("setContext", ContextKeys.isPicoProject, true);
553 Logger.info(LoggerSource.extension, "Extension deactivated.");