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