diff --git a/CMakeLists.txt b/CMakeLists.txt index dc6f8266d6778..3774cd07335ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2288,22 +2288,13 @@ 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) - - # 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}) - + # 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} @@ -2322,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 1475931968dd2..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,12 +152,34 @@ 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}) + list(FILTER llext_edk_cflags EXCLUDE REGEX "^${item}$") +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) @@ -210,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 078cca85782f5..7b6b24dc8a5f6 100644 --- a/scripts/schemas/build-schema.yml +++ b/scripts/schemas/build-schema.yml @@ -85,13 +85,13 @@ mapping: llext-edk: type: map mapping: - cflags: + file: + type: str + remove-cflags: type: seq sequence: - type: str - file: - type: str - include-dirs: + append-cflags: type: seq sequence: - type: str 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()