Skip to content

Commit

Permalink
cmake: New target which generates a sort of development kit for llext
Browse files Browse the repository at this point in the history
Loadable extensions need access to Zephyr (and Zephyr application)
includes and some CFLAGS to be properly built. This patch adds a new
target, `llext-edk`, which generates a tar file with those includes and
flags that can be loaded from cmake and make files.

A Zephyr application willing to expose some API to extensions it loads
only need to add the include directories describing such APIs to the
Zephyr ones via zephyr_include_directories() CMake call.

A new Kconfig option, CONFIG_LLEXT_EDK_NAME allows one to control some
aspects of the generated file, which enables some customization - think
of an application called ACME, willing to have a ACME_EXTENSION_KIT or
something.

All EDK Kconfig options are behind CONFIG_LLEXT_EDK, which doesn't
depend on LLEXT directly - so that EDK features can be leveraged by
downstream variations of loadable extensions.

Also, each arch may need different compiler flags for extensions: those
are handled by the `LLEXT_CFLAGS` cmake flag. An example is set for GCC
ARM.

Finally, EDK throughout this patch means Extension Development Kit,
which is a bad name, but at least doesn't conflict with SDK.

Signed-off-by: Ederson de Souza <[email protected]>
  • Loading branch information
edersondisouza authored and fabiobaltieri committed May 17, 2024
1 parent 9cba85b commit d156a03
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 0 deletions.
20 changes: 20 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2061,6 +2061,26 @@ if((CMAKE_BUILD_TYPE IN_LIST build_types) AND (NOT NO_BUILD_TYPE_WARNING))
endif()
endif()

# Extension Development Kit (EDK) generation.
set(llext_edk_file ${PROJECT_BINARY_DIR}/${CONFIG_LLEXT_EDK_NAME}.tar.xz)
add_custom_command(
OUTPUT ${llext_edk_file}
COMMAND ${CMAKE_COMMAND}
-DPROJECT_BINARY_DIR=${PROJECT_BINARY_DIR}
-DAPPLICATION_SOURCE_DIR=${APPLICATION_SOURCE_DIR}
-DINTERFACE_INCLUDE_DIRECTORIES="$<JOIN:$<TARGET_PROPERTY:zephyr_interface,INTERFACE_INCLUDE_DIRECTORIES>,:>"
-Dllext_edk_file=${llext_edk_file}
-DAUTOCONF_H=${AUTOCONF_H}
-DLLEXT_CFLAGS="${LLEXT_CFLAGS}"
-Dllext_edk_name=${CONFIG_LLEXT_EDK_NAME}
-DWEST_TOPDIR=${WEST_TOPDIR}
-DZEPHYR_BASE=${ZEPHYR_BASE}
-P ${ZEPHYR_BASE}/cmake/llext-edk.cmake
DEPENDS ${logical_target_for_zephyr_elf}
COMMAND_EXPAND_LISTS
)
add_custom_target(llext-edk DEPENDS ${llext_edk_file})

# @Intent: Set compiler specific flags for standard C/C++ includes
# Done at the very end, so any other system includes which may
# be added by Zephyr components were first in list.
Expand Down
6 changes: 6 additions & 0 deletions cmake/compiler/gcc/target_arm.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,9 @@ set(LLEXT_APPEND_FLAGS
-mlong-calls
-mthumb
)

set(LLEXT_CFLAGS
-mlong-calls
-mthumb
-nodefaultlibs
-c)
109 changes: 109 additions & 0 deletions cmake/llext-edk.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Copyright (c) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

# This script generates a tarball containing all headers and flags necessary to
# build an llext extension. It does so by copying all headers accessible from
# INTERFACE_INCLUDE_DIRECTORIES and generating a Makefile.cflags file (and a
# cmake.cflags one) with all flags necessary to build the extension.
#
# The tarball can be extracted and used in the extension build system to include
# all necessary headers and flags. File paths are made relative to a few key
# directories (build/zephyr, zephyr base, west top dir and application source
# dir), to avoid leaking any information about the host system.
#
# The following arguments are expected:
# - llext_edk_name: Name of the extension, used to name the tarball and the
# install directory variable for Makefile.
# - INTERFACE_INCLUDE_DIRECTORIES: List of include directories to copy headers
# from. It should simply be the INTERFACE_INCLUDE_DIRECTORIES property of the
# zephyr_interface target.
# - AUTOCONF_H: Name of the autoconf.h file, used to generate the imacros flag.
# - llext_edk_file: Output file name for the tarball.
# - LLEXT_CFLAGS: Additional flags to be added to the generated flags.
# - ZEPHYR_BASE: Path to the zephyr base directory.
# - WEST_TOPDIR: Path to the west top directory.
# - APPLICATION_SOURCE_DIR: Path to the application source directory.
# - PROJECT_BINARY_DIR: Path to the project binary build directory.

cmake_minimum_required(VERSION 3.20.0)

set(llext_edk ${PROJECT_BINARY_DIR}/${llext_edk_name})
set(llext_edk_inc ${llext_edk}/include)

string(REGEX REPLACE "[^a-zA-Z0-9]" "_" llext_edk_name_sane ${llext_edk_name})
string(TOUPPER ${llext_edk_name_sane} llext_edk_name_sane)
set(install_dir_var "${llext_edk_name_sane}_INSTALL_DIR")

