From d156a03074af3764f134f608ff8493daa8dda3a8 Mon Sep 17 00:00:00 2001 From: Ederson de Souza Date: Thu, 18 Jan 2024 20:28:46 -0800 Subject: [PATCH] cmake: New target which generates a sort of development kit for llext 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 --- CMakeLists.txt | 20 +++++ cmake/compiler/gcc/target_arm.cmake | 6 ++ cmake/llext-edk.cmake | 109 ++++++++++++++++++++++++++++ cmake/usage/usage.cmake | 1 + subsys/llext/Kconfig | 13 ++++ 5 files changed, 149 insertions(+) create mode 100644 cmake/llext-edk.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fa2d371c4a..22b60009bac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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="$,:>" + -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. diff --git a/cmake/compiler/gcc/target_arm.cmake b/cmake/compiler/gcc/target_arm.cmake index 77a718cc725..7675e7cb6d5 100644 --- a/cmake/compiler/gcc/target_arm.cmake +++ b/cmake/compiler/gcc/target_arm.cmake @@ -58,3 +58,9 @@ set(LLEXT_APPEND_FLAGS -mlong-calls -mthumb ) + +set(LLEXT_CFLAGS + -mlong-calls + -mthumb + -nodefaultlibs + -c) diff --git a/cmake/llext-edk.cmake b/cmake/llext-edk.cmake new file mode 100644 index 00000000000..7c8cf4201f5 --- /dev/null +++ b/cmake/llext-edk.cmake @@ -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 + # /include/zephyr/include/generated, files coming from zephyr base at + # /include/zephyr/include, files from west top dir (for instance, hal modules), + # at /include and application ones at /include/. + # Finally, everything else (such as external libs not at any of those places) will end up + # at /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}) diff --git a/cmake/usage/usage.cmake b/cmake/usage/usage.cmake index 2f4b0b76ddd..8ff24025954 100644 --- a/cmake/usage/usage.cmake +++ b/cmake/usage/usage.cmake @@ -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:") diff --git a/subsys/llext/Kconfig b/subsys/llext/Kconfig index 085ef775569..792ce4b2da5 100644 --- a/subsys/llext/Kconfig +++ b/subsys/llext/Kconfig @@ -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 .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