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";
15 consoleOverUART = "Console over UART",
16 consoleOverUSB = "Console over USB (disables other USB use)",
24 interp = "HW interpolation",
26 watch = "HW watchdog",
30 function enumToParam(e: BoardType | ConsoleOptions | Libraries): string {
33 return "--board pico";
35 return "--board pico_w";
36 case ConsoleOptions.consoleOverUART:
38 case ConsoleOptions.consoleOverUSB:
48 case Libraries.interp:
54 case Libraries.clocks:
57 throw new Error(`Unknown enum value: ${e as string}`);
61 interface NewProjectOptions {
65 consoleOptions: ConsoleOptions[];
66 libraries: Libraries[];
69 function getScriptsRoot(): string {
70 return join(dirname(fileURLToPath(import.meta.url)), "scripts");
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";
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."
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,
100 openLabel: "Select project root",
103 if (!projectRoot || projectRoot.length !== 1) {
108 const selectedName: string | undefined = await window.showInputBox({
109 placeHolder: "Enter a project name",
110 title: "New Pico Project",
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;
124 if (!selectedBoardType) {
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",
134 })) as ConsoleOptions[] | undefined;
136 if (!selectedConsoleOptions) {
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",
146 })) as Libraries[] | undefined;
148 if (!selectedLibraries) {
152 this.executePicoProjectGenerator({
154 projectRoot: projectRoot[0].fsPath,
155 boardType: selectedBoardType,
156 consoleOptions: selectedConsoleOptions,
157 libraries: selectedLibraries,
162 * Checks if all requirements are met to run the Pico Project Generator
164 * @returns true if all requirements are met, false otherwise
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",
174 // TODO: check python version
175 return ninja !== null && cmake !== null && python3 !== null;
179 * Executes the Pico Project Generator with the given options
181 * @param options {@link NewProjectOptions} to pass to the Pico Project Generator
183 private executePicoProjectGenerator(options: NewProjectOptions): void {
184 const PICO_SDK_PATH = "";
185 const COMPILER_PATH = "compiler-path/bin";
186 const customEnv: { [key: string]: string } = {
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,
193 customEnv["PATH"] = `${COMPILER_PATH}:${customEnv.PATH}`;
195 // TODO: --projectRoot
196 const command: string = [
198 join(getScriptsRoot(), "pico_project.py"),
199 enumToParam(options.boardType),
200 ...options.consoleOptions.map(option => enumToParam(option)),
201 ...options.libraries.map(option => enumToParam(option)),
203 `"${options.projectRoot}"`,
207 this._logger.debug(`Executing project generator command: ${command}`);
214 cwd: getScriptsRoot(),
218 (error, stdout, stderr) => {
220 this._logger.error(`Error: ${error.message}`);
226 this._logger.error(`Error: ${stderr}`);
231 this._logger.debug(`Output: ${stdout}`);