@@ -9,199 +9,202 @@ import { join as joinPosix } from "path/posix";
99import  {  cmakeToolsForcePicoKit  }  from  "../utils/cmakeToolsUtil.mjs" ; 
1010import  {  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  =  / p r o j e c t \( ( [ ^ ) \s ] + ) / ; 
27-     const  match  =  regex . exec ( fileContent ) ; 
28- 
29-     // Match for poll and threadsafe background inclusions 
30-     const  regexBg  =  / p i c o _ c y w 4 3 _ a r c h _ l w i p _ t h r e a d s a f e _ b a c k g r o u n d / ; 
31-     const  matchBg  =  regexBg . exec ( fileContent ) ; 
32-     const  regexPoll  =  / p i c o _ c y w 4 3 _ a r c h _ l w i p _ p o l l / ; 
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  =  / p r o j e c t \( ( [ ^ ) \s ] + ) / . exec ( fileContent ) ; 
66+   if  ( ! projectMatch ?. [ 1 ] )  { 
67+     return  null ; 
68+   } 
69+ 
70+   const  projectName  =  projectMatch [ 1 ] . trim ( ) ; 
71+ 
72+   const  hasBg  =  / p i c o _ c y w 4 3 _ a r c h _ l w i p _ t h r e a d s a f e _ b a c k g r o u n d / . test ( fileContent ) ; 
73+   const  hasPoll  =  / p i c o _ c y w 4 3 _ a r c h _ l w i p _ p o l l / . 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+ 
153158export  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} 
0 commit comments