]> Git Repo - pico-vscode.git/blob - src/commands/switchSDK.mts
Fix linting errors
[pico-vscode.git] / src / commands / switchSDK.mts
1 import { Command } from "./command.mjs";
2 import {
3   ProgressLocation,
4   type Uri,
5   window,
6   workspace,
7   commands,
8 } from "vscode";
9 import type UI from "../ui.mjs";
10 import { updateVSCodeStaticConfigs } from "../utils/vscodeConfigUtil.mjs";
11 import {
12   SDK_REPOSITORY_URL,
13   getCmakeReleases,
14   getNinjaReleases,
15   getSDKReleases,
16 } from "../utils/githubREST.mjs";
17 import {
18   downloadAndInstallCmake,
19   downloadAndInstallNinja,
20   downloadAndInstallSDK,
21   downloadAndInstallToolchain,
22   downloadAndInstallTools,
23 } from "../utils/download.mjs";
24 import { cmakeUpdateSDK } from "../utils/cmakeUtil.mjs";
25 import {
26   type SupportedToolchainVersion,
27   getSupportedToolchains,
28 } from "../utils/toolchainUtil.mjs";
29 import which from "which";
30 import VersionBundlesLoader, {
31   type VersionBundle,
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";
36
37 export interface AdvancedSwitchSDKOptions {
38   toolchainVersion: { label: string; toolchain: SupportedToolchainVersion };
39   cmakeVersion: string;
40   ninjaVersion?: string;
41 }
42
43 interface SwitchSDKOptions {
44   sdkVersion: string;
45   advancedOptions?: AdvancedSwitchSDKOptions;
46 }
47
48 export default class SwitchSDKCommand extends Command {
49   private _versionBundlesLoader: VersionBundlesLoader;
50
51   public static readonly id = "switchSDK";
52
53   constructor(private readonly _ui: UI, extensionUri: Uri) {
54     super(SwitchSDKCommand.id);
55
56     this._versionBundlesLoader = new VersionBundlesLoader(extensionUri);
57   }
58
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");
67     }
68     if (await SwitchSDKCommand.isSystemCMakeAvailable()) {
69       quickPickItems.push("Use system CMake");
70     }
71
72     if (cmakeVersions.length === 0) {
73       void window.showErrorMessage(
74         "Failed to get CMake releases. " +
75           "Make sure you are connected to the internet."
76       );
77
78       return;
79     }
80
81     // show quick pick for cmake version
82     const selectedCmakeVersion = await window.showQuickPick(quickPickItems, {
83       placeHolder: "Select CMake version",
84     });
85
86     if (selectedCmakeVersion === undefined) {
87       return;
88     }
89
90     switch (selectedCmakeVersion) {
91       case quickPickItems[0]:
92         return "Default";
93       case quickPickItems[1]: {
94         // show quick pick for cmake version
95         const selectedCmakeVersion = await window.showQuickPick(cmakeVersions, {
96           placeHolder: "Select CMake version",
97         });
98
99         if (selectedCmakeVersion === undefined) {
100           return;
101         }
102
103         return selectedCmakeVersion;
104       }
105       case quickPickItems[2]:
106         return "cmake";
107       case quickPickItems[3]: {
108         const cmakeExePath = await window.showOpenDialog({
109           canSelectFiles: true,
110           canSelectFolders: false,
111           canSelectMany: false,
112           openLabel: "Select",
113           title: "Select a cmake executable to use for this project",
114         });
115
116         if (cmakeExePath && cmakeExePath[0]) {
117           return posixJoin(...cmakeExePath[0].fsPath.split(sep));
118         }
119
120         return;
121       }
122     }
123   }
124
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");
133     }
134     if (await SwitchSDKCommand.isSystemNinjaAvailable()) {
135       quickPickItems.push("Use system Ninja");
136     }
137
138     if (ninjaVersions.length === 0) {
139       void window.showErrorMessage(
140         "Failed to get Ninja releases. " +
141           "Make sure you are connected to the internet."
142       );
143
144       return;
145     }
146
147     // show quick pick for ninja version
148     const selectedNinjaVersion = await window.showQuickPick(quickPickItems, {
149       placeHolder: "Select Ninja version",
150     });
151
152     if (selectedNinjaVersion === undefined) {
153       return;
154     }
155
156     switch (selectedNinjaVersion) {
157       case quickPickItems[0]:
158         return "Default";
159       case quickPickItems[1]: {
160         // show quick pick for ninja version
161         const selectedNinjaVersion = await window.showQuickPick(ninjaVersions, {
162           placeHolder: "Select Ninja version",
163         });
164
165         if (selectedNinjaVersion === undefined) {
166           return;
167         }
168
169         return selectedNinjaVersion;
170       }
171       case quickPickItems[2]:
172         return "ninja";
173       case quickPickItems[3]: {
174         const ninjaExePath = await window.showOpenDialog({
175           canSelectFiles: true,
176           canSelectFolders: false,
177           canSelectMany: false,
178           openLabel: "Select",
179           title: "Select a cmake executable to use for this project",
180         });
181
182         if (ninjaExePath && ninjaExePath[0]) {
183           return posixJoin(...ninjaExePath[0].fsPath.split(sep));
184         }
185
186         return;
187       }
188     }
189   }
190
191   public static async askToolchainVersion(
192     supportedToolchainVersions: SupportedToolchainVersion[]
193   ): Promise<
194     { label: string; toolchain: SupportedToolchainVersion } | undefined
195   > {
196     let selectedToolchainVersion:
197       | { label: string; toolchain: SupportedToolchainVersion }
198       | undefined;
199
200     try {
201       // show quick pick for toolchain version
202       selectedToolchainVersion = await window.showQuickPick(
203         supportedToolchainVersions.map(toolchain => ({
204           label: toolchain.version.replaceAll("_", "."),
205           toolchain: toolchain,
206         })),
207         {
208           placeHolder: "Select ARM Embeded Toolchain version",
209         }
210       );
211     } catch (error) {
212       void window.showErrorMessage(
213         "Failed to get supported toolchain versions. " +
214           "Make sure you are connected to the internet."
215       );
216
217       return;
218     }
219
220     return selectedToolchainVersion;
221   }
222
223   public static async askSDKVersion(): Promise<
224     { label: string; sdk: string } | undefined
225   > {
226     const sdks = await getSDKReleases();
227
228     if (sdks.length === 0) {
229       void window.showErrorMessage(
230         "Failed to get SDK releases. " +
231           "Make sure you are connected to the internet."
232       );
233
234       return;
235     }
236
237     // TODO: split sdk versions with version bundle and without
238     // show quick pick
239     const selectedSDK = await window.showQuickPick(
240       sdks.map(sdk => ({
241         label: `v${sdk}`,
242         sdk: sdk,
243       })),
244       {
245         placeHolder: "Select SDK version",
246       }
247     );
248
249     return selectedSDK;
250   }
251
252   public static async isSystemNinjaAvailable(): Promise<boolean> {
253     return (await which("ninja", { nothrow: true })) !== null;
254   }
255
256   public static async isSystemCMakeAvailable(): Promise<boolean> {
257     return (await which("cmake", { nothrow: true })) !== null;
258   }
259
260   /**
261    * Asks the user for advanced options.
262    *
263    * @param supportedToolchainVersions A list of supported toolchain versions the user can
264    * choose from.
265    * @param versionBundle The version bundle of the selected SDK version (if available).
266    * @returns The advanced options or undefined if the user canceled.
267    */
268   public static async getAdvancedOptions(
269     supportedToolchainVersions: SupportedToolchainVersion[],
270     versionBundle?: VersionBundle
271   ): Promise<AdvancedSwitchSDKOptions | undefined> {
272     const toolchainVersion = await SwitchSDKCommand.askToolchainVersion(
273       supportedToolchainVersions
274     );
275     if (toolchainVersion === undefined) {
276       return;
277     }
278
279     const cmakeVersion = await SwitchSDKCommand.askCmakeVersion(
280       versionBundle !== undefined
281     );
282     if (cmakeVersion === undefined) {
283       return;
284     }
285
286     let ninjaVersion: string | undefined;
287     if (!NINJA_AUTO_INSTALL_DISABLED) {
288       ninjaVersion = await SwitchSDKCommand.askNinjaVersion(
289         versionBundle !== undefined
290       );
291       if (ninjaVersion === undefined) {
292         return;
293       }
294     }
295
296     return {
297       toolchainVersion: toolchainVersion,
298       cmakeVersion:
299         cmakeVersion === "Default" ? versionBundle?.cmake : cmakeVersion,
300       ninjaVersion:
301         ninjaVersion === "Default" ? versionBundle?.ninja : ninjaVersion,
302     } as AdvancedSwitchSDKOptions;
303   }
304
305   async execute(): Promise<void> {
306     if (
307       workspace.workspaceFolders === undefined ||
308       workspace.workspaceFolders.length === 0
309     ) {
310       void window.showErrorMessage("Please open a Pico project folder first.");
311
312       return;
313     }
314
315     const workspaceFolder = workspace.workspaceFolders[0];
316
317     const selectedSDK = await SwitchSDKCommand.askSDKVersion();
318
319     if (selectedSDK === undefined) {
320       return;
321     }
322     const options: SwitchSDKOptions = {
323       sdkVersion: selectedSDK.label,
324     };
325     const versionBundle = await this._versionBundlesLoader.getModuleVersion(
326       selectedSDK.label.replace("v", "")
327     );
328
329     const configureAdvancedOptions = await window.showQuickPick(["No", "Yes"], {
330       title: "Switch Tools",
331       placeHolder: "Configure advanced options?",
332     });
333
334     if (configureAdvancedOptions === undefined) {
335       return;
336     }
337
338     const supportedToolchainVersions = await getSupportedToolchains();
339
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."
346       );
347
348       return;
349     }
350
351     // TODO: || versionBundle === undefined
352     if (configureAdvancedOptions === "Yes") {
353       const advancedOptions = await SwitchSDKCommand.getAdvancedOptions(
354         supportedToolchainVersions,
355         versionBundle
356       );
357
358       if (advancedOptions === undefined) {
359         return;
360       }
361       options.advancedOptions = advancedOptions;
362     }
363
364     const selectedToolchain =
365       configureAdvancedOptions === "No"
366         ? versionBundle?.toolchain !== undefined &&
367           supportedToolchainVersions.find(
368             t => t.version === versionBundle?.toolchain
369           ) !== undefined
370           ? {
371               label: versionBundle?.toolchain.replaceAll("_", "."),
372               toolchain: supportedToolchainVersions.find(
373                 t => t.version === versionBundle?.toolchain
374               )!,
375             }
376           : {
377               label: supportedToolchainVersions[0].version.replaceAll("_", "."),
378               toolchain: supportedToolchainVersions[0],
379             }
380         : // if configureAdvancedOptions is not "No" then
381           //options.advancedOptions is defined
382           options.advancedOptions!.toolchainVersion;
383
384     // show progress bar while installing
385     const result = await window.withProgress(
386       {
387         title:
388           `Installing SDK ${selectedSDK.label}, ` +
389           `toolchain ${selectedToolchain.label} ` +
390           "and tools...",
391         location: ProgressLocation.Notification,
392       },
393       async progress => {
394         // download and install selected SDK
395         if (
396           (await downloadAndInstallSDK(selectedSDK.sdk, SDK_REPOSITORY_URL)) &&
397           (await downloadAndInstallTools(
398             selectedSDK.sdk,
399             process.platform === "win32"
400           ))
401         ) {
402           progress.report({
403             increment: 40,
404           });
405
406           if (await downloadAndInstallToolchain(selectedToolchain.toolchain)) {
407             progress.report({
408               increment: 20,
409             });
410
411             if (
412               options.advancedOptions?.ninjaVersion !== undefined &&
413               (await downloadAndInstallNinja(
414                 options.advancedOptions.ninjaVersion
415               ))
416             ) {
417               progress.report({
418                 increment: 10,
419               });
420             }
421
422             // install if cmakeVersion is not a path and not a command
423             if (
424               options.advancedOptions?.cmakeVersion !== undefined &&
425               !options.advancedOptions.cmakeVersion.includes("/") &&
426               options.advancedOptions.cmakeVersion !== "cmake" &&
427               (await downloadAndInstallCmake(
428                 options.advancedOptions.cmakeVersion
429               ))
430             ) {
431               progress.report({
432                 increment: 10,
433               });
434             }
435
436             await updateVSCodeStaticConfigs(
437               workspaceFolder.uri.fsPath,
438               selectedSDK.sdk,
439               selectedToolchain.toolchain.version,
440               options.advancedOptions?.ninjaVersion,
441               options.advancedOptions?.cmakeVersion
442             );
443
444             progress.report({
445               increment: 10,
446             });
447
448             await cmakeUpdateSDK(
449               workspaceFolder.uri,
450               selectedSDK.sdk,
451               selectedToolchain.toolchain.version
452             );
453
454             progress.report({
455               // show sdk installed notification
456               message: `Successfully installed SDK ${selectedSDK.label}.`,
457               increment: 10,
458             });
459
460             return true;
461           } else {
462             progress.report({
463               message:
464                 "Failed to install " + `toolchain ${selectedToolchain.label}.`,
465               increment: 60,
466             });
467
468             return false;
469           }
470         }
471
472         progress.report({
473           // show sdk install failed notification
474           message:
475             `Failed to install SDK ${selectedSDK.label}.` +
476             "Make sure all requirements are met.",
477           increment: 100,
478         });
479
480         return false;
481       }
482     );
483
484     if (result) {
485       this._ui.updateSDKVersion(selectedSDK.label.replace("v", ""));
486
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.",
493         reloadWindowBtn
494       );
495
496       if (reload === reloadWindowBtn) {
497         void commands.executeCommand("workbench.action.reloadWindow");
498       }
499     }
500   }
501 }
This page took 0.052088 seconds and 4 git commands to generate.