Code View

mstring / mstring-1.3.1.2 / cmake / mstring-generate.cmake
# 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()