1 import { Command } from "./command.mjs";
9 import type UI from "../ui.mjs";
10 import { updateVSCodeStaticConfigs } from "../utils/vscodeConfigUtil.mjs";
16 } from "../utils/githubREST.mjs";
18 downloadAndInstallCmake,
19 downloadAndInstallNinja,
20 downloadAndInstallSDK,
21 downloadAndInstallToolchain,
22 downloadAndInstallTools,
23 } from "../utils/download.mjs";
24 import { cmakeUpdateSDK } from "../utils/cmakeUtil.mjs";
26 type SupportedToolchainVersion,
27 getSupportedToolchains,
28 } from "../utils/toolchainUtil.mjs";
29 import which from "which";
30 import VersionBundlesLoader, {
32 } from "../utils/versionBundles.mjs";
33 import { sep } from "path";
34 import { join as posixJoin } from "path/posix";
35 import { NINJA_AUTO_INSTALL_DISABLED } from "../webview/newProjectPanel.mjs";
37 export interface AdvancedSwitchSDKOptions {
38 toolchainVersion: { label: string; toolchain: SupportedToolchainVersion };
40 ninjaVersion?: string;
43 interface SwitchSDKOptions {
45 advancedOptions?: AdvancedSwitchSDKOptions;
48 export default class SwitchSDKCommand extends Command {
49 private _versionBundlesLoader: VersionBundlesLoader;
51 public static readonly id = "switchSDK";
53 constructor(private readonly _ui: UI, extensionUri: Uri) {
54 super(SwitchSDKCommand.id);
56 this._versionBundlesLoader = new VersionBundlesLoader(extensionUri);
59 // TODO: maybe move into UI helper file or something
60 public static async askCmakeVersion(
61 versionBundleAvailable: boolean
62 ): Promise<string | undefined> {
63 const cmakeVersions = await getCmakeReleases();
64 const quickPickItems = ["Specific version", "Custom path"];
65 if (versionBundleAvailable) {
66 quickPickItems.unshift("Default");
68 if (await SwitchSDKCommand.isSystemCMakeAvailable()) {
69 quickPickItems.push("Use system CMake");
72 if (cmakeVersions.length === 0) {
73 void window.showErrorMessage(
74 "Failed to get CMake releases. " +
75 "Make sure you are connected to the internet."
81 // show quick pick for cmake version
82 const selectedCmakeVersion = await window.showQuickPick(quickPickItems, {
83 placeHolder: "Select CMake version",
86 if (selectedCmakeVersion === undefined) {
90 switch (selectedCmakeVersion) {
91 case quickPickItems[0]:
93 case quickPickItems[1]: {
94 // show quick pick for cmake version
95 const selectedCmakeVersion = await window.showQuickPick(cmakeVersions, {
96 placeHolder: "Select CMake version",
99 if (selectedCmakeVersion === undefined) {
103 return selectedCmakeVersion;
105 case quickPickItems[2]:
107 case quickPickItems[3]: {
108 const cmakeExePath = await window.showOpenDialog({
109 canSelectFiles: true,
110 canSelectFolders: false,
111 canSelectMany: false,
113 title: "Select a cmake executable to use for this project",
116 if (cmakeExePath && cmakeExePath[0]) {
117 return posixJoin(...cmakeExePath[0].fsPath.split(sep));
125 // TODO: maybe move into UI helper file or something
126 public static async askNinjaVersion(
127 versionBundleAvailable: boolean
128 ): Promise<string | undefined> {
129 const ninjaVersions = await getNinjaReleases();
130 const quickPickItems = ["Specific version", "Custom path"];
131 if (versionBundleAvailable) {
132 quickPickItems.unshift("Default");
134 if (await SwitchSDKCommand.isSystemNinjaAvailable()) {
135 quickPickItems.push("Use system Ninja");
138 if (ninjaVersions.length === 0) {
139 void window.showErrorMessage(
140 "Failed to get Ninja releases. " +
141 "Make sure you are connected to the internet."
147 // show quick pick for ninja version
148 const selectedNinjaVersion = await window.showQuickPick(quickPickItems, {
149 placeHolder: "Select Ninja version",
152 if (selectedNinjaVersion === undefined) {
156 switch (selectedNinjaVersion) {
157 case quickPickItems[0]:
159 case quickPickItems[1]: {
160 // show quick pick for ninja version
161 const selectedNinjaVersion = await window.showQuickPick(ninjaVersions, {
162 placeHolder: "Select Ninja version",
165 if (selectedNinjaVersion === undefined) {
169 return selectedNinjaVersion;
171 case quickPickItems[2]:
173 case quickPickItems[3]: {
174 const ninjaExePath = await window.showOpenDialog({
175 canSelectFiles: true,
176 canSelectFolders: false,
177 canSelectMany: false,
179 title: "Select a cmake executable to use for this project",
182 if (ninjaExePath && ninjaExePath[0]) {
183 return posixJoin(...ninjaExePath[0].fsPath.split(sep));
191 public static async askToolchainVersion(
192 supportedToolchainVersions: SupportedToolchainVersion[]
194 { label: string; toolchain: SupportedToolchainVersion } | undefined
196 let selectedToolchainVersion:
197 | { label: string; toolchain: SupportedToolchainVersion }
201 // show quick pick for toolchain version
202 selectedToolchainVersion = await window.showQuickPick(
203 supportedToolchainVersions.map(toolchain => ({
204 label: toolchain.version.replaceAll("_", "."),
205 toolchain: toolchain,
208 placeHolder: "Select ARM Embeded Toolchain version",
212 void window.showErrorMessage(
213 "Failed to get supported toolchain versions. " +
214 "Make sure you are connected to the internet."
220 return selectedToolchainVersion;
223 public static async askSDKVersion(): Promise<
224 { label: string; sdk: string } | undefined
226 const sdks = await getSDKReleases();
228 if (sdks.length === 0) {
229 void window.showErrorMessage(
230 "Failed to get SDK releases. " +
231 "Make sure you are connected to the internet."
237 // TODO: split sdk versions with version bundle and without
239 const selectedSDK = await window.showQuickPick(
245 placeHolder: "Select SDK version",
252 public static async isSystemNinjaAvailable(): Promise<boolean> {
253 return (await which("ninja", { nothrow: true })) !== null;
256 public static async isSystemCMakeAvailable(): Promise<boolean> {
257 return (await which("cmake", { nothrow: true })) !== null;
261 * Asks the user for advanced options.
263 * @param supportedToolchainVersions A list of supported toolchain versions the user can
265 * @param versionBundle The version bundle of the selected SDK version (if available).
266 * @returns The advanced options or undefined if the user canceled.
268 public static async getAdvancedOptions(
269 supportedToolchainVersions: SupportedToolchainVersion[],
270 versionBundle?: VersionBundle
271 ): Promise<AdvancedSwitchSDKOptions | undefined> {
272 const toolchainVersion = await SwitchSDKCommand.askToolchainVersion(
273 supportedToolchainVersions
275 if (toolchainVersion === undefined) {
279 const cmakeVersion = await SwitchSDKCommand.askCmakeVersion(
280 versionBundle !== undefined
282 if (cmakeVersion === undefined) {
286 let ninjaVersion: string | undefined;
287 if (!NINJA_AUTO_INSTALL_DISABLED) {
288 ninjaVersion = await SwitchSDKCommand.askNinjaVersion(
289 versionBundle !== undefined
291 if (ninjaVersion === undefined) {
297 toolchainVersion: toolchainVersion,
299 cmakeVersion === "Default" ? versionBundle?.cmake : cmakeVersion,
301 ninjaVersion === "Default" ? versionBundle?.ninja : ninjaVersion,
302 } as AdvancedSwitchSDKOptions;
305 async execute(): Promise<void> {
307 workspace.workspaceFolders === undefined ||
308 workspace.workspaceFolders.length === 0
310 void window.showErrorMessage("Please open a Pico project folder first.");
315 const workspaceFolder = workspace.workspaceFolders[0];
317 const selectedSDK = await SwitchSDKCommand.askSDKVersion();
319 if (selectedSDK === undefined) {
322 const options: SwitchSDKOptions = {
323 sdkVersion: selectedSDK.label,
325 const versionBundle = await this._versionBundlesLoader.getModuleVersion(
326 selectedSDK.label.replace("v", "")
329 const configureAdvancedOptions = await window.showQuickPick(["No", "Yes"], {
330 title: "Switch Tools",
331 placeHolder: "Configure advanced options?",
334 if (configureAdvancedOptions === undefined) {
338 const supportedToolchainVersions = await getSupportedToolchains();
340 if (supportedToolchainVersions.length === 0) {
341 // internet is not required if locally cached version bundles are available
342 // but in this case a use shouldn't see this message
343 void window.showErrorMessage(
344 "Failed to get supported toolchain versions. " +
345 "Make sure you are connected to the internet."
351 // TODO: || versionBundle === undefined
352 if (configureAdvancedOptions === "Yes") {
353 const advancedOptions = await SwitchSDKCommand.getAdvancedOptions(
354 supportedToolchainVersions,
358 if (advancedOptions === undefined) {
361 options.advancedOptions = advancedOptions;
364 const selectedToolchain =
365 configureAdvancedOptions === "No"
366 ? versionBundle?.toolchain !== undefined &&
367 supportedToolchainVersions.find(
368 t => t.version === versionBundle?.toolchain
371 label: versionBundle?.toolchain.replaceAll("_", "."),
372 toolchain: supportedToolchainVersions.find(
373 t => t.version === versionBundle?.toolchain
377 label: supportedToolchainVersions[0].version.replaceAll("_", "."),
378 toolchain: supportedToolchainVersions[0],
380 : // if configureAdvancedOptions is not "No" then
381 //options.advancedOptions is defined
382 options.advancedOptions!.toolchainVersion;
384 // show progress bar while installing
385 const result = await window.withProgress(
388 `Installing SDK ${selectedSDK.label}, ` +
389 `toolchain ${selectedToolchain.label} ` +
391 location: ProgressLocation.Notification,
394 // download and install selected SDK
396 (await downloadAndInstallSDK(selectedSDK.sdk, SDK_REPOSITORY_URL)) &&
397 (await downloadAndInstallTools(
399 process.platform === "win32"
406 if (await downloadAndInstallToolchain(selectedToolchain.toolchain)) {
412 options.advancedOptions?.ninjaVersion !== undefined &&
413 (await downloadAndInstallNinja(
414 options.advancedOptions.ninjaVersion
422 // install if cmakeVersion is not a path and not a command
424 options.advancedOptions?.cmakeVersion !== undefined &&
425 !options.advancedOptions.cmakeVersion.includes("/") &&
426 options.advancedOptions.cmakeVersion !== "cmake" &&
427 (await downloadAndInstallCmake(
428 options.advancedOptions.cmakeVersion
436 await updateVSCodeStaticConfigs(
437 workspaceFolder.uri.fsPath,
439 selectedToolchain.toolchain.version,
440 options.advancedOptions?.ninjaVersion,
441 options.advancedOptions?.cmakeVersion
448 await cmakeUpdateSDK(
451 selectedToolchain.toolchain.version
455 // show sdk installed notification
456 message: `Successfully installed SDK ${selectedSDK.label}.`,
464 "Failed to install " + `toolchain ${selectedToolchain.label}.`,
473 // show sdk install failed notification
475 `Failed to install SDK ${selectedSDK.label}.` +
476 "Make sure all requirements are met.",
485 this._ui.updateSDKVersion(selectedSDK.label.replace("v", ""));
487 const reloadWindowBtn = "Reload Window";
488 // notify user that reloading the window is
489 // recommended to update intellisense
490 const reload = await window.showInformationMessage(
491 "It is recommended to reload the window to update intellisense " +
492 "with the new SDK version.",
496 if (reload === reloadWindowBtn) {
497 void commands.executeCommand("workbench.action.reloadWindow");