]> Git Repo - pico-vscode.git/blob - src/commands/newProject.mts
Remove helloWorld command
[pico-vscode.git] / src / commands / newProject.mts
1 import { Uri, commands, window } from "vscode";
2 import { Command } from "./command.mjs";
3 import Logger from "../logger.mjs";
4 import { dirname, join } from "path";
5 import { fileURLToPath } from "url";
6 import { type ExecOptions, exec } from "child_process";
7 import { detectInstalledSDKs } from "../utils/picoSDKUtil.mjs";
8 import type Settings from "../settings.mjs";
9 import {
10   checkForRequirements,
11   showRquirementsNotMetErrorMessage,
12 } from "../utils/requirementsUtil.mjs";
13 import { redirectVSCodeConfig } from "../utils/vscodeConfigUtil.mjs";
14 import { compare } from "semver";
15 import { SettingsKey } from "../settings.mjs";
16
17 enum BoardType {
18   pico = "Pico",
19   picoW = "Pico W",
20 }
21
22 enum ConsoleOptions {
23   consoleOverUART = "Console over UART",
24   consoleOverUSB = "Console over USB (disables other USB use)",
25 }
26
27 enum Libraries {
28   spi = "SPI",
29   i2c = "I2C",
30   dma = "DMA support",
31   pio = "PIO",
32   interp = "HW interpolation",
33   timer = "HW timer",
34   watch = "HW watchdog",
35   clocks = "HW clocks",
36 }
37
38 function enumToParam(e: BoardType | ConsoleOptions | Libraries): string {
39   switch (e) {
40     case BoardType.pico:
41       return "-board pico";
42     case BoardType.picoW:
43       return "-board pico_w";
44     case ConsoleOptions.consoleOverUART:
45       return "-uart";
46     case ConsoleOptions.consoleOverUSB:
47       return "-usb";
48     case Libraries.spi:
49       return "-f spi";
50     case Libraries.i2c:
51       return "-f i2c";
52     case Libraries.dma:
53       return "-f dma";
54     case Libraries.pio:
55       return "-f pio";
56     case Libraries.interp:
57       return "-f interp";
58     case Libraries.timer:
59       return "-f timer";
60     case Libraries.watch:
61       return "-f watch";
62     case Libraries.clocks:
63       return "-f clocks";
64     default:
65       throw new Error(`Unknown enum value: ${e as string}`);
66   }
67 }
68
69 interface NewProjectOptions {
70   name: string;
71   projectRoot: string;
72   boardType: BoardType;
73   consoleOptions: ConsoleOptions[];
74   libraries: Libraries[];
75 }
76
77 function getScriptsRoot(): string {
78   return join(dirname(fileURLToPath(import.meta.url)), "..", "scripts");
79 }
80
81 export default class NewProjectCommand extends Command {
82   private readonly _logger: Logger = new Logger("NewProjectCommand");
83   private readonly _settings: Settings;
84
85   constructor(settings: Settings) {
86     super("newProject");
87
88     this._settings = settings;
89   }
90
91   async execute(): Promise<void> {
92     // check if all requirements are met
93     if (!(await checkForRequirements(this._settings))) {
94       void showRquirementsNotMetErrorMessage();
95
96       return;
97     }
98
99     // TODO: maybe make it posible to also select a folder and
100     // not always create a new one with selectedName
101     const projectRoot: Uri[] | undefined = await window.showOpenDialog({
102       canSelectFiles: false,
103       canSelectFolders: true,
104       canSelectMany: false,
105       openLabel: "Select project root",
106     });
107
108     if (!projectRoot || projectRoot.length !== 1) {
109       return;
110     }
111
112     // get project name
113     const selectedName: string | undefined = await window.showInputBox({
114       placeHolder: "Enter a project name",
115       title: "New Pico Project",
116     });
117
118     if (!selectedName) {
119       return;
120     }
121
122     // get board type (single selection)
123     const selectedBoardType: BoardType | undefined =
124       (await window.showQuickPick(Object.values(BoardType), {
125         placeHolder: "Select a board type",
126         title: "New Pico Project",
127       })) as BoardType | undefined;
128
129     if (!selectedBoardType) {
130       return;
131     }
132
133     // [optional] get console options (multi selection)
134     const selectedConsoleOptions: ConsoleOptions[] | undefined =
135       (await window.showQuickPick(Object.values(ConsoleOptions), {
136         placeHolder: "Would you like to enable the USB console?",
137         title: "New Pico Project",
138         canPickMany: true,
139       })) as ConsoleOptions[] | undefined;
140
141     if (!selectedConsoleOptions) {
142       return;
143     }
144
145     // [optional] get libraries (multi selection)
146     const selectedLibraries: Libraries[] | undefined =
147       (await window.showQuickPick(Object.values(Libraries), {
148         placeHolder: "Select libraries to include",
149         title: "New Pico Project",
150         canPickMany: true,
151       })) as Libraries[] | undefined;
152
153     if (!selectedLibraries) {
154       return;
155     }
156
157     await this.executePicoProjectGenerator({
158       name: selectedName,
159       projectRoot: projectRoot[0].fsPath,
160       boardType: selectedBoardType,
161       consoleOptions: selectedConsoleOptions,
162       libraries: selectedLibraries,
163     });
164   }
165
166   private runGenerator(
167     command: string,
168     options: ExecOptions
169   ): Promise<number | null> {
170     return new Promise<number | null>(resolve => {
171       const generatorProcess = exec(command, options, error => {
172         if (error) {
173           console.error(`Error: ${error.message}`);
174           resolve(null); // Indicate error
175         }
176       });
177
178       generatorProcess.on("exit", code => {
179         // Resolve with exit code or -1 if code is undefined
180         resolve(code);
181       });
182     });
183   }
184
185   /**
186    * Executes the Pico Project Generator with the given options
187    *
188    * @param options {@link NewProjectOptions} to pass to the Pico Project Generator
189    */
190   private async executePicoProjectGenerator(
191     options: NewProjectOptions
192   ): Promise<void> {
193     /*const [PICO_SDK_PATH, COMPILER_PATH] = (await getSDKAndToolchainPath(
194       this._settings
195     )) ?? [];*/
196     const installedSDKs = detectInstalledSDKs().sort((a, b) =>
197       compare(a.version, b.version)
198     );
199
200     if (installedSDKs.length === 0) {
201       void window.showErrorMessage(
202         "Could not find Pico SDK or Toolchain. Please check the wiki."
203       );
204
205       return;
206     }
207
208     const PICO_SDK_PATH = installedSDKs[0].sdkPath;
209     const TOOLCHAIN_PATH = installedSDKs[0].toolchainPath;
210
211     const customEnv: { [key: string]: string } = {
212       ...process.env,
213       // eslint-disable-next-line @typescript-eslint/naming-convention
214       PICO_SDK_PATH: PICO_SDK_PATH,
215       // eslint-disable-next-line @typescript-eslint/naming-convention
216       PICO_TOOLCHAIN_PATH: TOOLCHAIN_PATH,
217     };
218     customEnv[
219       process.platform === "win32" ? "Path" : "PATH"
220     ] = `${TOOLCHAIN_PATH}:${
221       customEnv[process.platform === "win32" ? "Path" : "PATH"]
222     }`;
223     const pythonExe =
224       this._settings.getString(SettingsKey.python3Path) ??
225       process.platform === "win32"
226         ? "python"
227         : "python3";
228
229     const command: string = [
230       pythonExe,
231       join(getScriptsRoot(), "pico_project.py"),
232       enumToParam(options.boardType),
233       ...options.consoleOptions.map(option => enumToParam(option)),
234       !options.consoleOptions.includes(ConsoleOptions.consoleOverUART)
235         ? "-nouart"
236         : "",
237       ...options.libraries.map(option => enumToParam(option)),
238       // generate .vscode config
239       "--project",
240       "vscode",
241       "--projectRoot",
242       `"${options.projectRoot}"`,
243       options.name,
244     ].join(" ");
245
246     this._logger.debug(`Executing project generator command: ${command}`);
247
248     // execute command
249     // TODO: use exit codes to determine why the project generator failed (if it did)
250     // to be able to show the user a more detailed error message
251     const generatorExitCode = await this.runGenerator(command, {
252       env: customEnv,
253       cwd: getScriptsRoot(),
254       windowsHide: true,
255       timeout: 5000,
256     });
257     if (generatorExitCode === 0) {
258       await redirectVSCodeConfig(
259         join(options.projectRoot, options.name, ".vscode"),
260         this._settings.getExtensionId(),
261         this._settings.getExtensionName(),
262         TOOLCHAIN_PATH,
263         installedSDKs[0].version
264       );
265       void window.showInformationMessage(
266         `Successfully created project: ${options.name}`
267       );
268       // open new folder
269       void commands.executeCommand(
270         "vscode.openFolder",
271         Uri.file(join(options.projectRoot, options.name)),
272         true
273       );
274     } else {
275       this._logger.error(
276         `Generator Process exited with code: ${generatorExitCode ?? "null"}`
277       );
278
279       void window.showErrorMessage(`Could not create project ${options.name}`);
280     }
281   }
282 }
This page took 0.038046 seconds and 4 git commands to generate.