Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 "$<TARGET_PROPERTY:zephyr_interface,INTERFACE_INCLUDE_DIRECTORIES>")
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}
Expand All @@ -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})
Expand Down
32 changes: 27 additions & 5 deletions cmake/llext-edk.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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()

Expand Down
29 changes: 29 additions & 0 deletions misc/llext_edk/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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 $<COMPILE_LANGUAGE:...> 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 '<DEFINES>' > ${CMAKE_CURRENT_BINARY_DIR}/c_defs.txt"
"${CMAKE_COMMAND} -E echo '<INCLUDES>' > ${CMAKE_CURRENT_BINARY_DIR}/c_incs.txt"
"${CMAKE_COMMAND} -E echo '<FLAGS>' > ${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)
8 changes: 4 additions & 4 deletions scripts/schemas/build-schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions tests/misc/llext-edk/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
5 changes: 5 additions & 0 deletions tests/misc/llext-edk/extension/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@

#include <app_api.h>

#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);
1 change: 1 addition & 0 deletions tests/misc/llext-edk/pytest/test_edk.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading