1#
2# Arm SCP/MCP Software
3# Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
4#
5# SPDX-License-Identifier: BSD-3-Clause
6#
7
8include_guard()
9
10# cmake-lint: disable=C0113,C0301
11
12# .rst:
13#
14# .. command:: scp_preprocess_source
15#
16# Preprocess a file with the C preprocessor.
17#
18# .. cmake:: scp_preprocess_source(<target> <source> <output>)
19#
20# This macro creates a target ``<target>`` which preprocesses a source file
21# ``<source>``, giving the file ``<output>``.
22#
23# You can set properties on the target created by this macro through the
24# standard means. If you wish you to add extra compile definitions or include
25# directories, you can do so by manually adding them to the
26# ``INCLUDE_DIRECTORIES`` and ``COMPILE_DEFINITIONS`` target properties.
27# ``target_include_directories`` and friends cannot be used on the target
28# created by this macro.
29macro(scp_preprocess_source target source output)
30    #
31    # CMake provides the `CMAKE_C_CREATE_PREPROCESSED_SOURCE` variable, which
32    # describes the command line required to preprocess a C source file. This
33    # variable is in a format similar to this:
34    #
35    # <CMAKE_C_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -E <SOURCE> >
36    # <PREPROCESSED_SOURCE>
37    #
38    # We do some processing on this variable to convert these bracket-surrounded
39    # names to variables we set. For example, `<DEFINES>` is replaced with
40    # `${cpp_DEFINES}`. We then need to do some string replacement magic to
41    # expand that string out to the value of the actual variable.
42    #
43    # The values for some of these, namely include directories, definitions and
44    # other compiler options, come from properties set on the target by the
45    # caller. These are typically taken from the target that this preprocessed
46    # source file.
47    #
48
49    set(command ${CMAKE_C_CREATE_PREPROCESSED_SOURCE})
50    string(REPLACE " " ";" command ${command})
51
52    string(TOUPPER ${CMAKE_BUILD_TYPE} config)
53
54    set(cpp_SOURCE "${source}")
55    set(cpp_PREPROCESSED_SOURCE "${output}")
56
57    set(cpp_CMAKE_C_COMPILER "${CMAKE_C_COMPILER}")
58
59    unset(cpp_DEFINES)
60    unset(cpp_INCLUDES)
61
62    # cmake-format: off
63
64    separate_arguments(cpp_FLAGS
65        UNIX_COMMAND "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${config}} -P")
66
67    list(APPEND cpp_FLAGS "$<TARGET_PROPERTY:${target},COMPILE_OPTIONS>")
68    list(APPEND cpp_DEFINES "$<TARGET_PROPERTY:${target},COMPILE_DEFINITIONS>")
69    list(APPEND cpp_INCLUDES "$<TARGET_PROPERTY:${target},INCLUDE_DIRECTORIES>")
70
71    set(cpp_DEFINES "$<$<BOOL:${cpp_DEFINES}>:-D$<JOIN:${cpp_DEFINES},$<SEMICOLON>-D>>")
72    set(cpp_INCLUDES "$<$<BOOL:${cpp_INCLUDES}>:-I$<JOIN:${cpp_INCLUDES},$<SEMICOLON>-I>>")
73
74    # cmake-format: on
75
76    string(REGEX REPLACE "<([[A-Z_]+)>" "\${cpp_\\1}" command "${command}")
77    string(REGEX MATCH "\\\${[^}]*}" match "${command}")
78
79    while(match)
80        string(REGEX REPLACE "\\\${(.*)}" "\\1" variable "${match}")
81        string(REPLACE "\${${variable}}" "${${variable}}" command "${command}")
82        string(REGEX MATCH "\\\${[^}]*}" match "${command}")
83    endwhile()
84
85    add_custom_command(
86        OUTPUT ${output}
87        MAIN_DEPENDENCY ${source}
88        COMMAND "${command}"
89        VERBATIM COMMAND_EXPAND_LISTS)
90
91    add_custom_target(${target} DEPENDS ${output})
92endmacro()
93