# mstring-generate.cmake — run mstring at build time.
#
# mstring_generate( <out-var> <message-file>
# [MODULE <name>]
# [OUTPUT_DIRECTORY <dir>]
# [HEADER_EXTENSION <ext>] [SOURCE_EXTENSION <ext>]
# [NOHEADER] [NOSOURCE | INLINE]
# [OPTIONS <mstring option>...]
# [IMPORT_DIRECTORIES <dir>...]
# [DEPENDS <file>...] )
#
# Generates <name>.<header-ext> and <name>.<source-ext> from <message-file>
# and appends their paths to <out-var>; add them to a target's sources.
#
# MODULE passed as `mstring -m`, so CMake knows the output file
# names. Default: the message file's name without its
# extension. It overrides the file's own $MODULE name.
# OUTPUT_DIRECTORY default: ${CMAKE_CURRENT_BINARY_DIR}. Add it to the
# target's include directories to #include the header.
# HEADER_EXTENSION must match the file's `$MODULE ... HEADER <ext>`
# SOURCE_EXTENSION (defaults hh / cpp).
# NOHEADER the file says `$MODULE ... NOHEADER` / `NOSOURCE`:
# NOSOURCE that output is not produced (and not waited for).
# INLINE the file says `$MODULE ... INLINE`: header only.
# IMPORT_DIRECTORIES passed as `mstring -I`.
# OPTIONS extra mstring options, e.g. -w (no warnings).
# DEPENDS extra dependencies — list the files the message file
# $IMPORTs, so editing them regenerates the output.
#
# The mstring executable: the `mstring` target when it is part of the same
# build, else MSTRING_EXECUTABLE, else `mstring` found on the PATH (a Conan
# tool_requires puts it there).
function(mstring_generate out_var message_file)
cmake_parse_arguments(PARSE_ARGV 2 MSG
"NOHEADER;NOSOURCE;INLINE"
"MODULE;OUTPUT_DIRECTORY;HEADER_EXTENSION;SOURCE_EXTENSION"
"IMPORT_DIRECTORIES;DEPENDS;OPTIONS")
if(MSG_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "mstring_generate: unknown arguments: ${MSG_UNPARSED_ARGUMENTS}")
endif()
get_filename_component(input "${message_file}" ABSOLUTE)
if(NOT MSG_MODULE)
get_filename_component(MSG_MODULE "${message_file}" NAME_WE)
endif()
if(NOT MSG_OUTPUT_DIRECTORY)
set(MSG_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
endif()
if(NOT MSG_HEADER_EXTENSION)
set(MSG_HEADER_EXTENSION hh)
endif()
if(NOT MSG_SOURCE_EXTENSION)
set(MSG_SOURCE_EXTENSION cpp)
endif()
if(TARGET mstring)
set(executable $<TARGET_FILE:mstring>)
set(executable_dependency mstring)
else()
# Under Conan (CMakeDeps with build_context_activated) the recipe's
# tool_requires decides which mstring runs: take that package's
# executable on every configure. A cached path from an earlier
# configure may name a previous package revision whose folder still
# exists, and would keep running the old mstring.
set(_conan_mstring)
foreach(folder IN ITEMS "${mstring_PACKAGE_FOLDER_RELEASE}"
"${mstring_PACKAGE_FOLDER_DEBUG}"
"${mstring_PACKAGE_FOLDER_RELWITHDEBINFO}"
"${mstring_PACKAGE_FOLDER_MINSIZEREL}")
if(folder AND EXISTS "${folder}/bin/mstring")
set(_conan_mstring "${folder}/bin/mstring")
break()
endif()
endforeach()
if(_conan_mstring)
set(MSTRING_EXECUTABLE "${_conan_mstring}" CACHE FILEPATH "mstring code generator" FORCE)
else()
if(MSTRING_EXECUTABLE AND NOT EXISTS "${MSTRING_EXECUTABLE}")
unset(MSTRING_EXECUTABLE CACHE) # a cached path to a removed installation
endif()
if(NOT MSTRING_EXECUTABLE)
find_program(MSTRING_EXECUTABLE mstring REQUIRED)
endif()
endif()
set(executable "${MSTRING_EXECUTABLE}")
set(executable_dependency "${MSTRING_EXECUTABLE}")
endif()
set(imports)
foreach(folder IN LISTS MSG_IMPORT_DIRECTORIES)
get_filename_component(folder "${folder}" ABSOLUTE)
list(APPEND imports -I "${folder}")
endforeach()
set(outputs)
if(NOT MSG_NOHEADER)
list(APPEND outputs "${MSG_OUTPUT_DIRECTORY}/${MSG_MODULE}.${MSG_HEADER_EXTENSION}")
endif()
if(NOT MSG_NOSOURCE AND NOT MSG_INLINE)
list(APPEND outputs "${MSG_OUTPUT_DIRECTORY}/${MSG_MODULE}.${MSG_SOURCE_EXTENSION}")
endif()
if(NOT outputs)
message(FATAL_ERROR "mstring_generate: NOHEADER and NOSOURCE together produce nothing")
endif()
add_custom_command(
OUTPUT ${outputs}
COMMAND ${CMAKE_COMMAND} -E make_directory "${MSG_OUTPUT_DIRECTORY}"
COMMAND ${executable} ${MSG_OPTIONS} -m ${MSG_MODULE} -O "${MSG_OUTPUT_DIRECTORY}" ${imports} "${input}"
# mstring leaves an unchanged output file alone. Make has no notion of
# "ran, but nothing changed" (Ninja's restat), so an output older than
# its .msg would re-run mstring on every build; mark them current.
COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${outputs}
DEPENDS "${input}" ${executable_dependency} ${MSG_DEPENDS}
COMMENT "mstring: ${message_file} -> ${MSG_MODULE}"
VERBATIM)
set(${out_var} ${${out_var}} ${outputs} PARENT_SCOPE)
endfunction()