]> Git Repo - pico-vscode.git/blob - scripts/pico_project.py
Add global env vars and compile button
[pico-vscode.git] / scripts / pico_project.py
1 #!/usr/bin/env python3
2
3 #
4 # Copyright (c) 2020-2023 Raspberry Pi (Trading) Ltd.
5 #
6 # SPDX-License-Identifier: BSD-3-Clause
7 #
8
9 import argparse
10 from copy import copy
11 import os
12 from pyexpat import features
13 import shutil
14 from pathlib import Path
15 import string
16 import sys
17 import subprocess
18 import platform
19 import shlex
20 import csv
21
22 import tkinter as tk
23 from tkinter import messagebox as mb
24 from tkinter import filedialog as fd
25 from tkinter import simpledialog as sd
26 from tkinter import ttk
27
28 CMAKELIST_FILENAME = 'CMakeLists.txt'
29 CMAKECACHE_FILENAME = 'CMakeCache.txt'
30
31 COMPILER_NAME = 'arm-none-eabi-gcc'
32
33 VSCODE_LAUNCH_FILENAME = 'launch.json'
34 VSCODE_C_PROPERTIES_FILENAME = 'c_cpp_properties.json'
35 VSCODE_SETTINGS_FILENAME ='settings.json'
36 VSCODE_EXTENSIONS_FILENAME ='extensions.json'
37 VSCODE_TASKS_FILENAME ='tasks.json'
38 VSCODE_FOLDER='.vscode'
39
40 CONFIG_UNSET="Not set"
41
42 # Standard libraries for all builds
43 # And any more to string below, space separator
44 STANDARD_LIBRARIES = 'pico_stdlib'
45
46 # Indexed on feature name, tuple contains the C file, the H file and the CMake project name for the feature. 
47 # Some lists may contain an extra/ancillary file needed for that feature
48 GUI_TEXT = 0
49 C_FILE = 1
50 H_FILE = 2
51 LIB_NAME = 3
52 ANCILLARY_FILE = 4
53
54 features_list = {
55     'spi' :             ("SPI",             "spi.c",            "hardware/spi.h",       "hardware_spi"),
56     'i2c' :             ("I2C interface",   "i2c.c",            "hardware/i2c.h",       "hardware_i2c"),
57     'dma' :             ("DMA support",     "dma.c",            "hardware/dma.h",       "hardware_dma"),
58     'pio' :             ("PIO interface",   "pio.c",            "hardware/pio.h",       "hardware_pio"),
59     'interp' :          ("HW interpolation", "interp.c",        "hardware/interp.h",    "hardware_interp"),
60     'timer' :           ("HW timer",        "timer.c",          "hardware/timer.h",     "hardware_timer"),
61     'watchdog' :        ("HW watchdog",     "watch.c",          "hardware/watchdog.h",  "hardware_watchdog"),
62     'clocks' :          ("HW clocks",       "clocks.c",         "hardware/clocks.h",    "hardware_clocks"),
63 }
64
65 picow_options_list = {
66     'picow_none' :      ("None", "",                            "",    "",                                                                  ""),
67     'picow_led' :       ("PicoW onboard LED", "",               "pico/cyw43_arch.h",    "pico_cyw43_arch_none",                             ""),
68     'picow_poll' :      ("Polled lwIP",     "",                 "pico/cyw43_arch.h",    "pico_cyw43_arch_lwip_poll",                        "lwipopts.h"),
69     'picow_background' :("Background lwIP", "",                 "pico/cyw43_arch.h",    "pico_cyw43_arch_lwip_threadsafe_background",       "lwipopts.h"),
70 #    'picow_freertos' :  ("Full lwIP (FreeRTOS)", "",            "pico/cyw43_arch.h",    "pico_cyw43_arch_lwip_sys_freertos",                "lwipopts.h"),
71 }
72
73 stdlib_examples_list = {
74     'uart':     ("UART",                    "uart.c",           "hardware/uart.h",      "hardware_uart"),
75     'gpio' :    ("GPIO interface",          "gpio.c",           "hardware/gpio.h",      "hardware_gpio"),
76     'div' :     ("Low level HW Divider",    "divider.c",        "hardware/divider.h",   "hardware_divider")
77 }
78
79 debugger_list = ["DebugProbe (CMSIS-DAP)", "SWD (Pi host)"]
80 debugger_config_list = ["cmsis-dap.cfg", "raspberrypi-swd.cfg"]
81
82 DEFINES = 0
83 INITIALISERS = 1
84 # Could add an extra item that shows how to use some of the available functions for the feature
85 #EXAMPLE = 2
86
87 # This also contains example code for the standard library (see stdlib_examples_list)
88 code_fragments_per_feature = {
89     'uart' : [
90                ("// UART defines",
91                 "// By default the stdout UART is `uart0`, so we will use the second one",
92                 "#define UART_ID uart1",
93                 "#define BAUD_RATE 9600", "",
94                 "// Use pins 4 and 5 for UART1",
95                 "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments",
96                 "#define UART_TX_PIN 4",
97                 "#define UART_RX_PIN 5" ),
98
99                ( "// Set up our UART",
100                  "uart_init(UART_ID, BAUD_RATE);",
101                  "// Set the TX and RX pins by using the function select on the GPIO",
102                  "// Set datasheet for more information on function select",
103                  "gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);",
104                  "gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);", "" )
105             ],
106     'spi' : [
107               ( "// SPI Defines",
108                 "// We are going to use SPI 0, and allocate it to the following GPIO pins",
109                 "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments",
110                 "#define SPI_PORT spi0",
111                 "#define PIN_MISO 16",
112                 "#define PIN_CS   17",
113                 "#define PIN_SCK  18",
114                 "#define PIN_MOSI 19" ),
115
116               ( "// SPI initialisation. This example will use SPI at 1MHz.",
117                 "spi_init(SPI_PORT, 1000*1000);",
118                 "gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);",
119                 "gpio_set_function(PIN_CS,   GPIO_FUNC_SIO);",
120                 "gpio_set_function(PIN_SCK,  GPIO_FUNC_SPI);",
121                 "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);", "",
122                 "// Chip select is active-low, so we'll initialise it to a driven-high state",
123                 "gpio_set_dir(PIN_CS, GPIO_OUT);",
124                 "gpio_put(PIN_CS, 1);", "")
125             ],
126     'i2c' : [
127               (
128                 "// I2C defines",
129                 "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.",
130                 "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments",
131                 "#define I2C_PORT i2c0",
132                 "#define I2C_SDA 8",
133                 "#define I2C_SCL 9",
134               ),
135               (
136                 "// I2C Initialisation. Using it at 400Khz.",
137                 "i2c_init(I2C_PORT, 400*1000);","",
138                 "gpio_set_function(I2C_SDA, GPIO_FUNC_I2C);",
139                 "gpio_set_function(I2C_SCL, GPIO_FUNC_I2C);",
140                 "gpio_pull_up(I2C_SDA);",
141                 "gpio_pull_up(I2C_SCL);"
142               )
143             ],
144     "gpio" : [
145               (
146                 "// GPIO defines",
147                 "// Example uses GPIO 2",
148                 "#define GPIO 2"
149               ),
150               (
151                 "// GPIO initialisation.",
152                 "// We will make this GPIO an input, and pull it up by default",
153                 "gpio_init(GPIO);",
154                 "gpio_set_dir(GPIO, GPIO_IN);",
155                 "gpio_pull_up(GPIO);","",
156               )
157             ],
158     "interp" :[
159                (),
160                (
161                 "// Interpolator example code",
162                 "interp_config cfg = interp_default_config();",
163                 "// Now use the various interpolator library functions for your use case",
164                 "// e.g. interp_config_clamp(&cfg, true);",
165                 "//      interp_config_shift(&cfg, 2);",
166                 "// Then set the config ",
167                 "interp_set_config(interp0, 0, &cfg);",
168                )
169               ],
170
171     "timer"  : [
172                 (
173                  "int64_t alarm_callback(alarm_id_t id, void *user_data) {",
174                  "    // Put your timeout handler code in here",
175                  "    return 0;",
176                  "}"
177                 ),
178                 (
179                  "// Timer example code - This example fires off the callback after 2000ms",
180                  "add_alarm_in_ms(2000, alarm_callback, NULL, false);"
181                 )
182               ],
183
184     "watchdog":[ (),
185                 (
186                     "// Watchdog example code",
187                     "if (watchdog_caused_reboot()) {",
188                     "    // Whatever action you may take if a watchdog caused a reboot",
189                     "}","",
190                     "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot",
191                     "// second arg is pause on debug which means the watchdog will pause when stepping through code",
192                     "watchdog_enable(100, 1);","",
193                     "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot"
194                     "watchdog_update();",
195                 )
196               ],
197
198     "div"    : [ (),
199                  (
200                     "// Example of using the HW divider. The pico_divider library provides a more user friendly set of APIs ",
201                     "// over the divider (and support for 64 bit divides), and of course by default regular C language integer",
202                     "// divisions are redirected thru that library, meaning you can just use C level `/` and `%` operators and",
203                     "// gain the benefits of the fast hardware divider.",
204                     "int32_t dividend = 123456;",
205                     "int32_t divisor = -321;",
206                     "// This is the recommended signed fast divider for general use.",
207                     "divmod_result_t result = hw_divider_divmod_s32(dividend, divisor);",
208                     "printf(\"%d/%d = %d remainder %d\\n\", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result));",
209                     "// This is the recommended unsigned fast divider for general use.",
210                     "int32_t udividend = 123456;",
211                     "int32_t udivisor = 321;",
212                     "divmod_result_t uresult = hw_divider_divmod_u32(udividend, udivisor);",
213                     "printf(\"%d/%d = %d remainder %d\\n\", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult));"
214                  )
215                 ]
216 }
217
218 configuration_dictionary = list(dict())
219
220 isMac = False
221 isWindows = False
222 compilerPath = Path("/usr/bin/arm-none-eabi-gcc")
223
224 def GetBackground():
225     return 'white'
226
227 def GetButtonBackground():
228     return 'white'
229
230 def GetTextColour():
231     return 'black'
232
233 def GetButtonTextColour():
234     return '#c51a4a'
235
236 def RunGUI(sdkpath, args):
237     root = tk.Tk()
238     style = ttk.Style(root)
239     style.theme_use('default')
240
241     style.configure("TButton", padding=6, relief="groove", border=2, foreground=GetButtonTextColour(), background=GetButtonBackground())
242     style.configure("TLabel", foreground=GetTextColour(), background=GetBackground() )
243     style.configure("TCheckbutton", foreground=GetTextColour(), background=GetBackground())
244     style.configure("TRadiobutton", foreground=GetTextColour(), background=GetBackground() )
245     style.configure("TLabelframe", foreground=GetTextColour(), background=GetBackground() )
246     style.configure("TLabelframe.Label", foreground=GetTextColour(), background=GetBackground() )
247     style.configure("TCombobox", foreground=GetTextColour(), background=GetBackground() )
248     style.configure("TListbox", foreground=GetTextColour(), background=GetBackground() )
249
250     style.map("TCheckbutton", background = [('disabled', GetBackground())])
251     style.map("TRadiobutton", background = [('disabled', GetBackground())])
252     style.map("TButton", background = [('disabled', GetBackground())])
253     style.map("TLabel", background = [('background', GetBackground())])
254     style.map("TComboBox", background = [('readonly', GetBackground())])
255
256     app = ProjectWindow(root, sdkpath, args)
257
258     app.configure(background=GetBackground())
259
260     root.mainloop()
261     sys.exit(0)
262
263 def RunWarning(message):
264     mb.showwarning('Raspberry Pi Pico Project Generator', message)
265     sys.exit(0)
266
267 import threading
268
269 def thread_function(text, command, ok):
270     l = shlex.split(command)
271     proc = subprocess.Popen(l, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
272     for line in iter(proc.stdout.readline,''):
273         if not line:
274             if ok:
275                 ok["state"] = tk.NORMAL
276             return
277         text.insert(tk.END, line)
278         text.see(tk.END)
279
280 # Function to run an OS command and display the output in a new modal window
281 class DisplayWindow(tk.Toplevel):
282     def __init__(self, parent, title):
283         tk.Toplevel.__init__(self, parent)
284         self.parent = parent
285         self.init_window(title)
286
287     def init_window(self, title):
288         self.title(title)
289
290         frame = tk.Frame(self, borderwidth=5, relief=tk.RIDGE)
291         frame.pack(fill=tk.X, expand=True, side=tk.TOP)
292
293         scrollbar = tk.Scrollbar(frame)
294         self.text = tk.Text(frame, bg='gray14', fg='gray99')
295         scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
296         self.text.pack(side=tk.LEFT, fill=tk.Y)
297         scrollbar.config(command=self.text.yview)
298         self.text.config(yscrollcommand=scrollbar.set)
299
300         frame1 = tk.Frame(self, borderwidth=1)
301         frame1.pack(fill=tk.X, expand=True, side=tk.BOTTOM)
302         self.OKButton = ttk.Button(frame1, text="OK", command=self.OK)
303         self.OKButton["state"] = tk.DISABLED
304         self.OKButton.pack()
305
306         # make dialog modal
307         self.transient(self.parent)
308         self.grab_set()
309
310     def OK(self):
311         self.grab_release()
312         self.destroy()
313
314 def RunCommandInWindow(parent, command):
315     w = DisplayWindow(parent, command)
316     x = threading.Thread(target=thread_function, args=(w.text, command, w.OKButton))
317     x.start()
318     parent.wait_window(w)
319
320 class EditBoolWindow(sd.Dialog):
321
322     def __init__(self, parent, configitem, current):
323         self.parent = parent
324         self.config_item = configitem
325         self.current = current
326         sd.Dialog.__init__(self, parent, "Edit boolean configuration")
327
328
329     def body(self, master):
330         self.configure(background=GetBackground())
331         ttk.Label(self, text=self.config_item['name']).pack()
332         self.result = tk.StringVar()
333         self.result.set(self.current)
334         ttk.Radiobutton(master, text="True", variable=self.result, value="True").pack(anchor=tk.W)
335         ttk.Radiobutton(master, text="False", variable=self.result, value="False").pack(anchor=tk.W)
336         ttk.Radiobutton(master, text=CONFIG_UNSET, variable=self.result, value=CONFIG_UNSET).pack(anchor=tk.W)
337
338     def get(self):
339         return self.result.get()
340
341 class EditIntWindow(sd.Dialog):
342
343     def __init__(self, parent, configitem, current):
344         self.parent = parent
345         self.config_item = configitem
346         self.current = current
347         sd.Dialog.__init__(self, parent, "Edit integer configuration")
348
349     def body(self, master):
350         self.configure(background=GetBackground())
351         str = self.config_item['name'] + "  Max = " + self.config_item['max'] + "  Min = " + self.config_item['min']
352         ttk.Label(self, text=str).pack()
353         self.input =  tk.Entry(self)
354         self.input.pack(pady=4)
355         self.input.insert(0, self.current)
356         ttk.Button(self, text=CONFIG_UNSET, command=self.unset).pack(pady=5)
357
358     def validate(self):
359         self.result = self.input.get()
360         # Check for numeric entry
361         return True
362
363     def unset(self):
364         self.result = CONFIG_UNSET
365         self.destroy()
366
367     def get(self):
368         return self.result
369
370 class EditEnumWindow(sd.Dialog):
371     def __init__(self, parent, configitem, current):
372         self.parent = parent
373         self.config_item = configitem
374         self.current = current
375         sd.Dialog.__init__(self, parent, "Edit Enumeration configuration")
376
377     def body(self, master):
378         #self.configure(background=GetBackground())
379         values = self.config_item['enumvalues'].split('|')
380         values.insert(0,'Not set')
381         self.input =  ttk.Combobox(self, values=values, state='readonly')
382         self.input.set(self.current)
383         self.input.pack(pady=12)
384
385     def validate(self):
386         self.result = self.input.get()
387         return True
388
389     def get(self):
390         return self.result
391
392
393 class ConfigurationWindow(tk.Toplevel):
394
395     def __init__(self, parent, initial_config):
396         tk.Toplevel.__init__(self, parent)
397         self.master = parent
398         self.results = initial_config
399         self.init_window(self)
400
401     def init_window(self, args):
402         self.configure(background=GetBackground())
403         self.title("Advanced Configuration")
404         ttk.Label(self, text="Select the advanced options you wish to enable or change. Note that you really should understand the implications of changing these items before using them!").grid(row=0, column=0, columnspan=5)
405         ttk.Label(self, text="Name").grid(row=1, column=0, sticky=tk.W)
406         ttk.Label(self, text="Type").grid(row=1, column=1, sticky=tk.W)
407         ttk.Label(self, text="Min").grid(row=1, column=2, sticky=tk.W)
408         ttk.Label(self, text="Max").grid(row=1, column=3, sticky=tk.W)
409         ttk.Label(self, text="Default").grid(row=1, column=4, sticky=tk.W)
410         ttk.Label(self, text="User").grid(row=1, column=5, sticky=tk.W)
411
412         okButton = ttk.Button(self, text="OK", command=self.ok)
413         cancelButton = ttk.Button(self, text="Cancel", command=self.cancel)
414
415         self.namelist = tk.Listbox(self, selectmode=tk.SINGLE)
416         self.typelist = tk.Listbox(self, selectmode=tk.SINGLE)
417         self.minlist = tk.Listbox(self, selectmode=tk.SINGLE)
418         self.maxlist = tk.Listbox(self, selectmode=tk.SINGLE)
419         self.defaultlist = tk.Listbox(self, selectmode=tk.SINGLE)
420         self.valuelist = tk.Listbox(self, selectmode=tk.SINGLE)
421
422         self.descriptionText = tk.Text(self, state=tk.DISABLED, height=2)
423
424         ## Make a list of our list boxes to make it all easier to handle
425         self.listlist = [self.namelist, self.typelist, self.minlist, self.maxlist, self.defaultlist, self.valuelist]
426
427         scroll = tk.Scrollbar(self, orient=tk.VERTICAL, command=self.yview)
428
429         for box in self.listlist:
430             box.config(width=0)
431             box.config(yscrollcommand=scroll.set)
432             box.bind("<MouseWheel>", self.mousewheel)
433             box.bind("<Button-4>", self.mousewheel)
434             box.bind("<Button-5>", self.mousewheel)
435             box.bind("<<ListboxSelect>>", self.changeSelection)
436             box.bind("<Double-Button>", self.doubleClick)
437             box.config(exportselection=False)
438             box.bind("<Down>", self.OnEntryUpDown)
439             box.bind("<Up>", self.OnEntryUpDown)
440
441         scroll.grid(column=7, sticky=tk.N + tk.S)
442
443         i = 0
444         for box in self.listlist:
445             box.grid(row=2, column=i, padx=0, sticky=tk.W + tk.E)
446             i+=1
447
448         self.descriptionText.grid(row = 3, column=0, columnspan=4, sticky=tk.W + tk.E)
449         cancelButton.grid(column=5, row = 3, padx=5)
450         okButton.grid(column=4, row = 3, sticky=tk.E, padx=5)
451
452         # populate the list box with our config options
453         for conf in configuration_dictionary:
454             self.namelist.insert(tk.END, conf['name'])
455             s = conf['type']
456             if s == "":
457                 s = "int"
458             self.typelist.insert(tk.END, s)
459             self.maxlist.insert(tk.END, conf['max'])
460             self.minlist.insert(tk.END, conf['min'])
461             self.defaultlist.insert(tk.END, conf['default'])
462
463             # see if this config has a setting, our results member has this predefined from init
464             val = self.results.get(conf['name'], CONFIG_UNSET)
465             self.valuelist.insert(tk.END, val)
466             if val != CONFIG_UNSET:
467                 self.valuelist.itemconfig(self.valuelist.size() - 1, {'bg':'green'})
468
469     def yview(self, *args):
470         for box in self.listlist:
471             box.yview(*args)
472
473     def mousewheel(self, event):
474         if (event.num == 4):    # Linux encodes wheel as 'buttons' 4 and 5
475             delta = -1
476         elif (event.num == 5):
477             delta = 1
478         else:                   # Windows & OSX
479             delta = event.delta
480
481         for box in self.listlist:
482             box.yview("scroll", delta, "units")
483         return "break"
484
485     def changeSelection(self, evt):
486         box = evt.widget
487         sellist = box.curselection()
488
489         if sellist:
490             index = int(sellist[0])
491             config = self.namelist.get(index)
492             # Now find the description for that config in the dictionary
493             for conf in configuration_dictionary:
494                 if conf['name'] == config:
495                     self.descriptionText.config(state=tk.NORMAL)
496                     self.descriptionText.delete(1.0,tk.END)
497                     str = config + "\n" + conf['description']
498                     self.descriptionText.insert(1.0, str)
499                     self.descriptionText.config(state=tk.DISABLED)
500                     break
501             # Set all the other list boxes to the same index
502             for b in self.listlist:
503                 if b != box:
504                     b.selection_clear(0, tk.END)
505                     b.selection_set(index)
506
507     def OnEntryUpDown(self, event):
508         box = event.widget
509         selection = box.curselection()
510
511         if selection:
512             index = int(selection[0])
513             if event.keysym == 'Up':
514                 index -= 1
515             elif event.keysym == 'Down':
516                 index += 1
517
518             if 0 <= index < box.size():
519                 for b in self.listlist:
520                         b.selection_clear(0, tk.END)
521                         b.selection_set(index)
522                         b.see(index)
523
524     def doubleClick(self, evt):
525         box = evt.widget
526         index = int(box.curselection()[0])
527         config = self.namelist.get(index)
528         # Get the associated dict entry from our list of configs
529         for conf in configuration_dictionary:
530             if conf['name'] == config:
531                 if (conf['type'] == 'bool'):
532                     result = EditBoolWindow(self, conf, self.valuelist.get(index)).get()
533                 elif (conf['type'] == 'int' or conf['type'] == ""): # "" defaults to int
534                     result = EditIntWindow(self, conf, self.valuelist.get(index)).get()
535                 elif conf['type'] == 'enum':
536                     result = EditEnumWindow(self, conf, self.valuelist.get(index)).get()
537
538                 # Update the valuelist with our new item
539                 self.valuelist.delete(index)
540                 self.valuelist.insert(index, result)
541                 if result != CONFIG_UNSET:
542                     self.valuelist.itemconfig(index, {'bg':'green'})
543                 break
544
545     def ok(self):
546         # Get the selections, and create a list of them
547         for i, val in enumerate(self.valuelist.get(0, tk.END)):
548             if val != CONFIG_UNSET:
549                 self.results[self.namelist.get(i)] = val
550             else:
551                 self.results.pop(self.namelist.get(i), None)
552
553         self.destroy()
554
555     def cancel(self):
556         self.destroy()
557
558     def get(self):
559         return self.results
560
561 class WirelessSettingsWindow(sd.Dialog):
562
563     def __init__(self, parent):
564         sd.Dialog.__init__(self, parent, "Wireless settings")
565         self.parent = parent
566
567     def body(self, master):
568         self.configure(background=GetBackground())
569         master.configure(background=GetBackground())
570         self.ssid = tk.StringVar()
571         self.password = tk.StringVar()
572
573         a = ttk.Label(master, text='SSID :', background=GetBackground())
574         a.grid(row=0, column=0, sticky=tk.E)
575         a.configure(background=GetBackground())
576         ttk.Entry(master, textvariable=self.ssid).grid(row=0, column=1, sticky=tk.W+tk.E, padx=5)
577
578         ttk.Label(master, text='Password :').grid(row=1, column=0, sticky=tk.E)
579         ttk.Entry(master, textvariable=self.password).grid(row=1, column=1, sticky=tk.W+tk.E, padx=5)
580
581         self.transient(self.parent)
582         self.grab_set()
583
584     def ok(self):
585         self.grab_release()
586         self.destroy()
587
588     def cancel(self):
589         self.destroy()
590
591     def get(self):
592         return (self.ssid.get(), self.password.get())
593
594 # Our main window
595 class ProjectWindow(tk.Frame):
596
597     def __init__(self, parent, sdkpath, args):
598         tk.Frame.__init__(self, parent)
599         self.master = parent
600         self.sdkpath = sdkpath
601         self.init_window(args)
602         self.configs = dict()
603         self.ssid = str()
604         self.password = str()
605
606     def setState(self, thing, state):
607         for child in thing.winfo_children():
608             child.configure(state=state)
609
610     def boardtype_change_callback(self, event):
611         boardtype = self.boardtype.get()
612         if boardtype == "pico_w":
613             self.setState(self.picowSubframe, "enabled")
614         else:
615             self.setState(self.picowSubframe, "disabled")
616
617     def wirelessSettings(self):
618         result = WirelessSettingsWindow(self)
619         self.ssid, self.password = result.get()
620
621     def init_window(self, args):
622         self.master.title("Raspberry Pi Pico Project Generator")
623         self.master.configure(bg=GetBackground())
624
625         optionsRow = 0
626
627         mainFrame = tk.Frame(self, bg=GetBackground()).grid(row=optionsRow, column=0, columnspan=6, rowspan=12)
628
629         # Need to keep a reference to the image or it will not appear.
630         self.logo = tk.PhotoImage(file=GetFilePath("logo_alpha.gif"))
631         logowidget = ttk.Label(mainFrame, image=self.logo, borderwidth=0, relief="solid").grid(row=0,column=0, columnspan=5, pady=10)
632
633         optionsRow += 2
634
635         namelbl = ttk.Label(mainFrame, text='Project Name :').grid(row=optionsRow, column=0, sticky=tk.E)
636         self.projectName = tk.StringVar()
637
638         if args.name != None:
639             self.projectName.set(args.name)
640         else:
641             self.projectName.set('ProjectName')
642
643         nameEntry = ttk.Entry(mainFrame, textvariable=self.projectName).grid(row=optionsRow, column=1, sticky=tk.W+tk.E, padx=5)
644
645         optionsRow += 1
646
647         locationlbl = ttk.Label(mainFrame, text='Location :').grid(row=optionsRow, column=0, sticky=tk.E)
648         self.locationName = tk.StringVar()
649         self.locationName.set(os.getcwd())
650         locationEntry = ttk.Entry(mainFrame, textvariable=self.locationName).grid(row=optionsRow, column=1, columnspan=3, sticky=tk.W+tk.E, padx=5)
651         locationBrowse = ttk.Button(mainFrame, text='Browse', command=self.browse).grid(row=3, column=4)
652
653         optionsRow += 1
654
655         ttk.Label(mainFrame, text = "Board Type :").grid(row=optionsRow, column=0, padx=4, sticky=tk.E)
656         self.boardtype = ttk.Combobox(mainFrame, values=boardtype_list, )
657         self.boardtype.grid(row=4, column=1, padx=4, sticky=tk.W+tk.E)
658         self.boardtype.set('pico')
659         self.boardtype.bind('<<ComboboxSelected>>',self.boardtype_change_callback)
660         optionsRow += 1
661
662         # Features section
663         featuresframe = ttk.LabelFrame(mainFrame, text="Library Options", relief=tk.RIDGE, borderwidth=2)
664         featuresframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=5, ipadx=5, padx=5, pady=5, sticky=tk.E+tk.W)
665
666         s = (len(features_list)/3)
667
668         self.feature_checkbox_vars = []
669         row = 0
670         col = 0
671         for i in features_list:
672             var = tk.StringVar(value='') # Off by default for the moment
673             c = features_list[i][GUI_TEXT]
674             cb = ttk.Checkbutton(featuresframe, text = c, var=var, onvalue=i, offvalue='')
675             cb.grid(row=row, column=col, padx=15, pady=2, ipadx=1, ipady=1, sticky=tk.E+tk.W)
676             self.feature_checkbox_vars.append(var)
677             row+=1
678             if row >= s:
679                 col+=1
680                 row = 0
681
682         optionsRow += 5
683
684         # PicoW options section
685         self.picowSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Pico Wireless Options")
686         self.picowSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
687         self.pico_wireless = tk.StringVar()
688
689         col = 0
690         row = 0
691         for i in picow_options_list:
692             rb = ttk.Radiobutton(self.picowSubframe, text=picow_options_list[i][GUI_TEXT], variable=self.pico_wireless, val=i)
693             rb.grid(row=row, column=col,  padx=15, pady=1, sticky=tk.E+tk.W)
694             col+=1
695             if col == 3:
696                 col=0
697                 row+=1
698
699         # DOnt actually need any settings at the moment.
700         # ttk.Button(self.picowSubframe, text='Settings', command=self.wirelessSettings).grid(row=0, column=4, padx=5, pady=2, sticky=tk.E)
701
702         self.setState(self.picowSubframe, "disabled")
703
704         optionsRow += 3
705
706         # output options section
707         ooptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Console Options")
708         ooptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
709
710         self.wantUART = tk.IntVar()
711         self.wantUART.set(args.uart)
712         ttk.Checkbutton(ooptionsSubframe, text="Console over UART", variable=self.wantUART).grid(row=0, column=0, padx=4, sticky=tk.W)
713
714         self.wantUSB = tk.IntVar()
715         self.wantUSB.set(args.usb)
716         ttk.Checkbutton(ooptionsSubframe, text="Console over USB (Disables other USB use)", variable=self.wantUSB).grid(row=0, column=1, padx=4, sticky=tk.W)
717
718         optionsRow += 2
719
720         # Code options section
721         coptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Code Options")
722         coptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=3, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
723
724         self.wantExamples = tk.IntVar()
725         self.wantExamples.set(args.examples)
726         ttk.Checkbutton(coptionsSubframe, text="Add examples for Pico library", variable=self.wantExamples).grid(row=0, column=0, padx=4, sticky=tk.W)
727
728         self.wantRunFromRAM = tk.IntVar()
729         self.wantRunFromRAM.set(args.runFromRAM)
730         ttk.Checkbutton(coptionsSubframe, text="Run from RAM", variable=self.wantRunFromRAM).grid(row=0, column=1, padx=4, sticky=tk.W)
731
732         self.wantCPP = tk.IntVar()
733         self.wantCPP.set(args.cpp)
734         ttk.Checkbutton(coptionsSubframe, text="Generate C++", variable=self.wantCPP).grid(row=0, column=3, padx=4, sticky=tk.W)
735
736         ttk.Button(coptionsSubframe, text="Advanced...", command=self.config).grid(row=0, column=4, sticky=tk.E)
737
738         self.wantCPPExceptions = tk.IntVar()
739         self.wantCPPExceptions.set(args.cppexceptions)
740         ttk.Checkbutton(coptionsSubframe, text="Enable C++ exceptions", variable=self.wantCPPExceptions).grid(row=1, column=0, padx=4, sticky=tk.W)
741
742         self.wantCPPRTTI = tk.IntVar()
743         self.wantCPPRTTI.set(args.cpprtti)
744         ttk.Checkbutton(coptionsSubframe, text="Enable C++ RTTI", variable=self.wantCPPRTTI).grid(row=1, column=1, padx=4, sticky=tk.W)
745
746         optionsRow += 3
747
748         # Build Options section
749
750         boptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="Build Options")
751         boptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
752
753         self.wantBuild = tk.IntVar()
754         self.wantBuild.set(args.build)
755         ttk.Checkbutton(boptionsSubframe, text="Run build after generation", variable=self.wantBuild).grid(row=0, column=0, padx=4, sticky=tk.W)
756
757         self.wantOverwrite = tk.IntVar()
758         self.wantOverwrite.set(args.overwrite)
759         ttk.Checkbutton(boptionsSubframe, text="Overwrite existing projects", variable=self.wantOverwrite).grid(row=0, column=1, padx=4, sticky=tk.W)
760
761         optionsRow += 2
762         
763         # IDE Options section
764
765         vscodeoptionsSubframe = ttk.LabelFrame(mainFrame, relief=tk.RIDGE, borderwidth=2, text="IDE Options")
766         vscodeoptionsSubframe.grid_columnconfigure(2, weight=1)
767         vscodeoptionsSubframe.grid(row=optionsRow, column=0, columnspan=5, rowspan=2, padx=5, pady=5, ipadx=5, ipady=3, sticky=tk.E+tk.W)
768
769         self.wantVSCode = tk.IntVar()
770         if args.project is None:
771             self.wantVSCode.set(False)
772         else:
773             self.wantVSCode.set('vscode' in args.project)
774         ttk.Checkbutton(vscodeoptionsSubframe, text="Create VSCode project", variable=self.wantVSCode).grid(row=0, column=0, padx=4, sticky=tk.W)
775
776         ttk.Label(vscodeoptionsSubframe, text = "     Debugger:").grid(row=0, column=1, padx=4, sticky=tk.W)
777
778         self.debugger = ttk.Combobox(vscodeoptionsSubframe, values=debugger_list, state="readonly")
779         self.debugger.grid(row=0, column=2, padx=4, sticky=tk.EW)
780         self.debugger.current(args.debugger)
781
782         optionsRow += 2
783
784         # OK, Cancel, Help section
785         # creating buttons
786         QuitButton = ttk.Button(mainFrame, text="Quit", command=self.quit).grid(row=optionsRow, column=4, stick=tk.E, padx=10, pady=5)
787         OKButton = ttk.Button(mainFrame, text="OK", command=self.OK).grid(row=optionsRow, column=3, padx=4, pady=5, sticky=tk.E)
788
789         # TODO help not implemented yet
790         # HelpButton = ttk.Button(mainFrame, text="Help", command=self.help).grid(row=optionsRow, column=0, pady=5)
791
792         # You can set a default path here, replace the string with whereever you want.
793         # self.locationName.set('/home/pi/pico_projects')
794
795     def GetFeatures(self):
796         features = []
797
798         i = 0
799         for cb in self.feature_checkbox_vars:
800             s = cb.get()
801             if s != '':
802                 features.append(s)
803
804         picow_extra = self.pico_wireless.get()
805
806         if picow_extra != 'picow_none':
807             features.append(picow_extra)
808
809         return features
810
811     def quit(self):
812         # TODO Check if we want to exit here
813         sys.exit(0)
814
815     def OK(self):
816         # OK, grab all the settings from the page, then call the generators
817         projectPath = self.locationName.get()
818         features = self.GetFeatures()
819         projects = list()
820         if (self.wantVSCode.get()):
821             projects.append("vscode")
822
823         params={
824                 'sdkPath'       : self.sdkpath,
825                 'projectRoot'   : Path(projectPath),
826                 'projectName'   : self.projectName.get(),
827                 'wantGUI'       : True,
828                 'wantOverwrite' : self.wantOverwrite.get(),
829                 'wantBuild'     : self.wantBuild.get(),
830                 'boardtype'     : self.boardtype.get(),
831                 'features'      : features,
832                 'projects'      : projects,
833                 'configs'       : self.configs,
834                 'wantRunFromRAM': self.wantRunFromRAM.get(),
835                 'wantExamples'  : self.wantExamples.get(),
836                 'wantUART'      : self.wantUART.get(),
837                 'wantUSB'       : self.wantUSB.get(),
838                 'wantCPP'       : self.wantCPP.get(),
839                 'debugger'      : self.debugger.current(),
840                 'exceptions'    : self.wantCPPExceptions.get(),
841                 'rtti'          : self.wantCPPRTTI.get(),
842                 'ssid'          : self.ssid,
843                 'password'      : self.password,
844                 'envSuffix'     : self.envSuffix,
845                 }
846
847         DoEverything(self, params)
848
849     def browse(self):
850         name = fd.askdirectory()
851         self.locationName.set(name)
852
853     def help(self):
854         print("Help TODO")
855
856     def config(self):
857         # Run the configuration window
858         self.configs = ConfigurationWindow(self, self.configs).get()
859
860 def CheckPrerequisites():
861     global isMac, isWindows
862     isMac = (platform.system() == 'Darwin')
863     isWindows = (platform.system() == 'Windows')
864
865     # Do we have a compiler?
866     return shutil.which(COMPILER_NAME)
867
868
869 def CheckSDKPath(gui):
870     sdkPath = os.getenv('PICO_SDK_PATH')
871
872     if sdkPath == None:
873         m = 'Unable to locate the Raspberry Pi Pico SDK, PICO_SDK_PATH is not set'
874         if (gui):
875             RunWarning(m)
876         else:
877             print(m)
878     elif not os.path.isdir(sdkPath):
879         m = 'Unable to locate the Raspberry Pi Pico SDK, PICO_SDK_PATH does not point to a directory'
880         if (gui):
881             RunWarning(m)
882         else:
883             print(m)
884         sdkPath = None
885
886     return sdkPath
887
888 def GetFilePath(filename):
889     if os.path.islink(__file__):
890         script_file = os.readlink(__file__)
891     else:
892         script_file = __file__
893     return os.path.join(os.path.dirname(script_file), filename)
894
895 def ParseCommandLine():
896     debugger_flags = ', '.join('{} = {}'.format(i, v) for i, v in enumerate(debugger_list))
897     parser = argparse.ArgumentParser(description='Pico Project generator')
898     parser.add_argument("name", nargs="?", help="Name of the project")
899     parser.add_argument("-t", "--tsv", help="Select an alternative pico_configs.tsv file", default=GetFilePath("pico_configs.tsv"))
900     parser.add_argument("-o", "--output", help="Set an alternative CMakeList.txt filename", default="CMakeLists.txt")
901     parser.add_argument("-x", "--examples", action='store_true', help="Add example code for the Pico standard library")
902     parser.add_argument("-l", "--list", action='store_true', help="List available features")
903     parser.add_argument("-c", "--configs", action='store_true', help="List available project configuration items")
904     parser.add_argument("-f", "--feature", action='append', help="Add feature to generated project")
905     parser.add_argument("-over", "--overwrite", action='store_true', help="Overwrite any existing project AND files")
906     parser.add_argument("-b", "--build", action='store_true', help="Build after project created")
907     parser.add_argument("-g", "--gui", action='store_true', help="Run a GUI version of the project generator")
908     parser.add_argument("-p", "--project", action='append', help="Generate projects files for IDE. Options are: vscode")
909     parser.add_argument("-r", "--runFromRAM", action='store_true', help="Run the program from RAM rather than flash")
910     parser.add_argument("-uart", "--uart", action='store_true', default=1, help="Console output to UART (default)")
911     parser.add_argument("-nouart", "--nouart", action='store_true', default=0, help="Disable console output to UART")
912     parser.add_argument("-usb", "--usb", action='store_true', help="Console output to USB (disables other USB functionality")
913     parser.add_argument("-cpp", "--cpp", action='store_true', default=0, help="Generate C++ code")
914     parser.add_argument("-cpprtti", "--cpprtti", action='store_true', default=0, help="Enable C++ RTTI (Uses more memory)")
915     parser.add_argument("-cppex", "--cppexceptions", action='store_true', default=0, help="Enable C++ exceptions (Uses more memory)")
916     parser.add_argument("-d", "--debugger", type=int, help="Select debugger ({})".format(debugger_flags), default=0)
917     parser.add_argument("-board", "--boardtype", action="store", default='pico', help="Select board type (see --boardlist for available boards)")
918     parser.add_argument("-bl", "--boardlist", action="store_true", help="List available board types")
919     parser.add_argument("-cp", "--cpath", help="Override default VSCode compiler path")
920     parser.add_argument("-root", "--projectRoot", help="Override default project root where the new project will be created")
921     parser.add_argument("-envSuffix", "--envSuffix", help="[A suffix ot the PICO_SDK_PATH environment variable]")
922     parser.add_argument("-sdkVersion", "--sdkVersion", help="[A sdkVersion to set in settings]")
923
924     return parser.parse_args()
925
926
927 def GenerateMain(folder, projectName, features, cpp):
928
929     if cpp:
930         filename = Path(folder) / (projectName + '.cpp')
931     else:
932         filename = Path(folder) / (projectName + '.c')
933
934     file = open(filename, 'w')
935
936     main = ('#include <stdio.h>\n'
937             '#include "pico/stdlib.h"\n'
938             )
939     file.write(main)
940
941     if (features):
942
943         # Add any includes
944         for feat in features:
945             if (feat in features_list):
946                 o = f'#include "{features_list[feat][H_FILE]}"\n'
947                 file.write(o)
948             if (feat in stdlib_examples_list):
949                 o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n'
950                 file.write(o)
951             if (feat in picow_options_list):
952                 o = f'#include "{picow_options_list[feat][H_FILE]}"\n'
953                 file.write(o)
954
955         file.write('\n')
956
957         # Add any defines
958         for feat in features:
959             if (feat in code_fragments_per_feature):
960                 for s in code_fragments_per_feature[feat][DEFINES]:
961                     file.write(s)
962                     file.write('\n')
963                 file.write('\n')
964
965     main = ('\n\n'
966             'int main()\n'
967             '{\n'
968             '    stdio_init_all();\n\n'
969             )
970
971     if (features):
972         # Add any initialisers
973         indent = 4
974         for feat in features:
975             if (feat in code_fragments_per_feature):
976                 for s in code_fragments_per_feature[feat][INITIALISERS]:
977                     main += (" " * indent)
978                     main += s
979                     main += '\n'
980             main += '\n'
981
982     main += ('    puts("Hello, world!");\n\n'
983              '    return 0;\n'
984              '}\n'
985             )
986
987     file.write(main)
988
989     file.close()
990
991
992 def GenerateCMake(folder, params):
993    
994     filename = Path(folder) / CMAKELIST_FILENAME
995     projectName = params['projectName']
996     board_type = params['boardtype']
997
998     # OK, for the path, CMake will accept forward slashes on Windows, and thats
999     # seemingly a bit easier to handle than the backslashes
1000     p = str(params['sdkPath']).replace('\\','/')
1001
1002     cmake_header1 = (f"# Generated Cmake Pico project file\n\n"
1003                  "cmake_minimum_required(VERSION 3.13)\n\n"
1004                  "set(CMAKE_C_STANDARD 11)\n"
1005                  "set(CMAKE_CXX_STANDARD 17)\n\n"
1006                  "# Initialise pico_sdk from installed location\n"
1007                  "# (note this can come from environment, CMake cache etc)\n"
1008                  "# DO NOT EDIT THE NEXT TWO LINES for RaspberryPiPico VS Code Extension to work \n"
1009                  f"set(PICO_SDK_PATH $ENV{{PICO_SDK_PATH_{params['envSuffix']}}})\n"
1010                  f"set(PICO_TOOLCHAIN_PATH $ENV{{PICO_TOOLCHAIN_PATH_{params['envSuffix']}}})\n\n"
1011                  f"set(PICO_BOARD {board_type} CACHE STRING \"Board type\")\n\n"
1012                  "# Pull in Raspberry Pi Pico SDK (must be before project)\n"
1013                  "include(pico_sdk_import.cmake)\n\n"
1014                  "if (PICO_SDK_VERSION_STRING VERSION_LESS \"1.4.0\")\n"
1015                  "  message(FATAL_ERROR \"Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}\")\n"
1016                  "endif()\n\n"
1017                  f"project({projectName} C CXX ASM)\n"
1018                 )
1019     
1020     cmake_header3 = (
1021                 "\n# Initialise the Raspberry Pi Pico SDK\n"
1022                 "pico_sdk_init()\n\n"
1023                 "# Add executable. Default name is the project name, version 0.1\n\n"
1024                 )
1025
1026
1027     file = open(filename, 'w')
1028
1029     file.write(cmake_header1)
1030
1031     if params['exceptions']:
1032         file.write("\nset(PICO_CXX_ENABLE_EXCEPTIONS 1)\n")
1033
1034     if params['rtti']:
1035         file.write("\nset(PICO_CXX_ENABLE_RTTI 1)\n")
1036
1037     file.write(cmake_header3)
1038
1039     # add the preprocessor defines for overall configuration
1040     if params['configs']:
1041         file.write('# Add any PICO_CONFIG entries specified in the Advanced settings\n')
1042         for c, v in params['configs'].items():
1043             if v == "True":
1044                 v = "1"
1045             elif v == "False":
1046                 v = "0"
1047             file.write(f'add_compile_definitions({c} = {v})\n')
1048         file.write('\n')
1049
1050     # No GUI/command line to set a different executable name at this stage
1051     executableName = projectName
1052
1053     if params['wantCPP']:
1054         file.write(f'add_executable({projectName} {projectName}.cpp )\n\n')
1055     else:
1056         file.write(f'add_executable({projectName} {projectName}.c )\n\n')
1057
1058     file.write(f'pico_set_program_name({projectName} "{executableName}")\n')
1059     file.write(f'pico_set_program_version({projectName} "0.1")\n\n')
1060
1061     if params['wantRunFromRAM']:
1062         file.write(f'# no_flash means the target is to run from RAM\n')
1063         file.write(f'pico_set_binary_type({projectName} no_flash)\n\n')
1064
1065     # Console output destinations
1066     if params['wantUART']:
1067         file.write(f'pico_enable_stdio_uart({projectName} 1)\n')
1068     else:
1069         file.write(f'pico_enable_stdio_uart({projectName} 0)\n')
1070
1071     if params['wantUSB']:
1072         file.write(f'pico_enable_stdio_usb({projectName} 1)\n\n')
1073     else:
1074         file.write(f'pico_enable_stdio_usb({projectName} 0)\n\n')
1075
1076     # If we need wireless, check for SSID and password
1077     # removed for the moment as these settings are currently only needed for the pico-examples
1078     # but may be required in here at a later date.
1079     if False:
1080         if 'ssid' in params or 'password' in params:
1081             file.write('# Add any wireless access point information\n')
1082             file.write(f'target_compile_definitions({projectName} PRIVATE\n')
1083             if 'ssid' in params:
1084                 file.write(f'WIFI_SSID=\" {params["ssid"]} \"\n')
1085             else:
1086                 file.write(f'WIFI_SSID=\"${WIFI_SSID}\"')
1087
1088             if 'password' in params:
1089                 file.write(f'WIFI_PASSWORD=\"{params["password"]}\"\n')
1090             else:
1091                 file.write(f'WIFI_PASSWORD=\"${WIFI_PASSWORD}\"')
1092             file.write(')\n\n')
1093
1094     # Standard libraries
1095     file.write('# Add the standard library to the build\n')
1096     file.write(f'target_link_libraries({projectName}\n')
1097     file.write("        " + STANDARD_LIBRARIES)
1098     file.write(')\n\n')
1099
1100     # Standard include directories
1101     file.write('# Add the standard include files to the build\n')
1102     file.write(f'target_include_directories({projectName} PRIVATE\n')
1103     file.write("  ${CMAKE_CURRENT_LIST_DIR}\n")
1104     file.write("  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required\n")
1105     file.write(')\n\n')
1106
1107     # Selected libraries/features
1108     if (params['features']):
1109         file.write('# Add any user requested libraries\n')
1110         file.write(f'target_link_libraries({projectName} \n')
1111         for feat in params['features']:
1112             if (feat in features_list):
1113                 file.write("        " + features_list[feat][LIB_NAME] + '\n')
1114             if (feat in picow_options_list):
1115                 file.write("        " + picow_options_list[feat][LIB_NAME] + '\n')
1116         file.write('        )\n\n')
1117
1118     file.write(f'pico_add_extra_outputs({projectName})\n\n')
1119
1120     file.close()
1121
1122
1123 # Generates the requested project files, if any
1124 def generateProjectFiles(projectPath, projectName, sdkPath, projects, debugger, envSuffix, sdkVersion):
1125
1126     oldCWD = os.getcwd()
1127
1128     os.chdir(projectPath)
1129
1130     debugger = debugger_config_list[debugger]
1131     gdbPath =  "arm-none-eabi-gdb" if isWindows else "gdb-multiarch"
1132     # Need to escape windows files paths backslashes
1133     # TODO: env in currently not supported in compilerPath var
1134     #cPath = f"${{env:PICO_TOOLCHAIN_PATH_{envSuffix}}}" + os.path.sep + os.path.basename(str(compilerPath).replace('\\', '\\\\' ))
1135     cPath = str(compilerPath).replace('\\', '\\\\' )
1136
1137     for p in projects :
1138         if p == 'vscode':
1139             launch = f'''{{
1140     "version": "0.2.0",
1141     "configurations": [
1142         {{
1143             "name": "Pico Debug (Cortex-Debug)",
1144             "cwd": "${{workspaceRoot}}",
1145             "executable": "${{command:raspberry-pi-pico.launchTargetPath}}",
1146             "request": "launch",
1147             "type": "cortex-debug",
1148             "servertype": "openocd",
1149             "gdbPath": "{gdbPath}",
1150             "device": "RP2040",
1151             "configFiles": [
1152                 "interface/{debugger}",
1153                 "target/rp2040.cfg"
1154             ],
1155             "svdFile": "${{command:raspberry-pi-pico.getSDKPath}}/src/rp2040/hardware_regs/rp2040.svd",
1156             "runToEntryPoint": "main",
1157             // Give restart the same functionality as runToEntryPoint - main
1158             "postRestartCommands": [
1159                 "break main",
1160                 "continue"
1161             ],
1162             "openOCDLaunchCommands": [
1163                 "adapter speed 1000"
1164             ],
1165             "preLaunchTask": "Compile Project"
1166         }},
1167         {{
1168             "name": "Pico Debug (Cortex-Debug with external OpenOCD)",
1169             "cwd": "${{workspaceRoot}}",
1170             "executable": "${{command:raspberry-pi-pico.launchTargetPath}}",
1171             "request": "launch",
1172             "type": "cortex-debug",
1173             "servertype": "external",
1174             "gdbTarget": "localhost:3333",
1175             "gdbPath": "{gdbPath}",
1176             "device": "RP2040",
1177             "svdFile": "${{command:raspberry-pi-pico.getSDKPath}}/src/rp2040/hardware_regs/rp2040.svd",
1178             "runToEntryPoint": "main",
1179             // Give restart the same functionality as runToEntryPoint - main
1180             "postRestartCommands": [
1181                 "break main",
1182                 "continue"
1183             ],
1184             "preLaunchTask": "Compile Project"
1185         }},
1186         {{
1187             "name": "Pico Debug (C++ Debugger)",
1188             "type": "cppdbg",
1189             "request": "launch",
1190             "cwd": "${{workspaceRoot}}",
1191             "program": "${{command:raspberry-pi-pico.launchTargetPath}}",
1192             "MIMode": "gdb",
1193             "miDebuggerPath": "{gdbPath}",
1194             "miDebuggerServerAddress": "localhost:3333",
1195             "debugServerPath": "openocd",
1196             "debugServerArgs": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \\"adapter speed 1000\\"",
1197             "serverStarted": "Listening on port .* for gdb connections",
1198             "filterStderr": true,
1199             "stopAtEntry": true,
1200             "hardwareBreakpoints": {{
1201                 "require": true,
1202                 "limit": 4
1203             }},
1204             "preLaunchTask": "Flash",
1205             "svdPath": "${{command:raspberry-pi-pico.getSDKPath}}/src/rp2040/hardware_regs/rp2040.svd"
1206         }},
1207     ]
1208 }}
1209 '''
1210
1211             properties = f'''{{
1212     "configurations": [
1213         {{
1214             "name": "Pico",
1215             "includePath": [
1216                 "${{workspaceFolder}}/**",
1217                 "${{env:PICO_SDK_PATH_{envSuffix}}}/**"
1218             ],
1219             "defines": [],
1220             "compilerPath": "{cPath}",
1221             "cStandard": "c17",
1222             "cppStandard": "c++14",
1223             "intelliSenseMode": "linux-gcc-arm"
1224         }}
1225     ],
1226     "version": 4
1227 }}
1228 '''
1229
1230
1231             # settings
1232             settings = f'''{{
1233     "cmake.statusbar.visibility": "hidden",
1234     "cmake.configureOnEdit": false,
1235     "cmake.automaticReconfigure": false,
1236     "cmake.configureOnOpen": false,
1237     "cmake.generator": "Ninja",
1238     "raspberry-pi-pico.sdk": "{sdkVersion}",
1239     "raspberry-pi-pico.envSuffix": "{envSuffix}",
1240 }}
1241 '''
1242
1243             # extensions
1244             extensions = f'''{{
1245     "recommendations": [
1246         "marus25.cortex-debug",
1247         "ms-vscode.cpptools",
1248         "ms-vscode.cpptools-extension-pack",
1249         "ms-vscode.vscode-serial-monitor",
1250         "paulober.raspberry-pi-pico",
1251     ]
1252 }}
1253 '''
1254             tasks = f'''{{
1255     "version": "2.0.0",
1256     "tasks": [
1257         {{
1258             "label": "Compile Project",
1259             "type": "shell",
1260             "command": "ninja",
1261             "args": ["-C", "${{workspaceFolder}}/build"],
1262             "group": "build",
1263             "presentation": {{
1264                 "reveal": "always",
1265                 "panel": "dedicated"
1266             }},
1267             "problemMatcher": "$gcc"
1268         }}
1269     ]
1270 }}
1271 '''
1272
1273             # Create a build folder, and run our cmake project build from it
1274             if not os.path.exists(VSCODE_FOLDER):
1275                 os.mkdir(VSCODE_FOLDER)
1276
1277             os.chdir(VSCODE_FOLDER)
1278
1279             file = open(VSCODE_TASKS_FILENAME, 'w')
1280             file.write(tasks)
1281             file.close()
1282
1283             filename = VSCODE_LAUNCH_FILENAME
1284             file = open(filename, 'w')
1285             file.write(launch)
1286             file.close()
1287
1288             file = open(VSCODE_C_PROPERTIES_FILENAME, 'w')
1289             file.write(properties)
1290             file.close()
1291
1292             file = open(VSCODE_SETTINGS_FILENAME, 'w')
1293             file.write(settings)
1294             file.close()
1295
1296             file = open(VSCODE_EXTENSIONS_FILENAME, 'w')
1297             file.write(extensions)
1298             file.close()
1299
1300         else :
1301             print('Unknown project type requested')
1302
1303     os.chdir(oldCWD)
1304
1305
1306 def LoadConfigurations():
1307     try:
1308         with open(args.tsv) as tsvfile:
1309             reader = csv.DictReader(tsvfile, dialect='excel-tab')
1310             for row in reader:
1311                 configuration_dictionary.append(row)
1312     except:
1313         print("No Pico configurations file found. Continuing without")
1314
1315 def LoadBoardTypes(sdkPath):
1316     # Scan the boards folder for all header files, extract filenames, and make a list of the results
1317     # default folder is <PICO_SDK_PATH>/src/boards/include/boards/*
1318     # If the PICO_BOARD_HEADER_DIRS environment variable is set, use that as well
1319
1320     loc = sdkPath / "src/boards/include/boards"
1321     boards=[]
1322     for x in Path(loc).iterdir():
1323         if x.suffix == '.h':
1324             boards.append(x.stem)
1325
1326     loc = os.getenv('PICO_BOARD_HEADER_DIRS')
1327
1328     if loc != None:
1329         for x in Path(loc).iterdir():
1330             if x.suffix == '.h':
1331                 boards.append(x.stem)
1332
1333     return boards
1334
1335 def DoEverything(parent, params):
1336
1337     if not os.path.exists(params['projectRoot']):
1338         if params['wantGUI']:
1339             mb.showerror('Raspberry Pi Pico Project Generator', 'Invalid project path. Select a valid path and try again')
1340             return
1341         else:
1342             print('Invalid project path')
1343             sys.exit(-1)
1344
1345     oldCWD = os.getcwd()
1346     os.chdir(params['projectRoot'])
1347
1348     # Create our project folder as subfolder
1349     os.makedirs(params['projectName'], exist_ok=True)
1350
1351     os.chdir(params['projectName'])
1352
1353     projectPath = params['projectRoot'] / params['projectName']
1354
1355     # First check if there is already a project in the folder
1356     # If there is we abort unless the overwrite flag it set
1357     if os.path.exists(CMAKELIST_FILENAME):
1358         if not params['wantOverwrite'] :
1359             if params['wantGUI']:
1360                 # We can ask the user if they want to overwrite
1361                 y = mb.askquestion('Raspberry Pi Pico Project Generator', 'There already appears to be a project in this folder. \nPress Yes to overwrite project files, or Cancel to chose another folder')
1362                 if y != 'yes':
1363                     return
1364             else:
1365                 print('There already appears to be a project in this folder. Use the --overwrite option to overwrite the existing project')
1366                 sys.exit(-1)
1367
1368         # We should really confirm the user wants to overwrite
1369         #print('Are you sure you want to overwrite the existing project files? (y/N)')
1370         #c = input().split(" ")[0]
1371         #if c != 'y' and c != 'Y' :
1372         #    sys.exit(0)
1373
1374     # Copy the SDK finder cmake file to our project folder
1375     # Can be found here <PICO_SDK_PATH>/external/pico_sdk_import.cmake
1376     shutil.copyfile(params['sdkPath'] / 'external' / 'pico_sdk_import.cmake', projectPath / 'pico_sdk_import.cmake' )
1377
1378     if params['features']:
1379         features_and_examples = params['features'][:]
1380     else:
1381         features_and_examples= []
1382
1383     if params['wantExamples']:
1384         features_and_examples = list(stdlib_examples_list.keys()) + features_and_examples
1385
1386     GenerateMain('.', params['projectName'], features_and_examples, params['wantCPP'])
1387
1388     GenerateCMake('.', params)
1389
1390     # If we have any ancilliary files, copy them to our project folder
1391     # Currently only the picow with lwIP support needs an extra file, so just check that list
1392     for feat in features_and_examples:
1393         if feat in picow_options_list:
1394             if picow_options_list[feat][ANCILLARY_FILE] != "":
1395                 shutil.copy(sourcefolder + "/" + picow_options_list[feat][ANCILLARY_FILE], projectPath / picow_options_list[feat][ANCILLARY_FILE])
1396
1397     # Create a build folder, and run our cmake project build from it
1398     if not os.path.exists('build'):
1399         os.mkdir('build')
1400
1401     os.chdir('build')
1402
1403     # If we are overwriting a previous project, we should probably clear the folder, but that might delete something the users thinks is important, so
1404     # for the moment, just delete the CMakeCache.txt file as certain changes may need that to be recreated.
1405
1406     if os.path.exists(CMAKECACHE_FILENAME):
1407         os.remove(CMAKECACHE_FILENAME)
1408
1409     cpus = os.cpu_count()
1410     if cpus == None:
1411         cpus = 1
1412
1413     if isWindows:
1414         # Had a special case report, when using MinGW, need to check if using nmake or mingw32-make.
1415         if shutil.which("mingw32-make"):
1416             # Assume MinGW environment
1417             cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles" ..'
1418             makeCmd = 'mingw32-make '
1419         elif shutil.which("ninja"):
1420             # When installing SDK version 1.5.0 on windows with installer pico-setup-windows-x64-standalone.exe, ninja is used 
1421             cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug -G Ninja ..'
1422             makeCmd = 'ninja '        
1423         else:
1424             # Everything else assume nmake
1425             cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug -G "NMake Makefiles" ..'
1426             makeCmd = 'nmake '
1427     else:
1428         # Ninja now works OK under Linux, so if installed use it by default. It's faster.
1429         if shutil.which("ninja"):
1430             cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug -G Ninja ..'
1431             makeCmd = 'ninja '
1432         else:
1433             cmakeCmd = 'cmake -DCMAKE_BUILD_TYPE=Debug ..'
1434             makeCmd = 'make -j' + str(cpus)
1435
1436     if params['wantGUI']:
1437         RunCommandInWindow(parent, cmakeCmd)
1438     else:
1439         os.system(cmakeCmd)
1440
1441     if params['projects']:
1442         generateProjectFiles(projectPath, params['projectName'], params['sdkPath'], params['projects'], params['debugger'], params["envSuffix"], params["sdkVersion"])
1443
1444     if params['wantBuild']:
1445         if params['wantGUI']:
1446             RunCommandInWindow(parent, makeCmd)
1447         else:
1448             os.system(makeCmd)
1449             print('\nIf the application has built correctly, you can now transfer it to the Raspberry Pi Pico board')
1450
1451     os.chdir(oldCWD)
1452
1453
1454 ###################################################################################
1455 # main execution starteth here
1456
1457 sourcefolder = os.path.dirname(os.path.abspath(__file__))
1458
1459 args = ParseCommandLine()
1460
1461 if args.nouart:
1462     args.uart = False
1463
1464 if args.debugger > len(debugger_list) - 1:
1465     args.debugger = 0
1466
1467 # Check we have everything we need to compile etc
1468 c = CheckPrerequisites()
1469
1470 ## TODO Do both warnings in the same error message so user does have to keep coming back to find still more to do
1471
1472 if c == None:
1473     m = f'Unable to find the `{COMPILER_NAME}` compiler\n'
1474     m +='You will need to install an appropriate compiler to build a Raspberry Pi Pico project\n'
1475     m += 'See the Raspberry Pi Pico documentation for how to do this on your particular platform\n'
1476
1477     if (args.gui):
1478         RunWarning(m)
1479     else:
1480         print(m)
1481     sys.exit(-1)
1482
1483 if args.name == None and not args.gui and not args.list and not args.configs and not args.boardlist:
1484     print("No project name specfied\n")
1485     sys.exit(-1)
1486
1487 # Check if we were provided a compiler path, and override the default if so
1488 if args.cpath:
1489     compilerPath = Path(args.cpath)
1490 else:
1491     compilerPath = Path(c)
1492
1493 # load/parse any configuration dictionary we may have
1494 LoadConfigurations()
1495
1496 p = CheckSDKPath(args.gui)
1497
1498 if p == None:
1499     sys.exit(-1)
1500
1501 sdkPath = Path(p)
1502
1503 boardtype_list = LoadBoardTypes(sdkPath)
1504 boardtype_list.sort()
1505
1506 if args.gui:
1507     RunGUI(sdkPath, args) # does not return, only exits
1508
1509 projectRoot = Path(os.getcwd()) if not args.projectRoot else Path(args.projectRoot)
1510
1511 if args.list or args.configs or args.boardlist:
1512     if args.list:
1513         print("Available project features:\n")
1514         for feat in features_list:
1515             print(feat.ljust(6), '\t', features_list[feat][GUI_TEXT])
1516         print('\n')
1517
1518     if args.configs:
1519         print("Available project configuration items:\n")
1520         for conf in configuration_dictionary:
1521             print(conf['name'].ljust(40), '\t', conf['description'])
1522         print('\n')
1523
1524     if args.boardlist:
1525         print("Available board types:\n")
1526         for board in boardtype_list:
1527             print(board)
1528         print('\n')
1529
1530     sys.exit(0)
1531 else :
1532     params={
1533         'sdkPath'       : sdkPath,
1534         'projectRoot'   : projectRoot,
1535         'projectName'   : args.name,
1536         'wantGUI'       : False,
1537         'wantOverwrite' : args.overwrite,
1538         'boardtype'     : args.boardtype,
1539         'wantBuild'     : args.build,
1540         'features'      : args.feature,
1541         'projects'      : args.project,
1542         'configs'       : (),
1543         'wantRunFromRAM': args.runFromRAM,
1544         'wantExamples'  : args.examples,
1545         'wantUART'      : args.uart,
1546         'wantUSB'       : args.usb,
1547         'wantCPP'       : args.cpp,
1548         'debugger'      : args.debugger,
1549         'exceptions'    : args.cppexceptions,
1550         'rtti'          : args.cpprtti,
1551         'ssid'          : '',
1552         'password'      : '',
1553         'envSuffix'     : args.envSuffix,
1554         'sdkVersion'    : args.sdkVersion,
1555         }
1556
1557     DoEverything(None, params)
This page took 0.115855 seconds and 4 git commands to generate.