blob: 71e69c1c745244749e35eefbd70af00493164e26 [file] [log] [blame]
#[=======================================================================[.rst:
ArmPreprocessSource
-------------------
.. default-domain:: cmake
.. command:: arm_preprocess_source
Preprocess a file.
.. code-block:: cmake
arm_preprocess_source(TARGET <target> OUTPUT <output>
SOURCE <source> LANGUAGE <language>
[INHIBIT_LINEMARKERS])
Creates a target ``<target>`` which preprocesses a source file ``<source>`` and
outputs the result to the path ``<output>``. The compiler used to preprocess the
file is determined by ``<language>``; specifically, via the
:variable:`CMAKE_<LANG>_COMPILER <variable:CMAKE_<LANG>_COMPILER>` variable.
To pass preprocessor definitions, include directories or command line options to
the preprocessor, you can apply the following properties to the target
``<target>``:
- :prop_tgt:`COMPILE_OPTIONS <prop_tgt:COMPILE_OPTIONS>`
- :prop_tgt:`COMPILE_DEFINITIONS <prop_tgt:COMPILE_DEFINITIONS>`
- :prop_tgt:`INCLUDE_DIRECTORIES <prop_tgt:INCLUDE_DIRECTORIES>`
For example, if you wish to preprocess a file ``foo.c`` with the preprocessor
definition ``-DFOO=BAR`` you might use:
.. code-block:: cmake
:caption: Example usage
:linenos:
arm_preprocess_source(LANGUAGE C
TARGET foo SOURCE "foo.c" OUTPUT "foo.c.i")
set_target_properties(foo
PROPERTIES COMPILE_DEFINITIONS "FOO=BAR")
The ``INHIBIT_LINEMARKERS`` flag inhibits linemarkers on compilers that produce
them by default. These are often intended by the preprocessor to communicate
source line information to the compiler, but can interfere with tools that do
not expect them.
.. note::
The created target automatically inherits flags from the
:variable:`CMAKE_<LANG>_FLAGS <variable:CMAKE_<LANG>_FLAGS>` and
:variable:`CMAKE_<LANG>_FLAGS_<CONFIG> <variable:CMAKE_<LANG>_FLAGS_<CONFIG>>`
variables.
#]=======================================================================]
include_guard()
include(ArmAssert)
function(arm_preprocess_source)
set(options "INHIBIT_LINEMARKERS")
set(single-args "TARGET;SOURCE;OUTPUT;LANGUAGE")
set(multi-args "")
cmake_parse_arguments(PARSE_ARGV 0 ARG
"${options}" "${single-args}" "${multi-args}")
arm_assert(
CONDITION DEFINED ARG_TARGET
MESSAGE "No value was given for the `TARGET` argument.")
arm_assert(
CONDITION DEFINED ARG_SOURCE
MESSAGE "No value was given for the `SOURCE` argument.")
arm_assert(
CONDITION DEFINED ARG_OUTPUT
MESSAGE "No value was given for the `OUTPUT` argument.")
arm_assert(
CONDITION DEFINED ARG_LANGUAGE
MESSAGE "No value was given for the `LANGUAGE` argument.")
set(inhibit-linemarkers "${ARG_INHIBIT_LINEMARKERS}")
#
# Make the source path absolute so that there are no issues with path
# resolution during preprocessing.
#
cmake_path(ABSOLUTE_PATH ARG_SOURCE NORMALIZE
BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
cmake_path(ABSOLUTE_PATH ARG_OUTPUT NORMALIZE
BASE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
#
# CMake's toolchain detection logic creates a special variable -
# `CMAKE_<LANGUAGE>_CREATE_PREPROCESSED_SOURCE` - which describes the
# command line required to preprocess a source file for that language. For
# example, here it is for the GNU C preprocessor:
#
# <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.
#
arm_assert(
CONDITION DEFINED CMAKE_${ARG_LANGUAGE}_CREATE_PREPROCESSED_SOURCE
MESSAGE "Unable to determine the preprocessor command line for the "
"${ARG_LANGUAGE} language. Please report this to the "
"developer.")
set(command "${CMAKE_${ARG_LANGUAGE}_CREATE_PREPROCESSED_SOURCE}")
#
# Ensure the paths we provide to the command line are in OS-native form. We
# don't want the compiler complaining that the paths are malformed.
#
cmake_path(NATIVE_PATH ARG_SOURCE NORMALIZE SOURCE)
cmake_path(NATIVE_PATH ARG_OUTPUT NORMALIZE PREPROCESSED_SOURCE)
#
# Split up the command into a list.
#
separate_arguments(command NATIVE_COMMAND "${command}")
set(FLAGS "$<TARGET_PROPERTY:${ARG_TARGET},COMPILE_OPTIONS>")
set(DEFINES "$<TARGET_PROPERTY:${ARG_TARGET},COMPILE_DEFINITIONS>")
set(INCLUDES "$<TARGET_PROPERTY:${ARG_TARGET},INCLUDE_DIRECTORIES>")
#
# Until we come across an exception to the rule, use `-D` for definitions
# and `-I` for include directories.
#
set(DEFINES "$<$<BOOL:${DEFINES}>:-D$<JOIN:${DEFINES},$<SEMICOLON>-D>>")
set(INCLUDES "$<$<BOOL:${INCLUDES}>:-I$<JOIN:${INCLUDES},$<SEMICOLON>-I>>")
#
# Apply compiler-specific behaviours. The default assumption is that we're
# using a toolchain that behaves like GCC, as many tools attempt to emulate
# it.
#
if(ARG_LANGUAGE STREQUAL "ASM")
list(APPEND FLAGS "-x" "assembler-with-cpp")
elseif(language STREQUAL "C")
list(APPEND FLAGS "-x" "c")
elseif(language STREQUAL "CXX")
list(APPEND FLAGS "-x" "c++")
endif()
if(ARG_INHIBIT_LINEMARKERS)
list(APPEND FLAGS "-P")
endif()
#
# Here's the magic part. For every angle bracket-wrapped value in the
# command, replace it with its at-variable form, then replace it with the
# value of that variable.
#
string(REGEX REPLACE "<([[A-Z_]+)>" [[@\1@]] command "${command}")
arm_expand(OUTPUT command STRING "${command}" ATONLY)
#
# Finally, add the command which generates the preprocessed file.
#
add_custom_target(${ARG_TARGET}
SOURCES "${ARG_SOURCE}"
DEPENDS "${ARG_OUTPUT}")
add_custom_command(
OUTPUT "${ARG_OUTPUT}"
MAIN_DEPENDENCY "${ARG_SOURCE}"
COMMAND "${command}"
VERBATIM COMMAND_EXPAND_LISTS)
endfunction()