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 following arguments are expected: 15# - llext_edk_name: Name of the extension, used to name the tarball and the 16# install directory variable for Makefile. 17# - INTERFACE_INCLUDE_DIRECTORIES: List of include directories to copy headers 18# from. It should simply be the INTERFACE_INCLUDE_DIRECTORIES property of the 19# zephyr_interface target. 20# - llext_edk_file: Output file name for the tarball. 21# - llext_edk_cflags: Flags to be used for source compile commands. 22# - ZEPHYR_BASE: Path to the zephyr base directory. 23# - WEST_TOPDIR: Path to the west top directory. 24# - APPLICATION_SOURCE_DIR: Path to the application source directory. 25# - PROJECT_BINARY_DIR: Path to the project binary build directory. 26# - CONFIG_LLEXT_EDK_USERSPACE_ONLY: Whether to copy syscall headers from the 27# edk directory. This is necessary when building an extension that only 28# supports userspace, as the syscall headers are regenerated in the edk 29# directory. 30 31cmake_minimum_required(VERSION 3.20.0) 32 33if (CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID) 34 message(FATAL_ERROR 35 "The LLEXT EDK is not compatible with CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID.") 36endif() 37 38set(llext_edk ${PROJECT_BINARY_DIR}/${llext_edk_name}) 39set(llext_edk_inc ${llext_edk}/include) 40 41# Usage: 42# relative_dir(<dir> <relative_out> <bindir_out>) 43# 44# Helper function to generate relative paths to a few key directories 45# (PROJECT_BINARY_DIR, ZEPHYR_BASE, WEST_TOPDIR and APPLICATION_SOURCE_DIR). 46# The generated path is relative to the key directory, and the bindir_out 47# output variable is set to TRUE if the path is relative to PROJECT_BINARY_DIR. 48# 49function(relative_dir dir relative_out bindir_out) 50 cmake_path(IS_PREFIX PROJECT_BINARY_DIR ${dir} NORMALIZE to_prj_bindir) 51 cmake_path(IS_PREFIX ZEPHYR_BASE ${dir} NORMALIZE to_zephyr_base) 52 if("${WEST_TOPDIR}" STREQUAL "") 53 set(to_west_topdir FALSE) 54 else() 55 cmake_path(IS_PREFIX WEST_TOPDIR ${dir} NORMALIZE to_west_topdir) 56 endif() 57 cmake_path(IS_PREFIX APPLICATION_SOURCE_DIR ${dir} NORMALIZE to_app_srcdir) 58 59 # Overall idea is to place included files in the destination dir based on the source: 60 # files coming from build/zephyr/generated will end up at 61 # <install-dir>/include/zephyr/include/generated, files coming from zephyr base at 62 # <install-dir>/include/zephyr/include, files from west top dir (for instance, hal modules), 63 # at <install-dir>/include and application ones at <install-dir>/include/<application-dir>. 64 # Finally, everything else (such as external libs not at any of those places) will end up 65 # at <install-dir>/include/<full-path-to-external-include>, so we avoid any external lib 66 # stepping at any other lib toes. 67 if(to_prj_bindir) 68 cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${PROJECT_BINARY_DIR} OUTPUT_VARIABLE dir_tmp) 69 set(dest ${llext_edk_inc}/zephyr/${dir_tmp}) 70 elseif(to_zephyr_base) 71 cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${ZEPHYR_BASE} OUTPUT_VARIABLE dir_tmp) 72 set(dest ${llext_edk_inc}/zephyr/${dir_tmp}) 73 elseif(to_west_topdir) 74 cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${WEST_TOPDIR} OUTPUT_VARIABLE dir_tmp) 75 set(dest ${llext_edk_inc}/${dir_tmp}) 76 elseif(to_app_srcdir) 77 cmake_path(GET APPLICATION_SOURCE_DIR FILENAME app_dir) 78 cmake_path(RELATIVE_PATH dir BASE_DIRECTORY ${APPLICATION_SOURCE_DIR} OUTPUT_VARIABLE dir_tmp) 79 set(dest ${llext_edk_inc}/${app_dir}/${dir_tmp}) 80 else() 81 set(dest ${llext_edk_inc}/${dir}) 82 endif() 83 84 set(${relative_out} ${dest} PARENT_SCOPE) 85 if(to_prj_bindir) 86 set(${bindir_out} TRUE PARENT_SCOPE) 87 else() 88 set(${bindir_out} FALSE PARENT_SCOPE) 89 endif() 90endfunction() 91 92string(REGEX REPLACE "[^a-zA-Z0-9]" "_" llext_edk_name_sane ${llext_edk_name}) 93string(TOUPPER ${llext_edk_name_sane} llext_edk_name_sane) 94set(install_dir_var "${llext_edk_name_sane}_INSTALL_DIR") 95 96separate_arguments(llext_edk_cflags NATIVE_COMMAND ${llext_edk_cflags}) 97 98set(make_relative FALSE) 99foreach(flag ${llext_edk_cflags}) 100 if (flag STREQUAL "-imacros") 101 set(make_relative TRUE) 102 elseif (make_relative) 103 set(make_relative FALSE) 104 cmake_path(GET flag PARENT_PATH parent) 105 cmake_path(GET flag FILENAME name) 106 relative_dir(${parent} dest bindir) 107 cmake_path(RELATIVE_PATH dest BASE_DIRECTORY ${llext_edk} OUTPUT_VARIABLE dest_rel) 108 if(bindir) 109 list(APPEND imacros_gen_make "-imacros\$(${install_dir_var})/${dest_rel}/${name}") 110 list(APPEND imacros_gen_cmake "-imacros\${CMAKE_CURRENT_LIST_DIR}/${dest_rel}/${name}") 111 else() 112 list(APPEND imacros_make "-imacros\$(${install_dir_var})/${dest_rel}/${name}") 113 list(APPEND imacros_cmake "-imacros\${CMAKE_CURRENT_LIST_DIR}/${dest_rel}/${name}") 114 endif() 115 else() 116 list(APPEND new_cflags ${flag}) 117 endif() 118endforeach() 119set(llext_edk_cflags ${new_cflags}) 120 121list(APPEND base_flags_make ${llext_edk_cflags} ${imacros_make}) 122list(APPEND base_flags_cmake ${llext_edk_cflags} ${imacros_cmake}) 123 124separate_arguments(include_dirs NATIVE_COMMAND ${INTERFACE_INCLUDE_DIRECTORIES}) 125file(MAKE_DIRECTORY ${llext_edk_inc}) 126foreach(dir ${include_dirs}) 127 if (NOT EXISTS ${dir}) 128 continue() 129 endif() 130 131 relative_dir(${dir} dest bindir) 132 # Use destination parent, as the last part of the source directory is copied as well 133 cmake_path(GET dest PARENT_PATH dest_p) 134 135 file(MAKE_DIRECTORY ${dest_p}) 136 file(COPY ${dir} DESTINATION ${dest_p} FILES_MATCHING PATTERN "*.h") 137 138 cmake_path(RELATIVE_PATH dest BASE_DIRECTORY ${llext_edk} OUTPUT_VARIABLE dest_rel) 139 if(bindir) 140 list(APPEND inc_gen_flags_make "-I\$(${install_dir_var})/${dest_rel}") 141 list(APPEND inc_gen_flags_cmake "-I\${CMAKE_CURRENT_LIST_DIR}/${dest_rel}") 142 else() 143 list(APPEND inc_flags_make "-I\$(${install_dir_var})/${dest_rel}") 144 list(APPEND inc_flags_cmake "-I\${CMAKE_CURRENT_LIST_DIR}/${dest_rel}") 145 endif() 146 list(APPEND all_inc_flags_make "-I\$(${install_dir_var})/${dest_rel}") 147 list(APPEND all_inc_flags_cmake "-I\${CMAKE_CURRENT_LIST_DIR}/${dest_rel}") 148endforeach() 149 150if(CONFIG_LLEXT_EDK_USERSPACE_ONLY) 151 # Copy syscall headers from edk directory, as they were regenerated there. 152 file(COPY ${PROJECT_BINARY_DIR}/edk/include/generated/ DESTINATION ${llext_edk_inc}/zephyr/include/generated) 153endif() 154 155# Generate flags for Makefile 156list(APPEND all_flags_make ${base_flags_make} ${imacros_gen_make} ${all_inc_flags_make}) 157list(JOIN all_flags_make " " all_flags_str) 158file(WRITE ${llext_edk}/Makefile.cflags "LLEXT_CFLAGS = ${all_flags_str}") 159 160list(JOIN all_inc_flags_make " " all_inc_flags_str) 161file(APPEND ${llext_edk}/Makefile.cflags "\n\nLLEXT_ALL_INCLUDE_CFLAGS = ${all_inc_flags_str}") 162 163list(JOIN inc_flags_make " " inc_flags_str) 164file(APPEND ${llext_edk}/Makefile.cflags "\n\nLLEXT_INCLUDE_CFLAGS = ${inc_flags_str}") 165 166list(JOIN inc_gen_flags_make " " inc_gen_flags_str) 167file(APPEND ${llext_edk}/Makefile.cflags "\n\nLLEXT_GENERATED_INCLUDE_CFLAGS = ${inc_gen_flags_str}") 168 169list(JOIN base_flags_make " " base_flags_str) 170file(APPEND ${llext_edk}/Makefile.cflags "\n\nLLEXT_BASE_CFLAGS = ${base_flags_str}") 171 172list(JOIN imacros_gen_make " " imacros_gen_str) 173file(APPEND ${llext_edk}/Makefile.cflags "\n\nLLEXT_GENERATED_IMACROS_CFLAGS = ${imacros_gen_str}") 174 175# Generate flags for CMake 176list(APPEND all_flags_cmake ${base_flags_cmake} ${imacros_gen_cmake} ${all_inc_flags_cmake}) 177file(WRITE ${llext_edk}/cmake.cflags "set(LLEXT_CFLAGS ${all_flags_cmake})") 178 179file(APPEND ${llext_edk}/cmake.cflags "\n\nset(LLEXT_ALL_INCLUDE_CFLAGS ${all_inc_flags_cmake})") 180 181file(APPEND ${llext_edk}/cmake.cflags "\n\nset(LLEXT_INCLUDE_CFLAGS ${inc_flags_cmake})") 182 183file(APPEND ${llext_edk}/cmake.cflags "\n\nset(LLEXT_GENERATED_INCLUDE_CFLAGS ${inc_gen_flags_cmake})") 184 185file(APPEND ${llext_edk}/cmake.cflags "\n\nset(LLEXT_BASE_CFLAGS ${base_flags_cmake})") 186 187file(APPEND ${llext_edk}/cmake.cflags "\n\nset(LLEXT_GENERATED_IMACROS_CFLAGS ${imacros_gen_cmake})") 188 189# Generate the tarball 190file(ARCHIVE_CREATE 191 OUTPUT ${llext_edk_file} 192 PATHS ${llext_edk} 193 FORMAT gnutar 194 COMPRESSION XZ 195) 196 197file(REMOVE_RECURSE ${llext_edk}) 198