blob: a9502469d41e36e1a8ce4c0a628472fcce7e005e [file] [log] [blame]
Soby Mathewb4c6df42022-11-09 11:13:29 +00001#
2# SPDX-License-Identifier: BSD-3-Clause
3# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
4#
5
6#[=======================================================================[.rst:
7ArmPreprocessSource
8-------------------
9
10.. default-domain:: cmake
11
12.. command:: arm_preprocess_source
13
14Preprocess a file with the C preprocessor.
15
16.. code:: cmake
17
18 arm_preprocess_source(<target> <source>)
19
20Creates a target ``<target>`` which preprocesses an input file ``<source>``. The
21target created by this macro can then be used as a dependency for a higher-level
22target. The output file can be retrieved from the :prop_tgt:`LOCATION_<CONFIG>
23<prop_tgt:LOCATION_<CONFIG>>` target property.
24
Jean-Philippe Bruckerb5decc62025-05-02 13:17:15 +010025The path of the output file is built using only the file name of the source, so
26the source does not have to be in CMAKE_CURRENT_SOURCE_DIR.
27
Soby Mathewb4c6df42022-11-09 11:13:29 +000028The following target properties are passed to the preprocessor:
29
30- :prop_tgt:`COMPILE_OPTIONS <prop_tgt:COMPILE_OPTIONS>`
31- :prop_tgt:`COMPILE_DEFINITIONS <prop_tgt:COMPILE_DEFINITIONS>`
32- :prop_tgt:`INCLUDE_DIRECTORIES <prop_tgt:INCLUDE_DIRECTORIES>`
33
34.. note::
35
36 The created target automatically inherits :variable:`CMAKE_C_FLAGS
37 <variable:CMAKE_<LANG>_FLAGS>` and :variable:`CMAKE_C_FLAGS_<CONFIG>
38 <variable:CMAKE_<LANG>_FLAGS_<CONFIG>>`.
39
40For example, if you wish to preprocess a file while providing the preprocessor
41definition ``-DFOO=BAR``, you would use:
42
43.. code:: cmake
44
45 arm_preprocess_source(foo "bar.ld.S")
46
47 set_target_properties(foo PROPERTIES
48 COMPILE_DEFINITIONS "FOO=BAR")
49
50 get_target_property(location foo LOCATION_${CMAKE_BUILD_TYPE})
51
52 message(STATUS "My preprocessed file is here: ${location}")
53
54For processing linker scripts specifically, see the
55:command:`arm_target_linker_script` command instead.
56#]=======================================================================]
57
58include_guard()
59
60macro(arm_preprocess_source target source)
Jean-Philippe Bruckerb5decc62025-05-02 13:17:15 +010061 ##
62 ## Create the output file path from the basename of the source, and the
63 ## current binary directory.
64 ##
65 get_filename_component(preprocessed_source "${source}.i" NAME)
Soby Mathewb4c6df42022-11-09 11:13:29 +000066
67 #
68 # If we're using a multi-config generator, we need to place the output file
69 # into the correct configuration directory.
70 #
71
72 get_property(multi_config GLOBAL
73 PROPERTY "GENERATOR_IS_MULTI_CONFIG")
74
75 if(multi_config)
76 string(PREPEND preprocessed_source "$<CONFIG>/")
77 endif()
78
79 #
80 # Make the source path absolute so that we don't need to care which
81 # working directory the preprocessor uses.
82 #
83
84 string(PREPEND preprocessed_source "${CMAKE_CURRENT_BINARY_DIR}/")
85
86 #
87 # Create a single target for all configurations. It's differentiated based
88 # on the generator expression in the dependency.
89 #
90
91 add_custom_target(${target}
92 DEPENDS "${preprocessed_source}")
93
94 #
95 # Now that we've got that out of the way, we need to generate the
96 # preprocessing command for each of the enabled configurations. Multi-config
97 # generators will use `CMAKE_CONFIGURATION_TYPES`, whereas single-config
98 # generators will use `CMAKE_BUILD_TYPE`. Only one is ever non-empty.
99 #
100
101 foreach(config IN LISTS CMAKE_CONFIGURATION_TYPES CMAKE_BUILD_TYPE)
102 #
103 # CMake provides the `CMAKE_C_CREATE_PREPROCESSED_SOURCE` variable,
104 # which describes the command line required to preprocess a C source
105 # file. This variable is in a format similar to this:
106 #
107 # <CMAKE_C_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -E <SOURCE> >
108 # <PREPROCESSED_SOURCE>
109 #
110 # We do some processing on this variable to convert these
111 # bracket-surrounded names to variables we set. For example, `<DEFINES>`
112 # is replaced with `${DEFINES}`. We then need to do some string
113 # replacement magic to expand that string out to the value of the actual
114 # variable.
115 #
116 # The values for some of these, namely include directories, definitions
117 # and other compiler options, come from properties set on the target by
118 # the caller. These are typically taken from the target that this
119 # preprocessed source file belongs to.
120 #
121
122 set(command ${CMAKE_C_CREATE_PREPROCESSED_SOURCE})
123 string(REPLACE " " ";" command ${command})
124
125 get_filename_component(SOURCE "${source}" ABSOLUTE)
126 string(REPLACE "$<CONFIG>" "${config}" PREPROCESSED_SOURCE
127 "${preprocessed_source}")
128
129 separate_arguments(FLAGS UNIX_COMMAND
130 "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${config}} -P -x c")
131
132 if(CMAKE_C_COMPILER_TARGET)
133 if(CMAKE_C_COMPILER_ID MATCHES "Clang")
134 list(APPEND FLAGS "-target" "${CMAKE_C_COMPILER_TARGET}")
135 endif()
136 endif()
137
138 unset(DEFINES)
139 unset(INCLUDES)
140
141 list(APPEND FLAGS "$<TARGET_PROPERTY:${target},COMPILE_OPTIONS>")
142 list(APPEND DEFINES "$<TARGET_PROPERTY:${target},COMPILE_DEFINITIONS>")
143 list(APPEND INCLUDES "$<TARGET_PROPERTY:${target},INCLUDE_DIRECTORIES>")
144
145 set(DEFINES "$<$<BOOL:${DEFINES}>:-D$<JOIN:${DEFINES},$<SEMICOLON>-D>>")
146 set(INCLUDES "$<$<BOOL:${INCLUDES}>:-I$<JOIN:${INCLUDES},$<SEMICOLON>-I>>")
147
148 string(REGEX REPLACE "<([[A-Z_]+)>" "\${\\1}" command "${command}")
149 string(REGEX MATCH "\\\${[^}]*}" match "${command}")
150
151 while(match)
152 string(REGEX REPLACE "\\\${(.*)}" "\\1" variable "${match}")
153 string(REPLACE "\${${variable}}" "${${variable}}" command "${command}")
154 string(REGEX MATCH "\\\${[^}]*}" match "${command}")
155 endwhile()
156
157 add_custom_command(
158 OUTPUT "${PREPROCESSED_SOURCE}"
159 MAIN_DEPENDENCY ${source}
160 COMMAND "${command}"
161 VERBATIM COMMAND_EXPAND_LISTS)
162
163 set_target_properties(${target} PROPERTIES
164 LOCATION_${config} "${PREPROCESSED_SOURCE}")
165 endforeach()
166endmacro()