Skip to content

Commit 1892acc

Browse files
committed
Fix sbom target path
Signed-off-by: paulober <[email protected]>
1 parent a2733c4 commit 1892acc

File tree

4 files changed

+172
-150
lines changed

4 files changed

+172
-150
lines changed

package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,18 @@
249249
"title": "Get RTT Decoder module path",
250250
"category": "Raspberry Pi Pico",
251251
"enablement": "false"
252+
},
253+
{
254+
"command": "raspberry-pi-pico.sbomTargetPathDebug",
255+
"title": "Get path of the project debug SBOM (rust only)",
256+
"category": "Raspberry Pi Pico",
257+
"enablement": "false"
258+
},
259+
{
260+
"command": "raspberry-pi-pico.sbomTargetPathRelease",
261+
"title": "Get path of the project release SBOM (rust only)",
262+
"category": "Raspberry Pi Pico",
263+
"enablement": "false"
252264
}
253265
],
254266
"configuration": {

src/commands/launchTargetPath.mts

Lines changed: 148 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -9,199 +9,202 @@ import { join as joinPosix } from "path/posix";
99
import { cmakeToolsForcePicoKit } from "../utils/cmakeToolsUtil.mjs";
1010
import { rustProjectGetSelectedChip } from "../utils/rustUtil.mjs";
1111

12-
export default class LaunchTargetPathCommand extends CommandWithResult<string> {
13-
public static readonly id = "launchTargetPath";
12+
/* ----------------------------- Shared helpers ----------------------------- */
1413

15-
constructor() {
16-
super(LaunchTargetPathCommand.id);
14+
type SupportedChip = "rp2040" | "rp2350" | "rp2350-riscv" | null;
15+
16+
const wsRoot = (): string => workspace.workspaceFolders?.[0]?.uri.fsPath ?? "";
17+
18+
const norm = (p: string): string => p.replaceAll("\\", "/");
19+
20+
const isRustProject = (): boolean => State.getInstance().isRustProject;
21+
22+
const chipToTriple = (chip: SupportedChip): string => {
23+
switch (chip) {
24+
case "rp2040":
25+
return "thumbv6m-none-eabi";
26+
case "rp2350":
27+
return "thumbv8m.main-none-eabihf";
28+
case "rp2350-riscv":
29+
case null:
30+
default:
31+
// Default to RISC-V if unknown/null. Change if you prefer a different default.
32+
return "riscv32imac-unknown-none-elf";
1733
}
34+
};
1835

19-
private async readProjectNameFromCMakeLists(
20-
filename: string
21-
): Promise<string | null> {
22-
// Read the file
23-
const fileContent = readFileSync(filename, "utf-8");
24-
25-
// Match the project line using a regular expression
26-
const regex = /project\(([^)\s]+)/;
27-
const match = regex.exec(fileContent);
28-
29-
// Match for poll and threadsafe background inclusions
30-
const regexBg = /pico_cyw43_arch_lwip_threadsafe_background/;
31-
const matchBg = regexBg.exec(fileContent);
32-
const regexPoll = /pico_cyw43_arch_lwip_poll/;
33-
const matchPoll = regexPoll.exec(fileContent);
34-
35-
// Extract the project name from the matched result
36-
if (match && match[1]) {
37-
const projectName = match[1].trim();
38-
39-
if (matchBg && matchPoll) {
40-
// For examples with both background and poll, let user pick which to run
41-
const quickPickItems = ["Threadsafe Background", "Poll"];
42-
const backgroundOrPoll = await window.showQuickPick(quickPickItems, {
43-
placeHolder: "Select PicoW Architecture",
44-
});
45-
if (backgroundOrPoll === undefined) {
46-
return projectName;
47-
}
48-
49-
switch (backgroundOrPoll) {
50-
case quickPickItems[0]:
51-
return projectName + "_background";
52-
case quickPickItems[1]:
53-
return projectName + "_poll";
54-
}
55-
}
36+
const readCargoPackageName = (root: string): string | undefined => {
37+
try {
38+
const contents = readFileSync(join(root, "Cargo.toml"), "utf-8");
39+
const cargo = parseToml(contents) as
40+
| { package?: { name?: string } }
41+
| undefined;
5642

57-
return projectName;
58-
}
43+
return cargo?.package?.name;
44+
} catch {
45+
return undefined;
46+
}
47+
};
48+
49+
const rustTargetPath = (
50+
root: string,
51+
mode: "debug" | "release",
52+
file?: string
53+
): string => {
54+
const chip: SupportedChip = rustProjectGetSelectedChip(root);
55+
const triple = chipToTriple(chip);
56+
57+
return joinPosix(norm(root), "target", triple, mode, file ?? "");
58+
};
59+
60+
const readProjectNameFromCMakeLists = async (
61+
filename: string
62+
): Promise<string | null> => {
63+
const fileContent = readFileSync(filename, "utf-8");
64+
65+
const projectMatch = /project\(([^)\s]+)/.exec(fileContent);
66+
if (!projectMatch?.[1]) {
67+
return null;
68+
}
69+
70+
const projectName = projectMatch[1].trim();
71+
72+
const hasBg = /pico_cyw43_arch_lwip_threadsafe_background/.test(fileContent);
73+
const hasPoll = /pico_cyw43_arch_lwip_poll/.test(fileContent);
5974

60-
return null; // Return null if project line is not found
75+
if (hasBg && hasPoll) {
76+
const choice = await window.showQuickPick(
77+
["Threadsafe Background", "Poll"],
78+
{ placeHolder: "Select PicoW Architecture" }
79+
);
80+
if (choice === "Threadsafe Background") {
81+
return `${projectName}_background`;
82+
}
83+
if (choice === "Poll") {
84+
return `${projectName}_poll`;
85+
}
86+
// user dismissed → fall back to base name
6187
}
6288

63-
async execute(): Promise<string> {
64-
if (
65-
workspace.workspaceFolders === undefined ||
66-
workspace.workspaceFolders.length === 0
67-
) {
68-
return "";
89+
return projectName;
90+
};
91+
92+
const tryCMakeToolsLaunchPath = async (): Promise<string | null> => {
93+
const settings = Settings.getInstance();
94+
if (settings?.getBoolean(SettingsKey.useCmakeTools)) {
95+
await cmakeToolsForcePicoKit();
96+
const path: string = await commands.executeCommand(
97+
"cmake.launchTargetPath"
98+
);
99+
if (path) {
100+
return norm(path);
69101
}
102+
}
70103

71-
const isRustProject = State.getInstance().isRustProject;
104+
return null;
105+
};
72106

73-
if (isRustProject) {
74-
const cargoTomlPath = join(
75-
workspace.workspaceFolders[0].uri.fsPath,
76-
"Cargo.toml"
77-
);
78-
const contents = readFileSync(cargoTomlPath, "utf-8");
79-
const cargoToml = (await parseToml(contents)) as
80-
| {
81-
package?: { name?: string };
82-
}
83-
| undefined;
84-
85-
if (cargoToml?.package?.name) {
86-
const chip = rustProjectGetSelectedChip(
87-
workspace.workspaceFolders[0].uri.fsPath
88-
);
89-
const toolchain =
90-
chip === "rp2040"
91-
? "thumbv6m-none-eabi"
92-
: chip === "rp2350"
93-
? "thumbv8m.main-none-eabihf"
94-
: "riscv32imac-unknown-none-elf";
95-
96-
return joinPosix(
97-
workspace.workspaceFolders[0].uri.fsPath.replaceAll("\\", "/"),
98-
"target",
99-
toolchain,
100-
"debug",
101-
cargoToml.package.name
102-
);
103-
}
107+
/* ------------------------------ Main command ------------------------------ */
108+
109+
export default class LaunchTargetPathCommand extends CommandWithResult<string> {
110+
public static readonly id = "launchTargetPath";
111+
constructor() {
112+
super(LaunchTargetPathCommand.id);
113+
}
104114

115+
async execute(): Promise<string> {
116+
const root = wsRoot();
117+
if (!root) {
105118
return "";
106119
}
107120

108-
const settings = Settings.getInstance();
109-
if (
110-
settings !== undefined &&
111-
settings.getBoolean(SettingsKey.useCmakeTools)
112-
) {
113-
// Ensure the Pico kit is selected
114-
await cmakeToolsForcePicoKit();
115-
116-
// Compile with CMake Tools
117-
const path: string = await commands.executeCommand(
118-
"cmake.launchTargetPath"
119-
);
120-
if (path) {
121-
return path.replaceAll("\\", "/");
121+
if (isRustProject()) {
122+
const name = readCargoPackageName(root);
123+
if (!name) {
124+
return "";
122125
}
123-
}
124126

125-
const fsPathFolder = workspace.workspaceFolders[0].uri.fsPath;
127+
return rustTargetPath(root, "debug", name);
128+
}
126129

127-
const projectName = await this.readProjectNameFromCMakeLists(
128-
join(fsPathFolder, "CMakeLists.txt")
129-
);
130+
// Non-Rust: try CMake Tools first
131+
const cmakeToolsPath = await tryCMakeToolsLaunchPath();
132+
if (cmakeToolsPath) {
133+
return cmakeToolsPath;
134+
}
130135

131-
if (projectName === null) {
136+
// Fallback: parse CMakeLists + compile task, then return build/<name>.elf
137+
const cmakelists = join(root, "CMakeLists.txt");
138+
const projectName = await readProjectNameFromCMakeLists(cmakelists);
139+
if (!projectName) {
132140
return "";
133141
}
134142

135-
// Compile before returning
136143
const compiled = await commands.executeCommand(
137144
"raspberry-pi-pico.compileProject"
138145
);
139-
140146
if (!compiled) {
141147
throw new Error(
142148
"Failed to compile project - check output from the Compile Project task"
143149
);
144150
}
145151

146-
return join(fsPathFolder, "build", projectName + ".elf").replaceAll(
147-
"\\",
148-
"/"
149-
);
152+
return norm(join(root, "build", `${projectName}.elf`));
150153
}
151154
}
152155

156+
/* -------------------------- Rust-specific commands ------------------------- */
157+
153158
export class LaunchTargetPathReleaseCommand extends CommandWithResult<string> {
154159
public static readonly id = "launchTargetPathRelease";
155-
156160
constructor() {
157161
super(LaunchTargetPathReleaseCommand.id);
158162
}
159163

160-
async execute(): Promise<string> {
161-
if (
162-
workspace.workspaceFolders === undefined ||
163-
workspace.workspaceFolders.length === 0
164-
) {
164+
execute(): string {
165+
const root = wsRoot();
166+
if (!root || !isRustProject()) {
167+
return "";
168+
}
169+
170+
const name = readCargoPackageName(root);
171+
if (!name) {
165172
return "";
166173
}
167174

168-
const isRustProject = State.getInstance().isRustProject;
175+
return rustTargetPath(root, "release", name);
176+
}
177+
}
178+
179+
export class SbomTargetPathDebugCommand extends CommandWithResult<string> {
180+
public static readonly id = "sbomTargetPathDebug";
181+
constructor() {
182+
super(SbomTargetPathDebugCommand.id);
183+
}
169184

170-
if (!isRustProject) {
185+
execute(): string {
186+
const root = wsRoot();
187+
if (!root || !isRustProject()) {
171188
return "";
172189
}
173190

174-
const cargoTomlPath = join(
175-
workspace.workspaceFolders[0].uri.fsPath,
176-
"Cargo.toml"
177-
);
178-
const contents = readFileSync(cargoTomlPath, "utf-8");
179-
const cargoToml = (await parseToml(contents)) as
180-
| {
181-
package?: { name?: string };
182-
}
183-
| undefined;
191+
// sbom is a fixed filename living next to build artifacts
192+
return rustTargetPath(root, "debug", "sbom.spdx.json");
193+
}
194+
}
184195

185-
if (cargoToml?.package?.name) {
186-
const chip = rustProjectGetSelectedChip(
187-
workspace.workspaceFolders[0].uri.fsPath
188-
);
189-
const toolchain =
190-
chip === "rp2040"
191-
? "thumbv6m-none-eabi"
192-
: chip === "rp2350"
193-
? "thumbv8m.main-none-eabihf"
194-
: "riscv32imac-unknown-none-elf";
195-
196-
return joinPosix(
197-
workspace.workspaceFolders[0].uri.fsPath.replaceAll("\\", "/"),
198-
"target",
199-
toolchain,
200-
"release",
201-
cargoToml.package.name
202-
);
196+
export class SbomTargetPathReleaseCommand extends CommandWithResult<string> {
197+
public static readonly id = "sbomTargetPathRelease";
198+
constructor() {
199+
super(SbomTargetPathReleaseCommand.id);
200+
}
201+
202+
execute(): string {
203+
const root = wsRoot();
204+
if (!root || !isRustProject()) {
205+
return "";
203206
}
204207

205-
return "";
208+
return rustTargetPath(root, "release", "sbom.spdx.json");
206209
}
207210
}

src/extension.mts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import CompileProjectCommand from "./commands/compileProject.mjs";
3535
import RunProjectCommand from "./commands/runProject.mjs";
3636
import LaunchTargetPathCommand, {
3737
LaunchTargetPathReleaseCommand,
38+
SbomTargetPathDebugCommand,
39+
SbomTargetPathReleaseCommand,
3840
} from "./commands/launchTargetPath.mjs";
3941
import {
4042
GetPythonPathCommand,
@@ -148,6 +150,8 @@ export async function activate(context: ExtensionContext): Promise<void> {
148150
new UninstallPicoSDKCommand(),
149151
new CleanCMakeCommand(ui),
150152
new UpdateOpenOCDCommand(),
153+
new SbomTargetPathDebugCommand(),
154+
new SbomTargetPathReleaseCommand(),
151155
];
152156

153157
// register all command handlers

0 commit comments

Comments
 (0)