blob: 71e69c1c745244749e35eefbd70af00493164e26 [file] [log] [blame]
Chris Kay27dfa542021-06-25 16:31:11 +01001#[=======================================================================[.rst:
2ArmPreprocessSource
3-------------------
4
5.. default-domain:: cmake
6
7.. command:: arm_preprocess_source
8
9Preprocess a file.
10
11.. code-block:: cmake
12
13 arm_preprocess_source(TARGET <target> OUTPUT <output>
14 SOURCE <source> LANGUAGE <language>
15 [INHIBIT_LINEMARKERS])
16
17Creates a target ``<target>`` which preprocesses a source file ``<source>`` and
18outputs the result to the path ``<output>``. The compiler used to preprocess the
19file is determined by ``<language>``; specifically, via the
20:variable:`CMAKE_<LANG>_COMPILER <variable:CMAKE_<LANG>_COMPILER>` variable.
21
22To pass preprocessor definitions, include directories or command line options to
23the preprocessor, you can apply the following properties to the target
24``<target>``:
25
26 - :prop_tgt:`COMPILE_OPTIONS <prop_tgt:COMPILE_OPTIONS>`
27 - :prop_tgt:`COMPILE_DEFINITIONS <prop_tgt:COMPILE_DEFINITIONS>`
28 - :prop_tgt:`INCLUDE_DIRECTORIES <prop_tgt:INCLUDE_DIRECTORIES>`
29
30For example, if you wish to preprocess a file ``foo.c`` with the preprocessor
31definition ``-DFOO=BAR`` you might use:
32
33.. code-block:: cmake
34 :caption: Example usage
35 :linenos:
36
37 arm_preprocess_source(LANGUAGE C
38 TARGET foo SOURCE "foo.c" OUTPUT "foo.c.i")
39
40 set_target_properties(foo
41 PROPERTIES COMPILE_DEFINITIONS "FOO=BAR")
42
43The ``INHIBIT_LINEMARKERS`` flag inhibits linemarkers on compilers that produce
44them by default. These are often intended by the preprocessor to communicate
45source line information to the compiler, but can interfere with tools that do
46not expect them.
47
48.. note::
49
50 The created target automatically inherits flags from the
51 :variable:`CMAKE_<LANG>_FLAGS <variable:CMAKE_<LANG>_FLAGS>` and
52 :variable:`CMAKE_<LANG>_FLAGS_<CONFIG> <variable:CMAKE_<LANG>_FLAGS_<CONFIG>>`
53 variables.
54#]=======================================================================]
55
56include_guard()
57
58include(ArmAssert)
59
60function(arm_preprocess_source)
61 set(options "INHIBIT_LINEMARKERS")
62 set(single-args "TARGET;SOURCE;OUTPUT;LANGUAGE")
63 set(multi-args "")
64
65 cmake_parse_arguments(PARSE_ARGV 0 ARG
66 "${options}" "${single-args}" "${multi-args}")
67
68 arm_assert(
69 CONDITION DEFINED ARG_TARGET
70 MESSAGE "No value was given for the `TARGET` argument.")
71
72 arm_assert(
73 CONDITION DEFINED ARG_SOURCE
74 MESSAGE "No value was given for the `SOURCE` argument.")
75
76 arm_assert(
77 CONDITION DEFINED ARG_OUTPUT
78 MESSAGE "No value was given for the `OUTPUT` argument.")
79
80 arm_assert(
81 CONDITION DEFINED ARG_LANGUAGE
82 MESSAGE "No value was given for the `LANGUAGE` argument.")
83
84 set(inhibit-linemarkers "${ARG_INHIBIT_LINEMARKERS}")
85
86 #
87 # Make the source path absolute so that there are no issues with path
88 # resolution during preprocessing.
89 #
90
91 cmake_path(ABSOLUTE_PATH ARG_SOURCE NORMALIZE
92 BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
93 cmake_path(ABSOLUTE_PATH ARG_OUTPUT NORMALIZE
94 BASE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
95
96 #
97 # CMake's toolchain detection logic creates a special variable -
98 # `CMAKE_<LANGUAGE>_CREATE_PREPROCESSED_SOURCE` - which describes the
99 # command line required to preprocess a source file for that language. For
100 # example, here it is for the GNU C preprocessor:
101 #
102 # <CMAKE_C_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -E <SOURCE> >
103 # <PREPROCESSED_SOURCE>
104 #
105 # We do some processing on this variable to convert these
106 # bracket-surrounded names to variables we set. For example, `<DEFINES>`
107 # is replaced with `@DEFINES@`. We then need to do some string replacement
108 # magic to expand that string out to the value of the actual variable.
109 #
110 # The values for some of these, namely include directories, definitions
111 # and other compiler options, come from properties set on the target by
112 # the caller. These are typically taken from the target that this
113 # preprocessed source file belongs to.
114 #
115
116 arm_assert(
117 CONDITION DEFINED CMAKE_${ARG_LANGUAGE}_CREATE_PREPROCESSED_SOURCE
118 MESSAGE "Unable to determine the preprocessor command line for the "
119 "${ARG_LANGUAGE} language. Please report this to the "
120 "developer.")
121
122 set(command "${CMAKE_${ARG_LANGUAGE}_CREATE_PREPROCESSED_SOURCE}")
123
124 #
125 # Ensure the paths we provide to the command line are in OS-native form. We
126 # don't want the compiler complaining that the paths are malformed.
127 #
128
129 cmake_path(NATIVE_PATH ARG_SOURCE NORMALIZE SOURCE)
130 cmake_path(NATIVE_PATH ARG_OUTPUT NORMALIZE PREPROCESSED_SOURCE)
131
132 #
133 # Split up the command into a list.
134 #
135
136 separate_arguments(command NATIVE_COMMAND "${command}")
137
138 set(FLAGS "$<TARGET_PROPERTY:${ARG_TARGET},COMPILE_OPTIONS>")
139 set(DEFINES "$<TARGET_PROPERTY:${ARG_TARGET},COMPILE_DEFINITIONS>")
140 set(INCLUDES "$<TARGET_PROPERTY:${ARG_TARGET},INCLUDE_DIRECTORIES>")
141
142 #
143 # Until we come across an exception to the rule, use `-D` for definitions
144 # and `-I` for include directories.
145 #
146
147 set(DEFINES "$<$<BOOL:${DEFINES}>:-D$<JOIN:${DEFINES},$<SEMICOLON>-D>>")
148 set(INCLUDES "$<$<BOOL:${INCLUDES}>:-I$<JOIN:${INCLUDES},$<SEMICOLON>-I>>")
149
150 #
151 # Apply compiler-specific behaviours. The default assumption is that we're
152 # using a toolchain that behaves like GCC, as many tools attempt to emulate
153 # it.
154 #
155
156 if(ARG_LANGUAGE STREQUAL "ASM")
157 list(APPEND FLAGS "-x" "assembler-with-cpp")
158 elseif(language STREQUAL "C")
159 list(APPEND FLAGS "-x" "c")
160 elseif(language STREQUAL "CXX")
161 list(APPEND FLAGS "-x" "c++")
162 endif()
163
164 if(ARG_INHIBIT_LINEMARKERS)
165 list(APPEND FLAGS "-P")
166 endif()
167
168 #
169 # Here's the magic part. For every angle bracket-wrapped value in the
170 # command, replace it with its at-variable form, then replace it with the
171 # value of that variable.
172 #
173
174 string(REGEX REPLACE "<([[A-Z_]+)>" [[@\1@]] command "${command}")
175 arm_expand(OUTPUT command STRING "${command}" ATONLY)
176
177 #
178 # Finally, add the command which generates the preprocessed file.
179 #
180
181 add_custom_target(${ARG_TARGET}
182 SOURCES "${ARG_SOURCE}"
183 DEPENDS "${ARG_OUTPUT}")
184
185 add_custom_command(
186 OUTPUT "${ARG_OUTPUT}"
187 MAIN_DEPENDENCY "${ARG_SOURCE}"
188 COMMAND "${command}"
189 VERBATIM COMMAND_EXPAND_LISTS)
190endfunction()