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