How to add a new software component
Overview
The Open IoT SDK provides a collection of software components. They are specified by CMake scripts in the
components
directory and downloaded as part of the build process.
This guide explains how to contribute a new component to the Open IoT SDK.
Tip
This guide only applies to the process of adding components to the Open IoT SDK. If a user intends to add a component to their own project only, they can do so in any way that is valid in CMake without having to modify the Open IoT SDK.
Fetching
Components that offer major features (e.g. cloud service provider clients, storage APIs, etc.) are located in their respective repositories and fetched by the Open IoT SDK. The Open IoT SDK itself only provides integration with those repositories.
Example
Take Mbed TLS for example:
- The repository URL and revision are listed in
cmake/DeclareSdkComponents.cmake
as aFetchContent_Declare
entry, withmbedtls
as the fetching identifier - The script
components/CMakeLists.txt
addsmbedtls
as a subdirectory - The script
components/mbedtls/CMakeLists.txt
checks if the fetching identifiermbedtls
is listed inIOTSDK_FETCH_LIST
(set by the user project), and if so, callsFetchContent_MakeAvailable(mbedtls)
to fetch the repository
Note
The order of the add_subdirectory()
calls matters in components/CMakeLists.txt
.
For example, aws-iot-device-sdk-embedded-c
depends on mbedtls
, so mbedtls
must be made available first.
CMake support
Repositories with built-in CMake support
During the build process, if a fetched repository contains a CMakeLists.txt
in its root directory, it will be
automatically processed by the FetchContent_MakeAvailable()
call.
The repository's CMake support defines necessary CMake targets such as libraries.
Example
The Mbed TLS repository comes with CMake support and defines the libraries mbedtls
, mbedx509
and mbedcrypto
. Applications and other libraries can link against any of those through target_link_libraries()
in CMake.
Repositories without CMake support
If the fetched repository has no CMake support, we need to create CMake targets by ourselves in the Open IoT SDK.
Example
The CMSIS Version 5 repository does not support CMake, but we need the CMSIS-RTOS v2 code from it.
To add CMake support, the Open IoT SDK's components/cmsis_5/CMakeLists.txt
defines a library cmsis-rtos-api
that points to the source files and include directory inside the fetched repository:
add_library(cmsis-rtos-api
${cmsis-5_SOURCE_DIR}/CMSIS/RTOS2/Source/os_systick.c
${cmsis-5_SOURCE_DIR}/CMSIS/RTOS2/Source/os_tick_ptim.c
)
target_include_directories(cmsis-rtos-api
PUBLIC
${cmsis-5_SOURCE_DIR}/CMSIS/RTOS2/Include
)
The variable cmsis-5_SOURCE_DIR
used above contains the absolute path to the fetched repository. It is set by
FetchContent_MakeAvailable(cmsis-5)
earlier in the CMakeLists.txt
.
Configuration
Most components can be configured to cater for different use cases.
Some components come with CMake options for configuration. For example, Azure RTOS ThreadX provides a TX_USER_FILE
option, which a user can set to their own tx_user.h
header's path that overrides RTOS parameters. In this case, we simply let user projects use the component's built-in CMake options.
For a component that does not come with CMake configuration options, we should
- declare a configuration target,
add_library(<component-name>-config INTERFACE)
, where<component-name>
is the component's name - link each library of the component against
<component-name>-config
, so that the library picks up the configuration - mention in the documentation that a user application can add compile-time macros or include paths to this target.
Example
The Open IoT SDK's support for CMSIS Version 5 in components/cmsis_5/
:
- declares the configuration target
cmsis-config
inCMakeLists.txt
- links the existing library
iotsdk-cmsis-core-device
againstcmsis-config
in thatCMakeLists.txt
- documents the usage of
cmsis-config
inkeil_rtx_v5.md
Integration layers (glue) between components
Some components have been pre-integrated with one another via integration layers, also informally known as glue. An integration layer may have been provided by a fetched repository already, but if this is not the case, we can implement our own integration layer and ship the code in the Open IoT SDK.
Example
An implementation of Mbed TLS multithreading support based on CMSIS-RTOS v2 is
- coded in
mbedtls_threading_cmsis_rtos.c
- available as the CMake target
mbedtls-threading-cmsis-rtos
The code is entirely in the Open IoT SDK repository.
Custom builds
Certain components, such as TrustedFirmware-M and ML Embedded Evaluation Kit, must be built as standalone projects, separately from the Open IoT SDK itself.
Info
Those components allow only standalone builds, because their build options must not be intermingled with those from other projects (such as the Open IoT SDK). This ensures architectural features are enabled correctly for optimal security (in the case of TrustedFirmware-M) or performance (in the case of ML Embedded Evaluation Kit), exactly as they have been tested.
The Open IoT SDK sets the custom build command, triggers the custom build and makes use of the build outputs once it
completes. This is done using CMake's ExternalProject
modules.
Example
TrustedFirmware-M is built by the Open IoT SDK as follows:
The script components/trusted-firmware-m/CMakeLists.txt includes
BuildTfm.cmake which calls ExternalProject_Add()
to specify how
to build TrustedFirmware-M.
To understand what each parameter does, see the documentation for ExternalProject
. Those
detailed in the following paragraphs are particularly important.
Because the repository is fetched using the FetchContent
module as described above, the SOURCE_DIR
and BINARY_DIR
parameters for the build are set to ${trusted-firmware-m_SOURCE_DIR}
and
${trusted-firmware-m_BINARY_DIR}
which are the directories created by the FetchContent
module.
Note
This should be done for all ExternalProject
builds with the SDK. The reason is that we prefer FetchContent
over ExternalProject
to handle fetching, due to all the benefits described in Build Concepts.
By default, ExternalProject
silently performs the custom build in the background and suppresses the build's
terminal output until done. This may give the user a false impression that the build is stuck. To print the terminal
output in real time, allowing the user to see the progress, we set the following parameters in the body of the
ExternalProject_Add()
call:
Tip
Some projects require additional steps added by ExternalProject_Add_Step()
, and each additional step's terminal output
can be enabled in the body of the ExternalProject_Add_Step()
call:
An example is BuildMlEmbeddedEvaluationKit.cmake.
By default, ExternalProject
performs the custom build only once, and it does not do any rebuild even after the
user has modified the code. To ensure the build is performed every time, allowing any code changes to be picked up,
we set the following parameter:
Note
This enables an incremental rebuild rather than a clean build, so its impact on build time is minimal.
TrustedFirmware-M is a CMake projects, so we use the CMAKE_ARGS
parameter to specify the CMake command line
arguments for the build.
Tip
You can also build a non-CMake project by specifying the BUILD_COMMAND
parameter instead of CMAKE_ARGS
.
BUILD_BYPRODUCTS
lists build outputs that we will make use of, i.e. files that are referenced in the Open IoT
SDK's CMake scripts. By default, CMake checks that any referenced files exist before build starts, and
BUILD_BYPRODUCTS
disables upfront checks on files that will be generated later.
Warning
If a file is missing from BUILD_BYPRODUCTS
, you will get a CMake error saying the file cannot be found.