-
Notifications
You must be signed in to change notification settings - Fork 6.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
llext: additional CMake harmonizations #67997
Changes from all commits
c2b6016
8c0a051
947d3d0
cf0b25e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,5 +17,4 @@ set(LLEXT_APPEND_FLAGS | |
-fPIC | ||
-nostdlib | ||
-nodefaultlibs | ||
-shared | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# | ||
# Copyright (c) 2024, Arduino SA | ||
|
||
# Perform the same initialization as the Generic platform, then enable | ||
# dynamic library support if CONFIG_LLEXT is enabled. | ||
|
||
include(Platform/Generic) | ||
|
||
# Enable dynamic library support when CONFIG_LLEXT is enabled. | ||
if(CONFIG_LLEXT) | ||
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS true) | ||
endif() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,8 @@ include(CheckCXXCompilerFlag) | |
# 5.1. zephyr_linker* | ||
# 6 Function helper macros | ||
# 7 Linkable loadable extensions (llext) | ||
# 7.1 llext_* configuration functions | ||
# 7.2 add_llext_* build control functions | ||
|
||
######################################################## | ||
# 1. Zephyr-aware extensions | ||
|
@@ -5099,6 +5101,44 @@ endmacro() | |
# loadable extensions (llexts). | ||
# | ||
|
||
# 7.1 Configuration functions | ||
# | ||
# The following functions simplify access to the compilation/link stage | ||
# properties of an llext using the same API of the target_* functions. | ||
# | ||
|
||
function(llext_compile_definitions target_name) | ||
target_compile_definitions(${target_name}_llext_lib PRIVATE ${ARGN}) | ||
endfunction() | ||
|
||
function(llext_compile_features target_name) | ||
target_compile_features(${target_name}_llext_lib PRIVATE ${ARGN}) | ||
endfunction() | ||
|
||
function(llext_compile_options target_name) | ||
target_compile_options(${target_name}_llext_lib PRIVATE ${ARGN}) | ||
endfunction() | ||
|
||
function(llext_include_directories target_name) | ||
target_include_directories(${target_name}_llext_lib PRIVATE ${ARGN}) | ||
endfunction() | ||
|
||
function(llext_link_options target_name) | ||
target_link_options(${target_name}_llext_lib PRIVATE ${ARGN}) | ||
endfunction() | ||
|
||
# 7.2 Build control functions | ||
# | ||
# The following functions add targets and subcommands to the build system | ||
# to compile and link an llext. | ||
# | ||
|
||
# Usage: | ||
# add_llext_target(<target_name> | ||
# OUTPUT <output_file> | ||
# SOURCES <source_file> | ||
# ) | ||
# | ||
# Add a custom target that compiles a single source file to a .llext file. | ||
# | ||
# Output and source files must be specified using the OUTPUT and SOURCES | ||
|
@@ -5108,49 +5148,45 @@ endmacro() | |
# in the Zephyr build, but with some important modifications. The list of | ||
# flags to remove and flags to append is controlled respectively by the | ||
# LLEXT_REMOVE_FLAGS and LLEXT_APPEND_FLAGS global variables. | ||
|
||
# The C_FLAGS argument can be used to pass additional compiler flags to the | ||
# compilation of this particular llext. | ||
# | ||
# The following custom properties of <target_name> are defined and can be | ||
# retrieved using the get_target_property() function: | ||
# | ||
# - lib_target Target name for the source compilation and/or link step. | ||
# - lib_output The binary file resulting from compilation and/or | ||
# linking steps. | ||
# - pkg_input The file to be used as input for the packaging step. | ||
# - pkg_output The final .llext file. | ||
# | ||
# Example usage: | ||
# add_llext_target(hello_world | ||
# OUTPUT ${PROJECT_BINARY_DIR}/hello_world.llext | ||
# SOURCES ${PROJECT_SOURCE_DIR}/src/llext/hello_world.c | ||
# C_FLAGS -Werror | ||
# ) | ||
# will compile the source file src/llext/hello_world.c to a file | ||
# ${PROJECT_BINARY_DIR}/hello_world.llext, adding -Werror to the compilation. | ||
# named "${PROJECT_BINARY_DIR}/hello_world.llext". | ||
# | ||
function(add_llext_target target_name) | ||
set(single_args OUTPUT) | ||
set(multi_args SOURCES;C_FLAGS) | ||
set(multi_args SOURCES) | ||
cmake_parse_arguments(PARSE_ARGV 1 LLEXT "${options}" "${single_args}" "${multi_args}") | ||
|
||
# Check that the llext subsystem is enabled for this build | ||
if (NOT CONFIG_LLEXT) | ||
message(FATAL_ERROR "add_llext_target: CONFIG_LLEXT must be enabled") | ||
endif() | ||
|
||
# Output file must be provided | ||
if(NOT LLEXT_OUTPUT) | ||
message(FATAL_ERROR "add_llext_target: OUTPUT argument must be provided") | ||
endif() | ||
# Source and output files must be provided | ||
zephyr_check_arguments_required_all("add_llext_target" LLEXT OUTPUT SOURCES) | ||
|
||
# Source list length must currently be 1 | ||
list(LENGTH LLEXT_SOURCES source_count) | ||
if(NOT source_count EQUAL 1) | ||
message(FATAL_ERROR "add_llext_target: only one source file is supported") | ||
endif() | ||
|
||
set(output_file ${LLEXT_OUTPUT}) | ||
set(llext_pkg_output ${LLEXT_OUTPUT}) | ||
set(source_file ${LLEXT_SOURCES}) | ||
get_filename_component(output_name ${output_file} NAME) | ||
|
||
# Add user-visible target and dependency | ||
add_custom_target(${target_name} | ||
COMMENT "Compiling ${output_name}" | ||
DEPENDS ${output_file} | ||
) | ||
|
||
# Convert the LLEXT_REMOVE_FLAGS list to a regular expression, and use it to | ||
# filter out these flags from the Zephyr target settings | ||
|
@@ -5166,62 +5202,189 @@ function(add_llext_target target_name) | |
"$<FILTER:${zephyr_flags},EXCLUDE,${llext_remove_flags_regexp}>" | ||
) | ||
|
||
# Compile the source file to an object file using current Zephyr settings | ||
# but a different set of flags | ||
add_library(${target_name}_lib OBJECT ${source_file}) | ||
target_compile_definitions(${target_name}_lib PRIVATE | ||
# Compile the source file using current Zephyr settings but a different | ||
# set of flags. | ||
# This is currently arch-specific since the ARM loader for .llext files | ||
# expects object file format, while the Xtensa one uses shared libraries. | ||
set(llext_lib_target ${target_name}_llext_lib) | ||
if(CONFIG_ARM) | ||
|
||
# Create an object library to compile the source file | ||
add_library(${llext_lib_target} OBJECT ${source_file}) | ||
set(llext_lib_output $<TARGET_OBJECTS:${llext_lib_target}>) | ||
|
||
elseif(CONFIG_XTENSA) | ||
|
||
# Create a shared library | ||
add_library(${llext_lib_target} SHARED ${source_file}) | ||
set(llext_lib_output $<TARGET_FILE:${llext_lib_target}>) | ||
|
||
# Add the llext flags to the linking step as well | ||
target_link_options(${llext_lib_target} PRIVATE | ||
${LLEXT_APPEND_FLAGS} | ||
pillo79 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
endif() | ||
|
||
target_compile_definitions(${llext_lib_target} PRIVATE | ||
$<TARGET_PROPERTY:zephyr_interface,INTERFACE_COMPILE_DEFINITIONS> | ||
) | ||
target_compile_options(${target_name}_lib PRIVATE | ||
target_compile_options(${llext_lib_target} PRIVATE | ||
${zephyr_filtered_flags} | ||
${LLEXT_APPEND_FLAGS} | ||
${LLEXT_C_FLAGS} | ||
) | ||
target_include_directories(${target_name}_lib PRIVATE | ||
target_include_directories(${llext_lib_target} PRIVATE | ||
$<TARGET_PROPERTY:zephyr_interface,INTERFACE_INCLUDE_DIRECTORIES> | ||
) | ||
target_include_directories(${target_name}_lib SYSTEM PUBLIC | ||
target_include_directories(${llext_lib_target} SYSTEM PUBLIC | ||
$<TARGET_PROPERTY:zephyr_interface,INTERFACE_SYSTEM_INCLUDE_DIRECTORIES> | ||
) | ||
add_dependencies(${target_name}_lib | ||
add_dependencies(${llext_lib_target} | ||
zephyr_interface | ||
zephyr_generated_headers | ||
) | ||
|
||
# Arch-specific conversion of the object file to an llext | ||
# Set up an intermediate processing step between compilation and packaging | ||
# to be used to support POST_BUILD commands on targets that do not use a | ||
# dynamic library. | ||
set(llext_proc_target ${target_name}_llext_proc) | ||
set(llext_pkg_input ${PROJECT_BINARY_DIR}/${target_name}.llext.pkg_input) | ||
add_custom_target(${llext_proc_target} DEPENDS ${llext_pkg_input}) | ||
set_property(TARGET ${llext_proc_target} PROPERTY has_post_build_cmds 0) | ||
|
||
# By default this target must copy the `lib_output` binary file to the | ||
# expected `pkg_input` location. If actual POST_BUILD commands are defined, | ||
# they will take care of this and the default copy is replaced by a no-op. | ||
set(has_post_build_cmds "$<TARGET_PROPERTY:${llext_proc_target},has_post_build_cmds>") | ||
set(noop_cmd ${CMAKE_COMMAND} -E true) | ||
set(copy_cmd ${CMAKE_COMMAND} -E copy ${llext_lib_output} ${llext_pkg_input}) | ||
add_custom_command( | ||
OUTPUT ${llext_pkg_input} | ||
COMMAND "$<IF:${has_post_build_cmds},${noop_cmd},${copy_cmd}>" | ||
DEPENDS ${llext_lib_target} ${llext_lib_output} | ||
COMMAND_EXPAND_LISTS | ||
) | ||
|
||
# Arch-specific packaging of the built binary file into an .llext file | ||
if(CONFIG_ARM) | ||
|
||
# No conversion required, simply copy the object file | ||
# No packaging required, simply copy the object file | ||
add_custom_command( | ||
OUTPUT ${output_file} | ||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_OBJECTS:${target_name}_lib> ${output_file} | ||
DEPENDS ${target_name}_lib $<TARGET_OBJECTS:${target_name}_lib> | ||
OUTPUT ${llext_pkg_output} | ||
COMMAND ${CMAKE_COMMAND} -E copy ${llext_pkg_input} ${llext_pkg_output} | ||
DEPENDS ${llext_proc_target} ${llext_pkg_input} | ||
) | ||
|
||
elseif(CONFIG_XTENSA) | ||
|
||
# Generate an intermediate file name | ||
get_filename_component(output_dir ${output_file} DIRECTORY) | ||
get_filename_component(output_name_we ${output_file} NAME_WE) | ||
set(pre_output_file ${output_dir}/${output_name_we}.pre.llext) | ||
|
||
# Need to convert the object file to a shared library, then strip some sections | ||
# Need to strip the shared library of some sections | ||
add_custom_command( | ||
OUTPUT ${output_file} | ||
BYPRODUCTS ${pre_output_file} | ||
COMMAND ${CMAKE_C_COMPILER} ${LLEXT_APPEND_FLAGS} | ||
-o ${pre_output_file} | ||
$<TARGET_OBJECTS:${target_name}_lib> | ||
OUTPUT ${llext_pkg_output} | ||
COMMAND $<TARGET_PROPERTY:bintools,strip_command> | ||
$<TARGET_PROPERTY:bintools,strip_flag> | ||
$<TARGET_PROPERTY:bintools,strip_flag_remove_section>.xt.* | ||
$<TARGET_PROPERTY:bintools,strip_flag_infile>${pre_output_file} | ||
$<TARGET_PROPERTY:bintools,strip_flag_outfile>${output_file} | ||
$<TARGET_PROPERTY:bintools,strip_flag_infile>${llext_pkg_input} | ||
$<TARGET_PROPERTY:bintools,strip_flag_outfile>${llext_pkg_output} | ||
$<TARGET_PROPERTY:bintools,strip_flag_final> | ||
DEPENDS ${target_name}_lib $<TARGET_OBJECTS:${target_name}_lib> | ||
DEPENDS ${llext_proc_target} ${llext_pkg_input} | ||
) | ||
|
||
else() | ||
message(FATAL_ERROR "add_llext_target: unsupported architecture") | ||
endif() | ||
|
||
# Add user-visible target and dependency, and fill in properties | ||
get_filename_component(output_name ${llext_pkg_output} NAME) | ||
add_custom_target(${target_name} | ||
COMMENT "Generating ${output_name}" | ||
DEPENDS ${llext_pkg_output} | ||
) | ||
set_target_properties(${target_name} PROPERTIES | ||
lib_target ${llext_lib_target} | ||
lib_output ${llext_lib_output} | ||
pkg_input ${llext_pkg_input} | ||
pkg_output ${llext_pkg_output} | ||
) | ||
endfunction() | ||
|
||
# Usage: | ||
# add_llext_command( | ||
# TARGET <target_name> | ||
# PRE_BUILD | POST_BUILD | POST_PKG | ||
# COMMAND <command> [...] | ||
# ) | ||
# | ||
# Add a custom command to an llext target that will be executed during | ||
# the build. The command will be executed at the specified build step and | ||
# can refer to <target>'s properties for build-specific details. | ||
# | ||
# The differrent build steps are: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor typo in comment. Obviously not blocking. Do not force-push and lose approvals ONLY for this. |
||
# - PRE_BUILD: Before the llext code is linked, if the architecture uses | ||
# dynamic libraries. This step can access `lib_target` and | ||
# its own properties. | ||
# - POST_BUILD: After the llext code is built, but before packaging | ||
# it in an .llext file. This step is expected to create a | ||
# `pkg_input` file by reading the contents of `lib_output`. | ||
# - POST_PKG: After the .llext file has been created. This can operate on | ||
# the final llext file `pkg_output`. | ||
# | ||
# Anything else after COMMAND will be passed to add_custom_command() as-is | ||
# (including multiple commands and other options). | ||
function(add_llext_command) | ||
set(options PRE_BUILD POST_BUILD POST_PKG) | ||
set(single_args TARGET) | ||
# COMMAND and other options are passed to add_custom_command() as-is | ||
|
||
cmake_parse_arguments(PARSE_ARGV 0 LLEXT "${options}" "${single_args}" "${multi_args}") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. curious, why the Are we expecting special characters in the arguments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possibly? What lies after |
||
zephyr_check_arguments_required_all("add_llext_command" LLEXT TARGET) | ||
|
||
# Check the target exists and refers to an llext target | ||
set(target_name ${LLEXT_TARGET}) | ||
set(llext_lib_target ${target_name}_llext_lib) | ||
set(llext_proc_target ${target_name}_llext_proc) | ||
if(NOT TARGET ${llext_lib_target}) | ||
message(FATAL_ERROR "add_llext_command: not an llext target: ${target_name}") | ||
endif() | ||
|
||
# ARM uses an object file representation so there is no link step. | ||
if(CONFIG_ARM AND LLEXT_PRE_BUILD) | ||
message(FATAL_ERROR | ||
"add_llext_command: PRE_BUILD not supported on this arch") | ||
endif() | ||
|
||
# Determine the build step and the target to attach the command to | ||
# based on the provided options | ||
if(LLEXT_PRE_BUILD) | ||
# > before the object files are linked: | ||
# - execute user command(s) before the lib target's link step. | ||
set(cmd_target ${llext_lib_target}) | ||
set(build_step PRE_LINK) | ||
elseif(LLEXT_POST_BUILD) | ||
# > after linking, but before llext packaging: | ||
# - stop default file copy to prevent user files from being clobbered; | ||
# - execute user command(s) after the (now empty) `llext_proc_target`. | ||
set_property(TARGET ${llext_proc_target} PROPERTY has_post_build_cmds 1) | ||
set(cmd_target ${llext_proc_target}) | ||
set(build_step POST_BUILD) | ||
elseif(LLEXT_POST_PKG) | ||
# > after the final llext binary is ready: | ||
# - execute user command(s) after the main target is done. | ||
set(cmd_target ${target_name}) | ||
set(build_step POST_BUILD) | ||
else() | ||
message(FATAL_ERROR "add_llext_command: build step must be provided") | ||
endif() | ||
|
||
# Check that the first unparsed argument is the word COMMAND | ||
list(GET LLEXT_UNPARSED_ARGUMENTS 0 command_str) | ||
if(NOT command_str STREQUAL "COMMAND") | ||
message(FATAL_ERROR "add_llext_command: COMMAND argument must be provided") | ||
marc-hb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
endif() | ||
|
||
# Add the actual command(s) to the target | ||
add_custom_command( | ||
TARGET ${cmd_target} ${build_step} | ||
${LLEXT_UNPARSED_ARGUMENTS} | ||
COMMAND_EXPAND_LISTS | ||
) | ||
endfunction() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this and the
elseif(CONFIG_XTENSA)
below are really not something we should be checking in a common function.I notice it was introduced in #67431 to support shared libraries for the xtensa arch, but now it seems to be spreading more, and that is not good.
So while this is good:
then having (too much)
CONFIG_<feature>
is a bad sign.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not good but "spreading" is IMHO slightly exaggerated and @pillo79 has committed above to fix this as soon as the corresponding C code is fixed - if nothing else for the simple reason that this conditional makes his own maintenance work harder.
The LLEXT paint is still wet and we need more early adopters (like SOF) to starting using LLEXT in anger and provide feedback to make progress. One thing slowing down adoption and progress has been (as often) the lack of a build system, forcing everyone to write their own, different, custom build script. So @pillo79 stepped up and spent an unbelievable amount of time and patience addressing that gap in the CMake area where extremely few people ever volunteer. Blocking the (extension of) this small, documented CMake hack can re-inforce a chicken-and-egg situation where the corresponding C code lacks users and does not get fixed either.
From experience I know very well that "Fix it later" usually means either "never" or "someone else can do it" but I think this case deserves an exception because:
cc: @selescop, @bjarki-trackunit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So which better word would you have in mind to describe the fact that the
if(CONFIG_...)
was used in one location before, and now two more locations are added ?Or should I not point to the fact that such testing is not desired ?
Also, pointing at this could even have @pillo79 taking a second look at the code to see if this could be combined under the existing single
if(CONFIG_...)
test.Remember, others may even look at this code and think this is an acceptable way, but for such cases it's always good to have review comments to reference and stating,
yes, but his pattern was accepted under specific conditions, see PR xyz
.and I fully agree, and also a reason why I very early suggested that
llext
functions were made first class citizen and placed inextensions.cmake
. But with that we should also require a bit higher level compared to code in a Zephyr module 🙂and did I anywhere indicate that this was not the case ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@marc-hb the fact that something is observed in a PR doesn't mean that it cannot be accepted if reasonable explanation / answers are given.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "spreading" is due to the fact that build stages are now clearly separated, thus there are multiple (but closely packed) locations in which I need arch-specific behavior. I could probably minimize it somehow, but I chose readability over minimum code.
We can and probably will get rid of the arch-specific tags by defining behavior-specific Kconfigs (for example an enum like
CONFIG_LLEXT_TYPE_DYNAMIC
/OBJECT
). But this is definitely out of scope of this PR, which is not changing the "hack", just moving it around to make it more explicit.