1#------------------------------------------------------------------------------- 2# Copyright (c) 2019-2024, Arm Limited and Contributors. All rights reserved. 3# 4# SPDX-License-Identifier: BSD-3-Clause 5# 6#------------------------------------------------------------------------------- 7 8#[===[.rst: 9Compiler abstraction for GCC 10---------------------------- 11 12.. cmake:variable:: CROSS_COMPILE 13 14 A ';' separated GCC prefix triplets to use when searching for the cross-compiler. 15 (i.e. ``aarch64-none-elf;aarch64-elf``). 16 The variable can be set on the command line with ``-DCROSS_COMPILE=<value>`` or in the 17 environment. If both is specified, command line takes precedence. 18 19.. cmake:variable:: LIBGCC_PATH 20 21 An absolute path to specify the location of the gcc specific library. The name 22 of the library is libgcc.a. Note that it must be the full path with library name. 23 The variable can be set on the command line with ``-DLIBGCC_PATH=<value>`` or in the 24 environment. If both is specified, command line takes precedence. 25 26.. cmake:variable:: LIBGCC_INCLUDE_DIRS 27 28 A semicolon separated list of absolute paths to specify the location of gcc specific header 29 files. The variable can be set on the command line with ``-DLIBGCC_INCLUDE_DIRS=<value>`` 30 or in the environment. If both is specified, command line takes precedence. 31 32.. cmake:variable:: LIBGCC_LOCATE_CFLAGS 33 34 The compiler options used when searching for the gcc library (libgcc.a). 35 Setting the value is optional. 36 The variable can be set on the command line with ``-DLIBGCC_LOCATE_CFLAGS=<value>`` or 37 in the environment. 38 39#]===] 40 41include_guard(DIRECTORY) 42 43#Generate a list of tool names to look for. Store the result in CMAKE_<lang>_COMPILER. 44function(gcc_find_tool NAME LANG) 45 string(REGEX REPLACE "([^;]+);" "\\1${NAME};\\1${NAME}.exe;" _gcc_names "${CROSS_COMPILE};") 46 find_program(_cross_compile_gcc NAMES ${_gcc_names} REQUIRED) 47 if (NOT _cross_compile_gcc) 48 string(REPLACE ";" " " _msg "${_gcc_names}") 49 message(FATAL_ERROR "Failed to find ${NAME} with the names: ${_msg}") 50 endif() 51 set(CMAKE_${LANG}_COMPILER ${_cross_compile_gcc} CACHE STRING "${LANG} compiler executable.") 52endfunction() 53 54if(CMAKE_CROSSCOMPILING) 55 if(NOT CROSS_COMPILE AND NOT DEFINED ENV{CROSS_COMPILE}) 56 message(FATAL_ERROR "'CROSS_COMPILE' is not defined. Set it to the gcc prefix triplet, ie. cmake <..>-DCROSS_COMPILE=aarch64-elf-") 57 endif() 58 59 set(CROSS_COMPILE $ENV{CROSS_COMPILE} CACHE STRING "Prefix of the cross-compiler commands") 60 61 gcc_find_tool(gcc C) 62 gcc_find_tool(g++ CXX) 63 64 #Official solution to disable compiler checks 65 set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) 66endif() 67 68#By default when INTERFACE_INCUDES of libraries linked to an exe are treated 69#as system includes. gcc-arm-8.2-2019.01-i686-mingw32-aarch64-elf (gcc 8.2.1) will 70#set C linkage o these files, which will result in compilation errors for C++ projects. 71#This setting fixes that. 72set(CMAKE_NO_SYSTEM_FROM_IMPORTED True) 73 74#[===[.rst: 75.. cmake:command:: compiler_preprocess_file 76 77 .. code-block:: cmake 78 79 compiler_preprocess_file(SRC file.c DST file_pp.c) 80 compiler_preprocess_file(SRC file.c DST file_pp.c 81 DEFINES USE_LIB INCLUDES include/lib) 82 83 Run the preprocessor on a file and save the output to another file. Optionally 84 provide defines and include paths to the preprocessor. 85 86 Inputs: 87 88 ``SRC`` 89 Name of the source file to preprocess. 90 91 ``DST`` 92 Where to write the preprocessed output. 93 94 ``TARGET`` (optional) 95 Target that the custom command is tied to. 96 97 ``DEFINES`` (multi, optional) 98 Definitions for the preprocessor. 99 100 ``INCLUDES`` (multi, optional) 101 Include paths for the preprocessor. 102 103#]===] 104function(compiler_preprocess_file) 105 set(_OPTIONS_ARGS) 106 set(_ONE_VALUE_ARGS TARGET SRC DST) 107 set(_MULTI_VALUE_ARGS DEFINES INCLUDES) 108 cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN}) 109 110 check_args(compiler_preprocess_file SRC DST) 111 112 set(_flags "") 113 if(_MY_PARAMS_DEFINES) 114 list(TRANSFORM _MY_PARAMS_DEFINES PREPEND -D) 115 list(APPEND _flags ${_MY_PARAMS_DEFINES}) 116 endif() 117 if(_MY_PARAMS_INCLUDES) 118 list(TRANSFORM _MY_PARAMS_INCLUDES PREPEND -I) 119 list(APPEND _flags ${_MY_PARAMS_INCLUDES}) 120 endif() 121 122 if(_MY_PARAMS_TARGET) 123 add_custom_command( 124 TARGET ${_MY_PARAMS_TARGET} 125 POST_BUILD 126 DEPENDS ${_MY_PARAMS_SRC} 127 COMMAND ${CMAKE_C_COMPILER} -E -P -x assembler-with-cpp ${_flags} 128 ${_MY_PARAMS_SRC} -o ${_MY_PARAMS_DST} 129 ) 130 else() 131 add_custom_command( 132 DEPENDS ${_MY_PARAMS_SRC} 133 OUTPUT ${_MY_PARAMS_DST} 134 COMMAND ${CMAKE_C_COMPILER} -E -P -x assembler-with-cpp ${_flags} 135 ${_MY_PARAMS_SRC} -o ${_MY_PARAMS_DST} 136 ) 137 endif() 138endfunction() 139 140#[===[.rst: 141.. cmake:command:: compiler_set_linker_script 142 143 .. code-block:: cmake 144 145 compiler_set_linker_script(TARGET foo FILE foo.ld.S) 146 compiler_set_linker_script(TARGET foo FILE foo.ld.S DEF USE_LIB INC include/lib) 147 148 Set linker script for a target. The function adds an LDFLAG using the 149 toolchain specific syntax to the TARGET_linker_script group, which is applied 150 onto the target by the caller function. FILE will be preprocessed, optionally 151 defines and/or includes can be provided using DEF/INC arguments. 152 153 Inputs: 154 155 ``TARGET`` 156 Name of the target. 157 158 ``FILE`` 159 Linker script file for the target. 160 161 ``DEF`` (multi, optional) 162 Defines for the linker script preprocessor. 163 164 ``INC`` (multi, optional) 165 Include paths for the linker script preprocessor. 166 167#]===] 168function(compiler_set_linker_script) 169 set(_OPTIONS_ARGS) 170 set(_ONE_VALUE_ARGS TARGET FILE) 171 set(_MULTI_VALUE_ARGS DEF INC) 172 cmake_parse_arguments(_MY_PARAMS "${_OPTIONS_ARGS}" "${_ONE_VALUE_ARGS}" "${_MULTI_VALUE_ARGS}" ${ARGN}) 173 174 check_args(compiler_set_linker_script TARGET FILE) 175 176 get_filename_component(_src "${_MY_PARAMS_FILE}" ABSOLUTE) 177 get_filename_component(_src_ext "${_MY_PARAMS_FILE}" EXT) 178 set(_dst "${CMAKE_BINARY_DIR}/${_MY_PARAMS_TARGET}.ld") 179 180 if(NOT ("${_src_ext}" STREQUAL ".ld" OR "${_src_ext}" STREQUAL ".ld.S")) 181 message(WARNING "compiler_set_linker_script(): extension mismatch '${_src}'") 182 endif() 183 184 compiler_preprocess_file( 185 SRC ${_src} 186 DST ${_dst} 187 DEFINES ${_MY_PARAMS_DEF} __LINKER__ 188 INCLUDES ${_MY_PARAMS_INC} 189 ) 190 191 add_custom_target("${_MY_PARAMS_TARGET}_ld" DEPENDS "${_dst}") 192 add_dependencies("${_MY_PARAMS_TARGET}" "${_MY_PARAMS_TARGET}_ld") 193 194 target_link_options(${_MY_PARAMS_TARGET} PRIVATE "-Wl,--script=${_dst}") 195 set_target_properties(${_MY_PARAMS_TARGET} PROPERTIES LINK_DEPENDS "${_dst}") 196endfunction() 197 198#[===[.rst: 199.. cmake:command:: compiler_generate_binary_output 200 201 .. code-block:: cmake 202 203 compiler_generate_binary_output(TARGET <name> RES <var>) 204 205 Generate binary output for the target. The function converts the output 206 executable into bin file using toolchain specific syntax. 207 208 Inputs: 209 210 ``TARGET`` 211 Name of the target. 212 213 Outputs: 214 215 ``RES`` 216 Full patch to output file. 217 218#]===] 219function(compiler_generate_binary_output) 220 set(options) 221 set(oneValueArgs TARGET NAME RES) 222 set(multiValueArgs) 223 cmake_parse_arguments(MY "${options}" "${oneValueArgs}" 224 "${multiValueArgs}" ${ARGN} ) 225 add_custom_command( 226 TARGET ${MY_TARGET} POST_BUILD 227 COMMAND ${CMAKE_OBJCOPY} -O binary 228 $<TARGET_FILE:${MY_TARGET}> 229 $<TARGET_FILE_DIR:${MY_TARGET}>/${MY_NAME}) 230 if (MY_RES) 231 set(${MY_RES} $<TARGET_FILE_DIR:${MY_TARGET}>/${MY_NAME} PARENT_SCOPE) 232 endif() 233 234endfunction() 235 236#[===[.rst: 237.. cmake:command:: compiler_generate_stripped_elf 238 239 .. code-block:: cmake 240 241 compiler_generate_stripped_elf(TARGET foo NAME foo.stripped.elf RES var) 242 243 Strip all symbols that are not needed for relocation processing and return the location 244 of the result. 245 246 Inputs: 247 248 ``TARGET`` 249 Name of the target. 250 251 ``NAME`` 252 Name of output file 253 254 Outputs: 255 256 ``RES`` 257 Name of variable to store the full path of the stripped executable. 258 259#]===] 260 261function(compiler_generate_stripped_elf) 262 set(options) 263 set(oneValueArgs TARGET NAME RES) 264 set(multiValueArgs) 265 cmake_parse_arguments(MY "${options}" "${oneValueArgs}" 266 "${multiValueArgs}" ${ARGN} ) 267 268 add_custom_command( 269 TARGET ${MY_TARGET} POST_BUILD 270 COMMAND ${CMAKE_OBJCOPY} --strip-unneeded 271 $<TARGET_FILE:${MY_TARGET}> 272 $<TARGET_FILE_DIR:${MY_TARGET}>/${MY_NAME}) 273 if (MY_RES) 274 set(${MY_RES} $<TARGET_FILE_DIR:${MY_TARGET}>/${MY_NAME} PARENT_SCOPE) 275 endif() 276endfunction() 277 278#[===[.rst: 279.. cmake:command:: gcc_get_lib_location 280 281 .. code-block:: cmake 282 283 gcc_get_lib_location(TARGET foo NAME foo.stripped.elf RES var) 284 285 Query the location of a specific library part of the GCC binary release. Can 286 be used to find built in libraries like libgcc.a when i.w. -nostdlib option 287 is used. 288 289 The function uses the :variable:`LIBGCC_LOCATE_CFLAGS`. 290 291 Inputs: 292 293 ``LIBRARY_NAME`` 294 Name of the library to search for. 295 296 Outputs: 297 298 ``RES`` 299 Name of variable to store the full path of the library. 300 301#]===] 302function(gcc_get_lib_location) 303 set(options) 304 set(oneValueArgs LIBRARY_NAME RES) 305 set(multiValueArgs) 306 cmake_parse_arguments(MY "${options}" "${oneValueArgs}" 307 "${multiValueArgs}" ${ARGN} ) 308 309 if (DEFINED ENV{LIBGCC_LOCATE_CFLAGS}) 310 set(LIBGCC_LOCATE_CFLAGS $ENV{LIBGCC_LOCATE_CFLAGS} CACHE STRING "GCC library search options" ) 311 endif() 312 313 execute_process( 314 COMMAND ${CMAKE_C_COMPILER} ${LIBGCC_LOCATE_CFLAGS} "--print-file-name=${MY_LIBRARY_NAME}" 315 OUTPUT_VARIABLE _RES 316 RESULT_VARIABLE _GCC_ERROR_CODE 317 OUTPUT_STRIP_TRAILING_WHITESPACE 318 ) 319 320 if(_GCC_ERROR_CODE GREATER 0) 321 message(WARNING "GCC (${CMAKE_C_COMPILER}) invocation failed, cannot determine location of library \"${MY_LIBRARY_NAME}\".") 322 set(_RES "${LIBRARY_NAME}-NOTFOUND") 323 endif() 324 325 if (NOT IS_ABSOLUTE "${_RES}") 326 message(WARNING "GCC (${CMAKE_C_COMPILER}) failed to return the location of file \"${MY_LIBRARY_NAME}\".") 327 set(_RES "${LIBRARY_NAME}-NOTFOUND") 328 endif() 329 330 set(${MY_RES} ${_RES} PARENT_SCOPE) 331endfunction() 332 333 334#[===[.rst: 335.. cmake:command:: compiler_set_freestanding 336 337 .. code-block:: cmake 338 339 compiler_set_freestanding(TARGET foo) 340 341 Configure the target specified for "freestanging" compilation mode. Please see [1] for more information. 342 This will configure the target: 343 - to block access to the "built in" standard library and its headers 344 - link compiler specific libraries 345 - add include paths to compiler specific headers 346 347 All settings will be PUBLIC or INTERFACE (for imported targets) and thus will take effect on targets 348 depending on the configured one. 349 350 The function uses and manipulates the following CACHE variables: 351 - :variable:`LIBGCC_PATH` 352 - :variable:`LIBGCC_INCLUDE_DIRS` 353 354 CMake has a spacial behavior which needs a workaround. CMake is automatically filtering out built in compiler 355 include paths from the compiler command line. 356 As a workaround, compiler specific headers are copied to the build directory and set include path to the new 357 location. 358 359 Limitations: 360 - Inheritance of target settings may be problematic. Compiling components in "freestanding" mode may put 361 restrictions on reusing these. When such components are installed, the "freestanding" nature shall be 362 propagated to dependencies. This is not tested or implemented. 363 364 1: https://wiki.osdev.org/Implications_of_writing_a_freestanding_C_project 365 2: https://gitlab.kitware.com/cmake/cmake/-/issues/19227 366 367 Inputs: 368 369 ``TARGET`` 370 Name of the target to configure. 371 372 Outputs: 373 374 N/A 375 376#]===] 377function(compiler_set_freestanding) 378 set(options) 379 set(oneValueArgs TARGET) 380 set(multiValueArgs) 381 cmake_parse_arguments(MY "${options}" "${oneValueArgs}" 382 "${multiValueArgs}" ${ARGN} ) 383 384 # Validate parameters 385 if (NOT DEFINED MY_TARGET) 386 message(FATAL_ERROR "Mandatory parameter TARGET is missing!") 387 endif() 388 389 ### Get the location of libgcc.a 390 # Copy values from environment if present. Note: if the value is already in the CACHE, this set will have no effect. 391 if(DEFINED ENV{LIBGCC_PATH}) 392 set(LIBGCC_PATH $ENV{LIBGCC_PATH} CACHE PATH "location of libgcc.a") 393 endif() 394 if (NOT DEFINED LIBGCC_PATH) 395 gcc_get_lib_location(LIBRARY_NAME "libgcc.a" RES _TMP_VAR) 396 397 if (NOT _TMP_VAR) 398 message(FATAL_ERROR "Location of libgcc.a can not be determined. Please set LIBGCC_PATH on the command" 399 " line or in the environment.") 400 endif() 401 set(LIBGCC_PATH ${_TMP_VAR} CACHE PATH "location of libgcc.a") 402 unset(_TMP_VAR) 403 endif() 404 405 # Validate LIBGCC_PATH 406 if(NOT EXISTS "${LIBGCC_PATH}" OR IS_DIRECTORY "${LIBGCC_PATH}") 407 message(FATAL_ERROR "LIBGCC_PATH \"${LIBGCC_PATH}\" must be the full path of a library file." 408 " Either set LIBGCC_PATH on the command line (or in the environment), or fix the existing" 409 " value.") 410 endif() 411 message(STATUS "libgcc.a for target \"${MY_TARGET}\" is used from ${LIBGCC_PATH}") 412 413 ### Get the location of libgcc specific header files. 414 # Copy values from environment if present. Note: if the value is already in the CACHE, this set will have no effect. 415 if(DEFINED ENV{LIBGCC_INCLUDE_DIRS}) 416 set(LIBGCC_INCLUDE_DIRS $ENV{LIBGCC_INCLUDE_DIRS} CACHE STRING "GCC specific include PATHs") 417 endif() 418 if(NOT DEFINED LIBGCC_INCLUDE_DIRS) 419 # We can get the correct path if we ask for a location without a library name 420 gcc_get_lib_location(LIBRARY_NAME "" RES _TMP_VAR) 421 422 if (NOT _TMP_VAR) 423 message(FATAL_ERROR "Location of GCC specific include PATHs can not be determined. Please set" 424 " LIBGCC_INCLUDE_DIRS on the command line or in the environment.") 425 endif() 426 427 set(LIBGCC_INCLUDE_DIRS 428 "${_TMP_VAR}/include" 429 "${_TMP_VAR}/include-fixed" CACHE STRING "GCC specific include PATHs") 430 unset(_TMP_VAR) 431 endif() 432 433 # There is no way to stop cmake from filtering out built in compiler include paths 434 # from compiler command line (see https://gitlab.kitware.com/cmake/cmake/-/issues/19227). 435 # As a workaround copy headers to build directory and set include path to the new 436 # location. 437 # Also validate locations. 438 if (NOT GCC_INCLUDES_MOVED) 439 foreach(_dir IN LISTS LIBGCC_INCLUDE_DIRS) 440 if(NOT IS_DIRECTORY "${_dir}") 441 message(FATAL_ERROR "GCC specific include PATH \"${_dir}\" does not exist.") 442 endif() 443 444 file(COPY "${_dir}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gcc-include") 445 get_filename_component(_TMP_VAR "${_dir}" NAME) 446 list(APPEND _gcc_include_dirs "${CMAKE_CURRENT_BINARY_DIR}/gcc-include/${_TMP_VAR}") 447 message(STATUS "Compiler specific include path \"${_dir}\" mirrored to" 448 " \"${CMAKE_CURRENT_BINARY_DIR}/gcc-include/${_TMP_VAR}\".") 449 endforeach() 450 unset(_TMP_VAR) 451 set(GCC_INCLUDES_MOVED True CACHE BOOL "GCC include files are already copied.") 452 mark_as_advanced(GCC_INCLUDES_MOVED) 453 # Fix the variable in the CACHE. 454 set(LIBGCC_INCLUDE_DIRS ${_gcc_include_dirs} CACHE STRING "GCC specific include PATHs" FORCE) 455 endif() 456 457 # Configure the target for freestanding mode. 458 target_compile_options(${MY_TARGET} PUBLIC -nostdinc -ffreestanding -fno-builtin) 459 target_include_directories(${MY_TARGET} SYSTEM PUBLIC ${LIBGCC_INCLUDE_DIRS}) 460 target_link_options(${MY_TARGET} PUBLIC "-nostdlib" "-nostartfiles") 461 target_link_libraries(${MY_TARGET} PUBLIC "${LIBGCC_PATH}") 462endfunction() 463