Skip to content

Commit b02c94a

Browse files
authored
Merge pull request #223 from raspberrypi/fix-zephyr-setup
Fix zephyr setup
2 parents cec99da + da6ce4c commit b02c94a

File tree

6 files changed

+235
-7
lines changed

6 files changed

+235
-7
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
"command": "raspberry-pi-pico.switchSDK",
8282
"title": "Switch Pico SDK",
8383
"category": "Raspberry Pi Pico",
84-
"enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isZephyrProject"
84+
"enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject"
8585
},
8686
{
8787
"command": "raspberry-pi-pico.switchBoard",
@@ -277,7 +277,7 @@
277277
},
278278
{
279279
"command": "raspberry-pi-pico.openUninstaller",
280-
"title": "Open Uninstaller",
280+
"title": "Manage Installed Components",
281281
"category": "Raspberry Pi Pico"
282282
},
283283
{

src/utils/cmakeUtil.mts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import State from "../state.mjs";
1818
import {
1919
generateCustomZephyrEnv,
2020
getBoardFromZephyrProject,
21+
zephyrGetSelectedSnippets,
2122
} from "./setupZephyr.mjs";
2223

2324
export const CMAKE_DO_NOT_EDIT_HEADER_PREFIX =
@@ -248,12 +249,21 @@ export async function configureCmakeNinja(
248249

249250
return false;
250251
}
251-
const zephyrCommand = `${
252+
let zephyrCommand = `${
252253
process.env.ComSpec?.endsWith("powershell.exe") ? "&" : ""
253254
}"${westPath}" build --cmake-only -b ${
254255
zephyrBoard ?? ""
255256
} -d "${buildDir}" "${folder.fsPath}"`;
256257

258+
// check for selected snippets and include for cmake configuration
259+
if (isZephyrProject) {
260+
const snippets = await zephyrGetSelectedSnippets(folder);
261+
262+
for (const snippet of snippets) {
263+
zephyrCommand += ` -S ${snippet}`;
264+
}
265+
}
266+
257267
await new Promise<void>((resolve, reject) => {
258268
// use exec to be able to cancel the process
259269
const child = exec(

src/utils/semverUtil.mts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,87 @@ export function compareLtMajor(first: string, second: string): boolean {
141141
// Compare only the major versions
142142
return firstMajor < secondMajor;
143143
}
144+
145+
export type Pre = { tag: "alpha" | "beta" | "rc" | null; num: number };
146+
147+
export type ParsedVer = {
148+
major: number;
149+
minor: number;
150+
patch: number;
151+
pre: Pre; // null tag means "final"
152+
};
153+
154+
function parseVer(input: string): ParsedVer | null {
155+
const s = input.trim().toLowerCase().replace(/^v/, "");
156+
const [core, preRaw = ""] = s.split("-", 2);
157+
158+
// allow 2 or 3 core parts; pad missing with 0
159+
const parts = core.split(".").map(n => Number(n));
160+
if (parts.length < 2 || parts.length > 3) {
161+
return null;
162+
}
163+
const [major, minor, patch = 0] = parts;
164+
if (![major, minor, patch].every(n => Number.isInteger(n) && n >= 0)) {
165+
return null;
166+
}
167+
168+
let pre: Pre = { tag: null, num: 0 };
169+
if (preRaw) {
170+
// accept alpha/beta/rc with optional number (default 0)
171+
const m = /^(alpha|beta|rc)(\d+)?$/.exec(preRaw);
172+
if (!m) {
173+
return null;
174+
}
175+
pre = { tag: m[1] as Pre["tag"], num: m[2] ? Number(m[2]) : 0 };
176+
}
177+
178+
return { major, minor, patch, pre };
179+
}
180+
181+
const rank: Record<NonNullable<Pre["tag"]> | "final", number> = {
182+
alpha: 0,
183+
beta: 1,
184+
rc: 2,
185+
final: 3,
186+
};
187+
188+
/** Compare a vs b: -1 if a<b, 0 if equal, 1 if a>b */
189+
export function compareSemverPre(a: string, b: string): number {
190+
const A = parseVer(a),
191+
B = parseVer(b);
192+
if (!A || !B) {
193+
throw new Error(`Invalid version: "${a}" or "${b}"`);
194+
}
195+
196+
if (A.major !== B.major) {
197+
return A.major < B.major ? -1 : 1;
198+
}
199+
if (A.minor !== B.minor) {
200+
return A.minor < B.minor ? -1 : 1;
201+
}
202+
if (A.patch !== B.patch) {
203+
return A.patch < B.patch ? -1 : 1;
204+
}
205+
206+
// prerelease ranking: alpha < beta < rc < final
207+
const rA = rank[A.pre.tag ?? "final"];
208+
const rB = rank[B.pre.tag ?? "final"];
209+
if (rA !== rB) {
210+
return rA < rB ? -1 : 1;
211+
}
212+
213+
// same prerelease tag (or both final)
214+
if (A.pre.tag === null) {
215+
return 0;
216+
} // both final
217+
if (A.pre.num !== B.pre.num) {
218+
return A.pre.num < B.pre.num ? -1 : 1;
219+
}
220+
221+
return 0;
222+
}
223+
224+
export const geSemverPre = (a: string, b: string): boolean =>
225+
compareSemverPre(a, b) >= 0;
226+
export const ltSemverPre = (a: string, b: string): boolean =>
227+
compareSemverPre(a, b) < 0;

src/utils/setupZephyr.mts

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import type { ITask } from "../models/task.mjs";
4141
import { getWestConfigValue, updateZephyrBase } from "./westConfig.mjs";
4242
import { addZephyrVariant } from "./westManifest.mjs";
4343
import LastUsedDepsStore from "./lastUsedDeps.mjs";
44+
import { geSemverPre } from "./semverUtil.mjs";
4445

4546
interface ZephyrSetupValue {
4647
cmakeMode: number;
@@ -574,6 +575,9 @@ async function showNoWgetError(): Promise<void> {
574575
"wget not found in Path. Please install wget and ensure " +
575576
"it is available in Path. " +
576577
"See the Zephyr notes in the pico-vscode README for guidance.",
578+
{
579+
modal: true,
580+
},
577581
"Open README"
578582
);
579583
if (response === "Open README") {
@@ -593,7 +597,7 @@ async function checkMacosLinuxDeps(): Promise<boolean> {
593597

594598
const wget = await which("wget", { nothrow: true });
595599
if (!wget) {
596-
await showNoWgetError();
600+
void showNoWgetError();
597601

598602
return false;
599603
}
@@ -628,7 +632,7 @@ async function checkWindowsDeps(): Promise<boolean> {
628632

629633
const wget = await which("wget", { nothrow: true });
630634
if (!wget) {
631-
await showNoWgetError();
635+
void showNoWgetError();
632636

633637
return false;
634638
}
@@ -1628,6 +1632,78 @@ export async function updateZephyrCompilerPath(
16281632
}
16291633
}
16301634

1635+
// support for v1.0.0 zephyr sdk toolchain location change
1636+
const launchUri = Uri.joinPath(workspaceUri, ".vscode", "launch.json");
1637+
if (geSemverPre(sdkVersion, "v1.0.0-beta1")) {
1638+
zephyrConfig.compilerPath.replace(
1639+
`${sdkVersion}/arm-zephyr-eabi`,
1640+
`${sdkVersion}/gnu/arm-zephyr-eabi`
1641+
);
1642+
1643+
try {
1644+
await workspace.fs.stat(launchUri);
1645+
1646+
const launchJson = JSON.parse(
1647+
td.decode(await workspace.fs.readFile(launchUri))
1648+
) as {
1649+
configurations: Array<{ name: string; armToolchainPath: string }>;
1650+
};
1651+
1652+
const picoDebugConfig = launchJson.configurations.find(
1653+
c => c.name === "Pico Debug (Zephyr)"
1654+
);
1655+
if (picoDebugConfig) {
1656+
picoDebugConfig.armToolchainPath =
1657+
picoDebugConfig.armToolchainPath.replace(
1658+
"}/arm-zephyr-eabi/",
1659+
"}/gnu/arm-zephyr-eabi/"
1660+
);
1661+
1662+
const te = new TextEncoder();
1663+
await workspace.fs.writeFile(
1664+
launchUri,
1665+
te.encode(JSON.stringify(launchJson, null, 2))
1666+
);
1667+
}
1668+
} catch {
1669+
// do nothing
1670+
}
1671+
} else {
1672+
zephyrConfig.compilerPath.replace(
1673+
`${sdkVersion}/gnu/arm-zephyr-eabi`,
1674+
`${sdkVersion}/arm-zephyr-eabi`
1675+
);
1676+
1677+
try {
1678+
await workspace.fs.stat(launchUri);
1679+
1680+
const launchJson = JSON.parse(
1681+
td.decode(await workspace.fs.readFile(launchUri))
1682+
) as {
1683+
configurations: Array<{ name: string; armToolchainPath: string }>;
1684+
};
1685+
1686+
const picoDebugConfig = launchJson.configurations.find(
1687+
c => c.name === "Pico Debug (Zephyr)"
1688+
);
1689+
if (picoDebugConfig) {
1690+
picoDebugConfig.armToolchainPath =
1691+
picoDebugConfig.armToolchainPath.replace(
1692+
"}/gnu/arm-zephyr-eabi/",
1693+
"}/arm-zephyr-eabi/"
1694+
);
1695+
1696+
const te = new TextEncoder();
1697+
await workspace.fs.writeFile(
1698+
launchUri,
1699+
te.encode(JSON.stringify(launchJson, null, 2))
1700+
);
1701+
}
1702+
} catch {
1703+
// do nothing
1704+
}
1705+
}
1706+
16311707
const te = new TextEncoder();
16321708
await workspace.fs.writeFile(
16331709
cppPropertiesUri,
@@ -1718,3 +1794,48 @@ export async function zephyrVerifyCMakeCache(
17181794
return;
17191795
}
17201796
}
1797+
1798+
export async function zephyrGetSelectedSnippets(
1799+
workspaceUri: Uri
1800+
): Promise<string[]> {
1801+
const snippetsUri = Uri.joinPath(workspaceUri, ".vscode", "tasks.json");
1802+
1803+
// search for "Compile Project" get every i+1 where i is an index and args[i]=="-S" || "--snippet"
1804+
try {
1805+
await workspace.fs.stat(snippetsUri);
1806+
1807+
const td = new TextDecoder("utf-8");
1808+
const tasksJson = JSON.parse(
1809+
td.decode(await workspace.fs.readFile(snippetsUri))
1810+
) as { tasks: ITask[] };
1811+
1812+
const compileTask = tasksJson.tasks.find(
1813+
t => t.label === "Compile Project"
1814+
);
1815+
if (compileTask === undefined) {
1816+
return [];
1817+
}
1818+
1819+
const selectedSnippets: string[] = [];
1820+
for (let i = 0; i < compileTask.args.length; i++) {
1821+
if (compileTask.args[i] === "-S" || compileTask.args[i] === "--snippet") {
1822+
if (i + 1 < compileTask.args.length) {
1823+
selectedSnippets.push(compileTask.args[i + 1]);
1824+
}
1825+
}
1826+
}
1827+
1828+
return selectedSnippets;
1829+
} catch (error) {
1830+
Logger.warn(
1831+
LoggerSource.zephyrSetup,
1832+
`Failed to read tasks.json file: ${unknownErrorToString(error)}`
1833+
);
1834+
void window.showWarningMessage(
1835+
"Failed to read tasks.json file. " +
1836+
"Make sure the file exists and has a Compile Project task."
1837+
);
1838+
1839+
return [];
1840+
}
1841+
}

src/webview/activityBar.mts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
NEW_EXAMPLE_PROJECT,
2525
NEW_PROJECT,
2626
OPEN_SDK_DOCUMENTATION,
27+
OPEN_UNINSTALLER,
2728
RUN_PROJECT,
2829
SWITCH_BOARD,
2930
SWITCH_BUILD_TYPE,
@@ -61,6 +62,7 @@ const CLEAN_CMAKE_PROJECT_LABEL = "Clean CMake";
6162
const SWITCH_BUILD_TYPE_LABEL = "Switch Build Type";
6263
const DEBUG_PROJECT_LABEL = "Debug Project";
6364
const DEBUG_LAYOUT_PROJECT_LABEL = "Debug Layout";
65+
const MANAGE_COMPONENTS_LABEL = "Manage Components";
6466

6567
export class PicoProjectActivityBar
6668
implements TreeDataProvider<QuickAccessCommand>
@@ -126,6 +128,9 @@ export class PicoProjectActivityBar
126128
// alt. "file-code"
127129
element.iconPath = new ThemeIcon("file-symlink-directory");
128130
break;
131+
case MANAGE_COMPONENTS_LABEL:
132+
element.iconPath = new ThemeIcon("package");
133+
break;
129134

130135
case DEBUG_PROJECT_LABEL:
131136
element.iconPath = new ThemeIcon("debug-alt");
@@ -253,6 +258,14 @@ export class PicoProjectActivityBar
253258
arguments: [true],
254259
}
255260
),
261+
new QuickAccessCommand(
262+
MANAGE_COMPONENTS_LABEL,
263+
TreeItemCollapsibleState.None,
264+
{
265+
command: `${extensionName}.${OPEN_UNINSTALLER}`,
266+
title: MANAGE_COMPONENTS_LABEL,
267+
}
268+
),
256269
];
257270
} else if (element.label === PROJECT_COMMANDS_PARENT_LABEL) {
258271
return [

src/webview/uninstallerPanel.mts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export class UninstallerPanel {
4545

4646
const panel = window.createWebviewPanel(
4747
UninstallerPanel.viewType,
48-
"Pico SDK Uninstaller",
48+
"Manage Installed Components",
4949
column || ViewColumn.One,
5050
getWebviewOptions(extensionUri)
5151
);
@@ -176,7 +176,7 @@ export class UninstallerPanel {
176176
}
177177

178178
private async _update(): Promise<void> {
179-
this._panel.title = "Pico SDK Uninstaller";
179+
this._panel.title = "Manage Installed Components";
180180

181181
this._panel.iconPath = Uri.joinPath(
182182
this._extensionUri,

0 commit comments

Comments
 (0)