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_cflags: Additional flags to be added to the generated flags.
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_cflags NATIVE_COMMAND ${llext_cflags})
97
98set(make_relative FALSE)
99foreach(flag ${llext_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_cflags ${new_cflags})
120
121
122list(APPEND base_flags_make ${llext_cflags} ${imacros_make} -DLL_EXTENSION_BUILD)
123list(APPEND base_flags_cmake ${llext_cflags} ${imacros_cmake} -DLL_EXTENSION_BUILD)
124
125separate_arguments(include_dirs NATIVE_COMMAND ${INTERFACE_INCLUDE_DIRECTORIES})
126file(MAKE_DIRECTORY ${llext_edk_inc})
127foreach(dir ${include_dirs})
128    if (NOT EXISTS ${dir})
129        continue()
130    endif()
131
132    relative_dir(${dir} dest bindir)
133    # Use destination parent, as the last part of the source directory is copied as well
134    cmake_path(GET dest PARENT_PATH dest_p)
135
136    file(MAKE_DIRECTORY ${dest_p})
137    file(COPY ${dir} DESTINATION ${dest_p} FILES_MATCHING PATTERN "*.h")
138
139    cmake_path(RELATIVE_PATH dest BASE_DIRECTORY ${llext_edk} OUTPUT_VARIABLE dest_rel)
140    if(bindir)
141        list(APPEND inc_gen_flags_make "-I\$(${install_dir_var})/${dest_rel}")
142        list(APPEND inc_gen_flags_cmake "-I\${CMAKE_CURRENT_LIST_DIR}/${dest_rel}")
143    else()
144        list(APPEND inc_flags_make "-I\$(${install_dir_var})/${dest_rel}")
145        list(APPEND inc_flags_cmake "-I\${CMAKE_CURRENT_LIST_DIR}/${dest_rel}")
146    endif()
147    list(APPEND all_inc_flags_make "-I\$(${install_dir_var})/${dest_rel}")
148    list(APPEND all_inc_flags_cmake "-I\${CMAKE_CURRENT_LIST_DIR}/${dest_rel}")
149endforeach()
150
151if(CONFIG_LLEXT_EDK_USERSPACE_ONLY)
152    # Copy syscall headers from edk directory, as they were regenerated there.
153    file(COPY ${PROJECT_BINARY_DIR}/edk/include/generated/ DESTINATION ${llext_edk_inc}/zephyr/include/generated)
154endif()
155
156# Generate flags for Makefile
157list(APPEND all_flags_make ${base_flags_make} ${imacros_gen_make} ${all_inc_flags_make})
158list(JOIN all_flags_make " " all_flags_str)
159file(WRITE ${llext_edk}/Makefile.cflags "LLEXT_CFLAGS = ${all_flags_str}")
160
161list(JOIN all_inc_flags_make " " all_inc_flags_str)
162file(APPEND ${llext_edk}/Makefile.cflags "\n\nLLEXT_ALL_INCLUDE_CFLAGS = ${all_inc_flags_str}")
163
164list(JOIN inc_flags_make " " inc_flags_str)
165file(APPEND ${llext_edk}/Makefile.cflags "\n\nLLEXT_INCLUDE_CFLAGS = ${inc_flags_str}")
166
167list(JOIN inc_gen_flags_make " " inc_gen_flags_str)
168file(APPEND ${llext_edk}/Makefile.cflags "\n\nLLEXT_GENERATED_INCLUDE_CFLAGS = ${inc_gen_flags_str}")
169
170list(JOIN base_flags_make " " base_flags_str)
171file(APPEND ${llext_edk}/Makefile.cflags "\n\nLLEXT_BASE_CFLAGS = ${base_flags_str}")
172
173list(JOIN imacros_gen_make " " imacros_gen_str)
174file(APPEND ${llext_edk}/Makefile.cflags "\n\nLLEXT_GENERATED_IMACROS_CFLAGS = ${imacros_gen_str}")
175
176# Generate flags for CMake
177list(APPEND all_flags_cmake ${base_flags_cmake} ${imacros_gen_cmake} ${all_inc_flags_cmake})
178file(WRITE ${llext_edk}/cmake.cflags "set(LLEXT_CFLAGS ${all_flags_cmake})")
179
180file(APPEND ${llext_edk}/cmake.cflags "\n\nset(LLEXT_ALL_INCLUDE_CFLAGS ${all_inc_flags_cmake})")
181
182file(APPEND ${llext_edk}/cmake.cflags "\n\nset(LLEXT_INCLUDE_CFLAGS ${inc_flags_cmake})")
183
184file(APPEND ${llext_edk}/cmake.cflags "\n\nset(LLEXT_GENERATED_INCLUDE_CFLAGS ${inc_gen_flags_cmake})")
185
186file(APPEND ${llext_edk}/cmake.cflags "\n\nset(LLEXT_BASE_CFLAGS ${base_flags_cmake})")
187
188file(APPEND ${llext_edk}/cmake.cflags "\n\nset(LLEXT_GENERATED_IMACROS_CFLAGS ${imacros_gen_cmake})")
189
190# Generate the tarball
191file(ARCHIVE_CREATE
192    OUTPUT ${llext_edk_file}
193    PATHS ${llext_edk}
194    FORMAT gnutar
195    COMPRESSION XZ
196)
197
198file(REMOVE_RECURSE ${llext_edk})
199