From 36be75ad2243150660fa7e88fbf9c40f32f90d5d Mon Sep 17 00:00:00 2001 From: Luca Burelli Date: Mon, 27 Oct 2025 16:36:25 +0100 Subject: [PATCH 1/3] llext-edk: (refact) filter flags in post-processing step This changes the LLEXT EDK flag filtering to be done after the main Zephyr build is completed. Instead of filtering the flags via genexes, the full set is passed to the script along with the set of filters to be applied, and the filter is applied later. No functional change is intended. Signed-off-by: Luca Burelli --- CMakeLists.txt | 12 ++++-------- cmake/llext-edk.cmake | 7 +++++++ scripts/schemas/build-schema.yml | 8 ++++++++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dc6f8266d6778..72c8ae68c69dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2292,18 +2292,14 @@ if(CONFIG_LLEXT_EDK) zephyr_get_compile_definitions_for_lang(C zephyr_defs) zephyr_get_compile_options_for_lang(C zephyr_flags) - # Filter out non LLEXT and LLEXT_EDK flags - and add required ones - llext_filter_zephyr_flags(LLEXT_REMOVE_FLAGS ${zephyr_flags} llext_filt_flags) - llext_filter_zephyr_flags(LLEXT_EDK_REMOVE_FLAGS ${llext_filt_flags} llext_filt_flags) - - set(llext_edk_cflags ${zephyr_defs} -DLL_EXTENSION_BUILD) - list(APPEND llext_edk_cflags ${llext_filt_flags}) - list(APPEND llext_edk_cflags ${LLEXT_APPEND_FLAGS}) - list(APPEND llext_edk_cflags ${LLEXT_EDK_APPEND_FLAGS}) + set(llext_edk_cflags ${zephyr_defs} ${zephyr_flags}) + # Export current settings for use in LLEXT EDK generation build_info(llext-edk file PATH ${llext_edk_file}) build_info(llext-edk cflags VALUE ${llext_edk_cflags}) build_info(llext-edk include-dirs VALUE "$") + build_info(llext-edk remove-cflags VALUE ${LLEXT_REMOVE_FLAGS} ${LLEXT_EDK_REMOVE_FLAGS}) + build_info(llext-edk append-cflags VALUE ${LLEXT_APPEND_FLAGS} ${LLEXT_EDK_APPEND_FLAGS} -DLL_EXTENSION_BUILD) add_custom_command( OUTPUT ${llext_edk_file} diff --git a/cmake/llext-edk.cmake b/cmake/llext-edk.cmake index 1475931968dd2..07ce0c9747487 100644 --- a/cmake/llext-edk.cmake +++ b/cmake/llext-edk.cmake @@ -156,6 +156,13 @@ set(build_info_file ${PROJECT_BINARY_DIR}/../build_info.yml) yaml_load(FILE ${build_info_file} NAME build_info) yaml_get(llext_edk_cflags NAME build_info KEY cmake llext-edk cflags) +yaml_get(llext_remove_cflags NAME build_info KEY cmake llext-edk remove-cflags) +yaml_get(llext_append_cflags NAME build_info KEY cmake llext-edk append-cflags) +foreach(item IN_LIST ${llext_remove_cflags}) + list(FILTER llext_edk_cflags EXCLUDE REGEX "^${item}$") +endforeach() +list(APPEND llext_edk_cflags ${llext_append_cflags}) + yaml_get(llext_edk_file NAME build_info KEY cmake llext-edk file) yaml_get(INTERFACE_INCLUDE_DIRECTORIES NAME build_info KEY cmake llext-edk include-dirs) yaml_get(APPLICATION_SOURCE_DIR NAME build_info KEY cmake application source-dir) diff --git a/scripts/schemas/build-schema.yml b/scripts/schemas/build-schema.yml index 078cca85782f5..fcb9487d74450 100644 --- a/scripts/schemas/build-schema.yml +++ b/scripts/schemas/build-schema.yml @@ -95,6 +95,14 @@ mapping: type: seq sequence: - type: str + remove-cflags: + type: seq + sequence: + - type: str + append-cflags: + type: seq + sequence: + - type: str sysbuild: type: bool toolchain: From a004c9b4b194e7e0f026e3d91cb6e60c3f0550d7 Mon Sep 17 00:00:00 2001 From: Luca Burelli Date: Tue, 28 Oct 2025 09:27:14 +0100 Subject: [PATCH 2/3] llext-edk: capture full build properties for EDK export The current implementation of the LLEXT EDK exports only a partial set of compiler settings to the EDK build system, specifically only the ones defined by the Zephyr core (the 'zephyr_interface' target). Extending this to include application and module-specific settings (from the 'app' target and its dependencies) is tricky, because: - while evaluating the main CMakeLists.txt, the 'app' target is not fully configured yet, and - using generator expressions is not possible due to CMake not allowing expansion of $ outside of a proper compile context. To work around this, this change introduces a new CMake subdirectory (misc/llext_edk) that creates a dummy target which imports the build configuration from 'app', but overrides the C compile rules to simply output the resulting properties (defines, include directories, flags) to text files. The EDK generation script then reads those text files to get the full set of build properties, and includes them in the EDK tarball. Signed-off-by: Luca Burelli --- CMakeLists.txt | 13 ++++--------- cmake/llext-edk.cmake | 25 ++++++++++++++++++++----- misc/llext_edk/CMakeLists.txt | 29 +++++++++++++++++++++++++++++ scripts/schemas/build-schema.yml | 8 -------- 4 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 misc/llext_edk/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 72c8ae68c69dc..3774cd07335ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2288,19 +2288,14 @@ if(CONFIG_LLEXT_EDK) endif() set(llext_edk_file ${PROJECT_BINARY_DIR}/${CONFIG_LLEXT_EDK_NAME}.${llext_edk_extension}) - # TODO maybe generate flags for C CXX ASM - zephyr_get_compile_definitions_for_lang(C zephyr_defs) - zephyr_get_compile_options_for_lang(C zephyr_flags) - - set(llext_edk_cflags ${zephyr_defs} ${zephyr_flags}) - # Export current settings for use in LLEXT EDK generation build_info(llext-edk file PATH ${llext_edk_file}) - build_info(llext-edk cflags VALUE ${llext_edk_cflags}) - build_info(llext-edk include-dirs VALUE "$") build_info(llext-edk remove-cflags VALUE ${LLEXT_REMOVE_FLAGS} ${LLEXT_EDK_REMOVE_FLAGS}) build_info(llext-edk append-cflags VALUE ${LLEXT_APPEND_FLAGS} ${LLEXT_EDK_APPEND_FLAGS} -DLL_EXTENSION_BUILD) + # Generate the compiler flag files that will be used by the EDK + add_subdirectory(misc/llext_edk) + add_custom_command( OUTPUT ${llext_edk_file} # Regenerate syscalls in case CONFIG_LLEXT_EDK_USERSPACE_ONLY @@ -2318,7 +2313,7 @@ if(CONFIG_LLEXT_EDK) ${SYSCALL_SPLIT_TIMEOUT_ARG} COMMAND ${CMAKE_COMMAND} -P ${ZEPHYR_BASE}/cmake/llext-edk.cmake - DEPENDS ${logical_target_for_zephyr_elf} ${syscalls_json} build_info_yaml_saved + DEPENDS ${logical_target_for_zephyr_elf} ${syscalls_json} build_info_yaml_saved llext_edk_build_props COMMAND_EXPAND_LISTS ) add_custom_target(llext-edk DEPENDS ${llext_edk_file}) diff --git a/cmake/llext-edk.cmake b/cmake/llext-edk.cmake index 07ce0c9747487..b089d738b4a13 100644 --- a/cmake/llext-edk.cmake +++ b/cmake/llext-edk.cmake @@ -3,7 +3,7 @@ # 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 +# a C compiler command line 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 @@ -152,10 +152,14 @@ if (CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID) "The LLEXT EDK is not compatible with CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID.") endif() +set(build_flags_dir ${PROJECT_BINARY_DIR}/misc/llext_edk) set(build_info_file ${PROJECT_BINARY_DIR}/../build_info.yml) yaml_load(FILE ${build_info_file} NAME build_info) -yaml_get(llext_edk_cflags NAME build_info KEY cmake llext-edk cflags) +# process C flags +file(READ ${build_flags_dir}/c_flags.txt llext_edk_c_flags_raw) +string(STRIP ${llext_edk_c_flags_raw} llext_edk_c_flags) +string(REPLACE " " ";" llext_edk_cflags "${llext_edk_c_flags}") yaml_get(llext_remove_cflags NAME build_info KEY cmake llext-edk remove-cflags) yaml_get(llext_append_cflags NAME build_info KEY cmake llext-edk append-cflags) foreach(item IN_LIST ${llext_remove_cflags}) @@ -163,8 +167,19 @@ foreach(item IN_LIST ${llext_remove_cflags}) endforeach() list(APPEND llext_edk_cflags ${llext_append_cflags}) +# process C definitions +file(READ ${build_flags_dir}/c_defs.txt llext_edk_c_defs_raw) +string(STRIP ${llext_edk_c_defs_raw} llext_edk_c_defs) +string(REPLACE " " ";" llext_edk_c_defs "${llext_edk_c_defs}") +list(PREPEND llext_edk_cflags ${llext_edk_c_defs}) + +# process C include directories +file(READ ${build_flags_dir}/c_incs.txt llext_edk_c_incs_raw) +string(STRIP ${llext_edk_c_incs_raw} llext_edk_c_incs) +string(REPLACE " " ";" llext_edk_c_incs "${llext_edk_c_incs}") +list(TRANSFORM llext_edk_c_incs REPLACE "^-I" "") + yaml_get(llext_edk_file NAME build_info KEY cmake llext-edk file) -yaml_get(INTERFACE_INCLUDE_DIRECTORIES NAME build_info KEY cmake llext-edk include-dirs) yaml_get(APPLICATION_SOURCE_DIR NAME build_info KEY cmake application source-dir) yaml_get(WEST_TOPDIR NAME build_info KEY west topdir) @@ -217,8 +232,8 @@ set(llext_edk_cflags ${new_cflags}) list(APPEND base_flags ${llext_edk_cflags} ${imacros}) file(MAKE_DIRECTORY ${llext_edk_inc}) -foreach(dir ${INTERFACE_INCLUDE_DIRECTORIES}) - if (NOT EXISTS ${dir}) +foreach(dir ${llext_edk_c_incs}) + if(NOT EXISTS ${dir}) continue() endif() diff --git a/misc/llext_edk/CMakeLists.txt b/misc/llext_edk/CMakeLists.txt new file mode 100644 index 0000000000000..79c7ffd638b15 --- /dev/null +++ b/misc/llext_edk/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright 2025 Arduino SA +# SPDX-License-Identifier: Apache-2.0 + +# This CMake script is used to capture the full set of build properties for the +# Zephyr 'app' target so that it can be properly exported by the EDK build +# system. + +# Getting the whole list of build properties via CMake functions is tricky. +# The 'app' target is not fully configured at the time Zephyr scripts are +# included, so querying its properties directly would yield incomplete results. +# However, using CMake generator expressions is also forbidden, since the +# properties include $ entries, and CMake does not know +# how to resolve those outside of a proper compile context. + +# To work around this, the following code creates a dummy target that imports +# build configuration from 'app', but overrides the C compile rules to simply +# output the resulting properties to text files. The EDK will then read those +# text files to get the full set of build properties. + +unset(CMAKE_C_COMPILER_LAUNCHER) +unset(CMAKE_DEPFILE_FLAGS_C) +set(CMAKE_C_COMPILE_OBJECT + "${CMAKE_COMMAND} -E echo '' > ${CMAKE_CURRENT_BINARY_DIR}/c_defs.txt" + "${CMAKE_COMMAND} -E echo '' > ${CMAKE_CURRENT_BINARY_DIR}/c_incs.txt" + "${CMAKE_COMMAND} -E echo '' > ${CMAKE_CURRENT_BINARY_DIR}/c_flags.txt" + "${CMAKE_C_COMPILE_OBJECT}") + +add_library(llext_edk_build_props ${ZEPHYR_BASE}/misc/empty_file.c) +target_link_libraries(llext_edk_build_props PUBLIC app) diff --git a/scripts/schemas/build-schema.yml b/scripts/schemas/build-schema.yml index fcb9487d74450..7b6b24dc8a5f6 100644 --- a/scripts/schemas/build-schema.yml +++ b/scripts/schemas/build-schema.yml @@ -85,16 +85,8 @@ mapping: llext-edk: type: map mapping: - cflags: - type: seq - sequence: - - type: str file: type: str - include-dirs: - type: seq - sequence: - - type: str remove-cflags: type: seq sequence: From 1d048a9f8a2609ef1661313e58b9891f9427a7af Mon Sep 17 00:00:00 2001 From: Luca Burelli Date: Thu, 23 Oct 2025 19:34:16 +0200 Subject: [PATCH 3/3] llext-edk: add dependency flag testing Add an interface library to the test application that defines a macro whose value is later used in the extension. This verifies the full dependency chain for 'app' is evaluated during EDK generation. Signed-off-by: Luca Burelli --- tests/misc/llext-edk/CMakeLists.txt | 6 ++++++ tests/misc/llext-edk/extension/src/main.c | 5 +++++ tests/misc/llext-edk/pytest/test_edk.py | 1 + 3 files changed, 12 insertions(+) diff --git a/tests/misc/llext-edk/CMakeLists.txt b/tests/misc/llext-edk/CMakeLists.txt index 67d3c73061734..0f08aaef49457 100644 --- a/tests/misc/llext-edk/CMakeLists.txt +++ b/tests/misc/llext-edk/CMakeLists.txt @@ -2,11 +2,17 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +# verify information from dependencies is available to extensions +add_library(dependency INTERFACE) +target_compile_definitions(dependency INTERFACE GLOBAL_OPT="set") + project(llext_edk_test LANGUAGES C) target_sources(app PRIVATE src/main.c src/foo.c) zephyr_include_directories(include) zephyr_include_directories(${ZEPHYR_BASE}/boards/native/common) +target_link_libraries(app PUBLIC dependency) if(EXTENSION_DIR) target_include_directories(app PRIVATE ${EXTENSION_DIR}) diff --git a/tests/misc/llext-edk/extension/src/main.c b/tests/misc/llext-edk/extension/src/main.c index 235504f5f8c37..711078d36c2e9 100644 --- a/tests/misc/llext-edk/extension/src/main.c +++ b/tests/misc/llext-edk/extension/src/main.c @@ -9,9 +9,14 @@ #include +#ifndef GLOBAL_OPT +#define GLOBAL_OPT "MISSING" +#endif + int start(int bar) { printk("foo(%d) is %d\n", bar, foo(bar)); + printk("GLOBAL_OPT is %s\n", GLOBAL_OPT); return 0; } EXPORT_SYMBOL(start); diff --git a/tests/misc/llext-edk/pytest/test_edk.py b/tests/misc/llext-edk/pytest/test_edk.py index 9c5159373f307..258e233c55872 100644 --- a/tests/misc/llext-edk/pytest/test_edk.py +++ b/tests/misc/llext-edk/pytest/test_edk.py @@ -115,6 +115,7 @@ def test_edk(unlaunched_dut: DeviceAdapter): assert "Calling extension from user" in lines assert "foo(42) is 1764" in lines assert "foo(43) is 1849" in lines + assert "GLOBAL_OPT is set" in lines finally: unlaunched_dut.close()