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