Skip to content

Commit fb9437f

Browse files
committed
Add zephyr version pinning support
Signed-off-by: paulober <44974737+paulober@users.noreply.github.com>
1 parent 50f9f55 commit fb9437f

File tree

6 files changed

+192
-18
lines changed

6 files changed

+192
-18
lines changed

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,17 +302,24 @@
302302
"raspberry-pi-pico.cmakeAutoConfigure": {
303303
"type": "boolean",
304304
"default": true,
305-
"markdownDescription": "Automatically run configure when opening a Pico project"
305+
"description": "Automatically run configure when opening a Pico project"
306306
},
307307
"raspberry-pi-pico.useCmakeTools": {
308308
"type": "boolean",
309309
"default": false,
310-
"markdownDescription": "Use the CMake Tools extension for CMake configuration, instead of this extension"
310+
"description": "Use the CMake Tools extension for CMake configuration, instead of this extension"
311311
},
312312
"raspberry-pi-pico.githubToken": {
313313
"type": "string",
314314
"default": "",
315315
"markdownDescription": "A GitHub personal access token (classic) with the `public_repo` scope. Used to check GitHub for available versions of the Pico SDK and other tools."
316+
},
317+
"raspberry-pi-pico.zephyrVersion": {
318+
"type": "string",
319+
"default": "",
320+
"pattern": "^(?:main|v?\\d+\\.\\d+\\.\\d+(?:-rc\\d+)?)?$",
321+
"scope": "resource",
322+
"description": "Pin a Zephyr version (e.g. v4.2.0 or 4.2.0) for you project. Leave empty to always use the current system version."
316323
}
317324
}
318325
},

src/commands/switchSDK.mts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { SDK_REPOSITORY_URL } from "../utils/sharedConstants.mjs";
4444
import State from "../state.mjs";
4545
import { compare, compareGe } from "../utils/semverUtil.mjs";
4646
import { updateZephyrVersion } from "../utils/setupZephyr.mjs";
47+
import Settings, { SettingsKey } from "../settings.mjs";
4748

4849
const DEFAULT_PICOTOOL_VERSION = "2.2.0-a4";
4950

