Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"command": "raspberry-pi-pico.switchSDK",
"title": "Switch Pico SDK",
"category": "Raspberry Pi Pico",
"enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject && !raspberry-pi-pico.isZephyrProject"
"enablement": "raspberry-pi-pico.isPicoProject && !raspberry-pi-pico.isRustProject"
},
{
"command": "raspberry-pi-pico.switchBoard",
Expand Down Expand Up @@ -277,7 +277,7 @@
},
{
"command": "raspberry-pi-pico.openUninstaller",
"title": "Open Uninstaller",
"title": "Manage Installed Components",
"category": "Raspberry Pi Pico"
},
{
Expand Down
12 changes: 11 additions & 1 deletion src/utils/cmakeUtil.mts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import State from "../state.mjs";
import {
generateCustomZephyrEnv,
getBoardFromZephyrProject,
zephyrGetSelectedSnippets,
} from "./setupZephyr.mjs";

export const CMAKE_DO_NOT_EDIT_HEADER_PREFIX =
Expand Down Expand Up @@ -248,12 +249,21 @@ export async function configureCmakeNinja(

return false;
}
const zephyrCommand = `${
let zephyrCommand = `${
process.env.ComSpec?.endsWith("powershell.exe") ? "&" : ""
}"${westPath}" build --cmake-only -b ${
zephyrBoard ?? ""
} -d "${buildDir}" "${folder.fsPath}"`;

// check for selected snippets and include for cmake configuration
if (isZephyrProject) {
const snippets = await zephyrGetSelectedSnippets(folder);

for (const snippet of snippets) {
zephyrCommand += ` -S ${snippet}`;
}
}

await new Promise<void>((resolve, reject) => {
// use exec to be able to cancel the process
const child = exec(
Expand Down
84 changes: 84 additions & 0 deletions src/utils/semverUtil.mts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,87 @@ export function compareLtMajor(first: string, second: string): boolean {
// Compare only the major versions
return firstMajor < secondMajor;
}

export type Pre = { tag: "alpha" | "beta" | "rc" | null; num: number };

export type ParsedVer = {
major: number;
minor: number;
patch: number;
pre: Pre; // null tag means "final"
};

function parseVer(input: string): ParsedVer | null {
const s = input.trim().toLowerCase().replace(/^v/, "");
const [core, preRaw = ""] = s.split("-", 2);

// allow 2 or 3 core parts; pad missing with 0
const parts = core.split(".").map(n => Number(n));
if (parts.length < 2 || parts.length > 3) {
return null;
}
const [major, minor, patch = 0] = parts;
if (![major, minor, patch].every(n => Number.isInteger(n) && n >= 0)) {
return null;
}

let pre: Pre = { tag: null, num: 0 };
if (preRaw) {
// accept alpha/beta/rc with optional number (default 0)
const m = /^(alpha|beta|rc)(\d+)?$/.exec(preRaw);
if (!m) {
return null;
}
pre = { tag: m[1] as Pre["tag"], num: m[2] ? Number(m[2]) : 0 };
}

return { major, minor, patch, pre };
}

const rank: Record<NonNullable<Pre["tag"]> | "final", number> = {
alpha: 0,
beta: 1,
rc: 2,
final: 3,
};

/** Compare a vs b: -1 if a<b, 0 if equal, 1 if a>b */
export function compareSemverPre(a: string, b: string): number {
const A = parseVer(a),
B = parseVer(b);
if (!A || !B) {
throw new Error(`Invalid version: "${a}" or "${b}"`);
}

if (A.major !== B.major) {
return A.major < B.major ? -1 : 1;
}
if (A.minor !== B.minor) {
return A.minor < B.minor ? -1 : 1;
}
if (A.patch !== B.patch) {
return A.patch < B.patch ? -1 : 1;
}

// prerelease ranking: alpha < beta < rc < final
const rA = rank[A.pre.tag ?? "final"];
const rB = rank[B.pre.tag ?? "final"];
if (rA !== rB) {
return rA < rB ? -1 : 1;
}

// same prerelease tag (or both final)
if (A.pre.tag === null) {
return 0;
} // both final
if (A.pre.num !== B.pre.num) {
return A.pre.num < B.pre.num ? -1 : 1;
}

return 0;
}

export const geSemverPre = (a: string, b: string): boolean =>
compareSemverPre(a, b) >= 0;
export const ltSemverPre = (a: string, b: string): boolean =>
compareSemverPre(a, b) < 0;
125 changes: 123 additions & 2 deletions src/utils/setupZephyr.mts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import type { ITask } from "../models/task.mjs";
import { getWestConfigValue, updateZephyrBase } from "./westConfig.mjs";
import { addZephyrVariant } from "./westManifest.mjs";
import LastUsedDepsStore from "./lastUsedDeps.mjs";
import { geSemverPre } from "./semverUtil.mjs";

interface ZephyrSetupValue {
cmakeMode: number;
Expand Down Expand Up @@ -574,6 +575,9 @@ async function showNoWgetError(): Promise<void> {
"wget not found in Path. Please install wget and ensure " +
"it is available in Path. " +
"See the Zephyr notes in the pico-vscode README for guidance.",
{
modal: true,
},
"Open README"
);
if (response === "Open README") {
Expand All @@ -593,7 +597,7 @@ async function checkMacosLinuxDeps(): Promise<boolean> {

const wget = await which("wget", { nothrow: true });
if (!wget) {
await showNoWgetError();
void showNoWgetError();

return false;
}
Expand Down Expand Up @@ -628,7 +632,7 @@ async function checkWindowsDeps(): Promise<boolean> {

const wget = await which("wget", { nothrow: true });
if (!wget) {
await showNoWgetError();
void showNoWgetError();

return false;
}
Expand Down Expand Up @@ -1628,6 +1632,78 @@ export async function updateZephyrCompilerPath(
}
}

// support for v1.0.0 zephyr sdk toolchain location change
const launchUri = Uri.joinPath(workspaceUri, ".vscode", "launch.json");
if (geSemverPre(sdkVersion, "v1.0.0-beta1")) {
zephyrConfig.compilerPath.replace(
`${sdkVersion}/arm-zephyr-eabi`,
`${sdkVersion}/gnu/arm-zephyr-eabi`
);

try {
await workspace.fs.stat(launchUri);

const launchJson = JSON.parse(
td.decode(await workspace.fs.readFile(launchUri))
) as {
configurations: Array<{ name: string; armToolchainPath: string }>;
};

const picoDebugConfig = launchJson.configurations.find(
c => c.name === "Pico Debug (Zephyr)"
);
if (picoDebugConfig) {
picoDebugConfig.armToolchainPath =
picoDebugConfig.armToolchainPath.replace(
"}/arm-zephyr-eabi/",
"}/gnu/arm-zephyr-eabi/"
);

const te = new TextEncoder();
await workspace.fs.writeFile(
launchUri,
te.encode(JSON.stringify(launchJson, null, 2))
);
}
} catch {
// do nothing
}
} else {
zephyrConfig.compilerPath.replace(
`${sdkVersion}/gnu/arm-zephyr-eabi`,
`${sdkVersion}/arm-zephyr-eabi`
);

try {
await workspace.fs.stat(launchUri);

const launchJson = JSON.parse(
td.decode(await workspace.fs.readFile(launchUri))
) as {
configurations: Array<{ name: string; armToolchainPath: string }>;
};

const picoDebugConfig = launchJson.configurations.find(
c => c.name === "Pico Debug (Zephyr)"
);
if (picoDebugConfig) {
picoDebugConfig.armToolchainPath =
picoDebugConfig.armToolchainPath.replace(
"}/gnu/arm-zephyr-eabi/",
"}/arm-zephyr-eabi/"
);

const te = new TextEncoder();
await workspace.fs.writeFile(
launchUri,
te.encode(JSON.stringify(launchJson, null, 2))
);
}
} catch {
// do nothing
}
}

const te = new TextEncoder();
await workspace.fs.writeFile(
cppPropertiesUri,
Expand Down Expand Up @@ -1718,3 +1794,48 @@ export async function zephyrVerifyCMakeCache(
return;
}
}

export async function zephyrGetSelectedSnippets(
workspaceUri: Uri
): Promise<string[]> {
const snippetsUri = Uri.joinPath(workspaceUri, ".vscode", "tasks.json");

// search for "Compile Project" get every i+1 where i is an index and args[i]=="-S" || "--snippet"
try {
await workspace.fs.stat(snippetsUri);

const td = new TextDecoder("utf-8");
const tasksJson = JSON.parse(
td.decode(await workspace.fs.readFile(snippetsUri))
) as { tasks: ITask[] };

const compileTask = tasksJson.tasks.find(
t => t.label === "Compile Project"
);
if (compileTask === undefined) {
return [];
}

const selectedSnippets: string[] = [];
for (let i = 0; i < compileTask.args.length; i++) {
if (compileTask.args[i] === "-S" || compileTask.args[i] === "--snippet") {
if (i + 1 < compileTask.args.length) {
selectedSnippets.push(compileTask.args[i + 1]);
}
}
}

return selectedSnippets;
} catch (error) {
Logger.warn(
LoggerSource.zephyrSetup,
`Failed to read tasks.json file: ${unknownErrorToString(error)}`
);
void window.showWarningMessage(
"Failed to read tasks.json file. " +
"Make sure the file exists and has a Compile Project task."
);

return [];
}
}
13 changes: 13 additions & 0 deletions src/webview/activityBar.mts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
NEW_EXAMPLE_PROJECT,
NEW_PROJECT,
OPEN_SDK_DOCUMENTATION,
OPEN_UNINSTALLER,
RUN_PROJECT,
SWITCH_BOARD,
SWITCH_BUILD_TYPE,
Expand Down Expand Up @@ -61,6 +62,7 @@ const CLEAN_CMAKE_PROJECT_LABEL = "Clean CMake";
const SWITCH_BUILD_TYPE_LABEL = "Switch Build Type";
const DEBUG_PROJECT_LABEL = "Debug Project";
const DEBUG_LAYOUT_PROJECT_LABEL = "Debug Layout";
const MANAGE_COMPONENTS_LABEL = "Manage Components";

export class PicoProjectActivityBar
implements TreeDataProvider<QuickAccessCommand>
Expand Down Expand Up @@ -126,6 +128,9 @@ export class PicoProjectActivityBar
// alt. "file-code"
element.iconPath = new ThemeIcon("file-symlink-directory");
break;
case MANAGE_COMPONENTS_LABEL:
element.iconPath = new ThemeIcon("package");
break;

case DEBUG_PROJECT_LABEL:
element.iconPath = new ThemeIcon("debug-alt");
Expand Down Expand Up @@ -253,6 +258,14 @@ export class PicoProjectActivityBar
arguments: [true],
}
),
new QuickAccessCommand(
MANAGE_COMPONENTS_LABEL,
TreeItemCollapsibleState.None,
{
command: `${extensionName}.${OPEN_UNINSTALLER}`,
title: MANAGE_COMPONENTS_LABEL,
}
),
];
} else if (element.label === PROJECT_COMMANDS_PARENT_LABEL) {
return [
Expand Down
4 changes: 2 additions & 2 deletions src/webview/uninstallerPanel.mts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class UninstallerPanel {

const panel = window.createWebviewPanel(
UninstallerPanel.viewType,
"Pico SDK Uninstaller",
"Manage Installed Components",
column || ViewColumn.One,
getWebviewOptions(extensionUri)
);
Expand Down Expand Up @@ -176,7 +176,7 @@ export class UninstallerPanel {
}

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

this._panel.iconPath = Uri.joinPath(
this._extensionUri,
Expand Down
Loading