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