@@ -441,6 +442,21 @@ export default class SwitchSDKCommand extends Command {
441442
const result = await updateZephyrVersion(selectedVersion.label);
442443
if (result) {
443444
this._ui.updateSDKVersion(selectedVersion.label.replace("v", ""));
445+
446+
const pinVersion = await window.showInformationMessage(
447+
"Do you want to pin this Zephyr version for the current project?",
448+
{ modal: true },
449+
"Yes",
450+
"No"
451+
);
452+
if (pinVersion === "Yes") {
453+
const settings = Settings.getInstance();
454+
await settings?.update(
455+
SettingsKey.zephyrVersion,
456+
selectedVersion.label
457+
);
458+
}
459+
444460
// for reload the window
445461
void commands.executeCommand("workbench.action.reloadWindow");
446462
} else {

src/extension.mts

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ import {
113113
getZephyrVersion,
114114
setupZephyr,
115115
updateZephyrCompilerPath,
116-
zephyrVerifyCMakCache,
116+
updateZephyrVersion,
117+
zephyrVerifyCMakeCache,
117118
} from "./utils/setupZephyr.mjs";
118119
import { IMPORT_PROJECT } from "./commands/cmdIds.mjs";
119120
import {
@@ -400,6 +401,77 @@ export async function activate(context: ExtensionContext): Promise<void> {
400401
ninjaVersion = version;
401402
}
402403

404+
// check for pinned zephyr version in workspace settings
405+
const pinnedVersion = settings.getString(SettingsKey.zephyrVersion);
406+
if (pinnedVersion !== undefined && pinnedVersion.length > 0) {
407+
const systemVersion = await getZephyrVersion();
408+
if (systemVersion === undefined) {
409+
Logger.error(
410+
LoggerSource.extension,
411+
"Failed to get system Zephyr version."
412+
);
413+
void window.showErrorMessage(
414+
"Failed to get system Zephyr version. Cannot setup Zephyr project."
415+
);
416+
// TODO: instead reset zephyr workspace
417+
await commands.executeCommand(
418+
"setContext",
419+
ContextKeys.isPicoProject,
420+
false
421+
);
422+
423+
return;
424+
}
425+
426+
if (systemVersion !== pinnedVersion) {
427+
// ask user to switch zephyr version
428+
const switchVersion = await window.showInformationMessage(
429+
`Project Zephyr version (${pinnedVersion}) differs from system ` +
430+
`version (${systemVersion}). ` +
431+
`Do you want to switch the system version?`,
432+
{ modal: true },
433+
"Yes",
434+
"No - Use system version",
435+
"No - Pin to system version"
436+
);
437+
438+
if (switchVersion === "Yes") {
439+
const switchResult = await updateZephyrVersion(pinnedVersion);
440+
if (!switchResult) {
441+
void window.showErrorMessage(
442+
`Failed to switch Zephyr version to ${pinnedVersion}. ` +
443+
"Cannot setup Zephyr project."
444+
);
445+
await commands.executeCommand(
446+
"setContext",
447+
ContextKeys.isPicoProject,
448+
false
449+
);
450+
451+
return;
452+
} else {
453+
void window.showInformationMessage(
454+
`Switched Zephyr version to ${pinnedVersion}.`
455+
);
456+
Logger.info(
457+
LoggerSource.extension,
458+
`Switched Zephyr version to ${pinnedVersion}.`
459+
);
460+
}
461+
} else if (switchVersion === "No - Pin to system version") {
462+
// update workspace settings
463+
await settings.update(SettingsKey.zephyrVersion, systemVersion);
464+
void window.showInformationMessage(
465+
`Pinned Zephyr version to ${systemVersion}.`
466+
);
467+
Logger.info(
468+
LoggerSource.extension,
469+
`Pinned Zephyr version to ${systemVersion}.`
470+
);
471+
}
472+
}
473+
}
474+
403475
const result = await setupZephyr({
404476
extUri: context.extensionUri,
405477
cmakeMode: cmakeVersion !== "" ? 2 : 3,
@@ -463,8 +535,6 @@ export async function activate(context: ExtensionContext): Promise<void> {
463535
// TODO: maybe cancel activation
464536
}
465537

466-
await zephyrVerifyCMakCache(workspaceFolder.uri);
467-
468538
// Update the board info if it can be found in tasks.json
469539
const tasksJsonFilePath = join(
470540
workspaceFolder.uri.fsPath,
@@ -489,6 +559,8 @@ export async function activate(context: ExtensionContext): Promise<void> {
489559
}
490560
}
491561

562+
await zephyrVerifyCMakeCache(workspaceFolder.uri);
563+
492564
if (settings.getBoolean(SettingsKey.cmakeAutoConfigure)) {
493565
await cmakeSetupAutoConfigure(workspaceFolder, ui);
494566
} else {

src/settings.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export enum SettingsKey {
1313
cmakeAutoConfigure = "cmakeAutoConfigure",
1414
githubToken = "githubToken",
1515
useCmakeTools = "useCmakeTools",
16+
zephyrVersion = "zephyrVersion",
1617
}
1718

1819
/**

src/utils/setupZephyr.mts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,10 @@ export async function setupZephyr(
12621262
message: "Failed",
12631263
increment: 100,
12641264
});
1265+
void window.showErrorMessage(
1266+
"Failed to install Zephyr SDK. Cannot continue Zephyr setup. " +
1267+
"See extension host output for more details."
1268+
);
12651269

12661270
return false;
12671271
}
@@ -1558,7 +1562,11 @@ export async function updateZephyrCompilerPath(
15581562
}
15591563
}
15601564

1561-
export async function zephyrVerifyCMakCache(
1565+
/**
1566+
* Checks if the current CMakeCache is using the current zephyr version.
1567+
* If not the build folder will be deleted.
1568+
*/
1569+
export async function zephyrVerifyCMakeCache(
15621570
workspaceUri: Uri,
15631571
zephyrVersion?: string
15641572
): Promise<void> {
@@ -1599,7 +1607,6 @@ export async function zephyrVerifyCMakCache(
15991607
// drop build folder
16001608
const buildDir = Uri.joinPath(workspaceUri, "build");
16011609
try {
1602-
await workspace.fs.stat(buildDir);
16031610
await workspace.fs.delete(buildDir, { recursive: true, useTrash: false });
16041611
Logger.info(
16051612
LoggerSource.zephyrSetup,
@@ -1608,16 +1615,6 @@ export async function zephyrVerifyCMakCache(
16081615
void window.showInformationMessage(
16091616
"Deleted build folder to clear old Zephyr CMake cache."
16101617
);
1611-
1612-
if (await configureCmakeNinja(workspaceUri)) {
1613-
void window.showInformationMessage(
1614-
"Reconfigured CMake and Ninja for Zephyr project."
1615-
);
1616-
} else {
1617-
void window.showErrorMessage(
1618-
"Failed to reconfigure CMake and Ninja for Zephyr project."
1619-
);
1620-
}
16211618
} catch {
16221619
// does not exist
16231620
}

src/utils/westConfig.mts

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { homedir } from "os";
66
export interface ManifestSection {
77
path?: string;
88
file?: string;
9+
["project-filter"]?: string;
910
}
1011

1112
export interface ZephyrSection {
@@ -41,6 +42,29 @@ function parentUri(uri: Uri): Uri {
4142
return uri.with({ path });
4243
}
4344

45+
/** Escape a string to be used as a literal regex */
46+
function escapeRegexLiteral(s: string): string {
47+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
48+
}
49+
50+
/** Split a project-filter string into normalized elements */
51+
function splitProjectFilter(value?: string): string[] {
52+
if (!value) {
53+
return [];
54+
}
55+
56+
return value
57+
.split(",")
58+
.map(e => e.trim())
59+
.filter(Boolean);
60+
}
61+
62+
/** Keep only non-zephyr filter elements (so we can rebuild zephyr rules) */
63+
function keepNonZephyrFilters(elements: string[]): string[] {
64+
// Remove any rule that targets zephyr-* (e.g., +zephyr-foo, -zephyr-.*)
65+
return elements.filter(e => !/^[+-]\s*zephyr-/.test(e));
66+
}
67+
4468
/** Read a text file, return undefined if not found */
4569
async function readTextFile(uri: Uri): Promise<string | undefined> {
4670
try {
@@ -78,6 +102,9 @@ export async function saveWestConfig(
78102
configUri: Uri,
79103
config: WestConfig
80104
): Promise<void> {
105+
if (config.zephyr === undefined || Object.keys(config.zephyr).length === 0) {
106+
throw new Error("zephyr section is required");
107+
}
81108
const text = ini.stringify(config, { whitespace: true });
82109
await writeTextFile(configUri, text);
83110
}
@@ -89,13 +116,67 @@ export async function getZephyrBase(): Promise<string | undefined> {
89116
return cfg.zephyr?.base;
90117
}
91118

119+
/** Set `[manifest] project-filter` directly */
120+
export async function setManifestProjectFilter(value: string): Promise<void> {
121+
const cfg = await loadWestConfig(WEST_CONFIG_URI);
122+
if (!cfg.manifest) {
123+
cfg.manifest = {};
124+
}
125+
cfg.manifest["project-filter"] = value;
126+
await saveWestConfig(WEST_CONFIG_URI, cfg);
127+
}
128+
129+
/** Get `[manifest] project-filter` */
130+
export async function getManifestProjectFilter(): Promise<string | undefined> {
131+
const cfg = await loadWestConfig(WEST_CONFIG_URI);
132+
133+
return cfg.manifest?.["project-filter"];
134+
}
135+
136+
/**
137+
* Update `[manifest] project-filter` so that:
138+
* - all `zephyr-*` projects are deactivated, except the active one
139+
* - any non-zephyr rules are preserved
140+
*/
141+
function updateProjectFilterForZephyrBase(
142+
cfg: WestConfig,
143+
activeZephyrProjectName: string
144+
): void {
145+
if (!cfg.manifest) {
146+
cfg.manifest = {};
147+
}
148+
149+
const current = cfg.manifest["project-filter"];
150+
const existing = splitProjectFilter(current);
151+
const kept = keepNonZephyrFilters(existing);
152+
153+
const escaped = escapeRegexLiteral(activeZephyrProjectName);
154+
155+
// Order matters; last rule wins.
156+
// Deactivate all zephyr-* first, then explicitly re-activate the chosen one.
157+
const rebuilt = [...kept, "-zephyr-.*", `+${escaped}`];
158+
159+
// dedupe while preserving order
160+
const final = Array.from(new Set(rebuilt));
161+
162+
cfg.manifest["project-filter"] = final.join(",");
163+
}
164+
92165
/** Set `[zephyr] base` */
93-
export async function updateZephyrBase(newBase: string): Promise<void> {
166+
export async function updateZephyrBase(
167+
newBase: string,
168+
updateProjectFilter: boolean = true
169+
): Promise<void> {
94170
const cfg = await loadWestConfig(WEST_CONFIG_URI);
95171
if (!cfg.zephyr) {
96172
cfg.zephyr = {};
97173
}
98174
cfg.zephyr.base = newBase;
175+
176+
if (updateProjectFilter) {
177+
updateProjectFilterForZephyrBase(cfg, newBase);
178+
}
179+
99180
await saveWestConfig(WEST_CONFIG_URI, cfg);
100181
}
101182

0 commit comments

Comments
 (0)