blob: a9502469d41e36e1a8ce4c0a628472fcce7e005e [file] [log] [blame]
#
# SPDX-License-Identifier: BSD-3-Clause
# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
#
#[=======================================================================[.rst:
ArmPreprocessSource
-------------------
.. default-domain:: cmake
.. command:: arm_preprocess_source
Preprocess a file with the C preprocessor.
.. code:: cmake
arm_preprocess_source(<target> <source>)
Creates a target ``<target>`` which preprocesses an input file ``<source>``. The
target created by this macro can then be used as a dependency for a higher-level
target. The output file can be retrieved from the :prop_tgt:`LOCATION_<CONFIG>
<prop_tgt:LOCATION_<CONFIG>>` target property.
The path of the output file is built using only the file name of the source, so
the source does not have to be in CMAKE_CURRENT_SOURCE_DIR.
The following target properties are passed to the preprocessor:
- :prop_tgt:`COMPILE_OPTIONS <prop_tgt:COMPILE_OPTIONS>`
- :prop_tgt:`COMPILE_DEFINITIONS <prop_tgt:COMPILE_DEFINITIONS>`
- :prop_tgt:`INCLUDE_DIRECTORIES <prop_tgt:INCLUDE_DIRECTORIES>`
.. note::
The created target automatically inherits :variable:`CMAKE_C_FLAGS
<variable:CMAKE_<LANG>_FLAGS>` and :variable:`CMAKE_C_FLAGS_<CONFIG>
<variable:CMAKE_<LANG>_FLAGS_<CONFIG>>`.
For example, if you wish to preprocess a file while providing the preprocessor
definition ``-DFOO=BAR``, you would use:
.. code:: cmake
arm_preprocess_source(foo "bar.ld.S")
set_target_properties(foo PROPERTIES
COMPILE_DEFINITIONS "FOO=BAR")
get_target_property(location foo LOCATION_${CMAKE_BUILD_TYPE})
message(STATUS "My preprocessed file is here: ${location}")
For processing linker scripts specifically, see the
:command:`arm_target_linker_script` command instead.
#]=======================================================================]
include_guard()
macro(arm_preprocess_source target source)
##
## Create the output file path from the basename of the source, and the
## current binary directory.
##
get_filename_component(preprocessed_source "${source}.i" NAME)
#
# If we're using a multi-config generator, we need to place the output file
# into the correct configuration directory.
#
get_property(multi_config GLOBAL
PROPERTY "GENERATOR_IS_MULTI_CONFIG")
if(multi_config)
string(PREPEND preprocessed_source "$<CONFIG>/")
endif()
#
# Make the source path absolute so that we don't need to care which
# working directory the preprocessor uses.
#
string(PREPEND preprocessed_source "${CMAKE_CURRENT_BINARY_DIR}/")
#
# Create a single target for all configurations. It's differentiated based
# on the generator expression in the dependency.
#
add_custom_target(${target}
DEPENDS "${preprocessed_source}")
#
# Now that we've got that out of the way, we need to generate the
# preprocessing command for each of the enabled configurations. Multi-config
# generators will use `CMAKE_CONFIGURATION_TYPES`, whereas single-config
# generators will use `CMAKE_BUILD_TYPE`. Only one is ever non-empty.
#
foreach(config IN LISTS CMAKE_CONFIGURATION_TYPES CMAKE_BUILD_TYPE)
#
# CMake provides the `CMAKE_C_CREATE_PREPROCESSED_SOURCE` variable,
# which describes the command line required to preprocess a C source
# file. This variable is in a format similar to this:
#
# <CMAKE_C_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -E <SOURCE> >
# <PREPROCESSED_SOURCE>
#
# We do some processing on this variable to convert these
# bracket-surrounded names to variables we set. For example, `<DEFINES>`
# is replaced with `${DEFINES}`. We then need to do some string
# replacement magic to expand that string out to the value of the actual
# variable.
#
# The values for some of these, namely include directories, definitions
# and other compiler options, come from properties set on the target by
# the caller. These are typically taken from the target that this
# preprocessed source file belongs to.
#
set(command ${CMAKE_C_CREATE_PREPROCESSED_SOURCE})
string(REPLACE " " ";" command ${command})
get_filename_component(SOURCE "${source}" ABSOLUTE)
string(REPLACE "$<CONFIG>" "${config}" PREPROCESSED_SOURCE
"${preprocessed_source}")
separate_arguments(FLAGS UNIX_COMMAND
"${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${config}} -P -x c")
if(CMAKE_C_COMPILER_TARGET)
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
list(APPEND FLAGS "-target" "${CMAKE_C_COMPILER_TARGET}")
endif()
endif()
unset(DEFINES)
unset(INCLUDES)
list(APPEND FLAGS "$<TARGET_PROPERTY:${target},COMPILE_OPTIONS>")
list(APPEND DEFINES "$<TARGET_PROPERTY:${target},COMPILE_DEFINITIONS>")
list(APPEND INCLUDES "$<TARGET_PROPERTY:${target},INCLUDE_DIRECTORIES>")
set(DEFINES "$<$<BOOL:${DEFINES}>:-D$<JOIN:${DEFINES},$<SEMICOLON>-D>>")
set(INCLUDES "$<$<BOOL:${INCLUDES}>:-I$<JOIN:${INCLUDES},$<SEMICOLON>-I>>")
string(REGEX REPLACE "<([[A-Z_]+)>" "\${\\1}" command "${command}")
string(REGEX MATCH "\\\${[^}]*}" match "${command}")
while(match)
string(REGEX REPLACE "\\\${(.*)}" "\\1" variable "${match}")
string(REPLACE "\${${variable}}" "${${variable}}" command "${command}")
string(REGEX MATCH "\\\${[^}]*}" match "${command}")
endwhile()
add_custom_command(
OUTPUT "${PREPROCESSED_SOURCE}"
MAIN_DEPENDENCY ${source}
COMMAND "${command}"
VERBATIM COMMAND_EXPAND_LISTS)
set_target_properties(${target} PROPERTIES
LOCATION_${config} "${PREPROCESSED_SOURCE}")
endforeach()
endmacro()