cmake_path(CONVERT "${INTERFACE_INCLUDE_DIRECTORIES}" TO_CMAKE_PATH_LIST include_dirs)

set(autoconf_h_edk ${llext_edk_inc}/${AUTOCONF_H})
cmake_path(RELATIVE_PATH AUTOCONF_H BASE_DIRECTORY ${PROJECT_BINARY_DIR} OUTPUT_VARIABLE autoconf_h_rel)

list(APPEND all_flags_make
"${LLEXT_CFLAGS} -imacros\$(${install_dir_var})/include/zephyr/${autoconf_h_rel}")
list(APPEND all_flags_cmake
"${LLEXT_CFLAGS} -imacros\${CMAKE_CURRENT_LIST_DIR}/include/zephyr/${autoconf_h_rel}")

file(MAKE_DIRECTORY ${llext_edk_inc})
foreach(dir ${include_dirs})
if (NOT EXISTS ${dir})
continue()
endif()
cmake_path(IS_PREFIX PROJECT_BINARY_DIR ${dir} NORMALIZE to_prj_bindir)
cmake_path(IS_PREFIX ZEPHYR_BASE ${dir} NORMALIZE to_zephyr_base)
if("${WEST_TOPDIR}" STREQUAL "")
set(to_west_topdir FALSE)
else()
cmake_path(IS_PREFIX WEST_TOPDIR ${dir} NORMALIZE to_west_topdir)
endif()
cmake_path(IS_PREFIX APPLICATION_SOURCE_DIR ${dir} NORMALIZE to_app_srcdir)

# Overall idea is to place included files in the destination dir based on the source:
# files coming from build/zephyr/generated will end up at
# <install-dir>/include/zephyr/include/generated, files coming from zephyr base at
# <install-dir>/include/zephyr/include, files from west top dir (for instance, hal modules),
# at <install-dir>/include and application ones at <install-dir>/include/<application-dir>.
# Finally, everything else (such as external libs not at any of those places) will end up
# at <install-dir>/include/<full-path-to-external-include>, so we avoid any external lib
# stepping at any other lib toes.
if(to_prj_bindir)
cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${PROJECT_BINARY_DIR} OUTPUT_VARIABLE dir_tmp)
set(dest ${llext_edk_inc}/zephyr/${dir_tmp})
elseif(to_zephyr_base)
cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${ZEPHYR_BASE} OUTPUT_VARIABLE dir_tmp)
set(dest ${llext_edk_inc}/zephyr/${dir_tmp})
elseif(to_west_topdir)
cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${WEST_TOPDIR} OUTPUT_VARIABLE dir_tmp)
set(dest ${llext_edk_inc}/${dir_tmp})
elseif(to_app_srcdir)
cmake_path(GET APPLICATION_SOURCE_DIR FILENAME app_dir)
cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${APPLICATION_SOURCE_DIR} OUTPUT_VARIABLE dir_tmp)
set(dest ${llext_edk_inc}/${app_dir}/${dir_tmp})
else()
set(dest ${llext_edk_inc}/${dir})
endif()

# Use destination parent, as the last part of the source directory is copied as well
cmake_path(GET dest PARENT_PATH dest_p)

file(MAKE_DIRECTORY ${dest_p})
file(COPY ${dir} DESTINATION ${dest_p} FILES_MATCHING PATTERN "*.h")

cmake_path(RELATIVE_PATH dest BASE_DIRECTORY ${llext_edk} OUTPUT_VARIABLE dest_rel)
list(APPEND all_flags_make "-I\$(${install_dir_var})/${dest_rel}")
list(APPEND all_flags_cmake "-I\${CMAKE_CURRENT_LIST_DIR}/${dest_rel}")
endforeach()

list(JOIN all_flags_make " " all_flags_str)
file(WRITE ${llext_edk}/Makefile.cflags "LLEXT_CFLAGS = ${all_flags_str}")

file(WRITE ${llext_edk}/cmake.cflags "set(LLEXT_CFLAGS ${all_flags_cmake})")

file(ARCHIVE_CREATE
OUTPUT ${llext_edk_file}
PATHS ${llext_edk}
FORMAT gnutar
COMPRESSION XZ
)

file(REMOVE_RECURSE ${llext_edk})
1 change: 1 addition & 0 deletions cmake/usage/usage.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ message(" initlevels - Display the initialization sequence")
message(" boards - Display supported boards")
message(" shields - Display supported shields")
message(" usage - Display this text")
message(" llext-edk - Build the Linkable Loadable Extension (LLEXT) Extension Development Kit (EDK)")
message(" help - Display all build system targets")
message("")
message("Build flags:")
Expand Down
13 changes: 13 additions & 0 deletions subsys/llext/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,16 @@ module-str = llext
source "subsys/logging/Kconfig.template.log_config"

endif

menu "Linkable loadable Extension Development Kit (EDK)"

config LLEXT_EDK_NAME
string "Name for llext EDK (Extension Development Kit)"
default "llext-edk"
help
Name will be used when generating the EDK file, as <name>.tar.xz.
It will also be used, normalized, as the prefix for the variable
stating EDK location, used on generated Makefile.cflags. For
instance, the default name, "llext-edk", becomes LLEXT_EDK_INSTALL_DIR.

endmenu

0 comments on commit d156a03

Please sign in to comment.