Unable to build image using embedded swift with WiFi - C compiler being invoked?

I used the embedded-swift example blink as a starting point that builds nicely and runs on an ESP32C6, using VSC on macOS. I would like to connect to wifi.

I have checked that esp_wifi, etc. components are loaded. However, just adding the header files to BridgingHeader.h results in a mass of errors:

#include <WiFi.h>

The first error is:

- error: unknown type name 'class'

This is the error you get when you invoke the C compiler rather than the C++ compiler. If I remove this header and use some of the subsidiary ones, such as:

#include <esp_system.h>
#include <esp_wifi.h>
#include <nvs_flash.h>

Then these do not error and I can make successful calls to:

var ret = nvs_flash_init()

I am not experienced in CMake, but I guess the error is caused by stripping out the normal CMakeLists.txt to make it work with embedded swift. And lack of CMake knowledge means I have no idea where to start to try to fix it!

# Register the app as an IDF component
idf_component_register(
    SRCS /dev/null # We don't have any C++ sources
    PRIV_INCLUDE_DIRS "."
    LDFRAGMENTS "linker.lf"
)

idf_build_get_property(target IDF_TARGET)
idf_build_get_property(arch IDF_TARGET_ARCH)

if("${arch}" STREQUAL "xtensa")
    message(FATAL_ERROR "Not supported target: ${target}")
endif()

if(${target} STREQUAL "esp32c2" OR ${target} STREQUAL "esp32c3")
    set(march_flag "rv32imc_zicsr_zifencei")
    set(mabi_flag "ilp32")
elseif(${target} STREQUAL "esp32p4")
    set(march_flag "rv32imafc_zicsr_zifencei")
    set(mabi_flag "ilp32f")
else()
    set(march_flag "rv32imac_zicsr_zifencei")
    set(mabi_flag "ilp32")
endif()

# Clear the default COMPILE_OPTIONS which include a lot of C/C++ specific compiler flags that the Swift compiler will not accept
get_target_property(var ${COMPONENT_LIB} COMPILE_OPTIONS)
set_target_properties(${COMPONENT_LIB} PROPERTIES COMPILE_OPTIONS "")

# Compute -Xcc flags to set up the C and C++ header search paths for Swift (for bridging header).
set(SWIFT_INCLUDES)
foreach(dir ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES})
    string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-Xcc ")
    string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-I${dir} ")
endforeach()
foreach(dir ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
    string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-Xcc ")
    string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-I${dir} ")
endforeach()

# Swift compiler flags to build in Embedded Swift mode, optimize for size, choose the right ISA, ABI, etc.
target_compile_options(${COMPONENT_LIB} PUBLIC "$<$<COMPILE_LANGUAGE:Swift>:SHELL:
        -target riscv32-none-none-eabi
        -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library -Osize
        -Xcc -march=${march_flag} -Xcc -mabi=${mabi_flag}
        
        -pch-output-dir /tmp
        -Xfrontend -enable-single-module-llvm-emission
        
        ${SWIFT_INCLUDES}

        -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h
    >")

# Enable Swift support in CMake, force Whole Module builds (required by Embedded Swift), and use "CMAKE_Swift_COMPILER_WORKS" to
# skip the trial compilations which don't (yet) correctly work when cross-compiling.
set(CMAKE_Swift_COMPILER_WORKS YES)
set(CMAKE_Swift_COMPILATION_MODE_DEFAULT wholemodule)
set(CMAKE_Swift_COMPILATION_MODE wholemodule)
enable_language(Swift)

# List of Swift source files to build.
target_sources(${COMPONENT_LIB}
    PRIVATE
    Main.swift
    Led.swift
)
1 Like

Yes, that's exactly it. I think we need to turn on C++ interop, which then parses headers in C++ mode. The primary flag for that is -cxx-interoperability-mode=default, but there's a couple other ones you might want (specifying C++ language version), see e.g. https://github.com/apple/swift-matter-examples/blob/main/empty-template/main/CMakeLists.txt#L32 for a sample config.

Thank you very much for your reply. Including the one line

-cxx-interoperability-mode=default

Gets me further, but it then errors with:

esp-idf/components/esp_wifi/include/esp_wifi.h:251:9: note: macro 'WIFI_INIT_CONFIG_DEFAULT' unavailable: function like macros not supported

I have encountered this before when wrapping libraries for swift on a raspberry pi, it was only one and easy to re-write the macro as a swift function in that context. I get the feeling that this approach might not work here. Is there a better way? Thank you again for your help.

I tried creating a stub function for WIFI_INIT_CONFIG_DEFAULT and it then gives a final error in linking:

undefined reference to `_swift_exceptionPersonality'

There is an outstanding ticket on GitHub for this same error, which I see you are aware of:

I have done some digging in the source. It looks like the symbol is missing because the function isn't included in the build. Is it as simple as changing the #if directive to enable its inclusion?

Exception.cpp

I tried building my own toolchain having removed the #if, but after about 40 minutes it reports an error about a missing target that I can't resolve. I've misplaced the name of the target but I will find it later and update this post.

To resolve

undefined reference to `_swift_exceptionPersonality'

you need to tell Clang to disable exceptions support for the C++ code (which is likely what you want anyway, most embedded platforms don't want the relatively heavyweight unwinding runtime that is needed for C++ exceptions to work...):

        -Xcc -fno-exceptions
        -Xcc -fno-rtti

These are also in the sample setup in https://github.com/apple/swift-matter-examples/blob/main/empty-template/main/CMakeLists.txt#L32.

Thank you. This has taken me a long way forward! I can now get as far as:

#include <WiFi.h> // in BridgingHeader.h

And

count = WiFi.scanNetworks(false, true)

This compiles but gives:

undefined reference to `WiFiScanClass::scanNetworks(bool, bool, bool, unsigned int, unsigned char, char const*, unsigned char const*)

At link-time.
It's a bit odd, because WiFi.begin(said,pwd) fails at compile-time saying it doesn't exist. I thought it was implicitly included in the build, but I have tried adding it explicitly but with no difference. I can lots of esp_wifi/lib/esp32c6/lib... in the log.

However, I can successfully build an image with the lower-level calls, such as:

esp_wifi_scan_get_ap_num(&ap_count)

I thought it might be that I hadn't successfully downloaded the dependencies, but I can grep wifiscanclass in a number of places in the build folder.

So, thank you again for all your help.