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";
10 } from "../utils/githubREST.mjs";
12 downloadAndInstallCmake,
13 downloadAndInstallNinja,
14 downloadAndInstallSDK,
15 downloadAndInstallToolchain,
16 downloadAndInstallTools,
17 } from "../utils/download.mjs";
18 import { cmakeUpdateSDK } from "../utils/cmakeUtil.mjs";
20 type SupportedToolchainVersion,
21 getSupportedToolchains,
22 } from "../utils/toolchainUtil.mjs";
23 import which from "which";
24 import VersionBundlesLoader, {
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";
31 interface AdvancedSwitchSDKOptions {
32 toolchainVersion: { label: string; toolchain: SupportedToolchainVersion };
34 ninjaVersion?: string;
37 interface SwitchSDKOptions {
39 advancedOptions?: AdvancedSwitchSDKOptions;
42 export default class SwitchSDKCommand extends Command {
44 private _versionBundlesLoader: VersionBundlesLoader;
46 constructor(ui: UI, extensionUri: Uri) {
50 this._versionBundlesLoader = new VersionBundlesLoader(extensionUri);
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");
61 if (await this._isSystemCMakeAvailable()) {
62 quickPickItems.push("Use system CMake");
65 if (cmakeVersions.length === 0) {
66 void window.showErrorMessage(
67 "Failed to get CMake releases. " +
68 "Make sure you are connected to the internet."
74 // show quick pick for cmake version
75 const selectedCmakeVersion = await window.showQuickPick(quickPickItems, {
76 placeHolder: "Select CMake version",
79 if (selectedCmakeVersion === undefined) {
83 switch (selectedCmakeVersion) {
84 case quickPickItems[0]:
86 case quickPickItems[1]: {
87 // show quick pick for cmake version
88 const selectedCmakeVersion = await window.showQuickPick(cmakeVersions, {
89 placeHolder: "Select CMake version",
92 if (selectedCmakeVersion === undefined) {
96 return selectedCmakeVersion;
98 case quickPickItems[2]:
100 case quickPickItems[3]: {
101 const cmakeExePath = await window.showOpenDialog({
102 canSelectFiles: true,
103 canSelectFolders: false,
104 canSelectMany: false,
106 title: "Select a cmake executable to use for this project",
109 if (cmakeExePath && cmakeExePath[0]) {
110 return posixJoin(...cmakeExePath[0].fsPath.split(sep));
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");
126 if (await this._isSystemNinjaAvailable()) {
127 quickPickItems.push("Use system Ninja");
130 if (ninjaVersions.length === 0) {
131 void window.showErrorMessage(
132 "Failed to get Ninja releases. " +
133 "Make sure you are connected to the internet."
139 // show quick pick for ninja version
140 const selectedNinjaVersion = await window.showQuickPick(quickPickItems, {
141 placeHolder: "Select Ninja version",
144 if (selectedNinjaVersion === undefined) {
148 switch (selectedNinjaVersion) {
149 case quickPickItems[0]:
151 case quickPickItems[1]: {
152 // show quick pick for ninja version
153 const selectedNinjaVersion = await window.showQuickPick(ninjaVersions, {
154 placeHolder: "Select Ninja version",
157 if (selectedNinjaVersion === undefined) {
161 return selectedNinjaVersion;
163 case quickPickItems[2]:
165 case quickPickItems[3]: {
166 const ninjaExePath = await window.showOpenDialog({
167 canSelectFiles: true,
168 canSelectFolders: false,
169 canSelectMany: false,
171 title: "Select a cmake executable to use for this project",
174 if (ninjaExePath && ninjaExePath[0]) {
175 return posixJoin(...ninjaExePath[0].fsPath.split(sep));
183 private async askToolchainVersion(
184 supportedToolchainVersions: SupportedToolchainVersion[]
186 { label: string; toolchain: SupportedToolchainVersion } | undefined
188 let selectedToolchainVersion:
189 | { label: string; toolchain: SupportedToolchainVersion }
193 // show quick pick for toolchain version
194 selectedToolchainVersion = await window.showQuickPick(
195 supportedToolchainVersions.map(toolchain => ({
196 label: toolchain.version.replaceAll("_", "."),
197 toolchain: toolchain,
200 placeHolder: "Select ARM Embeded Toolchain version",
204 void window.showErrorMessage(
205 "Failed to get supported toolchain versions. " +
206 "Make sure you are connected to the internet."
212 return selectedToolchainVersion;
215 private async _isSystemNinjaAvailable(): Promise<boolean> {
216 return (await which("ninja", { nothrow: true })) !== null;
219 private async _isSystemCMakeAvailable(): Promise<boolean> {
220 return (await which("cmake", { nothrow: true })) !== null;
224 * Asks the user for advanced options.
226 * @param supportedToolchainVersions A list of supported toolchain versions the user can
228 * @param versionBundle The version bundle of the selected SDK version (if available).
229 * @returns The advanced options or undefined if the user canceled.
231 private async _getAdvancedOptions(
232 supportedToolchainVersions: SupportedToolchainVersion[],
233 versionBundle?: VersionBundle
234 ): Promise<AdvancedSwitchSDKOptions | undefined> {
235 const toolchainVersion = await this.askToolchainVersion(
236 supportedToolchainVersions
238 if (toolchainVersion === undefined) {
242 const cmakeVersion = await this.askCmakeVersion(
243 versionBundle !== undefined
245 if (cmakeVersion === undefined) {
249 let ninjaVersion: string | undefined;
250 if (!NINJA_AUTO_INSTALL_DISABLED) {
251 ninjaVersion = await this.askNinjaVersion(versionBundle !== undefined);
252 if (ninjaVersion === undefined) {
258 toolchainVersion: toolchainVersion,
260 cmakeVersion === "Default" ? versionBundle?.cmake : cmakeVersion,
262 ninjaVersion === "Default" ? versionBundle?.ninja : ninjaVersion,
263 } as AdvancedSwitchSDKOptions;
266 async execute(): Promise<void> {
268 workspace.workspaceFolders === undefined ||
269 workspace.workspaceFolders.length === 0
271 void window.showErrorMessage("Please open a Pico project folder first.");
276 const workspaceFolder = workspace.workspaceFolders[0];
278 const sdks = await getSDKReleases();
280 if (sdks.length === 0) {
281 void window.showErrorMessage(
282 "Failed to get SDK releases. " +
283 "Make sure you are connected to the internet."
289 // TODO: split sdk versions with version bundle and without
291 const selectedSDK = await window.showQuickPick(
297 placeHolder: "Select SDK version",
301 if (selectedSDK === undefined) {
304 const options: SwitchSDKOptions = {
305 sdkVersion: selectedSDK.label,
307 const versionBundle = this._versionBundlesLoader.getModuleVersion(
308 selectedSDK.label.replace("v", "")
311 const configureAdvancedOptions = await window.showQuickPick(["No", "Yes"], {
312 title: "Switch Tools",
313 placeHolder: "Configure advanced options?",
316 if (configureAdvancedOptions === undefined) {
320 const supportedToolchainVersions = await getSupportedToolchains();
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."
333 // TODO: || versionBundle === undefined
334 if (configureAdvancedOptions === "Yes") {
335 const advancedOptions = await this._getAdvancedOptions(
336 supportedToolchainVersions,
340 if (advancedOptions === undefined) {
343 options.advancedOptions = advancedOptions;
346 const selectedToolchain =
347 configureAdvancedOptions === "No"
348 ? versionBundle?.toolchain !== undefined &&
349 supportedToolchainVersions.find(
350 t => t.version === versionBundle?.toolchain
353 label: versionBundle?.toolchain.replaceAll("_", "."),
354 toolchain: supportedToolchainVersions.find(
355 t => t.version === versionBundle?.toolchain
359 label: supportedToolchainVersions[0].version.replaceAll("_", "."),
360 toolchain: supportedToolchainVersions[0],
362 : // if configureAdvancedOptions is not "No" then
363 //options.advancedOptions is defined
364 options.advancedOptions!.toolchainVersion;
366 // show progress bar while installing
367 const result = await window.withProgress(
370 `Installing SDK ${selectedSDK.label}, ` +
371 `toolchain ${selectedToolchain.label} ` +
373 location: ProgressLocation.Notification,
376 // download and install selected SDK
378 (await downloadAndInstallSDK(selectedSDK.sdk, SDK_REPOSITORY_URL)) &&
379 (await downloadAndInstallTools(
381 process.platform === "win32"
388 if (await downloadAndInstallToolchain(selectedToolchain.toolchain)) {
394 options.advancedOptions?.ninjaVersion !== undefined &&
395 (await downloadAndInstallNinja(
396 options.advancedOptions.ninjaVersion
404 // install if cmakeVersion is not a path and not a command
406 options.advancedOptions?.cmakeVersion !== undefined &&
407 !options.advancedOptions.cmakeVersion.includes("/") &&
408 options.advancedOptions.cmakeVersion !== "cmake" &&
409 (await downloadAndInstallCmake(
410 options.advancedOptions.cmakeVersion
418 await updateVSCodeStaticConfigs(
419 workspaceFolder.uri.fsPath,
421 selectedToolchain.toolchain.version,
422 options.advancedOptions?.ninjaVersion,
423 options.advancedOptions?.cmakeVersion
430 await cmakeUpdateSDK(
433 selectedToolchain.toolchain.version
437 // show sdk installed notification
438 message: `Successfully installed SDK ${selectedSDK.label}.`,
446 "Failed to install " + `toolchain ${selectedToolchain.label}.`,
455 // show sdk install failed notification
457 `Failed to install SDK ${selectedSDK.label}.` +
458 "Make sure all requirements are met.",
467 this._ui.updateSDKVersion(selectedSDK.label.replace("v", ""));