diff --git a/README.md b/README.md index 6e05f1e..b5bdae1 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. @@ -87,6 +88,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/PicoSDK.swift b/scripts/PicoSDK.swift new file mode 100644 index 0000000..ea40c7e --- /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.c b/scripts/bridge.c new file mode 100644 index 0000000..cb7296f --- /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 0000000..18a3dd6 --- /dev/null +++ b/scripts/bridge.h @@ -0,0 +1,108 @@ +#pragma once + +#ifndef PICO_DEFAULT_LED_PIN +#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) { + 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. + * + * 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 + +#ifdef uart0 +/** + * @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; +} +#endif + +#ifdef uart1 +/** + * @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 + +#endif diff --git a/scripts/pico_project.py b/scripts/pico_project.py index 89e0f92..291e444 100644 --- a/scripts/pico_project.py +++ b/scripts/pico_project.py @@ -387,6 +387,273 @@ 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 = 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: UInt32 = 4", + "let UART_RX_PIN: UInt32 = 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 = get_spi0()", + "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.", + "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", + "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", + ), + ], + "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 = get_i2c0()", + "let I2C_SDA: UInt32 = 8", + "let I2C_SCL: UInt32 = 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 = 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.", + "", + "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", + " 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", + "// 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": [ + ( + "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)", + "", + ' 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)", + " 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", + "guard let pio = get_pio0() else {", + ' fatalError("PIO0 not available")', + "}", + "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)", + "// For more pio examples see https://github.com/raspberrypi/pico-examples/tree/master/pio", + ), + ], + "clocks": [ + (), + ( + '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", + ), + ], + "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)", + "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", + ), + ], + "interp": [ + (), + ( + "// Interpolator example code", + "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(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: UnsafeMutableRawPointer?) -> Int64 {", + " // 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, nil, 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() {", + ' 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, true)", + "", + "// 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)", + '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("\\(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", + ), + ], + "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", + '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) {', + ' print("failed to connect.")', + " return 1", + "} else {", + ' print("Connected.")', + " // Read the ip address in a human readable way", + " let ip_address = (uint8_t*)&(cyw43_state.netif[0].ip_addr.addr)", + ' print("IP address \\(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[ @@ -445,6 +712,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)}" @@ -639,22 +909,38 @@ 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") + elif swift: + filename = Path(folder) / (executableName + ".swift") else: filename = Path(folder) / (executableName + ".c") file = open(filename, "w") - - main = "#include \n" '#include "pico/stdlib.h"\n' - file.write(main) + bridging_file = None + + 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: @@ -664,64 +950,130 @@ 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 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: if len(stdlib_examples_list[feat][H_FILE]) == 0: continue o = f'#include "{stdlib_examples_list[feat][H_FILE]}"\n' - file.write(o) + if swift and bridging_file: + 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 and bridging_file: + 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 bridging_file 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" - ' printf("Wi-Fi init failed\\n");\n' - " return -1;\n" - " }\n\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]: + 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" + " // 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" + ) file.write(main) + if bridging_file: + bridging_file.write("// always include last\n") + bridging_file.write("#include \n\n") + 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"] @@ -758,7 +1110,7 @@ def GenerateCMake(folder, params): 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"project({projectName} {"" if params["useSwift"] else "C CXX ASM"})\n" ) cmake_header3 = ( @@ -828,6 +1180,19 @@ 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 --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" + ' 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" + "endif()\n" + ) + file.write(cmake_if_apple) file.write(cmake_header2) if params["exceptions"]: @@ -838,6 +1203,29 @@ def GenerateCMake(folder, params): file.write(cmake_header3) + if params["useSwift"]: + 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' + ' 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" + '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_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") @@ -852,7 +1240,103 @@ 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} ${{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) + 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" + 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" + "# ==== 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' + 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" + " ${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" + ) + 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") @@ -900,20 +1384,40 @@ 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) - 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("# 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") + 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"]: + 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"]: @@ -941,6 +1445,7 @@ def generateProjectFiles( cmakePath, openOCDVersion, useCmakeTools, + useSwift, ): oldCWD = os.getcwd() @@ -951,6 +1456,7 @@ def generateProjectFiles( if not os.path.isfile(".gitignore"): file = open(".gitignore", "w") file.write("build\n") + file.write(".DS_Store\n") file.close() debugger = debugger_config_list[debugger] @@ -1065,7 +1571,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", @@ -1167,6 +1674,9 @@ def generateProjectFiles( ] } + if useSwift: + extensions["recommendations"].append("sswg.swift-lang") + tasks = f"""{{ "version": "2.0.0", "tasks": [ @@ -1352,6 +1862,7 @@ def DoEverything(params): features_and_examples, params["wantCPP"], params["wantEntryProjName"], + params["useSwift"], ) # If we have any ancilliary files, copy them to our project folder @@ -1407,6 +1918,7 @@ def DoEverything(params): params["cmakePath"], params["openOCDVersion"], params["useCmakeTools"], + params["useSwift"], ) os.chdir(oldCWD) @@ -1426,6 +1938,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 @@ -1483,6 +1998,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/commands/newProject.mts b/src/commands/newProject.mts index 45c01a7..ed690ff 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/utils/download.mts b/src/utils/download.mts index 5be48ed..852c321 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -96,6 +96,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(".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); } @@ -111,7 +120,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); @@ -477,6 +485,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) && @@ -941,6 +957,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( diff --git a/src/utils/swiftUtil.mts b/src/utils/swiftUtil.mts new file mode 100644 index 0000000..b2d7745 --- /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 3b3f4e7..1e99ff7 100644 --- a/src/webview/activityBar.mts +++ b/src/webview/activityBar.mts @@ -41,7 +41,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 d938486..45b190b 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; } @@ -159,6 +162,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)", } @@ -243,6 +247,8 @@ function enumToParam( return "-e"; case CodeOption.cpp: return "-cpp"; + case CodeOption.swift: + return "-swift"; case CodeOption.cppRtti: return "-cpprtti"; case CodeOption.cppExceptions: @@ -1212,6 +1218,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 ); @@ -1244,6 +1283,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), @@ -1771,7 +1811,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.

` }
@@ -2083,6 +2123,17 @@ export class NewProjectPanel {
+ ${ + !isWindows + ? ` +
  • +
    + + +
    +
  • ` + : "" + }
  • diff --git a/web/main.js b/web/main.js index f54de8e..201a368 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, @@ -748,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;