1# Copyright (c) 2024 Intel Corporation 2# SPDX-License-Identifier: Apache-2.0 3 4# This script generates a tarball containing all headers and flags necessary to 5# build an llext extension. It does so by copying all headers accessible from 6# INTERFACE_INCLUDE_DIRECTORIES and generating a Makefile.cflags file (and a 7# cmake.cflags one) with all flags necessary to build the extension. 8# 9# The tarball can be extracted and used in the extension build system to include 10# all necessary headers and flags. File paths are made relative to a few key 11# directories (build/zephyr, zephyr base, west top dir and application source 12# dir), to avoid leaking any information about the host system. 13# 14# The script expects a build_info.yml file in the project binary directory. 15# This file should contain the following entries: 16# - cmake application source-dir 17# - cmake board name 18# - cmake board qualifiers 19# - cmake board revision 20# - cmake llext-edk cflags 21# - cmake llext-edk file 22# - cmake llext-edk include-dirs 23# - west topdir 24 25cmake_minimum_required(VERSION 3.20.0) 26 27# initialize the same paths as the main CMakeLists.txt for consistency 28set(PROJECT_BINARY_DIR ${CMAKE_BINARY_DIR}) 29set(ZEPHYR_BASE ${CMAKE_CURRENT_LIST_DIR}/../) 30list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/modules") 31 32include(extensions) 33include(yaml) 34 35# Usage: 36# relative_dir(<dir> <relative_out> <bindir_out>) 37# 38# Helper function to generate relative paths to a few key directories 39# (PROJECT_BINARY_DIR, ZEPHYR_BASE, WEST_TOPDIR and APPLICATION_SOURCE_DIR). 40# The generated path is relative to the key directory, and the bindir_out 41# output variable is set to TRUE if the path is relative to PROJECT_BINARY_DIR. 42# 43function(relative_dir dir relative_out bindir_out) 44 cmake_path(IS_PREFIX PROJECT_BINARY_DIR ${dir} NORMALIZE to_prj_bindir) 45 cmake_path(IS_PREFIX ZEPHYR_BASE ${dir} NORMALIZE to_zephyr_base) 46 if("${WEST_TOPDIR}" STREQUAL "") 47 set(to_west_topdir FALSE) 48 else() 49 cmake_path(IS_PREFIX WEST_TOPDIR ${dir} NORMALIZE to_west_topdir) 50 endif() 51 cmake_path(IS_PREFIX APPLICATION_SOURCE_DIR ${dir} NORMALIZE to_app_srcdir) 52 53 # Overall idea is to place included files in the destination dir based on the source: 54 # files coming from build/zephyr/generated will end up at 55 # <install-dir>/include/zephyr/include/generated, files coming from zephyr base at 56 # <install-dir>/include/zephyr/include, files from west top dir (for instance, hal modules), 57 # at <install-dir>/include and application ones at <install-dir>/include/<application-dir>. 58 # Finally, everything else (such as external libs not at any of those places) will end up 59 # at <install-dir>/include/<full-path-to-external-include>, so we avoid any external lib 60 # stepping at any other lib toes. 61 if(to_prj_bindir) 62 cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${PROJECT_BINARY_DIR} OUTPUT_VARIABLE dir_tmp) 63 set(dest ${llext_edk_inc}/zephyr/${dir_tmp}) 64 elseif(to_zephyr_base) 65 cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${ZEPHYR_BASE} OUTPUT_VARIABLE dir_tmp) 66 set(dest ${llext_edk_inc}/zephyr/${dir_tmp}) 67 elseif(to_west_topdir) 68 cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${WEST_TOPDIR} OUTPUT_VARIABLE dir_tmp) 69 set(dest ${llext_edk_inc}/${dir_tmp}) 70 elseif(to_app_srcdir) 71 cmake_path(GET APPLICATION_SOURCE_DIR FILENAME app_dir) 72 cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${APPLICATION_SOURCE_DIR} OUTPUT_VARIABLE dir_tmp) 73 set(dest ${llext_edk_inc}/${app_dir}/${dir_tmp}) 74 else() 75 set(dest ${llext_edk_inc}/${dir}) 76 endif() 77 78 set(${relative_out} ${dest} PARENT_SCOPE) 79 if(to_prj_bindir) 80 set(${bindir_out} TRUE PARENT_SCOPE) 81 else() 82 set(${bindir_out} FALSE PARENT_SCOPE) 83 endif() 84endfunction() 85 86# Usage: 87# edk_escape(<target> <str_in> <str_out>) 88# 89# Escape problematic characters in the string <str_in> and store the result in 90# <str_out>. The escaping is done to make the string suitable for <target>. 91function(edk_escape target str_in str_out) 92 string(REPLACE "\\" "\\\\" str_escaped "${str_in}") 93 string(REPLACE "\"" "\\\"" str_escaped "${str_escaped}") 94 set(${str_out} "${str_escaped}" PARENT_SCOPE) 95endfunction() 96 97# Usage: 98# edk_write_header(<target>) 99# 100# Initialize the file associated with <target> and write its header. 101function(edk_write_header target) 102 file(WRITE ${edk_file_${target}} "") 103endfunction() 104 105# Usage: 106# edk_write_comment(<target> <text>) 107# 108# Write to the file associated with <target> the string <text> as a comment. 109function(edk_write_comment target text) 110 file(APPEND ${edk_file_${target}} "\n# ${text}\n") 111endfunction() 112 113# Usage: 114# edk_write_var(<target> <var_name> <var_value>) 115# 116# Write to the file associated with <target> an entry where <var_name> is 117# assigned the value <var_value>. 118function(edk_write_var target var_name var_value) 119 if(target STREQUAL "CMAKE") 120 # CMake: export assignments of the form: 121 # 122 # set(var "value1;value2;...") 123 # 124 set(DASHIMACROS "-imacros\${CMAKE_CURRENT_LIST_DIR}/") 125 set(DASHI "-I\${CMAKE_CURRENT_LIST_DIR}/") 126 edk_escape(${target} "${var_value}" var_value) 127 string(CONFIGURE "${var_value}" exp_var_value @ONLY) 128 # The list is otherwise exported verbatim, surrounded by quotes. 129 file(APPEND ${edk_file_${target}} "set(${var_name} \"${exp_var_value}\")\n") 130 elseif(target STREQUAL "MAKEFILE") 131 # Makefile: export assignments of the form: 132 # 133 # var = "value1" "value2" ... 134 # 135 set(DASHIMACROS "-imacros\$(${install_dir_var})/") 136 set(DASHI "-I\$(${install_dir_var})/") 137 edk_escape(${target} "${var_value}" var_value) 138 string(CONFIGURE "${var_value}" exp_var_value @ONLY) 139 # Each element of the list is wrapped in quotes and is separated by a space. 140 list(JOIN exp_var_value "\" \"" exp_var_value_str) 141 file(APPEND ${edk_file_${target}} "${var_name} = \"${exp_var_value_str}\"\n") 142 endif() 143endfunction() 144 145 146 147# read in computed build configuration 148import_kconfig(CONFIG ${PROJECT_BINARY_DIR}/.config) 149 150if (CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID) 151 message(FATAL_ERROR 152 "The LLEXT EDK is not compatible with CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID.") 153endif() 154 155set(build_info_file ${PROJECT_BINARY_DIR}/../build_info.yml) 156yaml_load(FILE ${build_info_file} NAME build_info) 157 158yaml_get(llext_edk_cflags NAME build_info KEY cmake llext-edk cflags) 159yaml_get(llext_edk_file NAME build_info KEY cmake llext-edk file) 160yaml_get(INTERFACE_INCLUDE_DIRECTORIES NAME build_info KEY cmake llext-edk include-dirs) 161yaml_get(APPLICATION_SOURCE_DIR NAME build_info KEY cmake application source-dir) 162yaml_get(WEST_TOPDIR NAME build_info KEY west topdir) 163 164yaml_get(board_name NAME build_info KEY cmake board name) 165yaml_get(board_qualifiers NAME build_info KEY cmake board qualifiers) 166yaml_get(board_revision NAME build_info KEY cmake board revision) 167zephyr_build_string(normalized_board_target 168 BOARD ${board_name} 169 BOARD_QUALIFIERS ${board_qualifiers} 170 BOARD_REVISION ${board_revision}) 171 172set(llext_edk_name ${CONFIG_LLEXT_EDK_NAME}) 173set(llext_edk ${PROJECT_BINARY_DIR}/${llext_edk_name}) 174set(llext_edk_inc ${llext_edk}/include) 175 176zephyr_string(SANITIZE TOUPPER var_prefix ${llext_edk_name}) 177set(install_dir_var "${var_prefix}_INSTALL_DIR") 178 179set(make_relative FALSE) 180foreach(flag ${llext_edk_cflags}) 181 # Detect all combinations of 'imacros' flag: 182 # - with one or two preceding dashes 183 # - separated from the argument, joined by '=', or joined (no separator) 184 if(flag MATCHES "^--?imacros$") 185 # imacros followed by a space, convert next argument 186 set(make_relative TRUE) 187 continue() 188 elseif(flag MATCHES "^--?imacros=?([^=].*)$") 189 # imacros=<stuff> or imacros<stuff>, immediately convert <stuff> 190 set(flag ${CMAKE_MATCH_1}) 191 set(make_relative TRUE) 192 endif() 193 194 if(make_relative) 195 set(make_relative FALSE) 196 cmake_path(GET flag PARENT_PATH parent) 197 cmake_path(GET flag FILENAME name) 198 relative_dir(${parent} dest bindir) 199 cmake_path(RELATIVE_PATH dest BASE_DIRECTORY ${llext_edk} OUTPUT_VARIABLE dest_rel) 200 if(bindir) 201 list(APPEND imacros_gen "@DASHIMACROS@${dest_rel}/${name}") 202 else() 203 list(APPEND imacros "@DASHIMACROS@${dest_rel}/${name}") 204 endif() 205 else() 206 list(APPEND new_cflags ${flag}) 207 endif() 208endforeach() 209set(llext_edk_cflags ${new_cflags}) 210 211list(APPEND base_flags ${llext_edk_cflags} ${imacros}) 212 213file(MAKE_DIRECTORY ${llext_edk_inc}) 214foreach(dir ${INTERFACE_INCLUDE_DIRECTORIES}) 215 if (NOT EXISTS ${dir}) 216 continue() 217 endif() 218 219 relative_dir(${dir} dest bindir) 220 # Use destination parent, as the last part of the source directory is copied as well 221 cmake_path(GET dest PARENT_PATH dest_p) 222 223 file(MAKE_DIRECTORY ${dest_p}) 224 file(COPY ${dir} DESTINATION ${dest_p} FILES_MATCHING PATTERN "*.h") 225 226 cmake_path(RELATIVE_PATH dest BASE_DIRECTORY ${llext_edk} OUTPUT_VARIABLE dest_rel) 227 if(bindir) 228 list(APPEND gen_inc_flags "@DASHI@${dest_rel}") 229 else() 230 list(APPEND inc_flags "@DASHI@${dest_rel}") 231 endif() 232 list(APPEND all_inc_flags "@DASHI@${dest_rel}") 233endforeach() 234 235list(APPEND all_flags ${base_flags} ${imacros_gen} ${all_inc_flags}) 236 237if(CONFIG_LLEXT_EDK_USERSPACE_ONLY) 238 # Copy syscall headers from edk directory, as they were regenerated there. 239 file(COPY ${PROJECT_BINARY_DIR}/edk/include/generated/ DESTINATION ${llext_edk_inc}/zephyr/include/generated) 240endif() 241 242# 243# Generate the EDK flags files 244# 245 246set(edk_targets MAKEFILE CMAKE) 247set(edk_file_MAKEFILE ${llext_edk}/Makefile.cflags) 248set(edk_file_CMAKE ${llext_edk}/cmake.cflags) 249 250foreach(target ${edk_targets}) 251 edk_write_header(${target}) 252 253 edk_write_comment(${target} "Target information") 254 edk_write_var(${target} "${var_prefix}_BOARD_NAME" "${board_name}") 255 edk_write_var(${target} "${var_prefix}_BOARD_QUALIFIERS" "${board_qualifiers}") 256 edk_write_var(${target} "${var_prefix}_BOARD_REVISION" "${board_revision}") 257 edk_write_var(${target} "${var_prefix}_BOARD_TARGET" "${normalized_board_target}") 258 259 edk_write_comment(${target} "Compile flags") 260 edk_write_var(${target} "LLEXT_CFLAGS" "${all_flags}") 261 edk_write_var(${target} "LLEXT_ALL_INCLUDE_CFLAGS" "${all_inc_flags}") 262 edk_write_var(${target} "LLEXT_INCLUDE_CFLAGS" "${inc_flags}") 263 edk_write_var(${target} "LLEXT_GENERATED_INCLUDE_CFLAGS" "${gen_inc_flags}") 264 edk_write_var(${target} "LLEXT_BASE_CFLAGS" "${base_flags}") 265 edk_write_var(${target} "LLEXT_GENERATED_IMACROS_CFLAGS" "${imacros_gen}") 266endforeach() 267 268if(CONFIG_LLEXT_EDK_FORMAT_TAR_XZ) 269 set(llext_edk_format FORMAT gnutar COMPRESSION XZ) 270elseif(CONFIG_LLEXT_EDK_FORMAT_TAR_ZSTD) 271 set(llext_edk_format FORMAT gnutar COMPRESSION Zstd) 272elseif(CONFIG_LLEXT_EDK_FORMAT_ZIP) 273 set(llext_edk_format FORMAT zip) 274else() 275 message(FATAL_ERROR "Unsupported LLEXT_EDK_FORMAT choice") 276endif() 277 278# Generate the tarball 279file(ARCHIVE_CREATE 280 OUTPUT ${llext_edk_file} 281 PATHS ${llext_edk} 282 ${llext_edk_format} 283) 284 285file(REMOVE_RECURSE ${llext_edk}) 286