1 import { readFile, writeFile } from "fs/promises";
2 import Logger from "../logger.mjs";
3 import { join } from "path";
4 import { SettingsKey } from "../settings.mjs";
5 import { type WorkspaceConfiguration, workspace } from "vscode";
6 import { dirname } from "path/posix";
7 import { compareGe } from "./semverUtil.mjs";
9 interface Configuration {
10 includePath: string[];
11 forcedInclude: string[];
15 interface CppProperties {
16 configurations: Configuration[];
19 async function updateCppPropertiesFile(
21 newSDKVersion: string,
22 newToolchainVersion: string
26 const jsonData = await readFile(file, "utf8");
27 const cppProperties: CppProperties = JSON.parse(jsonData) as CppProperties;
29 // Update the compilerPath value
30 cppProperties.configurations.forEach(config => {
31 // Remove the old pico-sdk includePath values set by this extension
32 config.includePath = config.includePath.filter(
33 item => !item.startsWith("${userHome}/.pico-sdk")
35 // Add the new pico-sdk includePath
36 config.includePath.push(`\${userHome}/.pico-sdk/sdk/${newSDKVersion}/**`);
38 // Remove the old pico-sdk forcedInclude values set by this extension
39 config.forcedInclude = config.forcedInclude.filter(
40 item => !item.startsWith("${userHome}/.pico-sdk")
42 const baseHeadersFolderName = compareGe(newSDKVersion, "2.0.0")
45 // Add the new pico-sdk forcedInclude
46 config.forcedInclude.push(
47 `\${userHome}/.pico-sdk/sdk/${newSDKVersion}` +
48 `/src/common/${baseHeadersFolderName}/include/pico.h`
51 // Update the compilerPath
53 "${userHome}/.pico-sdk/toolchain" +
54 `/${newToolchainVersion}/bin/${
55 newToolchainVersion.includes("RISCV")
56 ? newToolchainVersion.includes("COREV")
57 ? "riscv32-corev-elf-gcc"
58 : "riscv32-unknown-elf-gcc"
63 // Write the updated JSON back to the file
64 const updatedJsonData = JSON.stringify(cppProperties, null, 4);
65 await writeFile(file, updatedJsonData, "utf8");
67 console.log("cpp_properties.json file updated successfully.");
69 Logger.log("Error updating cpp_properties.json file.");
73 async function updateTasksFile(
75 newNinjaVersion: string
77 // read tasksFile (it contains the path)
78 let content = await readFile(tasksFile, "utf8");
80 // if a string with following format .pico-sdk/ninja/<version>/ exists
81 // then replace <version> with the new version
82 if (content.includes(".pico-sdk/ninja/")) {
83 const oldNinjaVersion = content.match(/(?<=\.pico-sdk\/ninja\/)(.*)(?=\/)/);
84 if (oldNinjaVersion !== null) {
85 content = content.replaceAll(
86 `.pico-sdk/ninja/${oldNinjaVersion[0]}`,
87 `.pico-sdk/ninja/${newNinjaVersion}`
93 await writeFile(tasksFile, content, "utf8");
96 function buildCMakePath(cmakeVersion: string): string {
97 return `\${userHome}/.pico-sdk/cmake/${cmakeVersion}/bin/cmake`;
100 function buildCMakeHomePath(cmakeVersion: string): string {
101 return `\${HOME}/.pico-sdk/cmake/${cmakeVersion}/bin/cmake`;
104 function buildNinjaHomePath(ninjaVersion: string): string {
105 return `\${HOME}/.pico-sdk/ninja/${ninjaVersion}/ninja`;
108 async function updateSettingsFile(
109 newSDKVersion: string,
110 newToolchainVersion: string,
111 newNinjaVersion?: string,
112 newCMakeVersion?: string
114 const workspaceFolder = workspace.workspaceFolders?.[0];
115 if (!workspaceFolder) {
119 const config: WorkspaceConfiguration = workspace.getConfiguration(
125 "terminal.integrated.env.windows",
126 "terminal.integrated.env.osx",
127 "terminal.integrated.env.linux",
129 if (!config.has(key)) {
130 await config.update(key, {}, null);
133 const currentValue: { [key: string]: string } = config.get(key) ?? {};
135 currentValue["PICO_SDK_PATH"] = `\${${
136 key.includes("windows") ? "env:USERPROFILE" : "env:HOME"
137 }}/.pico-sdk/sdk/${newSDKVersion}`;
138 currentValue["PICO_TOOLCHAIN_PATH"] = `\${${
139 key.includes("windows") ? "env:USERPROFILE" : "env:HOME"
140 }}/.pico-sdk/toolchain/${newToolchainVersion}`;
143 // replace env: with env_ before splitting at :
144 const oldPath = currentValue[
145 key.includes("windows") ? "Path" : "PATH"
146 ].replaceAll("${env:", "${env_");
147 Logger.log(`Oldpath ${oldPath}`);
148 const pathList = oldPath.split(key.includes("windows") ? ";" : ":");
149 let toolchainIdx = -1;
153 for (let i = 0; i < pathList.length; i++) {
154 pathList[i] = pathList[i].replaceAll("${env_", "${env:");
155 Logger.log(pathList[i]);
156 const item = pathList[i];
157 if (item.includes(".pico-sdk/toolchain")) {
160 if (item.includes(".pico-sdk/cmake")) {
163 if (item.includes(".pico-sdk/ninja")) {
168 Logger.log(`PathList ${pathList.join(" - ")}`);
170 pathList[toolchainIdx] = currentValue["PICO_TOOLCHAIN_PATH"] + "/bin";
172 if (newCMakeVersion && newCMakeVersion !== "cmake") {
173 let newCmakePath = "";
174 if (!newCMakeVersion.includes("/")) {
175 newCmakePath = `\${${
176 key.includes("windows") ? "env:USERPROFILE" : "env:HOME"
177 }}/.pico-sdk/cmake/${newCMakeVersion}/bin`;
179 newCmakePath = dirname(newCMakeVersion);
182 pathList[cmakeIdx] = newCmakePath;
184 pathList.splice(pathList.length - 1, 0, newCmakePath);
188 if (newNinjaVersion && newNinjaVersion !== "ninja") {
189 let newNinjaPath = "";
190 if (!newNinjaVersion.includes("/")) {
191 newNinjaPath = `\${${
192 key.includes("windows") ? "env:USERPROFILE" : "env:HOME"
193 }}/.pico-sdk/ninja/${newNinjaVersion}`;
195 newNinjaPath = dirname(newNinjaVersion);
198 pathList[ninjaIdx] = newNinjaPath;
200 pathList.splice(pathList.length - 1, 0, newNinjaPath);
204 const newPath = pathList.join(key.includes("windows") ? ";" : ":");
205 currentValue[key.includes("windows") ? "Path" : "PATH"] = newPath;
207 await config.update(key, currentValue, null);
210 if (newNinjaVersion) {
212 "raspberry-pi-pico." + SettingsKey.ninjaPath,
213 buildNinjaHomePath(newNinjaVersion),
217 if (newCMakeVersion) {
220 buildCMakePath(newCMakeVersion),
224 "raspberry-pi-pico." + SettingsKey.cmakePath,
225 buildCMakeHomePath(newCMakeVersion),
231 async function updateLaunchFile(
233 newSDKVersion: string
235 let content = await readFile(launchFile, "utf8");
237 if (content.includes(".pico-sdk/sdk/")) {
238 const oldPicoSDKVersion = content.match(
239 /(?<=\.pico-sdk\/sdk\/)([^/]*)(?=\/)/
241 if (oldPicoSDKVersion !== null) {
242 content = content.replaceAll(
243 `.pico-sdk/sdk/${oldPicoSDKVersion[0]}`,
244 `.pico-sdk/sdk/${newSDKVersion}`
250 await writeFile(launchFile, content, "utf8");
254 * Updates the configs in .vscode/*.json files with the new SDK and toolchain versions.
256 * @param folder Path to the workspace folder containing the .vscode folder.
257 * @param newSDKVersion The new SDK version to set.
258 * @param newToolchainVersion The new toolchain version to set.
260 export async function updateVSCodeStaticConfigs(
262 newSDKVersion: string,
263 newToolchainVersion: string,
264 newNinjaVersion?: string,
265 newCMakeVersion?: string
267 const cppPropertiesFile = join(folder, ".vscode", "c_cpp_properties.json");
268 await updateCppPropertiesFile(
274 await updateSettingsFile(
281 const launchFile = join(folder, ".vscode", "launch.json");
282 await updateLaunchFile(launchFile, newSDKVersion);
284 if (newNinjaVersion) {
285 const tasksFile = join(folder, ".vscode", "tasks.json");
286 await updateTasksFile(tasksFile, newNinjaVersion);