]> Git Repo - pico-vscode.git/blob - src/commands/newProject.mts
Project root support
[pico-vscode.git] / src / commands / newProject.mts
1 import { type Uri, window } from "vscode";
2 import Command from "./command.mjs";
3 import Logger from "../logger.mjs";
4 import which from "which";
5 import { dirname, join } from "path";
6 import { fileURLToPath } from "url";
7 import { exec } from "child_process";
8
9 enum BoardType {
10   pico = "Pico",
11   picoW = "Pico W",
12 }
13
14 enum ConsoleOptions {
15   consoleOverUART = "Console over UART",
16   consoleOverUSB = "Console over USB (disables other USB use)",
17 }
18
19 enum Libraries {
20   spi = "SPI",
21   i2c = "I2C",
22   dma = "DMA support",
23   pio = "PIO",
24   interp = "HW interpolation",
25   timer = "HW timer",
26   watch = "HW watchdog",
27   clocks = "HW clocks",
28 }
29
30 function enumToParam(e: BoardType | ConsoleOptions | Libraries): string {
31   switch (e) {
32     case BoardType.pico:
33       return "--board pico";
34     case BoardType.picoW:
35       return "--board pico_w";
36     case ConsoleOptions.consoleOverUART:
37       return "--uart";
38     case ConsoleOptions.consoleOverUSB:
39       return "--usb";
40     case Libraries.spi:
41       return "-f spi";
42     case Libraries.i2c:
43       return "-f i2c";
44     case Libraries.dma:
45       return "-f dma";
46     case Libraries.pio:
47       return "-f pio";
48     case Libraries.interp:
49       return "-f interp";
50     case Libraries.timer:
51       return "-f timer";
52     case Libraries.watch:
53       return "-f watch";
54     case Libraries.clocks:
55       return "-f clocks";
56     default:
57       throw new Error(`Unknown enum value: ${e as string}`);
58   }
59 }
60
61 interface NewProjectOptions {
62   name: string;
63   projectRoot: string;
64   boardType: BoardType;
65   consoleOptions: ConsoleOptions[];
66   libraries: Libraries[];
67 }
68
69 function getScriptsRoot(): string {
70   return join(dirname(fileURLToPath(import.meta.url)), "scripts");
71 }
72
73 export default class NewProjectCommand extends Command {
74   private readonly _logger: Logger = new Logger("NewProjectCommand");
75   // check at runtime if the OS is Windows
76   private readonly _isWindows: boolean = process.platform === "win32";
77
78   constructor() {
79     super("newProject");
80   }
81
82   async execute(): Promise<void> {
83     // check if all requirements are met
84     if (!(await this.checkForRequirements())) {
85       await window.showErrorMessage(
86         "The Pico development requires Ninja, CMake and Python 3 " +
87           "to be installed and available in the PATH. " +
88           "Please install and restart VS Code."
89       );
90
91       return;
92     }
93
94     // TODO: maybe make it posible to also select a folder and
95     // not always create a new one with selectedName
96     const projectRoot: Uri[] | undefined = await window.showOpenDialog({
97       canSelectFiles: false,
98       canSelectFolders: true,
99       canSelectMany: false,
100       openLabel: "Select project root",
101     });
102
103     if (!projectRoot || projectRoot.length !== 1) {
104       return;
105     }
106
107     // get project name
108     const selectedName: string | undefined = await window.showInputBox({
109       placeHolder: "Enter a project name",
110       title: "New Pico Project",
111     });
112
113     if (!selectedName) {
114       return;
115     }
116
117     // get board type (single selection)
118     const selectedBoardType: BoardType | undefined =
119       (await window.showQuickPick(Object.values(BoardType), {
120         placeHolder: "Select a board type",
121         title: "New Pico Project",
122       })) as BoardType | undefined;
123
124     if (!selectedBoardType) {
125       return;
126     }
127
128     // [optional] get console options (multi selection)
129     const selectedConsoleOptions: ConsoleOptions[] | undefined =
130       (await window.showQuickPick(Object.values(ConsoleOptions), {
131         placeHolder: "Would you like to enable the USB console?",
132         title: "New Pico Project",
133         canPickMany: true,
134       })) as ConsoleOptions[] | undefined;
135
136     if (!selectedConsoleOptions) {
137       return;
138     }
139
140     // [optional] get libraries (multi selection)
141     const selectedLibraries: Libraries[] | undefined =
142       (await window.showQuickPick(Object.values(Libraries), {
143         placeHolder: "Select libraries to include",
144         title: "New Pico Project",
145         canPickMany: true,
146       })) as Libraries[] | undefined;
147
148     if (!selectedLibraries) {
149       return;
150     }
151
152     this.executePicoProjectGenerator({
153       name: selectedName,
154       projectRoot: projectRoot[0].fsPath,
155       boardType: selectedBoardType,
156       consoleOptions: selectedConsoleOptions,
157       libraries: selectedLibraries,
158     });
159   }
160
161   /**
162    * Checks if all requirements are met to run the Pico Project Generator
163    *
164    * @returns true if all requirements are met, false otherwise
165    */
166   private async checkForRequirements(): Promise<boolean> {
167     const ninja: string | null = await which("ninja", { nothrow: true });
168     const cmake: string | null = await which("cmake", { nothrow: true });
169     const python3: string | null = await which(
170       this._isWindows ? "python" : "python3",
171       { nothrow: true }
172     );
173
174     // TODO: check python version
175     return ninja !== null && cmake !== null && python3 !== null;
176   }
177
178   /**
179    * Executes the Pico Project Generator with the given options
180    *
181    * @param options {@link NewProjectOptions} to pass to the Pico Project Generator
182    */
183   private executePicoProjectGenerator(options: NewProjectOptions): void {
184     const PICO_SDK_PATH = "";
185     const COMPILER_PATH = "compiler-path/bin";
186     const customEnv: { [key: string]: string } = {
187       ...process.env,
188       // eslint-disable-next-line @typescript-eslint/naming-convention
189       PICO_SDK_PATH: PICO_SDK_PATH,
190       // eslint-disable-next-line @typescript-eslint/naming-convention
191       PICO_TOOLCHAIN_PATH: COMPILER_PATH,
192     };
193     customEnv["PATH"] = `${COMPILER_PATH}:${customEnv.PATH}`;
194
195     // TODO: --projectRoot
196     const command: string = [
197       "python3",
198       join(getScriptsRoot(), "pico_project.py"),
199       enumToParam(options.boardType),
200       ...options.consoleOptions.map(option => enumToParam(option)),
201       ...options.libraries.map(option => enumToParam(option)),
202       "--projectRoot",
203       `"${options.projectRoot}"`,
204       options.name,
205     ].join(" ");
206
207     this._logger.debug(`Executing project generator command: ${command}`);
208
209     // execute command
210     exec(
211       command,
212       {
213         env: customEnv,
214         cwd: getScriptsRoot(),
215         windowsHide: true,
216         timeout: 5000,
217       },
218       (error, stdout, stderr) => {
219         if (error) {
220           this._logger.error(`Error: ${error.message}`);
221
222           return;
223         }
224
225         if (stderr) {
226           this._logger.error(`Error: ${stderr}`);
227
228           return;
229         }
230
231         this._logger.debug(`Output: ${stdout}`);
232       }
233     );
234   }
235 }
This page took 0.037952 seconds and 4 git commands to generate.