**/*.ts
.pnp*
*.vsix
+
+# make sure scripts are always included
+!scripts/**
-import { type Uri, window } from "vscode";
+import { Uri, commands, window } from "vscode";
import { Command } from "./command.mjs";
import Logger from "../logger.mjs";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
import { type ExecOptions, exec } from "child_process";
-import { getSDKAndToolchainPath } from "../utils/picoSDKUtil.mjs";
+import { detectInstalledSDKs } from "../utils/picoSDKUtil.mjs";
import type Settings from "../settings.mjs";
import {
checkForRequirements,
showRquirementsNotMetErrorMessage,
} from "../utils/requirementsUtil.mjs";
import { redirectVSCodeConfig } from "../utils/vscodeConfigUtil.mjs";
+import { compare } from "semver";
enum BoardType {
pico = "Pico",
function enumToParam(e: BoardType | ConsoleOptions | Libraries): string {
switch (e) {
case BoardType.pico:
- return "--board pico";
+ return "-board pico";
case BoardType.picoW:
- return "--board pico_w";
+ return "-board pico_w";
case ConsoleOptions.consoleOverUART:
- return "--uart";
+ return "-uart";
case ConsoleOptions.consoleOverUSB:
- return "--usb";
+ return "-usb";
case Libraries.spi:
return "-f spi";
case Libraries.i2c:
}
function getScriptsRoot(): string {
- return join(dirname(fileURLToPath(import.meta.url)), "scripts");
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "scripts");
}
export default class NewProjectCommand extends Command {
private readonly _logger: Logger = new Logger("NewProjectCommand");
- // check at runtime if the OS is Windows
- private readonly _isWindows: boolean = process.platform === "win32";
private readonly _settings: Settings;
constructor(settings: Settings) {
private async executePicoProjectGenerator(
options: NewProjectOptions
): Promise<void> {
- const [PICO_SDK_PATH, COMPILER_PATH] = (await getSDKAndToolchainPath(
+ /*const [PICO_SDK_PATH, COMPILER_PATH] = (await getSDKAndToolchainPath(
this._settings
- )) ?? [undefined, undefined];
+ )) ?? [];*/
+ const installedSDKs = (await detectInstalledSDKs()).sort((a, b) =>
+ compare(a.version, b.version)
+ );
- if (!PICO_SDK_PATH || !COMPILER_PATH) {
+ if (installedSDKs.length === 0) {
void window.showErrorMessage(
- "Could not find Pico SDK or Toolchain. Please check your settings."
+ "Could not find Pico SDK or Toolchain. Please check the wiki."
);
-
- return;
}
+ const PICO_SDK_PATH = installedSDKs[0].sdkPath;
+ const TOOLCHAIN_PATH = installedSDKs[0].toolchainPath;
+
const customEnv: { [key: string]: string } = {
...process.env,
// eslint-disable-next-line @typescript-eslint/naming-convention
PICO_SDK_PATH: PICO_SDK_PATH,
// eslint-disable-next-line @typescript-eslint/naming-convention
- PICO_TOOLCHAIN_PATH: COMPILER_PATH,
+ PICO_TOOLCHAIN_PATH: TOOLCHAIN_PATH,
};
- customEnv["PATH"] = `${COMPILER_PATH}:${customEnv.PATH}`;
+ customEnv["PATH"] = `${TOOLCHAIN_PATH}:${customEnv["PATH"]}`;
- // TODO: --projectRoot
const command: string = [
"python3",
join(getScriptsRoot(), "pico_project.py"),
enumToParam(options.boardType),
...options.consoleOptions.map(option => enumToParam(option)),
+ !options.consoleOptions.includes(ConsoleOptions.consoleOverUART)
+ ? "-nouart"
+ : "",
...options.libraries.map(option => enumToParam(option)),
// generate .vscode config
"--project",
+ "vscode",
"--projectRoot",
`"${options.projectRoot}"`,
options.name,
await redirectVSCodeConfig(
join(options.projectRoot, options.name, ".vscode"),
this._settings.getExtensionId(),
- this._settings.getExtensionName()
+ this._settings.getExtensionName(),
+ TOOLCHAIN_PATH,
+ installedSDKs[0].version
);
void window.showInformationMessage(
- `Successfully created project ${options.name}`
+ `Successfully created project: ${options.name}`
+ );
+ // open new folder
+ void commands.executeCommand(
+ "vscode.openFolder",
+ Uri.file(join(options.projectRoot, options.name)),
+ true
);
} else {
this._logger.error(
import { compare } from "semver";
-import { type PicoSDK, detectInstalledSDKs } from "../utils/picoSDKUtil.mjs";
+import { detectInstalledSDKs } from "../utils/picoSDKUtil.mjs";
import { Command } from "./command.mjs";
-import { window } from "vscode";
+import { window, workspace } from "vscode";
import type Settings from "../settings.mjs";
import { SettingsKey } from "../settings.mjs";
import type UI from "../ui.mjs";
+import { updateVSCodeStaticConfigs } from "../utils/vscodeConfigUtil.mjs";
+import { join } from "path";
+import { setPicoSDKPath, setToolchainPath } from "../utils/picoSDKEnvUtil.mjs";
export default class SwitchSDKCommand extends Command {
- private _settigs: Settings;
+ private _settings: Settings;
private _ui: UI;
constructor(settings: Settings, ui: UI) {
super("switchSDK");
- this._settigs = settings;
+ this._settings = settings;
this._ui = ui;
}
async execute(): Promise<void> {
- const availableSDKs = (await detectInstalledSDKs()).sort((a, b) =>
- compare(a.version, b.version)
- );
+ const availableSDKs = (await detectInstalledSDKs())
+ .sort((a, b) => compare(a.version, b.version))
+ .map(sdk => ({
+ label: `Pico SDK v${sdk.version}`,
+ // TODO: maybe remove description
+ description: `${sdk.sdkPath}; ${sdk.toolchainPath}`,
+ sdkPath: sdk.sdkPath,
+ toolchainPath: sdk.toolchainPath,
+ }));
- const selectedSDK = await window.showQuickPick<PicoSDK>(availableSDKs, {
+ const selectedSDK = await window.showQuickPick(availableSDKs, {
placeHolder: "Select Pico SDK",
canPickMany: false,
ignoreFocusOut: false,
return;
}
+ if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) {
+ await updateVSCodeStaticConfigs(
+ join(workspace.workspaceFolders?.[0].uri.fsPath, ".vscode"),
+ selectedSDK.toolchainPath
+ );
+ }
+
+ setPicoSDKPath(selectedSDK.sdkPath, this._settings.getExtensionId());
+ setToolchainPath(selectedSDK.toolchainPath);
+ void window.showWarningMessage(
+ "Reload window to apply changes to linting."
+ );
// TODO: maybe ensure workspace settings are used
// save selected SDK version to settings
- await this._settigs.update(SettingsKey.picoSDK, selectedSDK.version);
- this._ui.updateSDKVersion(selectedSDK.version);
+ await this._settings.update(SettingsKey.picoSDK, selectedSDK.label);
+ this._ui.updateSDKVersion(selectedSDK.label);
}
}
import SwitchSDKCommand from "./commands/switchSDK.mjs";
import GetSDKPathCommand from "./commands/getSDKPath.mjs";
import GetToolchainPathCommand from "./commands/getToolchainPath.mjs";
+import { setPicoSDKPath, setToolchainPath } from "./utils/picoSDKEnvUtil.mjs";
+import { existsSync } from "fs";
+import { join } from "path";
export async function activate(context: ExtensionContext): Promise<void> {
Logger.log("Congratulations the extension is now active!");
const ui = new UI();
ui.init();
+ // WORKAROUND: this check is required because of this extension
+ // beeing dependency of cpptools so it also gets activated in
+ // many non pico projects and ui.show... in these other projects
+ // would be confusing
+ // TODO: maybe also check subdirectories
+ if (
+ workspace.workspaceFolders &&
+ workspace.workspaceFolders.length > 0 &&
+ existsSync(
+ join(workspace.workspaceFolders?.[0].uri.fsPath, "pico_sdk_import.cmake")
+ )
+ ) {
+ ui.showStatusBarItems();
+ }
+
const COMMANDS: Array<Command | CommandWithResult<string>> = [
new HelloWorldCommand(),
new GetSDKPathCommand(settings),
// TODO: somehow allow custom toolchain version in Windows installer so that the
// user can also choose the version as the toolchainPath settings does normally
// contain a machine specific path and is not suitable for repository
+ // or thught a third party config/registry editor
// auto project configuration with cmake
if (
workspace.workspaceFolders.length > 0
) {
//run `cmake -G Ninja -B ./build ` in the root folder
- await configureCmakeNinja(workspace.workspaceFolders?.[0].uri);
+ // TODO: maybe do this after env variables have been set so that the paths
+ // are not queried twice
+ await configureCmakeNinja(workspace.workspaceFolders?.[0].uri, settings);
} else {
- Logger.log("No workspace folder for configuration found");
+ Logger.log(
+ "No workspace folder for configuration found " +
+ "or cmakeAutoConfigure disabled."
+ );
}
// register all command handlers
// check selected SDK version
const sdkVersion = settings.getString(SettingsKey.picoSDK);
+ const sdkPath = await getSDKAndToolchainPath(settings);
+ const customSDKPath =
+ settings.getString(SettingsKey.picoSDKPath) || undefined;
+ const customToolchainPath =
+ settings.getString(SettingsKey.toolchainPath) || undefined;
- if (sdkVersion) {
- ui.updateSDKVersion(sdkVersion);
+ if (
+ // a SDK version is selected
+ sdkVersion &&
+ sdkPath &&
+ // only one path is custom
+ (customSDKPath === undefined || customToolchainPath === undefined)
+ ) {
+ setPicoSDKPath(sdkPath[0], settings.getExtensionId());
+ setToolchainPath(sdkPath[1]);
+ ui.updateSDKVersion(
+ sdkVersion +
+ // if one custom path is set then the picoSDK is only partly replaced/modifed
+ (customSDKPath !== undefined || customToolchainPath !== undefined
+ ? " [MODIFIED]"
+ : "")
+ );
} else {
- // if still a path for sdk can be
- // retrieved (either by custom path settings or env), show "custom"
- if ((await getSDKAndToolchainPath(settings)) !== undefined) {
+ // both paths are custom, show "custom"
+ if (sdkPath !== undefined) {
+ setPicoSDKPath(sdkPath[0], settings.getExtensionId());
+ setToolchainPath(sdkPath[1]);
ui.updateSDKVersion("custom");
} else {
+ // could not find SDK && toolchain
ui.updateSDKVersion("N/A");
}
}
private pkg: PackageJSON;
constructor(context: Memento, packageJSON: PackageJSON) {
- this.config = workspace.getConfiguration("raspberry-pi-pico");
-
this.context = context;
this.pkg = packageJSON;
+
+ this.config = workspace.getConfiguration(packageJSON.name);
}
public get(key: SettingsKey): Setting {
});
}
+ public showStatusBarItems(): void {
+ Object.values(this._items).forEach(item => item.show());
+ }
+
public updateSDKVersion(version: string): void {
this._items[StatusBarItemKey.picoSDKQuickPick].text = STATUS_BAR_ITEMS[
StatusBarItemKey.picoSDKQuickPick
checkForRequirements,
showRquirementsNotMetErrorMessage,
} from "./requirementsUtil.mjs";
+import { join } from "path";
+import { getSDKAndToolchainPath } from "./picoSDKUtil.mjs";
+import type Settings from "../settings.mjs";
-export async function configureCmakeNinja(folder: Uri): Promise<boolean> {
+export async function configureCmakeNinja(
+ folder: Uri,
+ settings: Settings
+): Promise<boolean> {
try {
// check if CMakeLists.txt exists in the root folder
await workspace.fs.stat(
- folder.with({ path: folder.path + "/CMakeLists.txt" })
+ folder.with({ path: join(folder.path, "CMakeLists.txt") })
);
const rquirementsAvailable = await checkForRequirements();
}
void window.withProgress(
- { location: ProgressLocation.Notification, cancellable: true },
+ {
+ location: ProgressLocation.Notification,
+ cancellable: true,
+ title: "Configuring CMake...",
+ },
// eslint-disable-next-line @typescript-eslint/require-await
async (progress, token) => {
+ const sdkPaths = await getSDKAndToolchainPath(settings);
+
// TODO: analyze command result
// TODO: option for the user to choose the generator
const child = exec(`cmake -G Ninja -B ./build ${folder.fsPath}`, {
...process.env,
// TODO: set PICO_SDK_PATH
// eslint-disable-next-line @typescript-eslint/naming-convention
- PICO_SDK_PATH: "",
+ PICO_SDK_PATH: sdkPaths?.[0],
// eslint-disable-next-line @typescript-eslint/naming-convention
- PICO_TOOLCHAIN_PATH: "",
+ PICO_TOOLCHAIN_PATH: sdkPaths?.[1],
},
});
--- /dev/null
+import { readFileSync, writeFileSync } from "fs";
+import { join } from "path";
+import { extensions } from "vscode";
+import Logger from "../logger.mjs";
+
+/**
+ * Sets the PICO_SDK_PATH environment variable for vscode
+ * but also preserves the systems PICO_SDK_PATH variable if set.
+ *
+ * @param path New PICO_SDK_PATH
+ */
+export function setPicoSDKPath(path: string, extensionId?: string): void {
+ if (process.env.PICO_SDK_PATH) {
+ process.env.OLD_PICO_SDK_PATH = process.env.PICO_SDK_PATH;
+ }
+ process.env.PICO_SDK_PATH = path;
+
+ if (!extensionId) {
+ return;
+ }
+
+ // WORKAROUND: cpptools not supporting ${command:} syntax so ${env:} is the only one working
+ // but this requires us to set PICO_SDK_PATH before cpptools extension is loaded
+ // and checks its c_cpp_properties.json
+ const cppToolsPath =
+ extensions.getExtension("ms-vscode.cpptools")?.extensionPath;
+ if (cppToolsPath) {
+ const cppToolsPacakgeJsonPath = join(cppToolsPath, "package.json");
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ const jsonData = JSON.parse(
+ readFileSync(cppToolsPacakgeJsonPath, "utf8")
+ );
+
+ // Check if 'extensionDependencies' property exists
+ // eslint-disable-next-line max-len
+ // eslint-disable-next-line no-prototype-builtins, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
+ if (!jsonData.hasOwnProperty("extensionDependencies")) {
+ // Add 'extensionDependencies' property with an empty array
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ jsonData.extensionDependencies = [];
+ }
+
+ // Add 'extensid' to 'extensionDependencies' array if not already present
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ const extensionDependencies = jsonData.extensionDependencies as string[];
+ if (!extensionDependencies.includes("paulober.raspberry-pi-pico")) {
+ extensionDependencies.push("paulober.raspberry-pi-pico");
+ } else {
+ // don't write to avoid vscode asking to reload because ext changed on disk
+ return;
+ }
+
+ // Write the modified JSON back to the file
+ writeFileSync(cppToolsPacakgeJsonPath, JSON.stringify(jsonData, null, 4));
+ } catch (error) {
+ Logger.log("Error while modifying cpptools.");
+ }
+ }
+}
+
+export function setToolchainPath(path: string): void {
+ process.env.PICO_TOOLCHAIN_PATH = path;
+ process.env.PATH = `${path};${process.env.PATH ?? ""}`;
+}
+
+/**
+ * Retrieves the PICO_SDK_PATH environment variable from outside vscode.
+ * Usefull for detecting the pico-sdk set by the user outside of vscode by env variable.
+ *
+ * @returns PICO_SDK_PATH environment variable unmatched by the extension
+ */
+export function getSystemPicoSDKPath(): string | undefined {
+ // if there was PICO_SDK_PATH set outside of vscode, use it
+ return process.env.OLD_PICO_SDK_PATH || undefined;
+}
import type Settings from "../settings.mjs";
import { SettingsKey } from "../settings.mjs";
import which from "which";
+import { getSystemPicoSDKPath } from "./picoSDKEnvUtil.mjs";
export class PicoSDK implements QuickPickItem {
public version: string;
+ public label: string;
public sdkPath: string;
public toolchainPath: string;
public kind: QuickPickItemKind = QuickPickItemKind.Default;
constructor(version: string, sdkPath: string, toolchainPath: string) {
this.version = version;
+ this.label = `Pico SDK v${version}`;
this.sdkPath = sdkPath;
this.toolchainPath = toolchainPath;
}
- get label(): string {
+ /*get label(): string {
return `Pico SDK v${this.version}`;
- }
+ }*/
+
+ /*get description(): string {
+ return this.sdkPath;
+ }*/
}
/**
}
// use manual paths if set
- if (manualPicoSDKPath) {
+ if (manualPicoSDKPath !== undefined && manualPicoSDKPath !== "") {
sdkPath = manualPicoSDKPath;
}
- if (manualToolchainPath) {
+ if (manualToolchainPath !== undefined && manualToolchainPath !== "") {
toolchainPath = manualToolchainPath;
}
async function detectSDKAndToolchainFromEnv(): Promise<
[string, string] | undefined
> {
- if (process.env.PICO_SDK_PATH) {
+ const PICO_SDK_PATH = getSystemPicoSDKPath();
+ if (PICO_SDK_PATH) {
// TODO: move compiler name into variable (global)
+ // TODO: maybe also check PICO_TOOLCHAIN_PATH variable
const toolchainPath: string | null = await which("arm-none-eabi-gcc", {
nothrow: true,
});
if (toolchainPath !== null) {
// get grandparent directory of compiler
- return [process.env.PICO_SDK_PATH, dirname(toolchainPath)];
+ return [PICO_SDK_PATH, dirname(toolchainPath)];
}
}
sdkPath: "/Users/paulober/pico-sdk",
toolchainPath:
"/Applications/ArmGNUToolchain/12.2.mpacbti-rel1" +
- "/arm-none-eabi/bin/arm-none-eabi-gcc",
+ "/arm-none-eabi/bin",
} as PicoSDK,
];
} else {
import Logger from "../logger.mjs";
import { join } from "path";
import { EOL } from "os";
+import { SettingsKey } from "../settings.mjs";
interface Configuration {
name: string;
});
// Write the updated JSON back to the file
- const updatedJsonData = JSON.stringify(cppProperties, null, 2);
+ const updatedJsonData = JSON.stringify(cppProperties, null, 4);
await writeFile(file, updatedJsonData, "utf8");
console.log("cpp_properties.json file updated successfully.");
Logger.log("launch.json file updated successfully.");
} catch (error) {
- Logger.log("An error occurred while updating the launch.json file.");
+ Logger.log("An error occurred while replacing PICO_SDK_PATH variable.");
}
}
}
}
+interface SettingsJSON {
+ [key: string]: unknown;
+}
+
+async function addSetting(
+ filePath: string,
+ setting: string,
+ value: string | boolean
+): Promise<void> {
+ try {
+ // Read the JSON file
+ const jsonData = await readFile(filePath, "utf8");
+ const settingsJSON: SettingsJSON = JSON.parse(jsonData) as SettingsJSON;
+ settingsJSON[setting] = value;
+ await writeFile(filePath, JSON.stringify(settingsJSON, null, 4), "utf8");
+
+ Logger.log("Pico SDK version set successfully for new project.");
+ } catch (error) {
+ // TODO: log error
+ Logger.log(
+ "An error occurred while settings Pico SDK version for new project."
+ );
+ }
+}
+
+/**
+ * Modifies auto-generated VSCode configuration files to integrate
+ * with this extension. This includes:
+ * - replacing paths with ${command:extension.getSDKPath} and ${command:extension.getToolchainPath}
+ * - adding the extension to the list of recommended extensions
+ * - adding the Pico SDK version to the list of settings
+ * - removing the set(PICO_SDK_PATH ...) from CMakeLists.txt so it uses the one set by the ext
+ *
+ * @param folder
+ * @param extensionId
+ * @param extensionName
+ * @param toolchainPath
+ * @param newSDKVersion
+ */
export async function redirectVSCodeConfig(
folder: string,
extensionId: string,
- extensionName: string
+ extensionName: string,
+ // WORKAROUND: as c_cpp_properties.json from vscode-cpptools
+ // does not support ${command:} variables at the moment
+ toolchainPath: string,
+ newSDKVersion?: string
): Promise<void> {
// eslint-disable-next-line max-len
const redirectedPicoSDKPathVariable = `\${command:${extensionName}.getSDKPath}`;
// eslint-disable-next-line max-len
- const redirectedToolchainPathVariable = `\${command:${extensionName}.getToolchainPath}`;
+ // WORKAROUND: const redirectedToolchainPathVariable = `\${command:${extensionName}.getToolchainPath}`;
- const cppPropertiesFile = join(folder, "cpp_properties.json");
+ const cppPropertiesFile = join(folder, "c_cpp_properties.json");
await updateCppPropertiesFile(
cppPropertiesFile,
- redirectedToolchainPathVariable
+ join(
+ // WORKAROUND: redirectedToolchainPathVariable,
+ toolchainPath,
+ process.platform === "win32"
+ ? "arm-none-eabi-gcc.exe"
+ : "arm-none-eabi-gcc"
+ )
);
- await replacePicoSDKPathVariable(
+ // WORKAROUND: c_cpp_properties.json from vscode-cpptools
+ // does not support ${command:} variables at the moment
+ /*await replacePicoSDKPathVariable(
cppPropertiesFile,
redirectedPicoSDKPathVariable
- );
+ );*/
await updateExtensionsJSONFile(join(folder, "extensions.json"), extensionId);
);
// TODO: maybe search sub directories too to find the CMakeLists.txt file?
- await removeLineFromFile(join(folder, "CMakeLists.txt"), "set(PICO_SDK_PATH");
+ // TODO: maybe repalce the path with the one selected by the user so it can
+ // also be build from the command line? where the ext can edit the path before cmake
+ // but also once the project is configured
+ // with cmake -G Ninja, CMakeLists.txt is not used anymore (ideally)
+ await removeLineFromFile(
+ join(folder, "..", "CMakeLists.txt"),
+ "set(PICO_SDK_PATH"
+ );
+
+ if (newSDKVersion) {
+ await addSetting(
+ join(folder, "settings.json"),
+ [extensionName, SettingsKey.picoSDK].join("."),
+ newSDKVersion
+ );
+ }
+
+ // disable cmake confgiuring
+ await addSetting(
+ join(folder, "settings.json"),
+ "cmake.configureOnOpen",
+ false
+ );
+ await addSetting(
+ join(folder, "settings.json"),
+ "cmake.automaticReconfigure",
+ false
+ );
+ await addSetting(
+ join(folder, "settings.json"),
+ "cmake.configureOnEdit",
+ false
+ );
+}
+
+export async function updateVSCodeStaticConfigs(
+ folder: string,
+ toolchainPath: string
+): Promise<void> {
+ const cppPropertiesFile = join(folder, "c_cpp_properties.json");
+ // WORKAROUND: because c_cpp_properties.json from vscode-cpptools
+ // does not support dynamic variables
+ await updateCppPropertiesFile(
+ cppPropertiesFile,
+ join(
+ toolchainPath,
+ process.platform === "win32"
+ ? "arm-none-eabi-gcc.exe"
+ : "arm-none-eabi-gcc"
+ )
+ );
}