From 2cae6a37de60131ac1b39a8be4a935d4b37a4dfa Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:11:44 +0100 Subject: [PATCH 01/13] Basic swift functionality Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/pico_project.py | 464 ++++++++++++++++++++++++++++---- src/utils/swiftUtil.mts | 18 ++ src/webview/activityBar.mts | 2 +- src/webview/newProjectPanel.mts | 46 ++++ web/main.js | 2 + 5 files changed, 485 insertions(+), 47 deletions(-) create mode 100644 src/utils/swiftUtil.mts diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 6a0d62b6..bdda129d 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -393,6 +393,265 @@ def GDB_NAME(): ], } +code_fragments_per_feature_swift = { + 'uart' : [ + ( + "// UART defines", + "// By default the stdout UART is `uart0`, so we will use the second one", + "let UART_ID = \"uart1\"", + "let BAUD_RATE = 115200", "", + "// Use pins 4 and 5 for UART1", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + "let UART_TX_PIN = 4", + "let UART_RX_PIN = 5" + ), + ( + "// Set up our UART", + "uart_init(UART_ID, BAUD_RATE)", + "// Set the TX and RX pins by using the function select on the GPIO", + "// Set datasheet for more information on function select", + "gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART)", + "gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART)", + "", + "// Use some the various UART functions to send out data", + "// In a default system, printf will also output via the default UART", + "", + # here should be following + # "// Send out a character without any conversions", + #"uart_putc_raw(UART_ID, 'A');", + #"", + #"// Send out a character but do CR/LF conversions", + #"uart_putc(UART_ID, 'B');", + # "", + "// Send out a string, with CR/LF conversions", + "uart_puts(UART_ID, \" Hello, UART!\\n\")", + "", + "// For more examples of UART use see https://github.com/raspberrypi/pico-examples/tree/master/uart" + ) + ], + 'spi' : [ + ( + "// SPI Constants", + "// We are going to use SPI 0, and allocate it to the following GPIO pins", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + "let SPI_PORT = \"spi0\"", + "let PIN_MISO = 16", + "let PIN_CS = 17", + "let PIN_SCK = 18", + "let PIN_MOSI = 19" + ), + ( + "// SPI initialisation. This example will use SPI at 1MHz.", + "spi_init(SPI_PORT, 1000*1000)", + "gpio_set_function(PIN_MISO, GPIO_FUNC_SPI)", + "gpio_set_function(PIN_CS, GPIO_FUNC_SIO)", + "gpio_set_function(PIN_SCK, GPIO_FUNC_SPI)", + "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI)", "", + "// Chip select is active-low, so we'll initialise it to a driven-high state", + "gpio_set_dir(PIN_CS, GPIO_OUT)", + "gpio_put(PIN_CS, 1)", + "// For more examples of SPI use see https://github.com/raspberrypi/pico-examples/tree/master/spi" + ) + ], + 'i2c' : [ + ( + "// I2C defines", + "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + "let I2C_PORT = \"i2c0\"", + "let I2C_SDA = 8", + "let I2C_SCL = 9", + ), + ( + "// I2C Initialisation. Using it at 400Khz.", + "i2c_init(I2C_PORT, 400*1000)","", + "gpio_set_function(I2C_SDA, GPIO_FUNC_I2C)", + "gpio_set_function(I2C_SCL, GPIO_FUNC_I2C)", + "gpio_pull_up(I2C_SDA)", + "gpio_pull_up(I2C_SCL)", + "// For more examples of I2C use see https://github.com/raspberrypi/pico-examples/tree/master/i2c", + ) + ], + "dma" : [ + ( + '// Data will be copied from src to dst', + 'let src = "Hello, world! (from DMA)"', + 'var dst = [UInt8](repeating: 0, count: src.count)', + ), + ( + '// Get a free channel, panic() if there are none', + 'let chan = dma_claim_unused_channel(true)', + '', + '// 8 bit transfers. Both read and write address increment after each', + '// transfer (each pointing to a location in src or dst respectively).', + '// No DREQ is selected, so the DMA transfers as fast as it can.', + '', + 'let c = dma_channel_get_default_config(chan)', + 'channel_config_set_transfer_data_size(&c, DMA_SIZE_8)', + 'channel_config_set_read_increment(&c, true)', + 'channel_config_set_write_increment(&c, true)', + '', + 'dma_channel_configure(', + ' chan, // Channel to be configured', + ' &c, // The configuration we just created', + ' dst, // The initial write address', + ' src, // The initial read address', + ' count_of(src), // Number of transfers; in this case each is 1 byte.', + ' true // Start immediately.', + ')', + '', + '// We could choose to go and do something else whilst the DMA is doing its', + '// thing. In this case the processor has nothing else to do, so we just', + '// wait for the DMA to finish.', + 'dma_channel_wait_for_finish_blocking(chan)', + '', + '// The DMA has now copied our text from the transmit buffer (src) to the', + '// receive buffer (dst), so we can print it out from there.', + 'puts(dst)', + ) + ], + + "pio" : [ + ( + '#include \"blink.pio.h\"', + 'static func blink_pin_forever(pio: PIO, sm: uint, offset: uint, pin: uint, freq: uint) {', + ' blink_program_init(pio, sm, offset, pin)', + ' pio_sm_set_enabled(pio, sm, true)', + '', + ' printf("Blinking pin %d at %d Hz\\n", pin, freq)', + '', + ' // PIO counter program takes 3 more cycles in total than we pass as', + ' // input (wait for n + 1; mov; jmp)', + ' pio.txf[sm] = (125000000 / (2 * freq)) - 3', + '}', + ), + ( + '// PIO Blinking example', + 'let pio = pio0', + 'let offset = pio_add_program(pio, &blink_program)', + 'printf("Loaded program at %d\\n", offset)', + '', + '#ifdef PICO_DEFAULT_LED_PIN', + 'blink_pin_forever(pio, 0, offset, PICO_DEFAULT_LED_PIN, 3)', + '#else', + 'blink_pin_forever(pio, 0, offset, 6, 3)', + '#endif', + '// For more pio examples see https://github.com/raspberrypi/pico-examples/tree/master/pio', + ) + ], + + "clocks" : [ + (), + ( + 'printf("System Clock Frequency is %d Hz\\n", clock_get_hz(clk_sys))', + 'printf("USB Clock Frequency is %d Hz\\n", clock_get_hz(clk_usb))', + '// For more examples of clocks use see https://github.com/raspberrypi/pico-examples/tree/master/clocks', + ) + ], + + "gpio" : [ + ( + "// GPIO constants", + "// Example uses GPIO 2", + "let GPIO = 2" + ), + ( + "// GPIO initialisation.", + "// We will make this GPIO an input, and pull it up by default", + "gpio_init(GPIO)", + "gpio_set_dir(GPIO, GPIO_IN)", + "gpio_pull_up(GPIO)", + "// See https://github.com/raspberrypi/pico-examples/tree/master/gpio for other gpio examples, including using interrupts", + ) + ], + "interp" :[ + (), + ( + "// Interpolator example code", + "interp_config cfg = interp_default_config()", + "// Now use the various interpolator library functions for your use case", + "// e.g. interp_config_clamp(&cfg, true)", + "// interp_config_shift(&cfg, 2)", + "// Then set the config ", + "interp_set_config(interp0, 0, &cfg)", + "// For examples of interpolator use see https://github.com/raspberrypi/pico-examples/tree/master/interp" + ) + ], + + "timer" : [ + ( + "func alarm_callback(id: alarm_id_t, user_data: void): int64_t {", + " // Put your timeout handler code in here", + " return 0", + "}" + ), + ( + "// Timer example code - This example fires off the callback after 2000ms", + "add_alarm_in_ms(2000, alarm_callback, NULL, false)", + "// For more examples of timer use see https://github.com/raspberrypi/pico-examples/tree/master/timer" + ) + ], + + "watchdog":[ (), + ( + "// Watchdog example code", + "if watchdog_caused_reboot() {", + " printf(\"Rebooted by Watchdog!\\n\")", + " // Whatever action you may take if a watchdog caused a reboot", + "}","", + "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot", + "// second arg is pause on debug which means the watchdog will pause when stepping through code", + "watchdog_enable(100, 1)","", + "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot", + "watchdog_update()", + ) + ], + + "div" : [ (), + ( + "// Example of using the HW divider. The pico_divider library provides a more user friendly set of APIs ", + "// over the divider (and support for 64 bit divides), and of course by default regular C language integer", + "// divisions are redirected thru that library, meaning you can just use C level `/` and `%` operators and", + "// gain the benefits of the fast hardware divider.", + "let dividend = 123456", + "let divisor = -321", + "// This is the recommended signed fast divider for general use.", + "let result = hw_divider_divmod_s32(dividend, divisor)", + "printf(\"%d/%d = %d remainder %d\\n\", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result))", + "// This is the recommended unsigned fast divider for general use.", + "let udividend = 123456", + "let udivisor = 321", + "let uresult = hw_divider_divmod_u32(udividend, udivisor)", + "printf(\"%d/%d = %d remainder %d\\n\", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult))", + "// See https://github.com/raspberrypi/pico-examples/tree/master/divider for more complex use" + ) + ], + + "picow_led":[ (), + ( + "// Example to turn on the Pico W LED", + "cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1)" + ) + ], + + "picow_wifi":[ (), + ( + '// Enable wifi station', + 'cyw43_arch_enable_sta_mode()\n', + 'printf("Connecting to Wi-Fi...\\n")', + 'if cyw43_arch_wifi_connect_timeout_ms("Your Wi-Fi SSID", "Your Wi-Fi Password", CYW43_AUTH_WPA2_AES_PSK, 30000) {', + ' printf("failed to connect.\\n")', + ' return 1', + '} else {', + ' printf("Connected.\\n")', + ' // Read the ip address in a human readable way', + ' let ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr)', + ' printf("IP address %d.%d.%d.%d\\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3])', + '}', + ) + ] +} + # Add wifi example for poll and background modes code_fragments_per_feature["picow_poll"] = code_fragments_per_feature["picow_wifi"] code_fragments_per_feature["picow_background"] = code_fragments_per_feature[ @@ -645,22 +904,35 @@ def ParseCommandLine(): "--userHome", help="Full path to user's home directory", ) + parser.add_argument("-swift", "--swift", action='store_true', help="Use Swift as the language for the project") return parser.parse_args() -def GenerateMain(folder, projectName, features, cpp, wantEntryProjName): +def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): executableName = projectName if wantEntryProjName else "main" if cpp: - filename = Path(folder) / (executableName + ".cpp") + filename = Path(folder) / (executableName + '.cpp') + elif swift: + filename = Path(folder) / (executableName + '.swift') else: filename = Path(folder) / (executableName + ".c") - file = open(filename, "w") + file = open(filename, 'w') + bridging_file = None - main = "#include \n" '#include "pico/stdlib.h"\n' - file.write(main) + if swift: + # write bridging header + bridging_file = open(Path(folder) / "BridgingHeader.h", 'w') + bridging_file.write("#pragma once\n\n") + bridging_file.write("#include \n") + bridging_file.write("#include \"pico/stdlib.h\"\n") + else: + main = ('#include \n' + '#include "pico/stdlib.h"\n' + ) + file.write(main) if features: @@ -670,64 +942,114 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName): if len(features_list[feat][H_FILE]) == 0: continue o = f'#include "{features_list[feat][H_FILE]}"\n' - file.write(o) - if feat in stdlib_examples_list: + if swift: + bridging_file.write(o) + else: + file.write(o) + if (feat in stdlib_examples_list): if len(stdlib_examples_list[feat][H_FILE]) == 0: continue o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n' - file.write(o) - if feat in picow_options_list: + if swift: + bridging_file.write(o) + else: + file.write(o) + if (feat in picow_options_list): if len(picow_options_list[feat][H_FILE]) == 0: continue o = f'#include "{picow_options_list[feat][H_FILE]}"\n' - file.write(o) + if swift: + bridging_file.write(o) + else: + file.write(o) file.write("\n") + frags = (code_fragments_per_feature if not swift else code_fragments_per_feature_swift) + # Add any defines for feat in features: - if feat in code_fragments_per_feature: - for s in code_fragments_per_feature[feat][DEFINES]: + if (feat in frags): + for s in frags[feat][DEFINES]: + if swift and s.startswith("#include"): + bridging_file.write(s) + bridging_file.write('\n') file.write(s) file.write("\n") file.write("\n") - main = "\n\n" "int main()\n" "{\n" " stdio_init_all();\n\n" + main = None + if swift: + main = ('\n\n' + '@main\n' + 'struct Main {\n' + ' \n' + ' static func main() {\n' + ' stdio_init_all();\n\n' + ) + else: + main = ('\n\n' + 'int main()\n' + '{\n' + ' stdio_init_all();\n\n' + ) if any([feat in picow_options_list and feat != "picow_none" for feat in features]): - main += ( - " // Initialise the Wi-Fi chip\n" - " if (cyw43_arch_init()) {\n" + if swift: + main += ( + ' // Initialise the Wi-Fi chip\n' + ' if cyw43_arch_init() {\n' + ' printf("Wi-Fi init failed\\n")\n' + ' return -1\n' + ' }\n\n') + else: + main += ( + ' // Initialise the Wi-Fi chip\n' + ' if (cyw43_arch_init()) {\n' ' printf("Wi-Fi init failed\\n");\n' " return -1;\n" " }\n\n" ) if features: + frags = (code_fragments_per_feature if not swift else code_fragments_per_feature_swift) # Add any initialisers indent = 4 for feat in features: - if feat in code_fragments_per_feature: - for s in code_fragments_per_feature[feat][INITIALISERS]: - main += " " * indent + if (feat in frags): + for s in frags[feat][INITIALISERS]: + main += (" " * indent) main += s main += "\n" main += "\n" - main += ( - " while (true) {\n" - ' printf("Hello, world!\\n");\n' - " sleep_ms(1000);\n" - " }\n" - "}\n" - ) - + if swift: + main += (' while true {\n' + ' printf("Hello, world!\\n")\n' + ' sleep_ms(1000)\n' + ' }\n' + ' }\n' + '}\n' + ) + else: + main += (' while (true) {\n' + ' printf("Hello, world!\\n");\n' + ' sleep_ms(1000);\n' + ' }\n' + '}\n' + ) + file.write(main) + bridging_file.close() file.close() def GenerateCMake(folder, params): + if (params["wantConvert"] or params['wantCPP']) and params["useSwift"]: + print("Invalid combination of options - Swift and C++ are not compatible and Swift can't be used when converting - exiting") + exit(20) + filename = Path(folder) / CMAKELIST_FILENAME projectName = params["projectName"] board_type = params["boardtype"] @@ -760,11 +1082,11 @@ def GenerateCMake(folder, params): ) cmake_header2 = ( - f'set(PICO_BOARD {board_type} CACHE STRING "Board type")\n\n' - "# Pull in Raspberry Pi Pico SDK (must be before project)\n" - "include(pico_sdk_import.cmake)\n\n" - f"project({projectName} C CXX ASM)\n" - ) + f"set(PICO_BOARD {board_type} CACHE STRING \"Board type\")\n\n" + "# Pull in Raspberry Pi Pico SDK (must be before project)\n" + "include(pico_sdk_import.cmake)\n\n" + f"project({projectName} {"" if params["useSwift"] else "C CXX ASM"})\n" + ) cmake_header3 = ( "\n# Initialise the Raspberry Pi Pico SDK\n" @@ -861,6 +1183,28 @@ def GenerateCMake(folder, params): file.write(cmake_header3) + if params["useSwift"]: + cmake_if_apple = ( + "if(APPLE)\n" + " execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + "else()\n" + " execute_process(COMMAND which swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + "endif()\n" + ) + cmake_swift_target = ( + "set(SWIFT_TARGET \"armv6m-none-none-eabi\") # RP2040\n\n" + "if(PICO_PLATFORM STREQUAL \"rp2350-arm-s\")\n" + " message(STATUS \"PICO_PLATFORM is set to rp2350-arm-s, using armv7em\")\n" + " set(SWIFT_TARGET \"armv7em-none-none-eabi\")\n" + "elseif(PICO_PLATFORM STREQUAL \"rp2350-riscv\")\n" + " # Untested, gives PICO-SDK errors when building\n" + " message(WARNING \"PICO_PLATFORM is set to rp2350-riscv, using riscv32 (untested). It is recommended to use rp2350-arm-s.\")\n" + " set(SWIFT_TARGET \"riscv32-none-none-eabi\")\n" + "endif()\n" + ) + file.write(cmake_if_apple) + file.write(cmake_swift_target) + # add the preprocessor defines for overall configuration if params["configs"]: file.write("# Add any PICO_CONFIG entries specified in the Advanced settings\n") @@ -874,8 +1218,32 @@ def GenerateCMake(folder, params): entry_point_file_name = projectName if params["wantEntryProjName"] else "main" - if params["wantCPP"]: - file.write(f"add_executable({projectName} {entry_point_file_name}.cpp )\n\n") + if params['wantCPP']: + file.write(f'add_executable({projectName} {entry_point_file_name}.cpp )\n\n') + elif params['useSwift']: + file.write(f'add_executable({projectName})\n\n') + + main_file_name = f"{entry_point_file_name}.swift" + cmake_custom_swift_command = ( + "add_custom_command(\n" + " OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n" + " COMMAND\n" + " ${SWIFTC}\n" + " -target ${SWIFT_TARGET} -Xcc -mfloat-abi=soft -Xcc -fshort-enums\n" + " -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library\n" + " $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" + " $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" + " -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" + f" ${{CMAKE_CURRENT_LIST_DIR}}/{entry_point_file_name}.swift\n" + " -c -o ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n" + " DEPENDS\n" + " ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" + f" ${{CMAKE_CURRENT_LIST_DIR}}/{entry_point_file_name}.swift\n" + ")\n" + ) + file.write(cmake_custom_swift_command) + + file.write(f"add_custom_target({projectName}-swiftcode DEPENDS ${{CMAKE_CURRENT_BINARY_DIR}}/_swiftcode.o)\n\n") else: file.write(f"add_executable({projectName} {entry_point_file_name}.c )\n\n") @@ -927,7 +1295,9 @@ def GenerateCMake(folder, params): file.write("# Add the standard library to the build\n") file.write(f"target_link_libraries({projectName}\n") file.write(" " + STANDARD_LIBRARIES) - file.write(")\n\n") + if params["useSwift"]: + file.write(" ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n") + file.write(')\n\n') # Standard include directories file.write("# Add the standard include files to the build\n") @@ -935,6 +1305,9 @@ def GenerateCMake(folder, params): file.write(" " + "${CMAKE_CURRENT_LIST_DIR}\n") file.write(")\n\n") + if params["useSwift"]: + file.write(f'add_dependencies({projectName} {projectName}-swiftcode)\n') + # Selected libraries/features if params["features"]: file.write("# Add any user requested libraries\n") @@ -964,6 +1337,7 @@ def generateProjectFiles( cmakePath, openOCDVersion, useCmakeTools, + useSwift, ): oldCWD = os.getcwd() @@ -974,7 +1348,7 @@ def generateProjectFiles( if not os.path.isfile(".gitignore"): file = open(".gitignore", "w") file.write("build\n") - file.write("!.vscode/*\n") + file.write(".DS_Store\n") file.close() debugger = debugger_config_list[debugger] @@ -1191,6 +1565,9 @@ def generateProjectFiles( ] } + if useSwift: + extensions["recommendations"].append("sswg.swift-lang") + tasks = f"""{{ "version": "2.0.0", "tasks": [ @@ -1369,14 +1746,8 @@ def DoEverything(params): if "uart" not in features_and_examples: features_and_examples.append("uart") - if not (params["wantConvert"]): - GenerateMain( - projectPath, - params["projectName"], - features_and_examples, - params["wantCPP"], - params["wantEntryProjName"], - ) + if not (params['wantConvert']): + GenerateMain(projectPath, params['projectName'], features_and_examples, params['wantCPP'], params['wantEntryProjName'], params['useSwift']) # If we have any ancillary files, copy them to our project folder # Currently only the picow with lwIP support needs an extra file, so just check that list @@ -1430,8 +1801,8 @@ def DoEverything(params): params["ninjaPath"], params["cmakePath"], params["openOCDVersion"], - params["useCmakeTools"], - ) + params['useCmakeTools'], + params['useSwift']) os.chdir(oldCWD) @@ -1507,6 +1878,7 @@ def DoEverything(params): "openOCDVersion": args.openOCDVersion, "exampleLibs": args.exampleLibs if args.exampleLibs is not None else [], "useCmakeTools": args.useCmakeTools, + "useSwift": args.swift, } DoEverything(params) diff --git a/src/utils/swiftUtil.mts b/src/utils/swiftUtil.mts new file mode 100644 index 00000000..b2d7745a --- /dev/null +++ b/src/utils/swiftUtil.mts @@ -0,0 +1,18 @@ +import { exec } from "child_process"; +import { promisify } from "util"; + +const execAsync = promisify(exec); + +export default async function checkSwiftRequirements(): Promise { + return true; + // check if swift is installed + try { + await execAsync("swift --version"); + + // TODO: check swift version + + return true; + } catch (error) { + return false; + } +} diff --git a/src/webview/activityBar.mts b/src/webview/activityBar.mts index 5edc283e..8b68a23c 100644 --- a/src/webview/activityBar.mts +++ b/src/webview/activityBar.mts @@ -42,7 +42,7 @@ const COMMON_COMMANDS_PARENT_LABEL = "General"; const PROJECT_COMMANDS_PARENT_LABEL = "Project"; const DOCUMENTATION_COMMANDS_PARENT_LABEL = "Documentation"; -const NEW_C_CPP_PROJECT_LABEL = "New C/C++ Project"; +const NEW_C_CPP_PROJECT_LABEL = "New Pico Project"; const NEW_MICROPYTHON_PROJECT_LABEL = "New MicroPython Project"; const IMPORT_PROJECT_LABEL = "Import Project"; const EXAMPLE_PROJECT_LABEL = "New Project From Example"; diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index e2333733..6d24f203 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -13,6 +13,7 @@ import { ColorThemeKind, ProgressLocation, type Progress, + env, } from "vscode"; import { type ExecOptions, exec } from "child_process"; import { HOME_VAR } from "../settings.mjs"; @@ -61,6 +62,7 @@ import { import { unknownErrorToString } from "../utils/errorHelper.mjs"; import type { Progress as GotProgress } from "got"; import findPython, { showPythonNotFoundError } from "../utils/pythonHelper.mjs"; +import checkSwiftRequirements from "../utils/swiftUtil.mjs"; export const NINJA_AUTO_INSTALL_DISABLED = false; // process.platform === "linux" && process.arch === "arm64"; @@ -114,6 +116,7 @@ interface SubmitMessageValue extends ImportProjectMessageValue { runFromRAM: boolean; entryPointProjectName: boolean; cpp: boolean; + swift: boolean; cppRtti: boolean; cppExceptions: boolean; } @@ -160,6 +163,7 @@ enum CodeOption { runFromRAM = "Run the program from RAM rather than flash", entryPointProjectName = "Use project name as entry point file name", cpp = "Generate C++ code", + swift = "Generate Swift code", cppRtti = "Enable C++ RTTI (Uses more memory)", cppExceptions = "Enable C++ exceptions (Uses more memory)", } @@ -244,6 +248,8 @@ function enumToParam( return "-e"; case CodeOption.cpp: return "-cpp"; + case CodeOption.swift: + return "-swift"; case CodeOption.cppRtti: return "-cpprtti"; case CodeOption.cppExceptions: @@ -1258,6 +1264,39 @@ export class NewProjectPanel { if (example === undefined && !this._isProjectImport) { const theData = data as SubmitMessageValue; + if (theData.swift) { + const swiftResult = await window.withProgress( + { + location: ProgressLocation.Notification, + title: "Checking Swift installation", + cancellable: false, + }, + async () => checkSwiftRequirements() + ); + + if (!swiftResult) { + progress.report({ + message: "Failed", + increment: 100, + }); + void window + .showErrorMessage( + "Swift is required for Swift project generation. Please install Swift.", + "Install" + ) + .then(selected => { + if (selected) { + env.openExternal( + // TODO: check url + Uri.parse("https://swift.org/download/#releases") + ); + } + }); + + return; + } + } + await this._settings.setEntryPointNamingPref( theData.entryPointProjectName ); @@ -1290,6 +1329,7 @@ export class NewProjectPanel { ? CodeOption.entryPointProjectName : null, theData.cpp ? CodeOption.cpp : null, + theData.swift ? CodeOption.swift : null, theData.cppRtti ? CodeOption.cppRtti : null, theData.cppExceptions ? CodeOption.cppExceptions : null, ].filter(option => option !== null), @@ -2150,6 +2190,12 @@ export class NewProjectPanel { +
  • +
    + + +
    +
  • diff --git a/web/main.js b/web/main.js index 4008a192..0bd912d1 100644 --- a/web/main.js +++ b/web/main.js @@ -318,6 +318,7 @@ var exampleSupportedBoards = []; const runFromRAMCodeGen = document.getElementById('run-from-ram-code-gen-cblist').checked; const nameEntryPointProjectName = document.getElementById('entry-project-name-code-gen-cblist').checked; const cppCodeGen = document.getElementById('cpp-code-gen-cblist').checked; + const swiftCodeGen = document.getElementById('swift-code-gen-cblist').checked; const cppRttiCodeGen = document.getElementById('cpp-rtti-code-gen-cblist').checked; const cppExceptionsCodeGen = document.getElementById('cpp-exceptions-code-gen-cblist').checked; @@ -358,6 +359,7 @@ var exampleSupportedBoards = []; runFromRAM: runFromRAMCodeGen, entryPointProjectName: nameEntryPointProjectName, cpp: cppCodeGen, + swift: swiftCodeGen, cppRtti: cppRttiCodeGen, cppExceptions: cppExceptionsCodeGen, From b4aa9076f4d4c215fa80d872324b8255af327608 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:58:15 +0100 Subject: [PATCH 02/13] Fix cmake on linux and macOS Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/pico_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pico_project.py b/scripts/pico_project.py index bdda129d..53d7e2f0 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -1231,7 +1231,7 @@ def GenerateCMake(folder, params): " ${SWIFTC}\n" " -target ${SWIFT_TARGET} -Xcc -mfloat-abi=soft -Xcc -fshort-enums\n" " -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library\n" - " $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" + f" $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" f" ${{CMAKE_CURRENT_LIST_DIR}}/{entry_point_file_name}.swift\n" From 651531b37558c75704f321206760fe05857d681f Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:17:33 +0100 Subject: [PATCH 03/13] Update some swift examples and cmake config Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/pico_project.py | 738 ++++++++++++++++++++++------------------ 1 file changed, 400 insertions(+), 338 deletions(-) diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 53d7e2f0..33b6ab15 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -394,262 +394,259 @@ def GDB_NAME(): } code_fragments_per_feature_swift = { - 'uart' : [ - ( - "// UART defines", - "// By default the stdout UART is `uart0`, so we will use the second one", - "let UART_ID = \"uart1\"", - "let BAUD_RATE = 115200", "", - "// Use pins 4 and 5 for UART1", - "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - "let UART_TX_PIN = 4", - "let UART_RX_PIN = 5" - ), - ( - "// Set up our UART", - "uart_init(UART_ID, BAUD_RATE)", - "// Set the TX and RX pins by using the function select on the GPIO", - "// Set datasheet for more information on function select", - "gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART)", - "gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART)", - "", - "// Use some the various UART functions to send out data", - "// In a default system, printf will also output via the default UART", - "", - # here should be following - # "// Send out a character without any conversions", - #"uart_putc_raw(UART_ID, 'A');", - #"", - #"// Send out a character but do CR/LF conversions", - #"uart_putc(UART_ID, 'B');", - # "", - "// Send out a string, with CR/LF conversions", - "uart_puts(UART_ID, \" Hello, UART!\\n\")", - "", - "// For more examples of UART use see https://github.com/raspberrypi/pico-examples/tree/master/uart" - ) - ], - 'spi' : [ - ( - "// SPI Constants", - "// We are going to use SPI 0, and allocate it to the following GPIO pins", - "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - "let SPI_PORT = \"spi0\"", - "let PIN_MISO = 16", - "let PIN_CS = 17", - "let PIN_SCK = 18", - "let PIN_MOSI = 19" - ), - ( - "// SPI initialisation. This example will use SPI at 1MHz.", - "spi_init(SPI_PORT, 1000*1000)", - "gpio_set_function(PIN_MISO, GPIO_FUNC_SPI)", - "gpio_set_function(PIN_CS, GPIO_FUNC_SIO)", - "gpio_set_function(PIN_SCK, GPIO_FUNC_SPI)", - "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI)", "", - "// Chip select is active-low, so we'll initialise it to a driven-high state", - "gpio_set_dir(PIN_CS, GPIO_OUT)", - "gpio_put(PIN_CS, 1)", - "// For more examples of SPI use see https://github.com/raspberrypi/pico-examples/tree/master/spi" - ) - ], - 'i2c' : [ - ( - "// I2C defines", - "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.", - "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - "let I2C_PORT = \"i2c0\"", - "let I2C_SDA = 8", - "let I2C_SCL = 9", - ), - ( - "// I2C Initialisation. Using it at 400Khz.", - "i2c_init(I2C_PORT, 400*1000)","", - "gpio_set_function(I2C_SDA, GPIO_FUNC_I2C)", - "gpio_set_function(I2C_SCL, GPIO_FUNC_I2C)", - "gpio_pull_up(I2C_SDA)", - "gpio_pull_up(I2C_SCL)", - "// For more examples of I2C use see https://github.com/raspberrypi/pico-examples/tree/master/i2c", - ) - ], - "dma" : [ - ( - '// Data will be copied from src to dst', - 'let src = "Hello, world! (from DMA)"', - 'var dst = [UInt8](repeating: 0, count: src.count)', - ), - ( - '// Get a free channel, panic() if there are none', - 'let chan = dma_claim_unused_channel(true)', - '', - '// 8 bit transfers. Both read and write address increment after each', - '// transfer (each pointing to a location in src or dst respectively).', - '// No DREQ is selected, so the DMA transfers as fast as it can.', - '', - 'let c = dma_channel_get_default_config(chan)', - 'channel_config_set_transfer_data_size(&c, DMA_SIZE_8)', - 'channel_config_set_read_increment(&c, true)', - 'channel_config_set_write_increment(&c, true)', - '', - 'dma_channel_configure(', - ' chan, // Channel to be configured', - ' &c, // The configuration we just created', - ' dst, // The initial write address', - ' src, // The initial read address', - ' count_of(src), // Number of transfers; in this case each is 1 byte.', - ' true // Start immediately.', - ')', - '', - '// We could choose to go and do something else whilst the DMA is doing its', - '// thing. In this case the processor has nothing else to do, so we just', - '// wait for the DMA to finish.', - 'dma_channel_wait_for_finish_blocking(chan)', - '', - '// The DMA has now copied our text from the transmit buffer (src) to the', - '// receive buffer (dst), so we can print it out from there.', - 'puts(dst)', - ) - ], - - "pio" : [ - ( - '#include \"blink.pio.h\"', - 'static func blink_pin_forever(pio: PIO, sm: uint, offset: uint, pin: uint, freq: uint) {', - ' blink_program_init(pio, sm, offset, pin)', - ' pio_sm_set_enabled(pio, sm, true)', - '', - ' printf("Blinking pin %d at %d Hz\\n", pin, freq)', - '', - ' // PIO counter program takes 3 more cycles in total than we pass as', - ' // input (wait for n + 1; mov; jmp)', - ' pio.txf[sm] = (125000000 / (2 * freq)) - 3', - '}', - ), - ( - '// PIO Blinking example', - 'let pio = pio0', - 'let offset = pio_add_program(pio, &blink_program)', - 'printf("Loaded program at %d\\n", offset)', - '', - '#ifdef PICO_DEFAULT_LED_PIN', - 'blink_pin_forever(pio, 0, offset, PICO_DEFAULT_LED_PIN, 3)', - '#else', - 'blink_pin_forever(pio, 0, offset, 6, 3)', - '#endif', - '// For more pio examples see https://github.com/raspberrypi/pico-examples/tree/master/pio', - ) - ], - - "clocks" : [ - (), - ( - 'printf("System Clock Frequency is %d Hz\\n", clock_get_hz(clk_sys))', - 'printf("USB Clock Frequency is %d Hz\\n", clock_get_hz(clk_usb))', - '// For more examples of clocks use see https://github.com/raspberrypi/pico-examples/tree/master/clocks', - ) - ], - - "gpio" : [ - ( - "// GPIO constants", - "// Example uses GPIO 2", - "let GPIO = 2" - ), - ( - "// GPIO initialisation.", - "// We will make this GPIO an input, and pull it up by default", - "gpio_init(GPIO)", - "gpio_set_dir(GPIO, GPIO_IN)", - "gpio_pull_up(GPIO)", - "// See https://github.com/raspberrypi/pico-examples/tree/master/gpio for other gpio examples, including using interrupts", - ) - ], - "interp" :[ - (), - ( - "// Interpolator example code", - "interp_config cfg = interp_default_config()", - "// Now use the various interpolator library functions for your use case", - "// e.g. interp_config_clamp(&cfg, true)", - "// interp_config_shift(&cfg, 2)", - "// Then set the config ", - "interp_set_config(interp0, 0, &cfg)", - "// For examples of interpolator use see https://github.com/raspberrypi/pico-examples/tree/master/interp" - ) - ], - - "timer" : [ - ( - "func alarm_callback(id: alarm_id_t, user_data: void): int64_t {", - " // Put your timeout handler code in here", - " return 0", - "}" - ), - ( - "// Timer example code - This example fires off the callback after 2000ms", - "add_alarm_in_ms(2000, alarm_callback, NULL, false)", - "// For more examples of timer use see https://github.com/raspberrypi/pico-examples/tree/master/timer" - ) - ], - - "watchdog":[ (), - ( - "// Watchdog example code", - "if watchdog_caused_reboot() {", - " printf(\"Rebooted by Watchdog!\\n\")", - " // Whatever action you may take if a watchdog caused a reboot", - "}","", - "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot", - "// second arg is pause on debug which means the watchdog will pause when stepping through code", - "watchdog_enable(100, 1)","", - "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot", - "watchdog_update()", - ) - ], - - "div" : [ (), - ( - "// Example of using the HW divider. The pico_divider library provides a more user friendly set of APIs ", - "// over the divider (and support for 64 bit divides), and of course by default regular C language integer", - "// divisions are redirected thru that library, meaning you can just use C level `/` and `%` operators and", - "// gain the benefits of the fast hardware divider.", - "let dividend = 123456", - "let divisor = -321", - "// This is the recommended signed fast divider for general use.", - "let result = hw_divider_divmod_s32(dividend, divisor)", - "printf(\"%d/%d = %d remainder %d\\n\", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result))", - "// This is the recommended unsigned fast divider for general use.", - "let udividend = 123456", - "let udivisor = 321", - "let uresult = hw_divider_divmod_u32(udividend, udivisor)", - "printf(\"%d/%d = %d remainder %d\\n\", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult))", - "// See https://github.com/raspberrypi/pico-examples/tree/master/divider for more complex use" - ) - ], - - "picow_led":[ (), - ( - "// Example to turn on the Pico W LED", - "cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1)" - ) - ], - - "picow_wifi":[ (), - ( - '// Enable wifi station', - 'cyw43_arch_enable_sta_mode()\n', - 'printf("Connecting to Wi-Fi...\\n")', - 'if cyw43_arch_wifi_connect_timeout_ms("Your Wi-Fi SSID", "Your Wi-Fi Password", CYW43_AUTH_WPA2_AES_PSK, 30000) {', - ' printf("failed to connect.\\n")', - ' return 1', - '} else {', - ' printf("Connected.\\n")', - ' // Read the ip address in a human readable way', - ' let ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr)', - ' printf("IP address %d.%d.%d.%d\\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3])', - '}', - ) - ] + "uart": [ + ( + "// UART defines", + "// By default the stdout UART is `uart0`, so we will use the second one", + 'let UART_ID = "uart1"', + "let BAUD_RATE = 115200", + "", + "// Use pins 4 and 5 for UART1", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + "let UART_TX_PIN = 4", + "let UART_RX_PIN = 5", + ), + ( + "// Set up our UART", + "uart_init(UART_ID, BAUD_RATE)", + "// Set the TX and RX pins by using the function select on the GPIO", + "// Set datasheet for more information on function select", + "gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART)", + "gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART)", + "", + "// Use some the various UART functions to send out data", + "// In a default system, printf will also output via the default UART", + "", + # here should be following + # "// Send out a character without any conversions", + # "uart_putc_raw(UART_ID, 'A');", + # "", + # "// Send out a character but do CR/LF conversions", + # "uart_putc(UART_ID, 'B');", + # "", + "// Send out a string, with CR/LF conversions", + 'uart_puts(UART_ID, " Hello, UART!\\n")', + "", + "// For more examples of UART use see https://github.com/raspberrypi/pico-examples/tree/master/uart", + ), + ], + "spi": [ + ( + "// SPI Constants", + "// We are going to use SPI 0, and allocate it to the following GPIO pins", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + 'let SPI_PORT = "spi0"', + "let PIN_MISO = 16", + "let PIN_CS = 17", + "let PIN_SCK = 18", + "let PIN_MOSI = 19", + ), + ( + "// SPI initialisation. This example will use SPI at 1MHz.", + "spi_init(SPI_PORT, 1000*1000)", + "gpio_set_function(PIN_MISO, GPIO_FUNC_SPI)", + "gpio_set_function(PIN_CS, GPIO_FUNC_SIO)", + "gpio_set_function(PIN_SCK, GPIO_FUNC_SPI)", + "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI)", + "", + "// Chip select is active-low, so we'll initialise it to a driven-high state", + "gpio_set_dir(PIN_CS, GPIO_OUT)", + "gpio_put(PIN_CS, 1)", + "// For more examples of SPI use see https://github.com/raspberrypi/pico-examples/tree/master/spi", + ), + ], + "i2c": [ + ( + "// I2C defines", + "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.", + "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", + 'let I2C_PORT = "i2c0"', + "let I2C_SDA = 8", + "let I2C_SCL = 9", + ), + ( + "// I2C Initialisation. Using it at 400Khz.", + "i2c_init(I2C_PORT, 400*1000)", + "", + "gpio_set_function(I2C_SDA, GPIO_FUNC_I2C)", + "gpio_set_function(I2C_SCL, GPIO_FUNC_I2C)", + "gpio_pull_up(I2C_SDA)", + "gpio_pull_up(I2C_SCL)", + "// For more examples of I2C use see https://github.com/raspberrypi/pico-examples/tree/master/i2c", + ), + ], + "dma": [ + ( + "// Data will be copied from src to dst", + 'let src = "Hello, world! (from DMA)"', + "var dst = [UInt8](repeating: 0, count: src.count)", + ), + ( + "// Get a free channel, panic() if there are none", + "let chan = dma_claim_unused_channel(true)", + "", + "// 8 bit transfers. Both read and write address increment after each", + "// transfer (each pointing to a location in src or dst respectively).", + "// No DREQ is selected, so the DMA transfers as fast as it can.", + "", + "let c = dma_channel_get_default_config(chan)", + "channel_config_set_transfer_data_size(&c, DMA_SIZE_8)", + "channel_config_set_read_increment(&c, true)", + "channel_config_set_write_increment(&c, true)", + "", + "dma_channel_configure(", + " chan, // Channel to be configured", + " &c, // The configuration we just created", + " dst, // The initial write address", + " src, // The initial read address", + " count_of(src), // Number of transfers; in this case each is 1 byte.", + " true // Start immediately.", + ")", + "", + "// We could choose to go and do something else whilst the DMA is doing its", + "// thing. In this case the processor has nothing else to do, so we just", + "// wait for the DMA to finish.", + "dma_channel_wait_for_finish_blocking(chan)", + "", + "// The DMA has now copied our text from the transmit buffer (src) to the", + "// receive buffer (dst), so we can print it out from there.", + "puts(dst)", + ), + ], + "pio": [ + ( + '#include "blink.pio.h"', + "static func blink_pin_forever(pio: PIO, sm: uint, offset: uint, pin: uint, freq: uint) {", + " blink_program_init(pio, sm, offset, pin)", + " pio_sm_set_enabled(pio, sm, true)", + "", + ' printf("Blinking pin %d at %d Hz\\n", pin, freq)', + "", + " // PIO counter program takes 3 more cycles in total than we pass as", + " // input (wait for n + 1; mov; jmp)", + " pio.txf[sm] = (125000000 / (2 * freq)) - 3", + "}", + ), + ( + "// PIO Blinking example", + "let pio = pio0", + "let offset = pio_add_program(pio, &blink_program)", + 'printf("Loaded program at %d\\n", offset)', + "", + "#ifdef PICO_DEFAULT_LED_PIN", + "blink_pin_forever(pio, 0, offset, PICO_DEFAULT_LED_PIN, 3)", + "#else", + "blink_pin_forever(pio, 0, offset, 6, 3)", + "#endif", + "// For more pio examples see https://github.com/raspberrypi/pico-examples/tree/master/pio", + ), + ], + "clocks": [ + (), + ( + 'printf("System Clock Frequency is %d Hz\\n", clock_get_hz(clk_sys))', + 'printf("USB Clock Frequency is %d Hz\\n", clock_get_hz(clk_usb))', + "// For more examples of clocks use see https://github.com/raspberrypi/pico-examples/tree/master/clocks", + ), + ], + "gpio": [ + ("// GPIO constants", "// Example uses GPIO 2", "let GPIO = 2"), + ( + "// GPIO initialisation.", + "// We will make this GPIO an input, and pull it up by default", + "gpio_init(GPIO)", + "gpio_set_dir(GPIO, GPIO_IN)", + "gpio_pull_up(GPIO)", + "// See https://github.com/raspberrypi/pico-examples/tree/master/gpio for other gpio examples, including using interrupts", + ), + ], + "interp": [ + (), + ( + "// Interpolator example code", + "interp_config cfg = interp_default_config()", + "// Now use the various interpolator library functions for your use case", + "// e.g. interp_config_clamp(&cfg, true)", + "// interp_config_shift(&cfg, 2)", + "// Then set the config ", + "interp_set_config(interp0, 0, &cfg)", + "// For examples of interpolator use see https://github.com/raspberrypi/pico-examples/tree/master/interp", + ), + ], + "timer": [ + ( + "func alarm_callback(id: alarm_id_t, user_data: void): int64_t {", + " // Put your timeout handler code in here", + " return 0", + "}", + ), + ( + "// Timer example code - This example fires off the callback after 2000ms", + "add_alarm_in_ms(2000, alarm_callback, NULL, false)", + "// For more examples of timer use see https://github.com/raspberrypi/pico-examples/tree/master/timer", + ), + ], + "watchdog": [ + (), + ( + "// Watchdog example code", + "if watchdog_caused_reboot() {", + ' printf("Rebooted by Watchdog!\\n")', + " // Whatever action you may take if a watchdog caused a reboot", + "}", + "", + "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot", + "// second arg is pause on debug which means the watchdog will pause when stepping through code", + "watchdog_enable(100, 1)", + "", + "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot", + "watchdog_update()", + ), + ], + "div": [ + (), + ( + "// Example of using the HW divider. The pico_divider library provides a more user friendly set of APIs ", + "// over the divider (and support for 64 bit divides), and of course by default regular C language integer", + "// divisions are redirected thru that library, meaning you can just use C level `/` and `%` operators and", + "// gain the benefits of the fast hardware divider.", + "let dividend = 123456", + "let divisor = -321", + "// This is the recommended signed fast divider for general use.", + "let result = hw_divider_divmod_s32(dividend, divisor)", + 'printf("%d/%d = %d remainder %d\\n", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result))', + "// This is the recommended unsigned fast divider for general use.", + "let udividend = 123456", + "let udivisor = 321", + "let uresult = hw_divider_divmod_u32(udividend, udivisor)", + 'printf("%d/%d = %d remainder %d\\n", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult))', + "// See https://github.com/raspberrypi/pico-examples/tree/master/divider for more complex use", + ), + ], + "picow_led": [ + (), + ( + "// Example to turn on the Pico W LED", + "cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1)", + ), + ], + "picow_wifi": [ + (), + ( + "// Enable wifi station", + "cyw43_arch_enable_sta_mode()\n", + 'printf("Connecting to Wi-Fi...\\n")', + 'if cyw43_arch_wifi_connect_timeout_ms("Your Wi-Fi SSID", "Your Wi-Fi Password", CYW43_AUTH_WPA2_AES_PSK, 30000) {', + ' printf("failed to connect.\\n")', + " return 1", + "} else {", + ' printf("Connected.\\n")', + " // Read the ip address in a human readable way", + " let ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr)", + ' printf("IP address %d.%d.%d.%d\\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3])', + "}", + ), + ], } # Add wifi example for poll and background modes @@ -904,7 +901,12 @@ def ParseCommandLine(): "--userHome", help="Full path to user's home directory", ) - parser.add_argument("-swift", "--swift", action='store_true', help="Use Swift as the language for the project") + parser.add_argument( + "-swift", + "--swift", + action="store_true", + help="Use Swift as the language for the project", + ) return parser.parse_args() @@ -913,25 +915,23 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): executableName = projectName if wantEntryProjName else "main" if cpp: - filename = Path(folder) / (executableName + '.cpp') + filename = Path(folder) / (executableName + ".cpp") elif swift: - filename = Path(folder) / (executableName + '.swift') + filename = Path(folder) / (executableName + ".swift") else: filename = Path(folder) / (executableName + ".c") - file = open(filename, 'w') + file = open(filename, "w") bridging_file = None if swift: # write bridging header - bridging_file = open(Path(folder) / "BridgingHeader.h", 'w') + bridging_file = open(Path(folder) / "BridgingHeader.h", "w") bridging_file.write("#pragma once\n\n") bridging_file.write("#include \n") - bridging_file.write("#include \"pico/stdlib.h\"\n") + bridging_file.write('#include "pico/stdlib.h"\n') else: - main = ('#include \n' - '#include "pico/stdlib.h"\n' - ) + main = "#include \n" '#include "pico/stdlib.h"\n' file.write(main) if features: @@ -946,7 +946,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): bridging_file.write(o) else: file.write(o) - if (feat in stdlib_examples_list): + if feat in stdlib_examples_list: if len(stdlib_examples_list[feat][H_FILE]) == 0: continue o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n' @@ -954,7 +954,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): bridging_file.write(o) else: file.write(o) - if (feat in picow_options_list): + if feat in picow_options_list: if len(picow_options_list[feat][H_FILE]) == 0: continue o = f'#include "{picow_options_list[feat][H_FILE]}"\n' @@ -965,80 +965,89 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): file.write("\n") - frags = (code_fragments_per_feature if not swift else code_fragments_per_feature_swift) + frags = ( + code_fragments_per_feature + if not swift + else code_fragments_per_feature_swift + ) # Add any defines for feat in features: - if (feat in frags): + if feat in frags: for s in frags[feat][DEFINES]: if swift and s.startswith("#include"): bridging_file.write(s) - bridging_file.write('\n') + bridging_file.write("\n") file.write(s) file.write("\n") file.write("\n") main = None if swift: - main = ('\n\n' - '@main\n' - 'struct Main {\n' - ' \n' - ' static func main() {\n' - ' stdio_init_all();\n\n' - ) - else: - main = ('\n\n' - 'int main()\n' - '{\n' - ' stdio_init_all();\n\n' - ) + main = ( + "\n\n" + "@main\n" + "struct Main {\n" + " \n" + " static func main() {\n" + " stdio_init_all();\n\n" + ) + else: + main = "\n\n" "int main()\n" "{\n" " stdio_init_all();\n\n" if any([feat in picow_options_list and feat != "picow_none" for feat in features]): if swift: main += ( - ' // Initialise the Wi-Fi chip\n' - ' if cyw43_arch_init() {\n' - ' printf("Wi-Fi init failed\\n")\n' - ' return -1\n' - ' }\n\n') + " // Initialise the Wi-Fi chip\n" + " if cyw43_arch_init() {\n" + ' printf("Wi-Fi init failed\\n")\n' + " return -1\n" + " }\n\n" + ) else: main += ( - ' // Initialise the Wi-Fi chip\n' - ' if (cyw43_arch_init()) {\n' - ' printf("Wi-Fi init failed\\n");\n' - " return -1;\n" - " }\n\n" - ) + " // Initialise the Wi-Fi chip\n" + " if (cyw43_arch_init()) {\n" + ' printf("Wi-Fi init failed\\n");\n' + " return -1;\n" + " }\n\n" + ) if features: - frags = (code_fragments_per_feature if not swift else code_fragments_per_feature_swift) + frags = ( + code_fragments_per_feature + if not swift + else code_fragments_per_feature_swift + ) # Add any initialisers indent = 4 for feat in features: - if (feat in frags): + if feat in frags: for s in frags[feat][INITIALISERS]: - main += (" " * indent) + main += " " * indent main += s main += "\n" main += "\n" if swift: - main += (' while true {\n' - ' printf("Hello, world!\\n")\n' - ' sleep_ms(1000)\n' - ' }\n' - ' }\n' - '}\n' - ) + main += ( + " while true {\n" + " // print depends on stdio.h - putchar() in the background\n" + ' print("Hello, world!")\n' + " sleep_ms(1000)\n" + " }\n" + " }\n" + "}\n" + ) else: - main += (' while (true) {\n' - ' printf("Hello, world!\\n");\n' - ' sleep_ms(1000);\n' - ' }\n' - '}\n' - ) - + main += ( + " while (true) {\n" + ' printf("Hello, world!\\n");\n' + " sleep_ms(1000);\n" + " }\n" + "}\n" + ) + file.write(main) bridging_file.close() @@ -1046,8 +1055,10 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): def GenerateCMake(folder, params): - if (params["wantConvert"] or params['wantCPP']) and params["useSwift"]: - print("Invalid combination of options - Swift and C++ are not compatible and Swift can't be used when converting - exiting") + if (params["wantConvert"] or params["wantCPP"]) and params["useSwift"]: + print( + "Invalid combination of options - Swift and C++ are not compatible and Swift can't be used when converting - exiting" + ) exit(20) filename = Path(folder) / CMAKELIST_FILENAME @@ -1082,11 +1093,11 @@ def GenerateCMake(folder, params): ) cmake_header2 = ( - f"set(PICO_BOARD {board_type} CACHE STRING \"Board type\")\n\n" - "# Pull in Raspberry Pi Pico SDK (must be before project)\n" - "include(pico_sdk_import.cmake)\n\n" - f"project({projectName} {"" if params["useSwift"] else "C CXX ASM"})\n" - ) + f'set(PICO_BOARD {board_type} CACHE STRING "Board type")\n\n' + "# Pull in Raspberry Pi Pico SDK (must be before project)\n" + "include(pico_sdk_import.cmake)\n\n" + f"project({projectName} {"" if params["useSwift"] else "C CXX ASM"})\n" + ) cmake_header3 = ( "\n# Initialise the Raspberry Pi Pico SDK\n" @@ -1192,14 +1203,18 @@ def GenerateCMake(folder, params): "endif()\n" ) cmake_swift_target = ( - "set(SWIFT_TARGET \"armv6m-none-none-eabi\") # RP2040\n\n" - "if(PICO_PLATFORM STREQUAL \"rp2350-arm-s\")\n" - " message(STATUS \"PICO_PLATFORM is set to rp2350-arm-s, using armv7em\")\n" - " set(SWIFT_TARGET \"armv7em-none-none-eabi\")\n" - "elseif(PICO_PLATFORM STREQUAL \"rp2350-riscv\")\n" - " # Untested, gives PICO-SDK errors when building\n" - " message(WARNING \"PICO_PLATFORM is set to rp2350-riscv, using riscv32 (untested). It is recommended to use rp2350-arm-s.\")\n" - " set(SWIFT_TARGET \"riscv32-none-none-eabi\")\n" + 'set(SWIFT_TARGET "armv6m-none-none-eabi") # RP2040\n\n' + 'if(PICO_PLATFORM STREQUAL "rp2350-arm-s")\n' + ' message(STATUS "PICO_PLATFORM is set to rp2350-arm-s, using armv7em")\n' + ' set(SWIFT_TARGET "armv7em-none-none-eabi")\n' + ' list(APPEND CLANG_ARCH_ABI_FLAGS "-Xcc" "-mfloat-abi=soft")\n' + 'elseif(PICO_PLATFORM STREQUAL "rp2040")\n' + ' message(STATUS "PICO_PLATFORM is set to RP2040, using armv6m")\n' + ' list(APPEND CLANG_ARCH_ABI_FLAGS "-Xcc" "-mfloat-abi=soft")\n' + 'elseif(PICO_PLATFORM STREQUAL "rp2350-riscv")\n' + ' message(STATUS "PICO_PLATFORM is set to rp2350-riscv, using riscv32.")\n' + ' set(SWIFT_TARGET "riscv32-none-none-eabi")\n' + ' list(APPEND CLANG_ARCH_ABI_FLAGS "-Xcc" "-march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb" "-Xcc" "-mabi=ilp32")\n' "endif()\n" ) file.write(cmake_if_apple) @@ -1218,13 +1233,50 @@ def GenerateCMake(folder, params): entry_point_file_name = projectName if params["wantEntryProjName"] else "main" - if params['wantCPP']: - file.write(f'add_executable({projectName} {entry_point_file_name}.cpp )\n\n') - elif params['useSwift']: - file.write(f'add_executable({projectName})\n\n') - + if params["wantCPP"]: + file.write(f"add_executable({projectName} {entry_point_file_name}.cpp )\n\n") + elif params["useSwift"]: + file.write(f"add_executable({projectName})\n\n") + main_file_name = f"{entry_point_file_name}.swift" cmake_custom_swift_command = ( + "# Gather compile definitions from all dependencies\n\n" + 'set_property(GLOBAL PROPERTY visited_targets "")\n' + 'set_property(GLOBAL PROPERTY compilerdefs_list "")\n\n' + "function(gather_compile_definitions_recursive target)\n" + " # Get the current value of visited_targets\n" + " get_property(visited_targets GLOBAL PROPERTY visited_targets)\n\n" + " # make sure we don't visit the same target twice\n" + " # and that we don't visit the special generator expressions\n" + ' if (${target} MATCHES "\\$<" OR ${target} MATCHES "::@" OR ${target} IN_LIST visited_targets)\n' + " return()\n" + " endif()\n\n" + " # Append the target to visited_targets\n" + " list(APPEND visited_targets ${target})\n" + ' set_property(GLOBAL PROPERTY visited_targets "${visited_targets}")\n\n' + " # Get the current value of compilerdefs_list\n" + " get_property(compilerdefs_list GLOBAL PROPERTY compilerdefs_list)\n\n" + " get_target_property(target_definitions ${target} INTERFACE_COMPILE_DEFINITIONS)\n" + " if (target_definitions)\n" + " # Append the target definitions to compilerdefs_list\n" + " list(APPEND compilerdefs_list ${target_definitions})\n" + ' set_property(GLOBAL PROPERTY compilerdefs_list "${compilerdefs_list}")\n' + " endif()\n\n" + " get_target_property(target_linked_libs ${target} INTERFACE_LINK_LIBRARIES)\n" + " if (target_linked_libs)\n" + " foreach(linked_target ${target_linked_libs})\n" + " # Recursively gather compile definitions from dependencies\n" + " gather_compile_definitions_recursive(${linked_target})\n" + " endforeach()\n" + " endif()\n" + "endfunction()\n\n" + f"gather_compile_definitions_recursive({projectName})\n" + "get_property(COMPILE_DEFINITIONS GLOBAL PROPERTY compilerdefs_list)\n\n" + "# Parse compiler definitions into a format that swiftc can understand\n" + "list(REMOVE_DUPLICATES COMPILE_DEFINITIONS)\n" + 'list(PREPEND COMPILE_DEFINITIONS "")\n' + f'string(REPLACE "$" "$" COMPILE_DEFINITIONS "${{COMPILE_DEFINITIONS}}")\n' + 'string(REPLACE ";" ";-Xcc;-D" COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS}")\n' "add_custom_command(\n" " OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n" " COMMAND\n" @@ -1243,7 +1295,9 @@ def GenerateCMake(folder, params): ) file.write(cmake_custom_swift_command) - file.write(f"add_custom_target({projectName}-swiftcode DEPENDS ${{CMAKE_CURRENT_BINARY_DIR}}/_swiftcode.o)\n\n") + file.write( + f"add_custom_target({projectName}-swiftcode DEPENDS ${{CMAKE_CURRENT_BINARY_DIR}}/_swiftcode.o)\n\n" + ) else: file.write(f"add_executable({projectName} {entry_point_file_name}.c )\n\n") @@ -1297,7 +1351,7 @@ def GenerateCMake(folder, params): file.write(" " + STANDARD_LIBRARIES) if params["useSwift"]: file.write(" ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n") - file.write(')\n\n') + file.write(")\n\n") # Standard include directories file.write("# Add the standard include files to the build\n") @@ -1306,7 +1360,7 @@ def GenerateCMake(folder, params): file.write(")\n\n") if params["useSwift"]: - file.write(f'add_dependencies({projectName} {projectName}-swiftcode)\n') + file.write(f"add_dependencies({projectName} {projectName}-swiftcode)\n") # Selected libraries/features if params["features"]: @@ -1746,8 +1800,15 @@ def DoEverything(params): if "uart" not in features_and_examples: features_and_examples.append("uart") - if not (params['wantConvert']): - GenerateMain(projectPath, params['projectName'], features_and_examples, params['wantCPP'], params['wantEntryProjName'], params['useSwift']) + if not (params["wantConvert"]): + GenerateMain( + projectPath, + params["projectName"], + features_and_examples, + params["wantCPP"], + params["wantEntryProjName"], + params["useSwift"], + ) # If we have any ancillary files, copy them to our project folder # Currently only the picow with lwIP support needs an extra file, so just check that list @@ -1801,8 +1862,9 @@ def DoEverything(params): params["ninjaPath"], params["cmakePath"], params["openOCDVersion"], - params['useCmakeTools'], - params['useSwift']) + params["useCmakeTools"], + params["useSwift"], + ) os.chdir(oldCWD) From f9b51f6839f70eea75838384399558aec91e5d3e Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:25:23 +0100 Subject: [PATCH 04/13] Update swift examples and memory alignment support Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/bridge.c | 38 ++++++++++++ scripts/bridge.h | 38 ++++++++++++ scripts/pico_project.py | 129 +++++++++++++++++++++++++--------------- src/utils/download.mts | 23 ++++++- 4 files changed, 179 insertions(+), 49 deletions(-) create mode 100644 scripts/bridge.c create mode 100644 scripts/bridge.h diff --git a/scripts/bridge.c b/scripts/bridge.c new file mode 100644 index 00000000..cb7296f7 --- /dev/null +++ b/scripts/bridge.c @@ -0,0 +1,38 @@ +#include +#include + +/** + * @brief Allocates aligned memory in accordance with POSIX standards. + * + * The `posix_memalign` function allocates a block of memory with the specified alignment + * and size. The allocated memory is stored in the location pointed to by `memptr`. The + * alignment must be a power of two and a multiple of `sizeof(void *)`. This function is + * typically used for ensuring memory alignment for hardware or performance requirements. + * + * @param[out] memptr A pointer to the memory location where the aligned memory will be stored. + * This parameter must not be NULL. + * @param[in] alignment The alignment boundary in bytes. Must be a power of two and a multiple + * of `sizeof(void *)`. + * @param[in] size The size of the memory block to allocate in bytes. + * + * @return int Returns 0 on success. On failure, returns: + * - `EINVAL` if the alignment is invalid (not a power of two or not a multiple of `sizeof(void *)`). + * - `ENOMEM` if memory allocation fails. + * + * @note The caller is responsible for freeing the allocated memory using `free()` when it is no longer needed. + */ +int posix_memalign(void **memptr, size_t alignment, size_t size) { + // Validate alignment requirements + if ((alignment % sizeof(void *) != 0) || (alignment & (alignment - 1)) != 0) { + return EINVAL; // Invalid alignment + } + + // Use memalign to allocate memory + void *ptr = memalign(alignment, size); + if (ptr == NULL) { + return ENOMEM; // Memory allocation failure + } + + *memptr = ptr; // Set the memory pointer + return 0; // Success +} diff --git a/scripts/bridge.h b/scripts/bridge.h new file mode 100644 index 00000000..2647a58a --- /dev/null +++ b/scripts/bridge.h @@ -0,0 +1,38 @@ +#pragma once + +/** + * @file bridge.h + * @brief Simplifies the usage of specific SDK features in Swift by providing wrapper functions. + * + * This header acts as a bridge between C and Swift, exposing certain features of the Pico SDK + * in a simplified manner. For example it includes helper functions for accessing predefined + * UART instances (`uart0` and `uart1`), making them easily callable from Swift. + */ + +#ifdef _HARDWARE_STRUCTS_UART_H // Ensure that UART hardware structs are included before compiling + +/** + * @brief Retrieves the instance for UART0. + * + * This function provides access to the pre-defined `uart0` instance, + * allowing straightforward interaction with the UART hardware from Swift. + * + * @return Pointer to the `uart0` instance. + */ +uart_inst_t* get_uart0(void) { + return uart0; +} + +/** + * @brief Retrieves the instance for UART1. + * + * This function provides access to the pre-defined `uart1` instance, + * allowing straightforward interaction with the UART hardware from Swift. + * + * @return Pointer to the `uart1` instance. + */ +uart_inst_t* get_uart1(void) { + return uart1; +} + +#endif diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 33b6ab15..2c5571d6 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -398,13 +398,13 @@ def GDB_NAME(): ( "// UART defines", "// By default the stdout UART is `uart0`, so we will use the second one", - 'let UART_ID = "uart1"', - "let BAUD_RATE = 115200", + "let UART_ID = get_uart1()", + "let BAUD_RATE: UInt32 = 115200", "", "// Use pins 4 and 5 for UART1", "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - "let UART_TX_PIN = 4", - "let UART_RX_PIN = 5", + "let UART_TX_PIN: UInt32 = 4", + "let UART_RX_PIN: UInt32 = 5", ), ( "// Set up our UART", @@ -436,10 +436,10 @@ def GDB_NAME(): "// We are going to use SPI 0, and allocate it to the following GPIO pins", "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", 'let SPI_PORT = "spi0"', - "let PIN_MISO = 16", - "let PIN_CS = 17", - "let PIN_SCK = 18", - "let PIN_MOSI = 19", + "let PIN_MISO: UInt32 = 16", + "let PIN_CS: UInt32 = 17", + "let PIN_SCK: UInt32 = 18", + "let PIN_MOSI: UInt32 = 19", ), ( "// SPI initialisation. This example will use SPI at 1MHz.", @@ -461,8 +461,8 @@ def GDB_NAME(): "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.", "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", 'let I2C_PORT = "i2c0"', - "let I2C_SDA = 8", - "let I2C_SCL = 9", + "let I2C_SDA: UInt32 = 8", + "let I2C_SCL: UInt32 = 9", ), ( "// I2C Initialisation. Using it at 400Khz.", @@ -515,37 +515,47 @@ def GDB_NAME(): ], "pio": [ ( - '#include "blink.pio.h"', - "static func blink_pin_forever(pio: PIO, sm: uint, offset: uint, pin: uint, freq: uint) {", + "func blink_pin_forever(_ pio: PIO, sm: UInt32, offset: UInt32, pin: UInt32, freq: UInt32) {", " blink_program_init(pio, sm, offset, pin)", " pio_sm_set_enabled(pio, sm, true)", "", - ' printf("Blinking pin %d at %d Hz\\n", pin, freq)', + ' print("Blinking pin \\(pin) at \\(freq) Hz")', "", " // PIO counter program takes 3 more cycles in total than we pass as", " // input (wait for n + 1; mov; jmp)", - " pio.txf[sm] = (125000000 / (2 * freq)) - 3", + " let value = (125000000 / (2 * freq)) - 3", + " switch sm {", + " case 0:", + " pio.pointee.txf.0 = value", + " case 1:", + " pio.pointee.txf.1 = value", + " case 2:", + " pio.pointee.txf.2 = value", + " case 3:", + " pio.pointee.txf.3 = value", + " default:", + " // There are 4 state machines available", + ' fatalError("Invalid state machine index")', + " }", "}", ), ( "// PIO Blinking example", - "let pio = pio0", + "guard let pio = get_pio0() else {", + ' fatalError("PIO0 not available")', + "}", "let offset = pio_add_program(pio, &blink_program)", - 'printf("Loaded program at %d\\n", offset)', + 'print("Loaded program at \\(offset)")', "", - "#ifdef PICO_DEFAULT_LED_PIN", - "blink_pin_forever(pio, 0, offset, PICO_DEFAULT_LED_PIN, 3)", - "#else", - "blink_pin_forever(pio, 0, offset, 6, 3)", - "#endif", + "blink_pin_forever(pio, sm: 0, offset: UInt32(offset), pin: UInt32(PICO_DEFAULT_LED_PIN), freq: 3)", "// For more pio examples see https://github.com/raspberrypi/pico-examples/tree/master/pio", ), ], "clocks": [ (), ( - 'printf("System Clock Frequency is %d Hz\\n", clock_get_hz(clk_sys))', - 'printf("USB Clock Frequency is %d Hz\\n", clock_get_hz(clk_usb))', + 'print("System Clock Frequency is \\(clock_get_hz(clk_sys)) Hz")', + 'print("USB Clock Frequency is \\(clock_get_hz(clk_usb)) Hz")', "// For more examples of clocks use see https://github.com/raspberrypi/pico-examples/tree/master/clocks", ), ], @@ -614,12 +624,12 @@ def GDB_NAME(): "let divisor = -321", "// This is the recommended signed fast divider for general use.", "let result = hw_divider_divmod_s32(dividend, divisor)", - 'printf("%d/%d = %d remainder %d\\n", dividend, divisor, to_quotient_s32(result), to_remainder_s32(result))', + 'print("\\(dividend)/\\(divisor) = \\(to_quotient_s32(result)) remainder \\(to_remainder_s32(result))")', "// This is the recommended unsigned fast divider for general use.", "let udividend = 123456", "let udivisor = 321", "let uresult = hw_divider_divmod_u32(udividend, udivisor)", - 'printf("%d/%d = %d remainder %d\\n", udividend, udivisor, to_quotient_u32(uresult), to_remainder_u32(uresult))', + 'printf("\\(udividend)/\\(udivisor) = \\(to_quotient_u32(uresult)) remainder \\(to_remainder_u32(uresult))")', "// See https://github.com/raspberrypi/pico-examples/tree/master/divider for more complex use", ), ], @@ -635,15 +645,15 @@ def GDB_NAME(): ( "// Enable wifi station", "cyw43_arch_enable_sta_mode()\n", - 'printf("Connecting to Wi-Fi...\\n")', + 'print("Connecting to Wi-Fi...")', 'if cyw43_arch_wifi_connect_timeout_ms("Your Wi-Fi SSID", "Your Wi-Fi Password", CYW43_AUTH_WPA2_AES_PSK, 30000) {', - ' printf("failed to connect.\\n")', + ' print("failed to connect.")', " return 1", "} else {", - ' printf("Connected.\\n")', + ' print("Connected.")', " // Read the ip address in a human readable way", " let ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr)", - ' printf("IP address %d.%d.%d.%d\\n", ip_address[0], ip_address[1], ip_address[2], ip_address[3])', + ' print("IP address \\(ip_address[0]).\\(ip_address[1]).\\(ip_address[2]).\\(ip_address[3])")', "}", ), ], @@ -707,6 +717,9 @@ def codeSdkPath(sdkVersion): return f"${{userHome}}{relativeSDKPath(sdkVersion)}" +CODE_BASIC_TOOLCHAIN_PATH = "${userHome}/.pico-sdk/toolchain" + + def codeOpenOCDPath(openocdVersion): return f"${{userHome}}{relativeOpenOCDPath(openocdVersion)}" @@ -942,7 +955,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): if len(features_list[feat][H_FILE]) == 0: continue o = f'#include "{features_list[feat][H_FILE]}"\n' - if swift: + if swift and bridging_file: bridging_file.write(o) else: file.write(o) @@ -950,7 +963,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): if len(stdlib_examples_list[feat][H_FILE]) == 0: continue o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n' - if swift: + if swift and bridging_file: bridging_file.write(o) else: file.write(o) @@ -958,7 +971,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): if len(picow_options_list[feat][H_FILE]) == 0: continue o = f'#include "{picow_options_list[feat][H_FILE]}"\n' - if swift: + if swift and bridging_file: bridging_file.write(o) else: file.write(o) @@ -975,7 +988,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): for feat in features: if feat in frags: for s in frags[feat][DEFINES]: - if swift and s.startswith("#include"): + if swift and bridging_file and s.startswith("#include"): bridging_file.write(s) bridging_file.write("\n") file.write(s) @@ -1050,7 +1063,10 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): file.write(main) - bridging_file.close() + if bridging_file: + bridging_file.write("// always include last\n") + bridging_file.write("#include \n\n") + bridging_file.close() file.close() @@ -1234,9 +1250,17 @@ def GenerateCMake(folder, params): entry_point_file_name = projectName if params["wantEntryProjName"] else "main" if params["wantCPP"]: - file.write(f"add_executable({projectName} {entry_point_file_name}.cpp )\n\n") + file.write(f"add_executable({projectName} {entry_point_file_name}.cpp)\n\n") elif params["useSwift"]: - file.write(f"add_executable({projectName})\n\n") + file.write( + f"add_executable({projectName} ${{USERHOME}}/.pico-sdk/toolchain/bridge.c)\n\n" + ) + + # Standard libraries + file.write("# Add the standard library to the build\n") + file.write(f"target_link_libraries({projectName}\n") + file.write(" " + STANDARD_LIBRARIES) + file.write(")\n\n") main_file_name = f"{entry_point_file_name}.swift" cmake_custom_swift_command = ( @@ -1248,7 +1272,7 @@ def GenerateCMake(folder, params): " get_property(visited_targets GLOBAL PROPERTY visited_targets)\n\n" " # make sure we don't visit the same target twice\n" " # and that we don't visit the special generator expressions\n" - ' if (${target} MATCHES "\\$<" OR ${target} MATCHES "::@" OR ${target} IN_LIST visited_targets)\n' + ' if (${target} MATCHES "\\\\$<" OR ${target} MATCHES "::@" OR ${target} IN_LIST visited_targets)\n' " return()\n" " endif()\n\n" " # Append the target to visited_targets\n" @@ -1286,11 +1310,11 @@ def GenerateCMake(folder, params): f" $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" - f" ${{CMAKE_CURRENT_LIST_DIR}}/{entry_point_file_name}.swift\n" + f" ${{CMAKE_CURRENT_LIST_DIR}}/{main_file_name}\n" " -c -o ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n" " DEPENDS\n" " ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" - f" ${{CMAKE_CURRENT_LIST_DIR}}/{entry_point_file_name}.swift\n" + f" ${{CMAKE_CURRENT_LIST_DIR}}/{main_file_name}\n" ")\n" ) file.write(cmake_custom_swift_command) @@ -1345,18 +1369,26 @@ def GenerateCMake(folder, params): file.write(f'WIFI_PASSWORD="${WIFI_PASSWORD}"') file.write(")\n\n") - # Standard libraries - file.write("# Add the standard library to the build\n") - file.write(f"target_link_libraries({projectName}\n") - file.write(" " + STANDARD_LIBRARIES) - if params["useSwift"]: - file.write(" ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n") - file.write(")\n\n") + if not params["useSwift"]: + # Standard libraries + file.write("# Add the standard library to the build\n") + file.write(f"target_link_libraries({projectName}\n") + file.write(" " + STANDARD_LIBRARIES) + file.write(")\n\n") + else: + file.write("# Add the swift artifact to the build\n") + file.write(f"target_link_libraries({projectName}\n") + file.write(" ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n") + file.write(")\n\n") # Standard include directories file.write("# Add the standard include files to the build\n") file.write(f"target_include_directories({projectName} PRIVATE\n") - file.write(" " + "${CMAKE_CURRENT_LIST_DIR}\n") + file.write(" ${CMAKE_CURRENT_LIST_DIR}\n") + if params["useSwift"]: + # bridge.h includes serveral helper functions + # for swift-c-pico-sdk interop + file.write(" ${USERHOME}/.pico-sdk/toolchain\n") file.write(")\n\n") if params["useSwift"]: @@ -1517,7 +1549,8 @@ def generateProjectFiles( "name": "Pico", "includePath": [ "${{workspaceFolder}}/**", - "{codeSdkPath(sdkVersion)}/**" + "{codeSdkPath(sdkVersion)}/**"{f""", + "{CODE_BASIC_TOOLCHAIN_PATH}\"""" if useSwift else ""} ], "forcedInclude": [ "{codeSdkPath(sdkVersion)}/src/common/{base_headers_folder_name}/include/pico.h", diff --git a/src/utils/download.mts b/src/utils/download.mts index c7568079..0bd82056 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -100,6 +100,15 @@ const CMAKE_PLATFORMS: { [key: string]: string } = { // Compute and cache the home directory const homeDirectory: string = homedir(); +export function buildBasicToolchainPath(absolute: boolean): string { + const relPath = joinPosix(homeDirectory, ".pico-sdk", "toolchain"); + if (absolute) { + return joinPosix(homeDirectory.replaceAll("\\", "/"), relPath); + } else { + return joinPosix("${USERHOME}", relPath); + } +} + export function buildToolchainPath(version: string): string { return joinPosix(homeDirectory, ".pico-sdk", "toolchain", version); } @@ -115,7 +124,6 @@ export function buildSDKPath(version: string): string { } export function buildCMakeIncPath(absolute: boolean): string { - // TODO: maybe replace . with _ const relPath = joinPosix(".pico-sdk", "cmake"); if (absolute) { return joinPosix(homeDirectory.replaceAll("\\", "/"), relPath); @@ -947,6 +955,19 @@ export async function downloadAndInstallToolchain( return false; } + // Install bridge.h and bridge.c files - overwrite if already there + await mkdir(buildBasicToolchainPath(true), { recursive: true }); + // includes helper stuff for swift code - pico-sdk c interop + copyFileSync( + joinPosix(getScriptsRoot(), "bridge.h"), + joinPosix(buildCMakeIncPath(true), "bridge.h") + ); + // includes helper stuff for the swift compiler (workaround) + copyFileSync( + joinPosix(getScriptsRoot(), "bridge.c"), + joinPosix(buildCMakeIncPath(true), "bridge.c") + ); + const archiveFileName = `${toolchain.version}.${artifactExt}`; return downloadAndInstallArchive( From 7dff2f24f9b4b6c6249176c1e3ab9e6e8c515315 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 5 Dec 2024 02:04:18 +0100 Subject: [PATCH 05/13] Better swift support + examples Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/PicoSDK.swift | 17 ++++++ scripts/bridge.h | 56 ++++++++++++++++++++ scripts/pico_project.py | 93 ++++++++++++++++++++++++--------- src/utils/download.mts | 8 +++ src/webview/newProjectPanel.mts | 2 +- 5 files changed, 151 insertions(+), 25 deletions(-) create mode 100644 scripts/PicoSDK.swift diff --git a/scripts/PicoSDK.swift b/scripts/PicoSDK.swift new file mode 100644 index 00000000..ea40c7ec --- /dev/null +++ b/scripts/PicoSDK.swift @@ -0,0 +1,17 @@ +// This file contains a swift wrapper for some Pico SDK C style stuff +// Upercase library names can be used to only include helpers for available APIs + +#if PICO_STDLIB +public struct PicoGPIO { + /// Enum for GPIO direction, mirroring the C enum + public enum Direction: UInt8 { + case input = 0 + case output = 1 + } + + /// Swift-friendly wrapper for gpio_set_dir + public static func setDirection(pin: UInt32, direction: Direction) { + gpio_set_dir(pin, direction.rawValue != 0) + } +} +#endif diff --git a/scripts/bridge.h b/scripts/bridge.h index 2647a58a..2bb02cab 100644 --- a/scripts/bridge.h +++ b/scripts/bridge.h @@ -1,5 +1,57 @@ #pragma once +#ifndef PICO_DEFAULT_LED_PIN +#define PICO_DEFAULT_LED_PIN 6 +#endif + +#ifdef _HARDWARE_STRUCTS_SPI_H +#ifdef spi0 +spi_inst_t* get_spi0(void) { + return spi0; +} +#endif + +#ifdef spi1 +spi_inst_t* get_spi1(void) { + return spi1; +} +#endif +#endif + +#ifdef _HARDWARE_STRUCTS_I2C_H +#ifdef i2c0 +i2c_inst_t* get_i2c0(void) { + return i2c0; +} +#endif + +#ifdef i2c1 +i2c_inst_t* get_i2c1(void) { + return i2c1; +} +#endif +#endif + +#ifdef _HARDWARE_STRUCTS_PIO_H +#ifdef pio0 +pio_hw_t* get_pio0(void) { + return pio0; +} +#endif + +#ifdef pio1 +pio_hw_t* get_pio1(void) { + return pio1; +} +#endif + +#ifdef pio2 +pio_hw_t* get_pio2(void) { + return pio2; +} +#endif +#endif + /** * @file bridge.h * @brief Simplifies the usage of specific SDK features in Swift by providing wrapper functions. @@ -11,6 +63,7 @@ #ifdef _HARDWARE_STRUCTS_UART_H // Ensure that UART hardware structs are included before compiling +#ifdef uart0 /** * @brief Retrieves the instance for UART0. * @@ -22,7 +75,9 @@ uart_inst_t* get_uart0(void) { return uart0; } +#endif +#ifdef uart1 /** * @brief Retrieves the instance for UART1. * @@ -34,5 +89,6 @@ uart_inst_t* get_uart0(void) { uart_inst_t* get_uart1(void) { return uart1; } +#endif #endif diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 2c5571d6..77581091 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -435,7 +435,7 @@ def GDB_NAME(): "// SPI Constants", "// We are going to use SPI 0, and allocate it to the following GPIO pins", "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - 'let SPI_PORT = "spi0"', + "let SPI_PORT = get_spi0()", "let PIN_MISO: UInt32 = 16", "let PIN_CS: UInt32 = 17", "let PIN_SCK: UInt32 = 18", @@ -450,8 +450,8 @@ def GDB_NAME(): "gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI)", "", "// Chip select is active-low, so we'll initialise it to a driven-high state", - "gpio_set_dir(PIN_CS, GPIO_OUT)", - "gpio_put(PIN_CS, 1)", + "PicoGPIO.setDirection(pin: PIN_CS, direction: .output) // Use swift wrapper", + "gpio_put(PIN_CS, true)", "// For more examples of SPI use see https://github.com/raspberrypi/pico-examples/tree/master/spi", ), ], @@ -460,7 +460,7 @@ def GDB_NAME(): "// I2C defines", "// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz.", "// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments", - 'let I2C_PORT = "i2c0"', + "let I2C_PORT = get_i2c0()", "let I2C_SDA: UInt32 = 8", "let I2C_SCL: UInt32 = 9", ), @@ -483,24 +483,24 @@ def GDB_NAME(): ), ( "// Get a free channel, panic() if there are none", - "let chan = dma_claim_unused_channel(true)", + "let chan = UInt32(dma_claim_unused_channel(true))", "", "// 8 bit transfers. Both read and write address increment after each", "// transfer (each pointing to a location in src or dst respectively).", "// No DREQ is selected, so the DMA transfers as fast as it can.", "", - "let c = dma_channel_get_default_config(chan)", + "var c = dma_channel_get_default_config(chan)", "channel_config_set_transfer_data_size(&c, DMA_SIZE_8)", "channel_config_set_read_increment(&c, true)", "channel_config_set_write_increment(&c, true)", "", "dma_channel_configure(", - " chan, // Channel to be configured", - " &c, // The configuration we just created", - " dst, // The initial write address", - " src, // The initial read address", - " count_of(src), // Number of transfers; in this case each is 1 byte.", - " true // Start immediately.", + " chan, // Channel to be configured", + " &c, // The configuration we just created", + " &dst, // The initial write address", + " src, // The initial read address", + " UInt32(src.utf8.count), // Number of transfers; in this case each is 1 byte.", + " true // Start immediately.", ")", "", "// We could choose to go and do something else whilst the DMA is doing its", @@ -544,7 +544,7 @@ def GDB_NAME(): "guard let pio = get_pio0() else {", ' fatalError("PIO0 not available")', "}", - "let offset = pio_add_program(pio, &blink_program)", + "let offset = pio_add_program(pio, [blink_program])", 'print("Loaded program at \\(offset)")', "", "blink_pin_forever(pio, sm: 0, offset: UInt32(offset), pin: UInt32(PICO_DEFAULT_LED_PIN), freq: 3)", @@ -592,7 +592,7 @@ def GDB_NAME(): ), ( "// Timer example code - This example fires off the callback after 2000ms", - "add_alarm_in_ms(2000, alarm_callback, NULL, false)", + "add_alarm_in_ms(2000, alarm_callback, nil, false)", "// For more examples of timer use see https://github.com/raspberrypi/pico-examples/tree/master/timer", ), ], @@ -957,6 +957,8 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): o = f'#include "{features_list[feat][H_FILE]}"\n' if swift and bridging_file: bridging_file.write(o) + if feat == "pio": + bridging_file.write('#include "blink.pio.h"') else: file.write(o) if feat in stdlib_examples_list: @@ -1003,7 +1005,7 @@ def GenerateMain(folder, projectName, features, cpp, wantEntryProjName, swift): "struct Main {\n" " \n" " static func main() {\n" - " stdio_init_all();\n\n" + " stdio_init_all()\n\n" ) else: main = "\n\n" "int main()\n" "{\n" " stdio_init_all();\n\n" @@ -1200,6 +1202,20 @@ def GenerateCMake(folder, params): file.write(cmake_header1) file.write(cmake_header_us) + if params["useSwift"]: + cmake_if_apple = ( + "if(APPLE)\n" + " execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + " execute_process(COMMAND dirname ${SWIFTC} OUTPUT_VARIABLE SWIFTC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + "elseif(WIN32)\n" + " execute_process(COMMAND where.exe swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + ' string(REGEX REPLACE "(.*)\\\\\\\\[^\\\\\\\\]*$" "\\\\1" SWIFTC_DIR "${SWIFTC}")\n' + "else()\n" + " execute_process(COMMAND which swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + " execute_process(COMMAND dirname ${SWIFTC} OUTPUT_VARIABLE SWIFTC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + "endif()\n" + ) + file.write(cmake_if_apple) file.write(cmake_header2) if params["exceptions"]: @@ -1211,13 +1227,6 @@ def GenerateCMake(folder, params): file.write(cmake_header3) if params["useSwift"]: - cmake_if_apple = ( - "if(APPLE)\n" - " execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" - "else()\n" - " execute_process(COMMAND which swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" - "endif()\n" - ) cmake_swift_target = ( 'set(SWIFT_TARGET "armv6m-none-none-eabi") # RP2040\n\n' 'if(PICO_PLATFORM STREQUAL "rp2350-arm-s")\n' @@ -1232,8 +1241,12 @@ def GenerateCMake(folder, params): ' set(SWIFT_TARGET "riscv32-none-none-eabi")\n' ' list(APPEND CLANG_ARCH_ABI_FLAGS "-Xcc" "-march=rv32imac_zicsr_zifencei_zba_zbb_zbs_zbkb" "-Xcc" "-mabi=ilp32")\n' "endif()\n" + 'set(SWIFT_EMBEDDED_UNICODE_LIB "${SWIFTC_DIR}/../lib/swift/embedded/${SWIFT_TARGET}/libswiftUnicodeDataTables.a")\n\n' + "# Link the Swift Unicode Data Tables library\n" + "if(NOT EXISTS ${SWIFT_EMBEDDED_UNICODE_LIB})\n" + ' message(FATAL_ERROR "Required Swift library not found: ${SWIFT_EMBEDDED_LIB}")\n' + "endif()\n\n" ) - file.write(cmake_if_apple) file.write(cmake_swift_target) # add the preprocessor defines for overall configuration @@ -1260,6 +1273,12 @@ def GenerateCMake(folder, params): file.write("# Add the standard library to the build\n") file.write(f"target_link_libraries({projectName}\n") file.write(" " + STANDARD_LIBRARIES) + if params["features"]: + for feat in params["features"]: + if feat in features_list: + file.write(" " + features_list[feat][LIB_NAME] + "\n") + if feat in picow_options_list: + file.write(" " + picow_options_list[feat][LIB_NAME] + "\n") file.write(")\n\n") main_file_name = f"{entry_point_file_name}.swift" @@ -1296,6 +1315,22 @@ def GenerateCMake(folder, params): "endfunction()\n\n" f"gather_compile_definitions_recursive({projectName})\n" "get_property(COMPILE_DEFINITIONS GLOBAL PROPERTY compilerdefs_list)\n\n" + "# ==== BEGIN Generate compile definitions from linked libraries\n" + "# Gather libraries linked to project\n" + f"get_target_property({projectName}_LIBRARIES {projectName} INTERFACE_LINK_LIBRARIES)\n" + f"if(NOT {projectName}_LIBRARIES)\n" + f' set({projectName}_LIBRARIES "") # Default to an empty list if none found\n' + "endif()\n\n" + 'set(SWIFTC_LIB_DEFINITIONS "")\n' + f"foreach(LIBRARY IN LISTS {projectName}_LIBRARIES)\n" + " # Convert it to uppercase\n" + " string(TOUPPER ${LIBRARY} LIB_NAME_UPPER)\n" + ' list(APPEND SWIFTC_LIB_DEFINITIONS "-D" "${LIB_NAME_UPPER}")\n' + "endforeach()\n\n" + "# Convert the list to a string for the Swift command\n" + 'string(REPLACE " " ";" SWIFTC_DEFINITIONS_STR "${SWIFTC_LIB_DEFINITIONS}")\n' + 'message(STATUS "SWIFTC_LIB_DEFINITIONS: ${SWIFTC_DEFINITIONS_STR}")\n' + "# ==== END Generate compile definitions from linked libraries\n\n" "# Parse compiler definitions into a format that swiftc can understand\n" "list(REMOVE_DUPLICATES COMPILE_DEFINITIONS)\n" 'list(PREPEND COMPILE_DEFINITIONS "")\n' @@ -1307,13 +1342,16 @@ def GenerateCMake(folder, params): " ${SWIFTC}\n" " -target ${SWIFT_TARGET} -Xcc -mfloat-abi=soft -Xcc -fshort-enums\n" " -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library\n" + " ${SWIFTC_DEFINITIONS_STR}\n" f" $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" " -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" + " ${USERHOME}/.pico-sdk/sdk/PicoSDK.swift\n" f" ${{CMAKE_CURRENT_LIST_DIR}}/{main_file_name}\n" " -c -o ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n" " DEPENDS\n" " ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" + " ${USERHOME}/.pico-sdk/sdk/PicoSDK.swift\n" f" ${{CMAKE_CURRENT_LIST_DIR}}/{main_file_name}\n" ")\n" ) @@ -1376,6 +1414,13 @@ def GenerateCMake(folder, params): file.write(" " + STANDARD_LIBRARIES) file.write(")\n\n") else: + file.write("# Link Swift Unicode data tables to the build\n") + file.write(f"target_link_options({projectName} PRIVATE\n") + file.write( + " -Wl,--whole-archive ${SWIFT_EMBEDDED_UNICODE_LIB} -Wl,--no-whole-archive\n" + ) + file.write(")\n\n") + file.write("# Add the swift artifact to the build\n") file.write(f"target_link_libraries({projectName}\n") file.write(" ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o\n") @@ -1395,7 +1440,7 @@ def GenerateCMake(folder, params): file.write(f"add_dependencies({projectName} {projectName}-swiftcode)\n") # Selected libraries/features - if params["features"]: + if params["features"] and not params["useSwift"]: file.write("# Add any user requested libraries\n") file.write(f"target_link_libraries({projectName} \n") for feat in params["features"]: diff --git a/src/utils/download.mts b/src/utils/download.mts index 0bd82056..5a66545b 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -489,6 +489,14 @@ export async function downloadAndInstallSDK( const targetDirectory = buildSDKPath(version); + // Copy swift wrapper + const sdkRoot = dirname(targetDirectory); + await mkdir(sdkRoot, { recursive: true }); + copyFileSync( + joinPosix(getScriptsRoot(), "PicoSDK.swift"), + joinPosix(sdkRoot, "PicoSDK.swift") + ); + // Check if the SDK is already installed if ( existsSync(targetDirectory) && diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index 6d24f203..6cd6b363 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -1877,7 +1877,7 @@ export class NewProjectPanel {
    ` : `

    - Warning: Project Import Wizard may not work for all projects, and will often require manual correction after the import + Warning: Project Import Wizard may not work for all projects, and will often require manual correction after the import. Swift Projects aren't supported at the moment.

    ` }
    From 65c399ec643aec8c463927a714ec32f1a56a2ece Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 5 Dec 2024 02:47:26 +0100 Subject: [PATCH 06/13] Fix toolchain dir mkdirp Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/utils/download.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/download.mts b/src/utils/download.mts index 5a66545b..5b071667 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -101,7 +101,7 @@ const CMAKE_PLATFORMS: { [key: string]: string } = { const homeDirectory: string = homedir(); export function buildBasicToolchainPath(absolute: boolean): string { - const relPath = joinPosix(homeDirectory, ".pico-sdk", "toolchain"); + const relPath = joinPosix(".pico-sdk", "toolchain"); if (absolute) { return joinPosix(homeDirectory.replaceAll("\\", "/"), relPath); } else { From 8e795a60e67165a810185c30e1e61043f5d0c250 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:02:57 +0100 Subject: [PATCH 07/13] Finalize alpha Swift support Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- README.md | 41 +++++++++++++++++++++++++++++++++ scripts/bridge.h | 14 +++++++++++ scripts/pico_project.py | 21 +++++++++-------- src/commands/newProject.mts | 2 +- src/webview/newProjectPanel.mts | 7 +++++- web/main.js | 30 ++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 84f34128..d2d1fa98 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ For comprehensive setup instructions, refer to the [Getting Started guide](https ### Project Setup and Management - **Project Generator**: Easily create and configure new projects with support for advanced Pico features like I2C and PIO. The generator targets the Ninja build system and allows customization during project creation. +- **Swift Language Support**: Develop Pico projects using the Swift programming language by leveraging the latest **experimental** Swift toolchain. Generate Swift-enabled projects directly from the extension. - **Quick Project Setup**: Initiate new Pico projects directly from the Explorer view, when no workspace is open. - **MicroPython Support**: Create and develop MicroPython-based Pico projects with support provided through the [MicroPico](https://github.com/paulober/MicroPico) extension. @@ -89,6 +90,46 @@ For optimal functionality, consider enabling: When prompted, select the `Pico` kit in CMake Tools, and set your build and launch targets accordingly. Use CMake Tools for compilation, but continue using this extension for debugging, as CMake Tools debugging is not compatible with Pico. +## Swift Support + +The Pico VS Code extension supports Swift, enabling you to develop Raspberry Pi Pico projects using the Swift programming language. To enable Swift support, follow these steps: + +### 1. Install the Swift Experimental Toolchain + +Download and install the latest Swift experimental toolchain for your platform: + +- **Linux**: [Install Swift for Linux](https://www.swift.org/install/linux/#platforms) +- **macOS**: [Install Swift for macOS](https://www.swift.org/install/macos) + +> **Note:** Windows is not currently supported. + +### 2. Configure the Swift Toolchain + +#### **For Linux:** +Ensure the `swiftc` executable is included in your system's `PATH`. Once added, restart VS Code for the changes to take effect. + +#### **For macOS:** +If the build fails or the Swift toolchain isn’t detected, force the toolchain selection by adding the following line to your `~/.zprofile`: + +- **For system-wide installation:** + ```zsh + export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist) + ``` + +- **For user-specific installation:** + ```zsh + export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw $HOME/Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist) + ``` + +Then, restart your terminal and reopen VS Code. + +### 3. Create a New Pico Project with Swift + +1. Open VS Code. +2. Use the **"Generate Swift Code"** option to create a new Pico project with Swift support enabled. +> Note: At the moment, Swift support is only available for new projects based on Pico 2 and Pico SDK v2.1.0. +3. Start building your Swift-powered Pico project! + ## VS Code Profiles If you work with multiple microcontroller toolchains, consider installing this extension into a [VS Code Profile](https://code.visualstudio.com/docs/editor/profiles) to avoid conflicts with other toolchains. Follow these steps: diff --git a/scripts/bridge.h b/scripts/bridge.h index 2bb02cab..18a3dd67 100644 --- a/scripts/bridge.h +++ b/scripts/bridge.h @@ -4,6 +4,20 @@ #define PICO_DEFAULT_LED_PIN 6 #endif +#ifdef _HARDWARE_STRUCTS_INTERP_H +#ifdef interp0 +interp_hw_t* get_interp0(void) { + return interp0; +} +#endif + +#ifdef interp1 +interp_hw_t* get_interp1(void) { + return interp1; +} +#endif +#endif + #ifdef _HARDWARE_STRUCTS_SPI_H #ifdef spi0 spi_inst_t* get_spi0(void) { diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 77581091..ab36a42c 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -565,7 +565,8 @@ def GDB_NAME(): "// GPIO initialisation.", "// We will make this GPIO an input, and pull it up by default", "gpio_init(GPIO)", - "gpio_set_dir(GPIO, GPIO_IN)", + # "gpio_set_dir(GPIO, GPIO_IN)", + "PicoGPIO.setDirection(pin: GPIO, direction: .input)", "gpio_pull_up(GPIO)", "// See https://github.com/raspberrypi/pico-examples/tree/master/gpio for other gpio examples, including using interrupts", ), @@ -574,18 +575,18 @@ def GDB_NAME(): (), ( "// Interpolator example code", - "interp_config cfg = interp_default_config()", + "var cfg = interp_default_config()", "// Now use the various interpolator library functions for your use case", "// e.g. interp_config_clamp(&cfg, true)", "// interp_config_shift(&cfg, 2)", "// Then set the config ", - "interp_set_config(interp0, 0, &cfg)", + "interp_set_config(get_interp0(), 0, &cfg)", "// For examples of interpolator use see https://github.com/raspberrypi/pico-examples/tree/master/interp", ), ], "timer": [ ( - "func alarm_callback(id: alarm_id_t, user_data: void): int64_t {", + "func alarm_callback(id: alarm_id_t, user_data: UnsafeMutableRawPointer?) -> Int64 {", " // Put your timeout handler code in here", " return 0", "}", @@ -601,13 +602,13 @@ def GDB_NAME(): ( "// Watchdog example code", "if watchdog_caused_reboot() {", - ' printf("Rebooted by Watchdog!\\n")', + ' print("Rebooted by Watchdog!")', " // Whatever action you may take if a watchdog caused a reboot", "}", "", "// Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot", "// second arg is pause on debug which means the watchdog will pause when stepping through code", - "watchdog_enable(100, 1)", + "watchdog_enable(100, true)", "", "// You need to call this function at least more often than the 100ms in the enable call to prevent a reboot", "watchdog_update()", @@ -1205,11 +1206,10 @@ def GenerateCMake(folder, params): if params["useSwift"]: cmake_if_apple = ( "if(APPLE)\n" - " execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" + " execute_process(COMMAND xcrun --toolchain swift -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" " execute_process(COMMAND dirname ${SWIFTC} OUTPUT_VARIABLE SWIFTC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)\n" "elseif(WIN32)\n" - " execute_process(COMMAND where.exe swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" - ' string(REGEX REPLACE "(.*)\\\\\\\\[^\\\\\\\\]*$" "\\\\1" SWIFTC_DIR "${SWIFTC}")\n' + ' message(FATAL_ERROR "Embedded Swift is currently not supported on Windows")\n' "else()\n" " execute_process(COMMAND which swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)\n" " execute_process(COMMAND dirname ${SWIFTC} OUTPUT_VARIABLE SWIFTC_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)\n" @@ -1961,6 +1961,9 @@ def DoEverything(params): if args.debugger > len(debugger_list) - 1: args.debugger = 0 + if args.swift and args.cpp: + args.swift = False + if "RISCV" in args.toolchainVersion: if "COREV" in args.toolchainVersion: COMPILER_TRIPLE = COREV_TRIPLE diff --git a/src/commands/newProject.mts b/src/commands/newProject.mts index 45c01a74..ed690ff7 100644 --- a/src/commands/newProject.mts +++ b/src/commands/newProject.mts @@ -19,7 +19,7 @@ export default class NewProjectCommand extends CommandWithArgs { private readonly _logger: Logger = new Logger("NewProjectCommand"); private readonly _extensionUri: Uri; private static readonly micropythonOption = "MicroPython"; - private static readonly cCppOption = "C/C++"; + private static readonly cCppOption = "C/C++/Swift"; public static readonly id = "newProject"; diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index 6cd6b363..3c3218d2 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -2190,12 +2190,17 @@ export class NewProjectPanel {
  • + ${ + !isWindows + ? `
  • -
  • + ` + : "" + }
  • diff --git a/web/main.js b/web/main.js index 0bd912d1..934249c9 100644 --- a/web/main.js +++ b/web/main.js @@ -750,6 +750,36 @@ var exampleSupportedBoards = []; }); } + const swiftCodeGen = document.getElementById('swift-code-gen-cblist'); + if (swiftCodeGen) { + swiftCodeGen.addEventListener('change', function (event) { + const checked = event.currentTarget.checked; + if (!checked) { + return; + } + + const cppCodeGen = document.getElementById('cpp-code-gen-cblist'); + if (cppCodeGen) { + cppCodeGen.checked = false; + } + }); + } + + const cppCodeGen = document.getElementById('cpp-code-gen-cblist'); + if (cppCodeGen) { + cppCodeGen.addEventListener('change', function (event) { + const checked = event.currentTarget.checked; + if (!checked) { + return; + } + + const swiftCodeGen = document.getElementById('swift-code-gen-cblist'); + if (swiftCodeGen) { + swiftCodeGen.checked = false; + } + }); + } + const ninjaVersionRadio = document.getElementsByName('ninja-version-radio'); if (ninjaVersionRadio.length > 0) ninjaVersionRadio[0].checked = true; From e7aff64014aa8f8067ef933a9ce2a7c6dbfcd3bb Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:39:26 +0100 Subject: [PATCH 08/13] Fix invalid escape Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- scripts/pico_project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/pico_project.py b/scripts/pico_project.py index ab36a42c..eab042cd 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -1343,8 +1343,8 @@ def GenerateCMake(folder, params): " -target ${SWIFT_TARGET} -Xcc -mfloat-abi=soft -Xcc -fshort-enums\n" " -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library\n" " ${SWIFTC_DEFINITIONS_STR}\n" - f" $$\( echo '$' | tr '\;' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" - " $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\(.*\\\\\)/-Xcc -I\\\\1/g' \)\n" + f" $$\\( echo '$' | tr '\\;' '\\\\n' | sed -e 's/\\\\\\(.*\\\\\\)/-Xcc -I\\\\1/g' \\)\n" + " $$\\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\\\n' | sed -e 's/\\\\\\(.*\\\\\\)/-Xcc -I\\\\1/g' \\)\n" " -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h\n" " ${USERHOME}/.pico-sdk/sdk/PicoSDK.swift\n" f" ${{CMAKE_CURRENT_LIST_DIR}}/{main_file_name}\n" From da76b2047825983a205a733b8684fa6f2faf52de Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:04:59 +0100 Subject: [PATCH 09/13] Update phrasing Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/webview/newProjectPanel.mts | 49 +++++++++++++++++---------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index 3c3218d2..42ca3419 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -656,9 +656,10 @@ export class NewProjectPanel { this.dispose(); // required to support backslashes in macOS/Linux folder names - const targetPath = process.platform !== "win32" - ? this._projectRoot.fsPath - : this._projectRoot.fsPath.replaceAll("\\", "/"); + const targetPath = + process.platform !== "win32" + ? this._projectRoot.fsPath + : this._projectRoot.fsPath.replaceAll("\\", "/"); const result = await window.withProgress( { @@ -668,28 +669,29 @@ export class NewProjectPanel { }, async progress => { // download and install selected example - const result = await setupExample( - example, - targetPath - ); - + const result = await setupExample(example, targetPath); + if (result) { - this._logger.debug(`Successfully downloaded ${example.name} example.`); - + this._logger.debug( + `Successfully downloaded ${example.name} example.` + ); + progress.report({ increment: 100, message: `Successfully downloaded ${example.name} example.`, }); - + return true; } - - this._logger.error(`Failed to download ${example.name} example.`); - + + this._logger.error( + `Failed to download ${example.name} example.` + ); + progress.report({ increment: 100, }); - + return false; } ); @@ -775,7 +777,7 @@ export class NewProjectPanel { ); } break; - case 'updateSetting': + case "updateSetting": { const key = message.key as string; const value = message.value as boolean; @@ -1551,7 +1553,8 @@ export class NewProjectPanel { window.activeColorTheme.kind === ColorThemeKind.HighContrast ? "dark" : "light"; - const riscvDefaultSvgUri = defaultTheme === "dark" ? riscvWhiteSvgUri : riscvBlackSvgUri; + const riscvDefaultSvgUri = + defaultTheme === "dark" ? riscvWhiteSvgUri : riscvBlackSvgUri; this._versionBundlesLoader = new VersionBundlesLoader(this._extensionUri); @@ -1746,7 +1749,9 @@ export class NewProjectPanel { const riscvColorSvgUri = "${riscvColorSvgUri.toString()}"; - +
    ` : `

    - Warning: Project Import Wizard may not work for all projects, and will often require manual correction after the import. Swift Projects aren't supported at the moment. + Warning: Project Import Wizard may not work for all projects, and will often require manual correction after the import. Only C/C++ projects are supported at the moment.

    ` }
    @@ -2237,10 +2242,8 @@ export class NewProjectPanel {
    + defaultUseCmakeTools ? "checked" : "" + }>
    From 29726cff5e6a7e878b46c3492a6c4cd15471c69e Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:21:46 +0100 Subject: [PATCH 10/13] Fix toolchain helper files placement Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/utils/download.mts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/utils/download.mts b/src/utils/download.mts index 5b071667..2671356a 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -508,15 +508,14 @@ export async function downloadAndInstallSDK( ); // Constants taken from the SDK CMakeLists.txt files const TINYUSB_TEST_PATH = joinPosix( - "lib/tinyusb", "src/portable/raspberrypi/rp2040" + "lib/tinyusb", + "src/portable/raspberrypi/rp2040" ); const CYW43_DRIVER_TEST_FILE = joinPosix("lib/cyw43-driver", "src/cyw43.h"); const LWIP_TEST_PATH = joinPosix("lib/lwip", "src/Filelists.cmake"); const BTSTACK_TEST_PATH = joinPosix("lib/btstack", "src/bluetooth.h"); - const MBEDTLS_TEST_PATH = joinPosix("lib/mbedtls", "library/aes.c") - const submoduleChecks = [ - TINYUSB_TEST_PATH - ] + const MBEDTLS_TEST_PATH = joinPosix("lib/mbedtls", "library/aes.c"); + const submoduleChecks = [TINYUSB_TEST_PATH]; if (compareGe(version, "1.4.0")) { submoduleChecks.push(CYW43_DRIVER_TEST_FILE); submoduleChecks.push(LWIP_TEST_PATH); @@ -968,12 +967,12 @@ export async function downloadAndInstallToolchain( // includes helper stuff for swift code - pico-sdk c interop copyFileSync( joinPosix(getScriptsRoot(), "bridge.h"), - joinPosix(buildCMakeIncPath(true), "bridge.h") + joinPosix(buildBasicToolchainPath(true), "bridge.h") ); // includes helper stuff for the swift compiler (workaround) copyFileSync( joinPosix(getScriptsRoot(), "bridge.c"), - joinPosix(buildCMakeIncPath(true), "bridge.c") + joinPosix(buildBasicToolchainPath(true), "bridge.c") ); const archiveFileName = `${toolchain.version}.${artifactExt}`; From 660133054899bb461813cac249c8a5973e081154 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:34:38 +0100 Subject: [PATCH 11/13] Added swift dev toolchain detection Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/utils/swiftUtil.mts | 10 +++++----- src/webview/newProjectPanel.mts | 13 ++++++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/utils/swiftUtil.mts b/src/utils/swiftUtil.mts index b2d7745a..113e1611 100644 --- a/src/utils/swiftUtil.mts +++ b/src/utils/swiftUtil.mts @@ -4,15 +4,15 @@ import { promisify } from "util"; const execAsync = promisify(exec); export default async function checkSwiftRequirements(): Promise { - return true; - // check if swift is installed try { - await execAsync("swift --version"); + const result = await execAsync("swift --version"); - // TODO: check swift version + if (!result.stdout.includes("-dev")) { + return false; + } return true; - } catch (error) { + } catch { return false; } } diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index 42ca3419..297ae01b 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -1283,14 +1283,16 @@ export class NewProjectPanel { }); void window .showErrorMessage( - "Swift is required for Swift project generation. Please install Swift.", - "Install" + "Swift dev snapshot is required for Swift project generation. Please install the latest downloadable 'main' Swift Development Snapshot from swift.org to use Embedded Swift.", + "Open instructions (section 1-2 only)" ) .then(selected => { if (selected) { env.openExternal( - // TODO: check url - Uri.parse("https://swift.org/download/#releases") + //Uri.parse("https://swift.org/download/#releases") + Uri.parse( + "https://apple.github.io/swift-matter-examples/tutorials/swiftmatterexamples/setup-macos/" + ) ); } }); @@ -1691,6 +1693,7 @@ export class NewProjectPanel { // Restrict the webview to only load specific scripts const nonce = getNonce(); const isWindows = process.platform === "win32"; + const isMacOS = process.platform === "darwin"; // Get the default values from global state const useProjectNameAsEntryPointFileName = @@ -2196,7 +2199,7 @@ export class NewProjectPanel {
  • ${ - !isWindows + isMacOS ? `
  • From 5a53810fba00ac92b1e1fa84c7749840551414e6 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:39:58 +0100 Subject: [PATCH 12/13] Exclude linux from swift install instructions as not tested yet Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d2d1fa98..58492fbd 100644 --- a/README.md +++ b/README.md @@ -98,15 +98,15 @@ The Pico VS Code extension supports Swift, enabling you to develop Raspberry Pi Download and install the latest Swift experimental toolchain for your platform: -- **Linux**: [Install Swift for Linux](https://www.swift.org/install/linux/#platforms) +[//]: <> (- **Linux**: [Install Swift for Linux](https://www.swift.org/install/linux/#platforms)) - **macOS**: [Install Swift for macOS](https://www.swift.org/install/macos) -> **Note:** Windows is not currently supported. +> **Note:** Windows and Linux are currently not supported. ### 2. Configure the Swift Toolchain -#### **For Linux:** -Ensure the `swiftc` executable is included in your system's `PATH`. Once added, restart VS Code for the changes to take effect. +[//]: <> (#### **For Linux:**) +[//]: <> (Ensure the `swiftc` executable is included in your system's `PATH`. Once added, restart VS Code for the changes to take effect.) #### **For macOS:** If the build fails or the Swift toolchain isn’t detected, force the toolchain selection by adding the following line to your `~/.zprofile`: From bef2cf8ebf34db6ff2dcccedc3184d66a8880cf3 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:41:07 +0100 Subject: [PATCH 13/13] Fix typo Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/webview/newProjectPanel.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index 297ae01b..c7415c49 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -1284,7 +1284,7 @@ export class NewProjectPanel { void window .showErrorMessage( "Swift dev snapshot is required for Swift project generation. Please install the latest downloadable 'main' Swift Development Snapshot from swift.org to use Embedded Swift.", - "Open instructions (section 1-2 only)" + "Open instructions (section 1,2,4 only)" ) .then(selected => { if (selected) {