]> Git Repo - pico-vscode.git/blob - scripts/pico_project.py
Fix python error on linux
[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     parser.add_argument("-cupy", "--customPython", action='store_true', help="Custom python path used to execute the script.")
962
963     return parser.parse_args()
964
965
966 def GenerateMain(folder, projectName, features, cpp):
967
968     if cpp:
969         filename = Path(folder) / (projectName + '.cpp')
970     else:
971         filename = Path(folder) / (projectName + '.c')
972
973     file = open(filename, 'w')
974
975     main = ('#include <stdio.h>\n'
976             '#include "pico/stdlib.h"\n'
977             )
978     file.write(main)
979
980     if (features):
981
982         # Add any includes
983         for feat in features:
984             if (feat in features_list):
985                 o = f'#include "{features_list[feat][H_FILE]}"\n'
986                 file.write(o)
987             if (feat in stdlib_examples_list):
988                 o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n'
989                 file.write(o)
990             if (feat in picow_options_list):
991                 o = f'#include "{picow_options_list[feat][H_FILE]}"\n'
992                 file.write(o)
993
994         file.write('\n')
995
996         # Add any defines
997         for feat in features:
998             if (feat in code_fragments_per_feature):
999                 for s in code_fragments_per_feature[feat][DEFINES]:
1000                     file.write(s)
1001                     file.write('\n')
1002                 file.write('\n')
1003
1004     main = ('\n\n'
1005             'int main()\n'
1006             '{\n'
1007             '    stdio_init_all();\n\n'
1008             )
1009
1010     if (features):
1011         # Add any initialisers
1012         indent = 4
1013         for feat in features:
1014             if (feat in code_fragments_per_feature):
1015                 for s in code_fragments_per_feature[feat][INITIALISERS]:
1016                     main += (" " * indent)
1017                     main += s
1018                     main += '\n'
1019             main += '\n'
1020
1021     main += ('    puts("Hello, world!");\n\n'
1022              '    return 0;\n'
1023              '}\n'
1024             )
1025
1026     file.write(main)
1027
1028     file.close()
1029
1030
1031 def GenerateCMake(folder, params):
1032    
1033     filename = Path(folder) / CMAKELIST_FILENAME
1034     projectName = params['projectName']
1035     board_type = params['boardtype']
1036
1037     # OK, for the path, CMake will accept forward slashes on Windows, and thats
1038     # seemingly a bit easier to handle than the backslashes
1039     p = str(params['sdkPath']).replace('\\','/')
1040
1041     cmake_header1 = (f"# Generated Cmake Pico project file\n\n"
1042                  "cmake_minimum_required(VERSION 3.13)\n\n"
1043                  "set(CMAKE_C_STANDARD 11)\n"
1044                  "set(CMAKE_CXX_STANDARD 17)\n\n"
1045                  "# Initialise pico_sdk from installed location\n"
1046                  "# (note this can come from environment, CMake cache etc)\n\n"
1047                  "# == DO NEVER EDIT THE NEXT LINES for RaspberryPiPico VS Code Extension to work == \n"
1048                  "if(WIN32)\n"
1049                  "   set(USERHOME $ENV{USERPROFILE})\n"
1050                  "else()\n"
1051                  "    set(USERHOME $ENV{HOME})\n"
1052                  "endif()\n"
1053                  f"set(PICO_SDK_PATH {cmakeSdkPath(params['sdkVersion'])})\n"
1054                  f"set(PICO_TOOLCHAIN_PATH {cmakeToolchainPath(params['toolchainVersion'])})\n"
1055                  "# ====================================================================================\n"
1056                  f"set(PICO_BOARD {board_type} CACHE STRING \"Board type\")\n\n"
1057                  "# Pull in Raspberry Pi Pico SDK (must be before project)\n"
1058                  "include(pico_sdk_import.cmake)\n\n"
1059                  "if (PICO_SDK_VERSION_STRING VERSION_LESS \"1.4.0\")\n"
1060                  "  message(FATAL_ERROR \"Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}\")\n"
1061                  "endif()\n\n"
1062                  f"project({projectName} C CXX ASM)\n"
1063                 )
1064     
1065     cmake_header3 = (
1066                 "\n# Initialise the Raspberry Pi Pico SDK\n"
1067                 "pico_sdk_init()\n\n"
1068                 "# Add executable. Default name is the project name, version 0.1\n\n"
1069                 )
1070
1071
1072     file = open(filename, 'w')
1073
1074     file.write(cmake_header1)
1075
1076     if params['exceptions']:
1077         file.write("\nset(PICO_CXX_ENABLE_EXCEPTIONS 1)\n")
1078
1079     if params['rtti']:
1080         file.write("\nset(PICO_CXX_ENABLE_RTTI 1)\n")
1081
1082     file.write(cmake_header3)
1083
1084     # add the preprocessor defines for overall configuration
1085     if params['configs']:
1086         file.write('# Add any PICO_CONFIG entries specified in the Advanced settings\n')
1087         for c, v in params['configs'].items():
1088             if v == "True":
1089                 v = "1"
1090             elif v == "False":
1091                 v = "0"
1092             file.write(f'add_compile_definitions({c} = {v})\n')
1093         file.write('\n')
1094
1095     # No GUI/command line to set a different executable name at this stage
1096     executableName = projectName
1097
1098     if params['wantCPP']:
1099         file.write(f'add_executable({projectName} {projectName}.cpp )\n\n')
1100     else:
1101         file.write(f'add_executable({projectName} {projectName}.c )\n\n')
1102
1103     file.write(f'pico_set_program_name({projectName} "{executableName}")\n')
1104     file.write(f'pico_set_program_version({projectName} "0.1")\n\n')
1105
1106     if params['wantRunFromRAM']:
1107         file.write(f'# no_flash means the target is to run from RAM\n')
1108         file.write(f'pico_set_binary_type({projectName} no_flash)\n\n')
1109
1110     # Console output destinations
1111     if params['wantUART']:
1112         file.write(f'pico_enable_stdio_uart({projectName} 1)\n')
1113     else:
1114         file.write(f'pico_enable_stdio_uart({projectName} 0)\n')
1115
1116     if params['wantUSB']:
1117         file.write(f'pico_enable_stdio_usb({projectName} 1)\n\n')
1118     else:
1119         file.write(f'pico_enable_stdio_usb({projectName} 0)\n\n')
1120
1121     # If we need wireless, check for SSID and password
1122     # removed for the moment as these settings are currently only needed for the pico-examples
1123     # but may be required in here at a later date.
1124     if False:
1125         if 'ssid' in params or 'password' in params:
1126             file.write('# Add any wireless access point information\n')
1127             file.write(f'target_compile_definitions({projectName} PRIVATE\n')
1128             if 'ssid' in params:
1129                 file.write(f'WIFI_SSID=\" {params["ssid"]} \"\n')
1130             else:
1131                 file.write(f'WIFI_SSID=\"${WIFI_SSID}\"')
1132
1133             if 'password' in params:
1134                 file.write(f'WIFI_PASSWORD=\"{params["password"]}\"\n')
1135             else:
1136                 file.write(f'WIFI_PASSWORD=\"${WIFI_PASSWORD}\"')
1137             file.write(')\n\n')
1138
1139     # Standard libraries
1140     file.write('# Add the standard library to the build\n')
1141     file.write(f'target_link_libraries({projectName}\n')
1142     file.write("        " + STANDARD_LIBRARIES)
1143     file.write(')\n\n')
1144
1145     # Standard include directories
1146     file.write('# Add the standard include files to the build\n')
1147     file.write(f'target_include_directories({projectName} PRIVATE\n')
1148     file.write("  ${CMAKE_CURRENT_LIST_DIR}\n")
1149     file.write("  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required\n")
1150     file.write(')\n\n')
1151
1152     # Selected libraries/features
1153     if (params['features']):
1154         file.write('# Add any user requested libraries\n')
1155         file.write(f'target_link_libraries({projectName} \n')
1156         for feat in params['features']:
1157             if (feat in features_list):
1158                 file.write("        " + features_list[feat][LIB_NAME] + '\n')
1159             if (feat in picow_options_list):
1160                 file.write("        " + picow_options_list[feat][LIB_NAME] + '\n')
1161         file.write('        )\n\n')
1162
1163     file.write(f'pico_add_extra_outputs({projectName})\n\n')
1164
1165     file.close()
1166
1167
1168 # Generates the requested project files, if any
1169 def generateProjectFiles(projectPath, projectName, sdkPath, projects, debugger, sdkVersion, toolchainVersion, ninjaPath, cmakePath, customPython):
1170
1171     oldCWD = os.getcwd()
1172
1173     os.chdir(projectPath)
1174
1175     debugger = debugger_config_list[debugger]
1176     gdbPath =  Path(codeToolchainPath(toolchainVersion)+"/bin/arm-none-eabi-gdb").as_posix() if isWindows else "gdb-multiarch"
1177     # Need to escape windows files paths backslashes
1178     # TODO: env in currently not supported in compilerPath var
1179     #cPath = f"${{env:PICO_TOOLCHAIN_PATH_{envSuffix}}}" + os.path.sep + os.path.basename(str(compilerPath).replace('\\', '\\\\' ))
1180     cPath = compilerPath.as_posix()
1181
1182     # if this is a path in the .pico-sdk homedir tell the settings to use the homevar
1183     user_home = os.path.expanduser("~").replace("\\", "/")
1184     use_home_var = f"{user_home}/.pico-sdk" in ninjaPath
1185
1186     for p in projects :
1187         if p == 'vscode':
1188             launch = f'''{{
1189     "version": "0.2.0",
1190     "configurations": [
1191         {{
1192             "name": "Pico Debug (Cortex-Debug)",
1193             "cwd": "${{workspaceRoot}}",
1194             "executable": "${{command:raspberry-pi-pico.launchTargetPath}}",
1195             "request": "launch",
1196             "type": "cortex-debug",
1197             "servertype": "openocd",
1198             "gdbPath": "{gdbPath}",
1199             "device": "RP2040",
1200             "configFiles": [
1201                 "interface/{debugger}",
1202                 "target/rp2040.cfg"
1203             ],
1204             "svdFile": "{codeSdkPath(sdkVersion)}/src/rp2040/hardware_regs/rp2040.svd",
1205             "runToEntryPoint": "main",
1206             // Give restart the same functionality as runToEntryPoint - main
1207             "postRestartCommands": [
1208                 "break main",
1209                 "continue"
1210             ],
1211             "openOCDLaunchCommands": [
1212                 "adapter speed 1000"
1213             ],
1214             "preLaunchTask": "Compile Project"
1215         }},
1216         {{
1217             "name": "Pico Debug (Cortex-Debug with external OpenOCD)",
1218             "cwd": "${{workspaceRoot}}",
1219             "executable": "${{command:raspberry-pi-pico.launchTargetPath}}",
1220             "request": "launch",
1221             "type": "cortex-debug",
1222             "servertype": "external",
1223             "gdbTarget": "localhost:3333",
1224             "gdbPath": "{gdbPath}",
1225             "device": "RP2040",
1226             "svdFile": "{codeSdkPath(sdkVersion)}/src/rp2040/hardware_regs/rp2040.svd",
1227             "runToEntryPoint": "main",
1228             // Give restart the same functionality as runToEntryPoint - main
1229             "postRestartCommands": [
1230                 "break main",
1231                 "continue"
1232             ],
1233             "preLaunchTask": "Compile Project"
1234         }},
1235         {{
1236             "name": "Pico Debug (C++ Debugger)",
1237             "type": "cppdbg",
1238             "request": "launch",
1239             "cwd": "${{workspaceRoot}}",
1240             "program": "${{command:raspberry-pi-pico.launchTargetPath}}",
1241             "MIMode": "gdb",
1242             "miDebuggerPath": "{gdbPath}",
1243             "miDebuggerServerAddress": "localhost:3333",
1244             "debugServerPath": "openocd",
1245             "debugServerArgs": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \\"adapter speed 1000\\"",
1246             "serverStarted": "Listening on port .* for gdb connections",
1247             "filterStderr": true,
1248             "stopAtEntry": true,
1249             "hardwareBreakpoints": {{
1250                 "require": true,
1251                 "limit": 4
1252             }},
1253             "preLaunchTask": "Flash",
1254             "svdPath": "{codeSdkPath(sdkVersion)}/src/rp2040/hardware_regs/rp2040.svd"
1255         }},
1256     ]
1257 }}
1258 '''
1259
1260             properties = f'''{{
1261     "configurations": [
1262         {{
1263             "name": "Pico",
1264             "includePath": [
1265                 "${{workspaceFolder}}/**",
1266                 "{propertiesSdkPath(sdkVersion)}/**"
1267             ],
1268             "defines": [],
1269             "compilerPath": "{cPath}",
1270             "cStandard": "c17",
1271             "cppStandard": "c++14",
1272             "intelliSenseMode": "linux-gcc-arm"
1273         }}
1274     ],
1275     "version": 4
1276 }}
1277 '''
1278
1279             pythonExe = sys.executable.replace("\\", "/").replace(user_home, "${HOME}") if use_home_var else sys.executable
1280
1281             # settings
1282             settings = f'''{{
1283     "cmake.statusbar.visibility": "hidden",
1284     "cmake.configureOnEdit": false,
1285     "cmake.automaticReconfigure": false,
1286     "cmake.configureOnOpen": false,
1287     "cmake.generator": "Ninja",
1288     "cmake.cmakePath": "{cmakePath}",
1289     "raspberry-pi-pico.cmakeAutoConfigure": true,
1290     "raspberry-pi-pico.cmakePath": "{cmakePath.replace(user_home, "${HOME}") if use_home_var else cmakePath}",
1291     "raspberry-pi-pico.ninjaPath": "{ninjaPath.replace(user_home, "${HOME}") if use_home_var else ninjaPath}"'''
1292
1293             if customPython:
1294                 settings += f''',
1295     "raspberry-pi-pico.python3Path": "{pythonExe}"'''
1296                 
1297             settings += '\n}\n'
1298
1299             # extensions
1300             extensions = f'''{{
1301     "recommendations": [
1302         "marus25.cortex-debug",
1303         "ms-vscode.cpptools",
1304         "ms-vscode.cpptools-extension-pack",
1305         "ms-vscode.vscode-serial-monitor",
1306         "paulober.raspberry-pi-pico",
1307     ]
1308 }}
1309 '''
1310             tasks = f'''{{
1311     "version": "2.0.0",
1312     "tasks": [
1313         {{
1314             "label": "Compile Project",
1315             "type": "shell",
1316             "command": "{ninjaPath.replace(user_home, "${userHome}") if use_home_var else ninjaPath}",
1317             "args": ["-C", "${{workspaceFolder}}/build"],
1318             "group": "build",
1319             "presentation": {{
1320                 "reveal": "always",
1321                 "panel": "dedicated"
1322             }},
1323             "problemMatcher": "$gcc"
1324         }}
1325     ]
1326 }}
1327 '''
1328
1329             # Create a build folder, and run our cmake project build from it
1330             if not os.path.exists(VSCODE_FOLDER):
1331                 os.mkdir(VSCODE_FOLDER)
1332
1333             os.chdir(VSCODE_FOLDER)
1334
1335             file = open(VSCODE_TASKS_FILENAME, 'w')
1336             file.write(tasks)
1337             file.close()
1338
1339             filename = VSCODE_LAUNCH_FILENAME
1340             file = open(filename, 'w')
1341             file.write(launch)
1342             file.close()
1343
1344             file = open(VSCODE_C_PROPERTIES_FILENAME, 'w')
1345             file.write(properties)
1346             file.close()
1347
1348             file = open(VSCODE_SETTINGS_FILENAME, 'w')
1349             file.write(settings)
1350             file.close()
1351
1352             file = open(VSCODE_EXTENSIONS_FILENAME, 'w')
1353             file.write(extensions)
1354             file.close()
1355
1356         else :
1357             print('Unknown project type requested')
1358
1359     os.chdir(oldCWD)
1360
1361
1362 def LoadConfigurations():
1363     try:
1364         with open(args.tsv) as tsvfile:
1365             reader = csv.DictReader(tsvfile, dialect='excel-tab')
1366             for row in reader:
1367                 configuration_dictionary.append(row)
1368     except:
1369         print("No Pico configurations file found. Continuing without")
1370
1371 def LoadBoardTypes(sdkPath):
1372     # Scan the boards folder for all header files, extract filenames, and make a list of the results
1373     # default folder is <PICO_SDK_PATH>/src/boards/include/boards/*
1374     # If the PICO_BOARD_HEADER_DIRS environment variable is set, use that as well
1375
1376     loc = sdkPath / "src/boards/include/boards"
1377     boards=[]
1378     for x in Path(loc).iterdir():
1379         if x.suffix == '.h':
1380             boards.append(x.stem)
1381
1382     loc = os.getenv('PICO_BOARD_HEADER_DIRS')
1383
1384     if loc != None:
1385         for x in Path(loc).iterdir():
1386             if x.suffix == '.h':
1387                 boards.append(x.stem)
1388
1389     return boards
1390
1391 def DoEverything(parent, params):
1392
1393     if not os.path.exists(params['projectRoot']):
1394         if params['wantGUI'] and ENABLE_TK_GUI:
1395             mb.showerror('Raspberry Pi Pico Project Generator', 'Invalid project path. Select a valid path and try again')
1396             return
1397         else:
1398             print('Invalid project path')
1399             sys.exit(-1)
1400
1401     oldCWD = os.getcwd()
1402     os.chdir(params['projectRoot'])
1403
1404     # Create our project folder as subfolder
1405     os.makedirs(params['projectName'], exist_ok=True)
1406
1407     os.chdir(params['projectName'])
1408
1409     projectPath = params['projectRoot'] / params['projectName']
1410
1411     # First check if there is already a project in the folder
1412     # If there is we abort unless the overwrite flag it set
1413     if os.path.exists(CMAKELIST_FILENAME):
1414         if not params['wantOverwrite'] :
1415             if params['wantGUI'] and ENABLE_TK_GUI:
1416                 # We can ask the user if they want to overwrite
1417                 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')
1418                 if y != 'yes':
1419                     return
1420             else:
1421                 print('There already appears to be a project in this folder. Use the --overwrite option to overwrite the existing project')
1422                 sys.exit(-1)
1423
1424         # We should really confirm the user wants to overwrite
1425         #print('Are you sure you want to overwrite the existing project files? (y/N)')
1426         #c = input().split(" ")[0]
1427         #if c != 'y' and c != 'Y' :
1428         #    sys.exit(0)
1429
1430     # Copy the SDK finder cmake file to our project folder
1431     # Can be found here <PICO_SDK_PATH>/external/pico_sdk_import.cmake
1432     shutil.copyfile(params['sdkPath'] / 'external' / 'pico_sdk_import.cmake', projectPath / 'pico_sdk_import.cmake' )
1433
1434     if params['features']:
1435         features_and_examples = params['features'][:]
1436     else:
1437         features_and_examples= []
1438
1439     if params['wantExamples']:
1440         features_and_examples = list(stdlib_examples_list.keys()) + features_and_examples
1441
1442     GenerateMain('.', params['projectName'], features_and_examples, params['wantCPP'])
1443
1444     GenerateCMake('.', params)
1445
1446     # If we have any ancilliary files, copy them to our project folder
1447     # Currently only the picow with lwIP support needs an extra file, so just check that list
1448     for feat in features_and_examples:
1449         if feat in picow_options_list:
1450             if picow_options_list[feat][ANCILLARY_FILE] != "":
1451                 shutil.copy(sourcefolder + "/" + picow_options_list[feat][ANCILLARY_FILE], projectPath / picow_options_list[feat][ANCILLARY_FILE])
1452
1453     # Create a build folder, and run our cmake project build from it
1454     if not os.path.exists('build'):
1455         os.mkdir('build')
1456
1457     os.chdir('build')
1458
1459     # If we are overwriting a previous project, we should probably clear the folder, but that might delete something the users thinks is important, so
1460     # for the moment, just delete the CMakeCache.txt file as certain changes may need that to be recreated.
1461
1462     if os.path.exists(CMAKECACHE_FILENAME):
1463         os.remove(CMAKECACHE_FILENAME)
1464
1465     cpus = os.cpu_count()
1466     if cpus == None:
1467         cpus = 1
1468
1469     if isWindows:
1470         if shutil.which("ninja") or (params["ninjaPath"] != None and params["ninjaPath"] != ""):
1471             # When installing SDK version 1.5.0 on windows with installer pico-setup-windows-x64-standalone.exe, ninja is used 
1472             cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug -G Ninja ..'
1473             makeCmd = params['ninjaPath'] + ' '        
1474         else:
1475             # Everything else assume nmake
1476             cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug -G "NMake Makefiles" ..'
1477             makeCmd = 'nmake '
1478     else:
1479         # Ninja now works OK under Linux, so if installed use it by default. It's faster.
1480         if shutil.which("ninja") or (params["ninjaPath"] != None and params["ninjaPath"] != ""):
1481             cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug -G Ninja ..'
1482             makeCmd = params['ninjaPath'] + ' '
1483         else:
1484             cmakeCmd = params['cmakePath'] + ' -DCMAKE_BUILD_TYPE=Debug ..'
1485             makeCmd = 'make -j' + str(cpus)
1486
1487     if params['wantGUI'] and ENABLE_TK_GUI:
1488         RunCommandInWindow(parent, cmakeCmd)
1489     else:
1490         os.system(cmakeCmd)
1491
1492     if params['projects']:
1493         generateProjectFiles(
1494             projectPath, 
1495             params['projectName'], 
1496             params['sdkPath'], 
1497             params['projects'], 
1498             params['debugger'], 
1499             params["sdkVersion"], 
1500             params["toolchainVersion"], 
1501             params["ninjaPath"], 
1502             params["cmakePath"],
1503             params["customPython"])
1504
1505     if params['wantBuild']:
1506         if params['wantGUI'] and ENABLE_TK_GUI:
1507             RunCommandInWindow(parent, makeCmd)
1508         else:
1509             os.system(makeCmd)
1510             print('\nIf the application has built correctly, you can now transfer it to the Raspberry Pi Pico board')
1511
1512     os.chdir(oldCWD)
1513
1514
1515 ###################################################################################
1516 # main execution starteth here
1517
1518 sourcefolder = os.path.dirname(os.path.abspath(__file__))
1519
1520 args = ParseCommandLine()
1521
1522 if args.nouart:
1523     args.uart = False
1524
1525 if args.debugger > len(debugger_list) - 1:
1526     args.debugger = 0
1527
1528 # Check we have everything we need to compile etc
1529 c = CheckPrerequisites()
1530
1531 ## TODO Do both warnings in the same error message so user does have to keep coming back to find still more to do
1532
1533 if c == None:
1534     m = f'Unable to find the `{COMPILER_NAME}` compiler\n'
1535     m +='You will need to install an appropriate compiler to build a Raspberry Pi Pico project\n'
1536     m += 'See the Raspberry Pi Pico documentation for how to do this on your particular platform\n'
1537
1538     if args.gui and ENABLE_TK_GUI:
1539         RunWarning(m)
1540     else:
1541         print(m)
1542     sys.exit(-1)
1543
1544 if args.name == None and not args.gui and not args.list and not args.configs and not args.boardlist:
1545     print("No project name specfied\n")
1546     sys.exit(-1)
1547
1548 # Check if we were provided a compiler path, and override the default if so
1549 if args.cpath:
1550     compilerPath = Path(args.cpath)
1551 elif args.toolchainVersion:
1552     compilerPath = Path(propertiesToolchainPath(args.toolchainVersion)+"/bin/"+COMPILER_NAME)
1553 else:
1554     compilerPath = Path(c)
1555
1556 # load/parse any configuration dictionary we may have
1557 LoadConfigurations()
1558
1559 p = CheckSDKPath(args.gui)
1560
1561 if p == None:
1562     sys.exit(-1)
1563
1564 sdkPath = Path(p)
1565
1566 boardtype_list = LoadBoardTypes(sdkPath)
1567 boardtype_list.sort()
1568
1569 if args.gui and ENABLE_TK_GUI:
1570     RunGUI(sdkPath, args) # does not return, only exits
1571
1572 projectRoot = Path(os.getcwd()) if not args.projectRoot else Path(args.projectRoot)
1573
1574 if args.list or args.configs or args.boardlist:
1575     if args.list:
1576         print("Available project features:\n")
1577         for feat in features_list:
1578             print(feat.ljust(6), '\t', features_list[feat][GUI_TEXT])
1579         print('\n')
1580
1581     if args.configs:
1582         print("Available project configuration items:\n")
1583         for conf in configuration_dictionary:
1584             print(conf['name'].ljust(40), '\t', conf['description'])
1585         print('\n')
1586
1587     if args.boardlist:
1588         print("Available board types:\n")
1589         for board in boardtype_list:
1590             print(board)
1591         print('\n')
1592
1593     sys.exit(0)
1594 else :
1595     params={
1596         'sdkPath'       : sdkPath,
1597         'projectRoot'   : projectRoot,
1598         'projectName'   : args.name,
1599         'wantGUI'       : False,
1600         'wantOverwrite' : args.overwrite,
1601         'boardtype'     : args.boardtype,
1602         'wantBuild'     : args.build,
1603         'features'      : args.feature,
1604         'projects'      : args.project,
1605         'configs'       : (),
1606         'wantRunFromRAM': args.runFromRAM,
1607         'wantExamples'  : args.examples,
1608         'wantUART'      : args.uart,
1609         'wantUSB'       : args.usb,
1610         'wantCPP'       : args.cpp,
1611         'debugger'      : args.debugger,
1612         'exceptions'    : args.cppexceptions,
1613         'rtti'          : args.cpprtti,
1614         'ssid'          : '',
1615         'password'      : '',
1616         'sdkVersion'    : args.sdkVersion,
1617         'toolchainVersion': args.toolchainVersion,
1618         'ninjaPath'     : args.ninjaPath,
1619         'cmakePath'     : args.cmakePath,
1620         'customPython'  : args.customPython
1621         }
1622
1623     DoEverything(None, params)
1624     sys.exit(0)
This page took 0.121295 seconds and 4 git commands to generate.