|
| 1 | +# Integrating with Zephyr |
| 2 | + |
| 3 | +**⚠️ Embedded Swift is experimental. This document might be out of date with latest development.** |
| 4 | + |
| 5 | +For an introduction and motivation into Embedded Swift, please see "[A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)", a Swift Evolution document highlighting the main goals and approaches. |
| 6 | + |
| 7 | +Refer to the [nrfx-blink-sdk](../../../../nrfx-blink-sdk/) project for a complete working "blinky" example that can be used as a starting point. However, for a formal introduction to setting up a Swift Zephyr project and explanations into the various options, continue reading this document. |
| 8 | + |
| 9 | +## Zephyr Setup |
| 10 | + |
| 11 | +Before setting up a Swift project that works with Zephyr, you need to setup dependencies and a Zephyr workspace as per the [Getting Started Guide](https://docs.zephyrproject.org/latest/develop/getting_started/index.html). Regardless of your platform (macOS or Linux), ensure that you can build the blinky example without errors before starting with Swift integration: |
| 12 | + |
| 13 | +```bash |
| 14 | +cd ~/zephyrproject/zephyr |
| 15 | +west build -p always -b <your-board-name> samples/basic/blinky |
| 16 | +``` |
| 17 | + |
| 18 | +By default, the `main` revision of the Zephyr sources are checked out when calling `west init`, which contains pre-release and development changes that may cause instability and changing APIs that are not desirable. To checkout a specific release version of Zephyr, use the following commands: |
| 19 | + |
| 20 | +```bash |
| 21 | +cd ~/zephyrproject/zephyr |
| 22 | +git checkout v4.1.0 |
| 23 | +west update |
| 24 | +west packages pip --install |
| 25 | + |
| 26 | +# For older versions of Zephyr (pre 4.1.0), use: |
| 27 | +pip install -r ~/zephyrproject/zephyr/scripts/requirements.txt |
| 28 | +``` |
| 29 | + |
| 30 | +Refer to the [Zephyr Releases](https://docs.zephyrproject.org/latest/releases/index.html) page for more information on current and LTS releases. |
| 31 | + |
| 32 | +## Project Setup |
| 33 | + |
| 34 | +Once Zephyr is setup, the next step is to setup a project with a bridging header, `CMakeLists.txt`, `Main.swift`, and `prj.conf`: |
| 35 | + |
| 36 | +```plain |
| 37 | +SwiftZephyrProject/BridgingHeader.h |
| 38 | +SwiftZephyrProject/CMakeLists.txt |
| 39 | +SwiftZephyrProject/Main.swift |
| 40 | +SwiftZephyrProject/prj.conf |
| 41 | +``` |
| 42 | + |
| 43 | +These are the minimum required files in order to build a Zephyr project. For example, `prj.conf` is required even if it is empty, or the project will not build. |
| 44 | + |
| 45 | +Inside of `BridgingHeader.h`, add the following content as a minimum: |
| 46 | + |
| 47 | +```c |
| 48 | +#pragma once |
| 49 | + |
| 50 | +#include <autoconf.h> |
| 51 | +``` |
| 52 | + |
| 53 | +The `Main.swift` file must contain a `static func main()` as follows: |
| 54 | + |
| 55 | +```swift |
| 56 | +@main |
| 57 | +struct Main { |
| 58 | + static func main() { |
| 59 | + // code |
| 60 | + } |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +### CMakeLists.txt Setup |
| 65 | + |
| 66 | +The `CMakeLists.txt` setup is more involved and complex since target, compilation flags, and library linking must be specified for Swift. First, some initial setup and flags: |
| 67 | + |
| 68 | +```cmake |
| 69 | +cmake_minimum_required(VERSION 3.29) |
| 70 | +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) |
| 71 | +
|
| 72 | +# Use the armv7em-none-none-eabi target triple for Swift |
| 73 | +set(CMAKE_Swift_COMPILER_TARGET armv7em-none-none-eabi) |
| 74 | +# Enable "wmo" as needed by Embedded Swift |
| 75 | +set(CMAKE_Swift_COMPILATION_MODE wholemodule) |
| 76 | +# FIXME: Skip checking if the compiler works |
| 77 | +set(CMAKE_Swift_COMPILER_WORKS true) |
| 78 | +
|
| 79 | +# Create a new project called "SwiftZephyrProject" and enable "Swift" as a supported language |
| 80 | +project(SwiftZephyrProject Swift) |
| 81 | +``` |
| 82 | + |
| 83 | +The following flags have been curated to work well with `armv7em-none-none-eabi` target with `swiftc`. Some flags may need to change depending on the chosen target: |
| 84 | + |
| 85 | +```cmake |
| 86 | +# Set global Swift compiler flags |
| 87 | +add_compile_options( |
| 88 | + # Enable Embedded Swift |
| 89 | + "$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature Embedded>" |
| 90 | +
|
| 91 | + # Enable function sections to enable dead code stripping on elf |
| 92 | + "$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -function-sections>" |
| 93 | +
|
| 94 | + # Use software floating point operations matching GCC |
| 95 | + "$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -mfloat-abi=soft>" |
| 96 | +
|
| 97 | + # Use compacted C enums matching GCC |
| 98 | + "$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -fshort-enums>" |
| 99 | +
|
| 100 | + # Disable PIC |
| 101 | + "$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -fno-pic>" |
| 102 | +
|
| 103 | + # Add Libc include paths |
| 104 | + "$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -I -Xcc ${ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi/picolibc/include>" |
| 105 | +) |
| 106 | +``` |
| 107 | + |
| 108 | +The following block will automatically grab Zephyr compilation flags (such as `-D__ZEPHYR__=1` and `-DKERNEL`) and set them as Swift compiler definitions. This is required to successfully build Swift code that works with Zephyr: |
| 109 | + |
| 110 | +```cmake |
| 111 | +# Add definitions from Zephyr to -Xcc flags |
| 112 | +get_target_property(ZEPHYR_DEFINES zephyr_interface INTERFACE_COMPILE_DEFINITIONS) |
| 113 | +if(ZEPHYR_DEFINES) |
| 114 | + foreach(flag ${ZEPHYR_DEFINES}) |
| 115 | + # Ignore expressions like "$<SOMETHING>" |
| 116 | + string(FIND "${flag}" "$<" start_of_expression) |
| 117 | + if(NOT start_of_expression EQUAL -1) |
| 118 | + continue() |
| 119 | + endif() |
| 120 | +
|
| 121 | + add_compile_options("$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xcc -D${flag}>") |
| 122 | + endforeach() |
| 123 | +endif() |
| 124 | +``` |
| 125 | + |
| 126 | +Finally, setup targets, libraries, and additional compile options: |
| 127 | + |
| 128 | +```cmake |
| 129 | +# The Swift code providing "main" needs to be in an OBJECT library (instead of STATIC library) to make sure it actually gets linker. |
| 130 | +# A STATIC library would get dropped from linking because Zephyr provides a default weak empty main definition. |
| 131 | +add_library(app_swift OBJECT Main.swift) |
| 132 | +
|
| 133 | +add_dependencies(app_swift syscall_list_h_target) |
| 134 | +target_compile_options(app_swift PRIVATE |
| 135 | + -parse-as-library |
| 136 | +
|
| 137 | + -Osize |
| 138 | +
|
| 139 | + -Xfrontend -disable-stack-protector |
| 140 | +
|
| 141 | + # FIXME: add dependency on BridgingHeader.h |
| 142 | + -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h |
| 143 | +) |
| 144 | +
|
| 145 | +# Copy include paths from C target to Swift target |
| 146 | +target_include_directories(app_swift PRIVATE |
| 147 | + "$<TARGET_PROPERTY:app,INCLUDE_DIRECTORIES>" |
| 148 | +) |
| 149 | +
|
| 150 | +# Link the Swift target into the primary target |
| 151 | +target_link_libraries(app PRIVATE app_swift) |
| 152 | +``` |
0 commit comments