diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 4ec5b01e..ef839098 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -19,22 +19,22 @@ endif() option(CUCUMBER_MESSAGES_BUILD_TESTS "Build the unit tests." ${CUCUMBER_MESSAGES_BUILD_TESTS_INIT}) - find_package(nlohmann_json CONFIG REQUIRED) + add_subdirectory(src/lib/messages) + if(CUCUMBER_MESSAGES_BUILD_TESTS) include(CTest) enable_testing() endif() - install( TARGETS cucumber_messages_lib EXPORT cucumber_messages-config - RUNTIME DESTINATION \${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION \${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION \${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) install( @@ -43,3 +43,8 @@ install( NAMESPACE cucumber:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cucumber_messages ) + +install( + DIRECTORY "${PROJECT_SOURCE_DIR}/include/messages" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cucumber +) diff --git a/cpp/cmake/cmate b/cpp/cmake/cmate index 01ec6ca2..d9157588 100755 --- a/cpp/cmake/cmate +++ b/cpp/cmake/cmate @@ -3,87 +3,83 @@ set(CMATE "cmate") set(CMATE_VER "X.Y.Z") -set(CMATE_TARGETS "") set(CMATE_CMDS "") set(CMATE_DEPSFILE "deps.txt") -set(CMATE_PRJFILE "project.json") -set(CMATE_LINKFILE "link.json") +set(CMATE_PRJFILE "project.yaml") +set(CMATE_LINKFILE "link.yaml") set(CMATE_GIT_HOST "GH") set(CMATE_GH "https://github.com") set(CMATE_GL "https://gitlab.com") set(CMATE_BB "https://bitbucket.org") + cmake_policy(SET CMP0057 NEW) +cmake_policy(SET CMP0007 NEW) ############################################################################### # # Content of cmate/utilities.cmake # ############################################################################### -function(cmate_die MSG) - message(FATAL_ERROR "CMate: error: ${MSG}") +function(cmate_die) + list(JOIN ARGV " " MSGS) + message(FATAL_ERROR "CMate: error: ${MSGS}") endfunction() function(cmate_msg) - list(JOIN ARGV "" MSGS) + list(JOIN ARGV " " MSGS) message("CMate: ${MSGS}") endfunction() -function(cmate_warn MSG) - message(WARNING "CMate: ${MSG}") +function(cmate_warn) + list(JOIN ARGV " " MSGS) + message(WARNING "CMate: ${MSGS}") endfunction() -function(cmate_info MSG) +function(cmate_info) + list(JOIN ARGV " " MSGS) + if(CMATE_VERBOSE) - cmate_msg(${MSG}) + cmate_msg(${MSGS}) endif() endfunction() function(cmate_setg VAR VAL) + if("$ENV{CMATE_SET_TRACE}") + message("SET: ${VAR}=\"${VAL}\"") + endif() + set(${VAR} "${VAL}" CACHE INTERNAL "${VAR}") endfunction() +function(cmate_appendg VAR VAL) + if(${VAR}) + set(VAL "${${VAR}};${VAL}") + endif() + + cmate_setg(${VAR} "${VAL}") +endfunction() + function(cmate_setgdir VAR VAL) cmate_setg(${VAR} "${VAL}") file(MAKE_DIRECTORY ${${VAR}}) endfunction() -function(cmate_load_version) - if(NOT "${CMATE_VERSION}" STREQUAL "") - return() - endif() - - if("${CMATE_VERSION_FILE}" STREQUAL "") - cmate_setg( - CMATE_VERSION_FILE - "${CMATE_ROOT_DIR}/version.txt" - ) - endif() - - if(EXISTS ${CMATE_VERSION_FILE}) - file( - STRINGS ${CMATE_VERSION_FILE} VER - REGEX "^[^\\.]+\\.[^\\.]+\\.[^\\.]+$" - LIMIT_COUNT 1 - ) - - cmate_setg(CMATE_VERSION ${VER}) - endif() +function(cmate_sleep DURATION) + execute_process(COMMAND ${CMAKE_COMMAND} -E sleep ${DURATION}) endfunction() function(cmate_set_version) - cmate_load_version() - - if("${CMATE_PROJECT_VERSION}" STREQUAL "") + if("${CMATE_PROJECT.version}" STREQUAL "") cmate_warn("using default version: 0.1.0") - cmate_setg(CMATE_PROJECT_VERSION "0.1.0") + cmate_setg(CMATE_PROJECT.version "0.1.0") endif() - if("${CMATE_PROJECT_VERSION}" MATCHES "^([^\\.]+)\\.([^\\.]+)\\.([^\\.]+)$") - cmate_setg(CMATE_PROJECT_VERSION_MAJOR ${CMAKE_MATCH_1}) - cmate_setg(CMATE_PROJECT_VERSION_MINOR ${CMAKE_MATCH_2}) - cmate_setg(CMATE_PROJECT_VERSION_PATCH ${CMAKE_MATCH_3}) + if("${CMATE_PROJECT.version}" MATCHES "^([^\\.]+)\\.([^\\.]+)\\.([^\\.]+)$") + cmate_setg(CMATE_PROJECT.version_major ${CMAKE_MATCH_1}) + cmate_setg(CMATE_PROJECT.version_minor ${CMAKE_MATCH_2}) + cmate_setg(CMATE_PROJECT.version_patch ${CMAKE_MATCH_3}) else() - cmate_die("unable to parse version: ${CMATE_PROJECT_VERSION}") + cmate_die("unable to parse version: ${CMATE_PROJECT.version}") endif() endfunction() @@ -93,52 +89,167 @@ macro(cmate_setv VAR VAL) endif() endmacro() -function(cmate_json_get_array JSON KEY VAR) - string(JSON ARRAY ERROR_VARIABLE ERR GET ${JSON} ${KEY}) +function(cmate_json_array_to_list JSON VAR) set(ITEMS "") + string(JSON T ERROR_VARIABLE ERR TYPE ${JSON}) - if (NOT ERR) - string(JSON N LENGTH ${ARRAY}) + if(T STREQUAL "ARRAY") + string(JSON N LENGTH ${JSON}) if(${N} GREATER_EQUAL 1) math(EXPR N "${N}-1") foreach(I RANGE ${N}) - string(JSON ITEM GET ${ARRAY} ${I}) + string(JSON ITEM GET ${JSON} ${I}) list(APPEND ITEMS ${ITEM}) endforeach() endif() + else() + set(ITEMS "${JSON}") + endif() + + set(${VAR} "${ITEMS}" PARENT_SCOPE) +endfunction() + +function(cmate_json_get_array JSON KEY VAR) + string(JSON VALUES ERROR_VARIABLE ERR GET ${JSON} ${KEY}) + set(ITEMS "") + + if (NOT ERR) + cmate_json_array_to_list("${VALUES}" ITEMS) endif() set(${VAR} ${ITEMS} PARENT_SCOPE) endfunction() +function(cmate_json_set_array JVAR JSON KEY VAR) + set(ARRAY "[]") + set(I 0) + + foreach(ITEM ${VAR}) + string(JSON ARRAY SET "${ARRAY}" "${I}" "\"${ITEM}\"") + math(EXPR I "${I}+1") + endforeach() + + string(JSON JSON SET ${JSON} ${KEY} ${ARRAY}) + set(${JVAR} ${JSON} PARENT_SCOPE) +endfunction() + +function(cmate_json_get_str JSON KEY VAR DEF) + string(JSON STR ERROR_VARIABLE ERR GET ${JSON} ${KEY}) + + if(ERR) + set(STR ${DEF}) + endif() + + set(${VAR} ${STR} PARENT_SCOPE) +endfunction() + +function(cmate_split STR SEP VAR) + set(VALUES "") + + while(STR MATCHES "^([^${SEP}]+)${SEP}(.+)$") + list(APPEND VALUES "${CMAKE_MATCH_1}") + set(STR "${CMAKE_MATCH_2}") + endwhile() + + if(NOT STR STREQUAL "") + list(APPEND VALUES "${STR}") + endif() + + set(${VAR} "${VALUES}" PARENT_SCOPE) +endfunction() + +function(cmate_split_lines STR VAR) + set(VALUES "") + set(SEP "\r\n") + + # REGEX MATCHALL can't match empty strings, so "manual" solution... Yeah... + while(STR MATCHES "^([^${SEP}]*)[${SEP}](.*)$") + if(CMAKE_MATCH_1 STREQUAL "") + list(APPEND VALUES "${CMATE_EMPTY_LINE_MARKER}") + else() + list(APPEND VALUES "${CMAKE_MATCH_1}") + endif() + + set(STR "${CMAKE_MATCH_2}") + endwhile() + + if(NOT STR STREQUAL "") + list(APPEND VALUES "${STR}") + endif() + + set(${VAR} "${VALUES}" PARENT_SCOPE) +endfunction() + +macro(cmate_split_conf_path PATH VAR) + cmate_split("${PATH}" "\\." ${VAR}) +endmacro() + +function(cmate_conf_get PATH VAR) + cmate_split_conf_path(${PATH} KEYS) + + if(${ARGC} GREATER 2) + cmate_json_get_array("${ARGV2}" "${KEYS}" VALUE) + else() + cmate_json_get_array("${CMATE_CONF}" "${KEYS}" VALUE) + endif() + + set(${VAR} "${VALUE}" PARENT_SCOPE) +endfunction() + function(cmate_load_conf FILE) set(PKGS "") - if(EXISTS ${FILE}) - file(READ ${FILE} JSON) + cmate_yaml_load(${FILE} CMATE_CONF) + cmate_setg(CMATE_CONF "${CMATE_CONF}") + + foreach(VNAME "name" "version" "namespace" "std") + cmate_conf_get(${VNAME} VAL) + + if("${VAL}" STREQUAL "") + cmate_die("project variable \"${VNAME}\" no set") + else() + cmate_setg(CMATE_PROJECT.${VNAME} "${VAL}") + endif() + endforeach() + + cmate_set_version() +endfunction() + +function(cmate_project_varname NAME VAR) + string(TOUPPER "${CMATE_PROJECT.name}_${NAME}" VNAME) + string(REPLACE "-" "_" VNAME ${VNAME}) + set(${VAR} ${VNAME} PARENT_SCOPE) +endfunction() + +function(cmate_join_escape_list LVAR OVAR) + list(JOIN ${LVAR} "_semicolon_" ESCAPED) + set(${OVAR} ${ESCAPED} PARENT_SCOPE) +endfunction() - string(JSON PROJECT GET ${JSON} "name") - cmate_setg(CMATE_PROJECT_NAME ${PROJECT}) - string(JSON VERSION GET ${JSON} "version") - cmate_setg(CMATE_PROJECT_VERSION "${VERSION}") - cmate_set_version() - string(JSON NAMESPACE GET ${JSON} "namespace") - cmate_setg(CMATE_PROJECT_NAMESPACE ${NAMESPACE}) +macro(cmate_unescape_list LVAR) + list(TRANSFORM ${LVAR} REPLACE "_semicolon_" "\\\;") +endmacro() - string(JSON PKGS GET ${JSON} "packages") +function(cmate_unquote STR VAR) + set(VAL "") + + if(STR MATCHES "^\"((\\\\.|[^\"])*)?\"$") + set(VAL "${CMAKE_MATCH_1}") + elseif(STR MATCHES "^'([^']*(''[^']*)*)?'$") + set(VAL "${CMAKE_MATCH_1}") + else() + set(VAL "${STR}") endif() - cmate_setg(CMATE_PACKAGES "${PKGS}") + set(${VAR} ${VAL} PARENT_SCOPE) endfunction() function(cmate_run_prog) cmake_parse_arguments(RUN "" "DIR" "CMD" ${ARGN}) - if(CMATE_SIMULATE) - list(PREPEND RUN_CMD "echo") - endif() + cmate_unescape_list(RUN_CMD) execute_process( COMMAND ${RUN_CMD} @@ -176,70 +287,78 @@ endfunction() function(cmate_download URL FILE) if(CMATE_SIMULATE) cmate_msg("download ${URL} to ${FILE}") - else() - file(DOWNLOAD ${URL} ${FILE} STATUS ST) + return() endif() - list(GET ST 0 RC) + set(WAIT_INTERVAL 5) + set(MAX_RETRIES 10) + set(RETRIES ${MAX_RETRIES}) + set(RC 1) - if(RC) - cmate_die("download of ${URL} failed: ${ST}") - endif() -endfunction() - -function(cmate_set_build_type RELEASE_FLAG_VAR) - if(CMATE_BUILD_DIR) - return() - endif() + while(RC) + file(DOWNLOAD ${URL} ${FILE} STATUS ST) - if(${RELEASE_FLAG_VAR}) - set(TYPE "Release") - else() - set(TYPE "Debug") - endif() + list(GET ST 0 RC) - string(TOLOWER ${TYPE} TDIR) - cmate_setg(CMATE_BUILD_DIR "${CMATE_BUILD_BASE_DIR}/${TDIR}") + if(RC) + if(RETRIES) + math(EXPR RETRIES "${RETRIES} - 1") + math(EXPR ATTEMPT "${MAX_RETRIES} - ${RETRIES}") + cmate_warn( + "download of ${URL} failed" + " (attempt ${ATTEMPT} of ${MAX_RETRIES}" + ", retrying in ${WAIT_INTERVAL}s)" + ) + cmate_sleep(${WAIT_INTERVAL}) + else() + cmate_die("download of ${URL} failed: ${ST}") + endif() + else() + # TODO: REMOVE ME: Fake few errors + if(RETRIES GREATER 8) + set(RC 1) + endif() + endif() + endwhile() endfunction() -function(cmate_github_get_latest REPO VAR RE) - set(URL "https://api.github.com/repos/${REPO}/releases/latest") - set(TDIR "${CMATE_TMP_DIR}/${REPO}") - set(INFO "${TDIR}/info.json") +function(cmate_set_build_types DEBUGVAR RELEASEVAR DEFAULTS) + set(TYPES "") - if (NOT EXISTS ${INFO}) - file(MAKE_DIRECTORY ${TDIR}) - cmate_download(${URL} ${INFO}) - endif() + if(NOT "${${DEBUGVAR}}" AND NOT "${${RELEASEVAR}}") + set(TYPES ${DEFAULTS}) + else() + foreach(TYPE "Debug" "Release") + string(TOUPPER "${TYPE}VAR" TVAR) + set(TVAR "${${TVAR}}") - file(READ ${INFO} VINFO) - cmate_json_get_array(${VINFO} "assets" ASSETS) + if("${${TVAR}}") + list(APPEND TYPES "${TYPE}") + endif() + endforeach() + endif() - foreach(ASSET ${ASSETS}) - string( - JSON - BDURL - ERROR_VARIABLE ERR - GET "${ASSET}" "browser_download_url" - ) + cmate_setg(CMATE_BUILD_TYPES "${TYPES}") +endfunction() - if(NOT ERR AND ${BDURL} MATCHES ${RE}) - string(JSON FILE GET "${ASSET}" "name") - set(FILE "${CMATE_DL_DIR}/${FILE}") +function(cmate_github_get_latest REPO PKG VAR) + set(URL "https://github.com/${REPO}/releases/latest/download/${PKG}") - if (NOT EXISTS ${FILE}) - cmate_download(${BDURL} ${FILE}) - endif() + set(FILE "${CMATE_DL_DIR}/${PKG}") - set(${VAR} ${FILE} PARENT_SCOPE) - break() - endif() - endforeach() + if (NOT EXISTS ${FILE}) + cmate_download(${URL} ${FILE}) + endif() - file(REMOVE_RECURSE ${TDIR}) + set(${VAR} ${FILE} PARENT_SCOPE) endfunction() -function(cmate_check_ninja VAR) +function(cmate_check_ninja) + if(CMATE_NO_NINJA) + unset(CMATE_NINJA) + return() + endif() + find_program(NINJA ninja) set(TDIR "${CMATE_TMP_DIR}/ninja") @@ -259,11 +378,7 @@ function(cmate_check_ninja VAR) endif() if(NOT EXISTS "${CMATE_ENV_BIN_DIR}/${NCMD}") - cmate_github_get_latest( - "ninja-build/ninja" - NZIP - "ninja-${NOS}.zip$" - ) + cmate_github_get_latest("ninja-build/ninja" "ninja-${NOS}.zip" NZIP) file(REMOVE_RECURSE ${TDIR}) file(ARCHIVE_EXTRACT INPUT ${NZIP} DESTINATION ${TDIR}) @@ -274,16 +389,247 @@ function(cmate_check_ninja VAR) set(NINJA "${CMATE_ENV_BIN_DIR}/${NCMD}") endif() - set(${VAR} ${NINJA} PARENT_SCOPE) + cmate_setg(CMATE_NINJA ${NINJA}) +endfunction() +############################################################################### +# +# Content of cmate/yaml.cmake +# +############################################################################### +############################################################################### +# +# Simple YAML parser based on YAML::Tiny +# +############################################################################### +function(cmate_yaml_type PATH VAR) + set(RES "UNKNOWN") + + foreach(TYPE "scalar" "array" "hash") + if("${PATH}.__type__" STREQUAL "${TYPE}") + string(TOUPPER ${TYPE} RES) + break() + endif() + endforeach() + + set(${VAR} ${RES} PARENT_SCOPE) +endfunction() + +function(cmate_yaml_check_type PATH TYPE) + cmate_yaml_type(${PATH} T) + + if(NOT "${T}" STREQUAL "${TYPE}") + cmate_die("invalid type for ${PATH}: expected ${TYPE}, got ${T}") + endif() +endfunction() + +function(cmate_yaml_is_subkey STR VAR) + set(RES 0) + + if(STR MATCHES "^__[0-9]+$") + set(RES 1) + endif() + + set(${VAR} ${RES} PARENT_SCOPE) +endfunction() + +function(cmate_yaml_keys PATH VAR) + set(${VAR} "${${PATH}.__keys__}" PARENT_SCOPE) +endfunction() + +function(cmate_yaml_load FILE VAR) + set(LINES "") + + if(EXISTS ${FILE}) + file(STRINGS ${FILE} FLINES) + endif() + + foreach(LINE ${FLINES}) + if(LINE MATCHES "^[ ]*(#.*)?$") + # Strip comments + continue() + else() + list(APPEND LINES "${LINE}") + endif() + endforeach() + + list(LENGTH LINES PREV_LINE_COUNT) + + while(LINES) + list(GET LINES 0 LINE) + + if(LINE STREQUAL "---") + # A document (ignored) + list(POP_FRONT LINES) + elseif(LINE MATCHES "^[ ]*-([ ]|$|-+$)") + cmate_yaml_load_array("0" "${LINES}" LINES JSON) + elseif(LINE MATCHES "^([ ]*)[^ ]") + string(LENGTH "${CMAKE_MATCH_1}" LEN) + cmate_yaml_load_hash("${LEN}" "${LINES}" LINES JSON) + endif() + + list(LENGTH LINES LINE_COUNT) + + if(NOT ${LINE_COUNT} LESS ${PREV_LINE_COUNT}) + cmate_die("cmate_yaml_load: no lines consumed") + endif() + endwhile() + + set(${VAR} "${JSON}" PARENT_SCOPE) +endfunction() + +# +# Implementation +# +macro(cmate_yaml_set JSON KEY VAL) + list(APPEND CMATE_YAML__VARS "${VAR}") + + if(${VAR}) + set(VAL "${${VAR}};${VAL}") + endif() + + set(${VAR} "${VAL}") +endmacro() + +macro(cmate_yaml_set_type VAR TYPE) + unset(${VAR}) + cmate_yaml_set("${VAR}.__type__" "${TYPE}") +endmacro() + +function(cmate_yaml_load_scalar STR VAR) + set(VALUE "") + set(IS_KEY 0) + + if(${ARGC} GREATER 2 AND ARGV2 STREQUAL "1") + set(IS_KEY 1) + endif() + + # Trim whitespace and comments + string(REGEX REPLACE "^[ ]+" "" STR ${STR}) + string(REGEX REPLACE "[ ]+$" "" STR ${STR}) + string(REGEX REPLACE "#.*$" "" STR ${STR}) + + if("${STR}" STREQUAL "~") + set(VALUE "null") + else() + cmate_unquote(${STR} VALUE) + + if(VALUE MATCHES "[^0-9]" AND NOT ${IS_KEY}) + set(VALUE "\"${VALUE}\"") + endif() + endif() + + set(${VAR} "${VALUE}" PARENT_SCOPE) endfunction() -function(cmate_set_ninja) - if(NOT CMATE_NINJA) - cmate_check_ninja(NINJA) - cmate_setg(CMATE_NINJA ${NINJA}) +macro(cmate_check_indent INDENTS LINE) + if("${LINE}" MATCHES "^([ ]*)") + string(LENGTH "${CMAKE_MATCH_1}" LEN) + list(GET INDENTS -1 INDENT) + + if(${LEN} LESS ${INDENT}) + break() + elseif(${LEN} GREATER ${INDENT}) + cmate_die("bad indenting: (${LEN} > ${INDENT}): '${LINE}'") + endif() + else() + # Should not happen + cmate_die("invalid array line: ${LINE}") endif() +endmacro() + +function(cmate_yaml_load_array INDENTS LINES LINES_VAR JSON_VAR) + set(ARRAY "[]") + + while(LINES) + list(GET LINES 0 LINE) + + cmate_check_indent("${INDENTS}" "${LINE}") + + if(${LINE} MATCHES "^([ ]*-[ ]+)[^\\'\"][^ ]*[ ]*:([ ]+|$)") + # Inline nested hash + string(LENGTH "${CMAKE_MATCH_1}" INDENT2) + + string(REPLACE "-" " " LINE "${LINE}") + list(POP_FRONT LINES) + list(PREPEND LINES "${LINE}") + + cmate_yaml_load_hash( + "${INDENTS};${INDENT2}" + "${LINES}" + LINES + OBJ + ) + + string(JSON POS LENGTH "${ARRAY}") + string(JSON ARRAY SET ${ARRAY} ${POS} ${OBJ}) + elseif(${LINE} MATCHES "^[ ]*-([ ]*)(.+)[ ]*$") + # Array entry with value + list(POP_FRONT LINES) + cmate_yaml_load_scalar("${CMAKE_MATCH_2}" VALUE) + + string(JSON POS LENGTH "${ARRAY}") + string(JSON ARRAY SET ${ARRAY} ${POS} ${VALUE}) + endif() + endwhile() + + set(${LINES_VAR} ${LINES} PARENT_SCOPE) + set(${JSON_VAR} ${ARRAY} PARENT_SCOPE) +endfunction() - cmate_setg(CMAKE_MAKE_PROGRAM ${CMATE_NINJA}) +function(cmate_yaml_load_hash INDENTS LINES LINES_VAR JSON_VAR) + set(HASH "{}") + + while(LINES) + list(GET LINES 0 LINE) + + cmate_check_indent("${INDENTS}" "${LINE}") + + if(${LINE} MATCHES "^([ ]*(.+):)") + string(LENGTH "${CMAKE_MATCH_1}" TOSTRIP) + cmate_yaml_load_scalar("${CMAKE_MATCH_2}" KEY 1) + string(SUBSTRING ${LINE} ${TOSTRIP} -1 LINE) + endif() + + if(NOT "${LINE}" STREQUAL "") + # We have a value + cmate_yaml_load_scalar("${LINE}" VALUE) + string(JSON HASH SET ${HASH} ${KEY} ${VALUE}) + list(POP_FRONT LINES) + else() + # Indent/sub hash + list(POP_FRONT LINES) + + if(NOT LINES) + string(JSON HASH SET ${HASH} ${KEY} "null") + break() + endif() + + list(GET LINES 0 LINE) + + if(${LINE} MATCHES "^([ ]*)-") + string(LENGTH "${CMAKE_MATCH_1}" LEN) + cmate_yaml_load_array( + "${INDENTS};${LEN}" + "${LINES}" + LINES + OBJ + ) + string(JSON HASH SET ${HASH} ${KEY} ${OBJ}) + elseif(${LINE} MATCHES "^([ ]*).") + string(LENGTH "${CMAKE_MATCH_1}" LEN) + cmate_yaml_load_hash( + "${INDENTS};${LEN}" + "${LINES}" + LINES + OBJ + ) + string(JSON HASH SET ${HASH} ${KEY} ${OBJ}) + endif() + endif() + endwhile() + + set(${LINES_VAR} ${LINES} PARENT_SCOPE) + set(${JSON_VAR} ${HASH} PARENT_SCOPE) endfunction() ############################################################################### # @@ -368,65 +714,23 @@ endfunction() # Content of cmate/target_deps.cmake # ############################################################################### -function(cmate_load_cmake_package_deps JSON PREFIX) - cmate_json_get_array("${JSON}" "cmake" "PKGS") - set(PACKAGES "") - - foreach(PKG ${PKGS}) - set(PKGTYPE "STRING") - - if("${PKG}" MATCHES "^[[{].*$") - string(JSON PKGTYPE TYPE ${PKG}) - endif() - - if(${PKGTYPE} STREQUAL "STRING") - # Simple module - list(APPEND PACKAGES ${PKG}) - elseif(${PKGTYPE} STREQUAL "OBJECT") - # Module and components - string(JSON PKGNAME MEMBER ${PKG} 0) - list(APPEND PACKAGES ${PKGNAME}) - - cmate_json_get_array(${PKG} ${PKGNAME} "COMPS") - - set("${PREFIX}_CMAKE_${PKGNAME}_COMPS" ${COMPS} PARENT_SCOPE) - endif() - endforeach() - - set("${PREFIX}_CMAKE_PACKAGES" ${PACKAGES} PARENT_SCOPE) -endfunction() - -function(cmate_load_pkgconfig_package_deps JSON PREFIX) - cmate_json_get_array("${JSON}" "pkgconfig" "PKGS") - set("${PREFIX}_PKGCONFIG_PACKAGES" ${PKGS} PARENT_SCOPE) -endfunction() - function(cmate_load_link_deps FILE PREFIX) - set(PUBLIC_DEPS "") - set(PRIVATE_DEPS "") - set(LVAR "PUBLIC_DEPS") + set(TOTAL 0) - if(EXISTS ${FILE}) - file(READ ${FILE} JSON) - string(JSON LIBS GET ${JSON} "libs") + cmate_yaml_load(${FILE} LINK) - foreach(TYPE PUBLIC PRIVATE) - # TODO: add more checks for correct JSON structure - string(TOLOWER ${TYPE} KEY) - cmate_json_get_array(${LIBS} ${KEY} "${TYPE}_DEPS") - endforeach() - endif() + foreach(TYPE "public" "private") + cmate_conf_get("libs.${TYPE}" DEPS ${LINK}) - set(${PREFIX}_PUBLIC_DEPS ${PUBLIC_DEPS} PARENT_SCOPE) - list(LENGTH PUBLIC_DEPS PUBLIC_DEPS_COUNT) - set(${PREFIX}_PUBLIC_DEPS_COUNT ${PUBLIC_DEPS_COUNT} PARENT_SCOPE) + string(TOUPPER ${TYPE} UTYPE) + set(${PREFIX}_${UTYPE}_DEPS ${DEPS} PARENT_SCOPE) + list(LENGTH DEPS DEPS_COUNT) + set(${PREFIX}_${UTYPE}_DEPS_COUNT ${DEPS_COUNT} PARENT_SCOPE) - set(${PREFIX}_PRIVATE_DEPS ${PRIVATE_DEPS} PARENT_SCOPE) - list(LENGTH PRIVATE_DEPS PRIVATE_DEPS_COUNT) - set(${PREFIX}_PRIVATE_DEPS_COUNT ${PRIVATE_DEPS_COUNT} PARENT_SCOPE) + math(EXPR TOTAL "${TOTAL} + ${DEPS_COUNT}") + endforeach() - math(EXPR DEPS_COUNT "${PUBLIC_DEPS_COUNT} + ${PRIVATE_DEPS_COUNT}") - set(${PREFIX}_DEPS_COUNT ${DEPS_COUNT} PARENT_SCOPE) + set(${PREFIX}_DEPS_COUNT ${TOTAL} PARENT_SCOPE) endfunction() function(cmate_target_link_deps NAME FILE VAR) @@ -451,7 +755,7 @@ function(cmate_target_link_deps NAME FILE VAR) endfunction() function(cmate_target_name NAME TYPE VAR) - string(TOLOWER "${CMATE_PROJECT_NAMESPACE}_${NAME}_${TYPE}" TBASE) + string(TOLOWER "${CMATE_PROJECT.namespace}_${NAME}_${TYPE}" TBASE) string(REPLACE "-" "_" TBASE ${TBASE}) set(${VAR} ${TBASE} PARENT_SCOPE) endfunction() @@ -552,6 +856,182 @@ function(cmate_dep_get_url URL) endfunction() ############################################################################### # +# Content of cmate/tmpl.cmake +# +############################################################################### +function(cmate_tmpl_process_includes FROM VAR) + cmate_split_lines("${FROM}" LINES) + set(CONTENT "") + + foreach(LINE ${LINES}) + if(LINE MATCHES "^%#include <(.+)>$") + set(INC "${CMAKE_MATCH_1}") + cmate_tmpl_load("${INC}" TMPL) + string(APPEND CONTENT "${TMPL}") + else() + string(APPEND CONTENT "${LINE}\n") + endif() + endforeach() + + set(${VAR} "${CONTENT}" PARENT_SCOPE) +endfunction() + +macro(cmate_tmpl_block_begin) + if(NOT IN_BLOCK) + set(IN_BLOCK TRUE) + string(APPEND TMPL "string(APPEND RESULT [=[\n") + endif() +endmacro() + +macro(cmate_tmpl_block_end) + if(IN_BLOCK) + set(IN_BLOCK FALSE) + string(APPEND TMPL "]=])\n") + endif() +endmacro() + +function(cmate_tmpl_eval FROM TO) + set(IN_CM_BLOCK FALSE) + set(IN_BLOCK FALSE) + set(LINENUM 0) + set(INLINES "") + set(TMPL "") + set(RESULT "") + + cmate_split_lines("${FROM}" LINES) + + foreach(LINE ${LINES}) + math(EXPR LINENUM "${LINENUM}+1") + + if(LINE MATCHES "^%{CMake}%") + # Verbatim CMake block begin + if(IN_CM_BLOCK) + cmate_die("line ${LINENUM}: unclosed previous block") + else() + set(IN_CM_BLOCK TRUE) + endif() + + continue() + elseif(LINE MATCHES "^%{/CMake}%") + # Verbatim CMake block begin + if(NOT IN_CM_BLOCK) + cmate_die("line ${LINENUM}: no previous opened block") + else() + set(IN_CM_BLOCK FALSE) + endif() + + continue() + elseif(IN_CM_BLOCK) + string(APPEND TMPL "${LINE}\n") + continue() + elseif(LINE MATCHES "^%[ \t]*$") + # Skip empty lines + continue() + elseif(LINE MATCHES "^%#") + # Skip comment lines + continue() + elseif(NOT LINE MATCHES "^%[ \t]+") + if(LINE MATCHES "(.*)%{[ \t]+(.*)") + cmate_tmpl_block_end() + + while(LINE MATCHES "(.*)%{[ \t]+(.*)") + string(APPEND TMPL "string(APPEND RESULT [=[${CMAKE_MATCH_1}]=])\n") + set(REST "${CMAKE_MATCH_2}") + + if(REST MATCHES "(.*)[ \t]+}%(.*)") + string(APPEND TMPL "string(APPEND RESULT \"${CMAKE_MATCH_1}\")\n") + set(LINE "${CMAKE_MATCH_2}") + else() + cmate_die("unmatched inline in: ${LINE}") + endif() + endwhile() + endif() + + cmate_tmpl_block_begin() + else() + cmate_tmpl_block_end() + string(REGEX REPLACE "^% " "" LINE "${LINE}") + endif() + + string(APPEND TMPL "${LINE}\n") + endforeach() + + cmate_tmpl_block_end() + + cmake_language(EVAL CODE "${TMPL}") + + set(${TO} "${RESULT}" PARENT_SCOPE) +endfunction() + +function(cmate_tmpl_load FILE_OR_VAR VAR) + set(TFILE "${CMATE_TMPL_DIR}/${FILE_OR_VAR}") + string(TOUPPER "CMATE_${FILE_OR_VAR}" TVAR) + string(REGEX REPLACE "[-/\\.]" "_" TVAR "${TVAR}") + set(CONTENT "") + + if(${TVAR}) + # In amalgamate mode, template is stored in a variable + set(CONTENT "${${TVAR}}") + elseif(EXISTS "${TFILE}") + # In dev/filesystem mode, template is in a file + file(STRINGS "${TFILE}" LINES) + list(FILTER LINES EXCLUDE REGEX "^# -[*]-") + list(JOIN LINES "\n" CONTENT) + #string(APPEND CONTENT "\n") + else() + cmate_die("no template content for '${FILE_OR_VAR}'") + endif() + + cmate_tmpl_process_includes("${CONTENT}" CONTENT) + + set(${VAR} "${CONTENT}" PARENT_SCOPE) +endfunction() + +function(cmate_tmpl_process) + set(OPTS APPEND) + set(SINGLE FROM TO_FILE TO_VAR PRE) + set(MULTI "") + cmake_parse_arguments(TMPL "${OPTS}" "${SINGLE}" "${MULTI}" ${ARGN}) + + if(NOT TMPL_FROM) + cmate_die("missing template") + endif() + + # TODO: handle conflicting FILE/VAR + if(NOT TMPL_TO_FILE AND NOT TMPL_TO_VAR) + # No output specified, assume file derived from TMPL_FROM + get_filename_component(TMPL_TO_FILE "${TMPL_FROM}" NAME) + endif() + + cmate_tmpl_load("${TMPL_FROM}" TMPL) + cmate_tmpl_eval("${TMPL}" CONTENT) + string(CONFIGURE "${CONTENT}" CONTENT @ONLY) + + if(TMPL_TO_FILE) + if(TMPL_APPEND) + set(FILE_MODE "APPEND") + else() + set(FILE_MODE "WRITE") + endif() + + file(${FILE_MODE} "${TMPL_TO_FILE}" "${CONTENT}") + cmate_msg("wrote ${TMPL_TO_FILE}") + elseif(TMPL_TO_VAR) + if(TMPL_APPEND) + set(VALUE "${TMPL_TO_VAR}") + else() + set(VALUE "") + endif() + + string(APPEND VALUE "${CONTENT}") + + set(${TMPL_TO_VAR} "${VALUE}" PARENT_SCOPE) + else() + cmate_die("missing template destination") + endif() +endfunction() +############################################################################### +# # Content of cmate/commands/configure.cmake # ############################################################################### @@ -559,8 +1039,8 @@ list(APPEND CMATE_CMDS "configure") list( APPEND CMATE_CONFIGURE_OPTIONS - "dry-run" - "dump" + "no-tests" + "toolchain" "namespace" "version" "version-file" @@ -576,10 +1056,8 @@ Usage: cmate configure [OPTIONS] ${CMATE_CONFIGURE_SHORT_HELP} Options: + --no-tests Don't build tests --toolchain=FILE CMake toolchain file - --dry-run Don't touch anything - --dump Dump generated CMakeLists.txt - --namespace=NS CMake package namespace --version=SEMVER CMake package version --version-file=FILE CMake package version from FILE --version-file=FILE CMake package version from FILE @@ -589,86 +1067,29 @@ Options: (default: \$CACHE{CMATE_HEADER_PAT})" ) -function(cmate_configure_lib NAME TBASE INC_BASE SRC_BASE) - string(TOUPPER ${TBASE} VBASE) - +function(cmate_configure_lib NAME TARGET SRC_BASE) if(${CMATE_DRY_RUN}) - cmate_msg( - "found library ${NAME}" - " (I:${INC_BASE}/${NAME}" - ", S:${SRC_BASE}/${NAME})" - ) + cmate_msg("found library ${NAME}") return() endif() - list(APPEND CMATE_TARGETS ${TBASE}) - - set(HDIR "${CMATE_ROOT_DIR}/${INC_BASE}/${NAME}") set(SDIR "${CMATE_ROOT_DIR}/${SRC_BASE}/${NAME}") set(CM_FILE "${SDIR}/CMakeLists.txt") set(LINK_FILE "${SDIR}/${CMATE_LINKFILE}") - file(GLOB_RECURSE HEADERS "${HDIR}/${CMATE_HEADER_PAT}") - file(GLOB_RECURSE SOURCES "${SDIR}/${CMATE_SOURCE_PAT}") - - string(APPEND CONTENT "add_library(${TBASE})\n") - - if(CMATE_PROJECT_NAMESPACE) - string( - APPEND - CONTENT - "add_library(${CMATE_PROJECT_NAMESPACE}::${NAME} ALIAS ${TBASE})\n" - ) - endif() - - string( - APPEND - CONTENT - " -set(${VBASE}_INC_DIR \"\${PROJECT_SOURCE_DIR}/${INC_BASE}/${NAME}\") -file(GLOB_RECURSE ${VBASE}_HEADERS \${${VBASE}_INC_DIR}/${CMATE_HEADER_PAT}) -list(APPEND ${VBASE}_ALL_SOURCES \${${VBASE}_HEADERS}) - -set(${VBASE}_SRC_DIR \"\${CMAKE_CURRENT_SOURCE_DIR}\") -file(GLOB_RECURSE ${VBASE}_SOURCES \${${VBASE}_SRC_DIR}/${CMATE_SOURCE_PAT}) -list(APPEND ${VBASE}_ALL_SOURCES \${${VBASE}_SOURCES}) - -target_sources( - ${TBASE} - PRIVATE - \${${VBASE}_ALL_SOURCES} -) - -target_include_directories( - ${TBASE} - PUBLIC - $ - $ - PRIVATE - \${CMAKE_CURRENT_SOURCE_DIR} -) -" - ) - - cmate_target_link_deps(${TBASE} ${LINK_FILE} DEPS) - string(APPEND CONTENT ${DEPS}) + # Set target template variables + set(T.NAME "${NAME}") + set(T.TNAME "${TARGET}") + string(TOUPPER ${TARGET} T.UTNAME) - string( - APPEND - CONTENT - " -set_target_properties( - ${TBASE} - PROPERTIES - VERSION ${CMATE_PROJECT_VERSION} - SOVERSION ${CMATE_PROJECT_VERSION_MAJOR}.${CMATE_PROJECT_VERSION_MINOR} - EXPORT_NAME ${NAME} - OUTPUT_NAME ${CMATE_PROJECT_NAMESPACE}_${NAME} -) -" + cmate_load_link_deps(${LINK_FILE} TARGET) + cmate_tmpl_process( + FROM "targets/lib/CMakeLists.txt.in" + TO_VAR CONTENT ) if(${CMATE_DUMP}) + message(${DEPS}) message(${CONTENT}) endif() @@ -722,6 +1143,7 @@ target_include_directories( set_target_properties( ${TBASE} PROPERTIES + CXX_STANDARD ${CMATE_PROJECT.std} OUTPUT_NAME ${NAME} ) " @@ -742,164 +1164,270 @@ function(cmate_configure_test NAME TBASE SRC_BASE) cmate_configure_prog("test" ${NAME} ${TBASE} ${SRC_BASE}) endfunction() -function(cmate_configure_project_packages VAR) - # CMake style packages - cmate_load_cmake_package_deps("${CMATE_PACKAGES}" "PRJ") - set(CONTENT "") +function(cmate_configure_cmake_package PKGDESC VAR) + set(COMPS "") + string(JSON T ERROR_VARIABLE ERR TYPE ${PKGDESC}) - if(PRJ_CMAKE_PACKAGES) - string(APPEND CONTENT "\n") + if(T STREQUAL "OBJECT") + string(JSON PKG MEMBER ${PKGDESC} 0) + cmate_json_get_array(${PKGDESC} ${PKG} COMPS) + else() + set(PKG "${PKGDESC}") endif() - foreach(PKG ${PRJ_CMAKE_PACKAGES}) - if(PRJ_CMAKE_${PKG}_COMPS) - string( - APPEND - CONTENT - "find_package( - ${PKG} CONFIG REQUIRED - COMPONENTS -" - ) + set("${VAR}.PKG" ${PKG} PARENT_SCOPE) + set("${VAR}.COMPS" ${COMPS} PARENT_SCOPE) + + list(LENGTH COMPS COMP_COUNT) + set("${VAR}.COMP_COUNT" ${COMP_COUNT} PARENT_SCOPE) +endfunction() + +function(cmate_configure_project_cmake_packages VAR) + cmate_conf_get("packages.cmake" PKGS) + + list(LENGTH PKGS COUNT) + set(PKGNAMES "") + + foreach(PKG ${PKGS}) + cmate_configure_cmake_package(${PKG} PC) + list(APPEND PKGNAMES "${PC.PKG}") + set("${VAR}.PKGS.${PC.PKG}.COMPS" "${PC.COMPS}" PARENT_SCOPE) + set("${VAR}.PKGS.${PC.PKG}.COMP_COUNT" "${PC.COMP_COUNT}" PARENT_SCOPE) + endforeach() + + set("${VAR}.PKGS" ${PKGNAMES} PARENT_SCOPE) + + list(LENGTH PKGNAMES PKG_COUNT) + set("${VAR}.PKG_COUNT" ${PKG_COUNT} PARENT_SCOPE) +endfunction() + +function(cmate_configure_project_pkgconfig_packages VAR) + cmate_conf_get("packages.pkgconfig" PKGS) + + list(LENGTH PKGNAMES COUNT) + set("${VAR}.PKGS" ${PKGNAMES} PARENT_SCOPE) + + list(LENGTH PKGNAMES PKG_COUNT) + set("${VAR}.PKG_COUNT" ${PKG_COUNT} PARENT_SCOPE) +endfunction() + +function(cmate_configure_project) + if(${CMATE_DRY_RUN}) + return() + endif() + + set(CM_FILE "${CMATE_ROOT_DIR}/CMakeLists.txt") + set(CMATE_CMAKE_VER 3.12) + + # Prepare dependencies names/structure + foreach(PLIST "cmake;CM" "pkgconfig;PC") + list(GET PLIST 0 PTYPE) + list(GET PLIST 1 PVAR) + cmake_language( + CALL "cmate_configure_project_${PTYPE}_packages" + "P.${PVAR}" + ) + endforeach() + + set(P.TARGETS.BIN "") + set(P.TARGETS.LIB "") + + # Target subdirs + if(CMATE_BINS OR CMATE_LIBS) + foreach(TYPE "LIB" "BIN") + foreach(T ${CMATE_${TYPE}S}) + cmate_target_name(${T} ${TYPE} TNAME) + list(APPEND P.TARGETS.${TYPE} ${TNAME}) + + set(TDIR "src/${TYPE}/${T}") + string(TOLOWER "${TDIR}" TDIR) + + set("P.TARGETS.${TYPE}.${TNAME}.SUBDIR" "${TDIR}") + set("P.TARGETS.${TYPE}.${TNAME}.NAME" "${T}") + endforeach() + endforeach() + else() + cmate_die("no targets to configure") + endif() + + list(APPEND P.TARGETS.INSTALL "${P.TARGETS.LIB}" "${P.TARGETS.BIN}") + + set(P.TARGETS.TEST "") + + if(CMATE_TESTS) + set(TYPE "TEST") + + foreach(T ${CMATE_TESTS}) + cmate_target_name(${T} ${TYPE} TNAME) + list(APPEND P.TARGETS.TEST ${TNAME}) + + set(TDIR "src/${TYPE}/${T}") + string(TOLOWER "${TDIR}" TDIR) + + set("P.TARGETS.${TYPE}.${TNAME}.SUBDIR" "${TDIR}") + endforeach() + endif() + + cmate_tmpl_process(FROM "project/CMakeLists.txt.in" TO_VAR CONTENT) + + file(WRITE ${CM_FILE} ${CONTENT}) +endfunction() + +function(cmate_configure_load_targets PREFIX) + set(JSON "{}") + set(TARGETS "") + + if(EXISTS ${CMATE_TARGETS_FILE}) + file(READ ${CMATE_TARGETS_FILE} JSON) + endif() + + foreach(TYPE "LIB" "BIN" "TEST") + string(TOLOWER "${TYPE}S" KEY) + cmate_json_get_array(${JSON} ${KEY} LST) - foreach(PC ${PRJ_CMAKE_${PKG}_COMPS}) - string(APPEND CONTENT " ${PC}\n") - endforeach() + foreach(T ${LST}) + cmate_target_name(${T} ${TYPE} "TNAME") + list(APPEND TARGETS "${TNAME}") + endforeach() - string(APPEND CONTENT ")\n") - else() - string(APPEND CONTENT "find_package(${PKG} CONFIG REQUIRED)\n") - endif() + set("${PREFIX}_${TYPE}S" "${LST}" PARENT_SCOPE) endforeach() - # PkgConfig style packages - cmate_load_pkgconfig_package_deps("${CMATE_PACKAGES}" "PRJ") + set("${PREFIX}_TARGETS" "${TARGETS}" PARENT_SCOPE) +endfunction() - if(PRJ_PKGCONFIG_PACKAGES) - string(APPEND CONTENT "find_package(PkgConfig REQUIRED)\n") - endif() +function(cmate_configure_save_targets) + set(JSON "{}") - foreach(PKG ${PRJ_PKGCONFIG_PACKAGES}) - string( - APPEND - CONTENT - "pkg_check_modules(${PKG} REQUIRED IMPORTED_TARGET ${PKG})\n" - ) + foreach(LST "LIBS" "BINS" "TESTS") + string(TOLOWER "${LST}" KEY) + cmate_json_set_array(JSON ${JSON} ${KEY} "${CMATE_${LST}}") endforeach() - set(${VAR} ${CONTENT} PARENT_SCOPE) + file(WRITE "${CMATE_TARGETS_FILE}" ${JSON}) endfunction() -function(cmate_configure_project TARGETS SUBDIRS) - if(${CMATE_DRY_RUN}) - return() - endif() +function(cmate_configure_find_targets) + file(GLOB LIB_INC_DIRS "${CMATE_ROOT_DIR}/include/*") + set(TARGETS "") + set(LIBS "") + set(BINS "") + set(TESTS "") - set(CM_FILE "${CMATE_ROOT_DIR}/CMakeLists.txt") + # Libraries + foreach(LIB_INC_DIR ${LIB_INC_DIRS}) + string(REPLACE "${CMATE_ROOT_DIR}/include/" "" NAME ${LIB_INC_DIR}) + cmate_target_name(${NAME} "lib" "TNAME") + list(APPEND TARGETS ${TNAME}) + list(APPEND LIBS ${NAME}) + endforeach() - string( - APPEND - CONTENT - "cmake_minimum_required(VERSION 3.12 FATAL_ERROR) + # Binaries and tests + foreach(TYPE bin test) + file(GLOB SRC_DIRS "${CMATE_ROOT_DIR}/src/${TYPE}/*") + set(TVAR "${TYPE}s") + string(TOUPPER ${TVAR} TVAR) -project(${CMATE_PROJECT_NAME} VERSION ${CMATE_PROJECT_VERSION} LANGUAGES C CXX) + foreach(SRC_DIR ${SRC_DIRS}) + string(REPLACE "${CMATE_ROOT_DIR}/src/${TYPE}/" "" NAME ${SRC_DIR}) + cmate_target_name(${NAME} ${TYPE} "TNAME") + list(APPEND TARGETS ${TNAME}) + list(APPEND ${TVAR} ${NAME}) + endforeach() + endforeach() -include(GNUInstallDirs) + foreach(LST "TARGETS" "LIBS" "BINS" "TESTS") + list(SORT ${LST}) + set(LVAR "CMATE_${LST}") + cmate_setg(${LVAR} "${${LST}}") + endforeach() +endfunction() -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) +function(cmate_configure_clean) + foreach(TYPE "BIN" "LIB" "TEST") + if(NOT CMATE_${TYPE}S) + continue() + endif() -if (CMAKE_CXX_COMPILER_ID STREQUAL \"MSVC\") - add_compile_definitions(_CRT_SECURE_NO_WARNINGS _SCL_SECURE_NO_WARNINGS) -endif() -" - ) + foreach(T ${CMATE_${TYPE}S}) + set(TDIR "${CMATE_ROOT_DIR}/src/${TYPE}/${T}") + string(TOLOWER "${TDIR}" TDIR) + file(REMOVE "${TDIR}/CMakeLists.txt") + endforeach() + endforeach() - cmate_configure_project_packages(PKGS) + file(REMOVE "${CMATE_ROOT_DIR}/CMakeLists.txt") +endfunction() - if(PKGS) - string(APPEND CONTENT "${PKGS}") - endif() +function(cmate_configure_needed VAR LIBS BINS TESTS) + set(RES FALSE) - # Target subdirs - if(SUBDIRS) - string(APPEND CONTENT "\n") + foreach(LST "LIBS" "BINS" "TESTS") + set(REFL ${CMATE_${LST}}) + list(SORT REFL) + list(JOIN REFL "_" REFS) + set(L ${${LST}}) + list(SORT L) + list(JOIN L "_" S) - foreach(SUBDIR ${SUBDIRS}) - string(APPEND CONTENT "add_subdirectory(${SUBDIR})\n") - endforeach() - endif() + if(NOT "${S}" STREQUAL "${REFS}") + set(RES TRUE) + break() + endif() + endforeach() - string( - APPEND - CONTENT - " -install( - TARGETS" - ) + set(${VAR} ${RES} PARENT_SCOPE) +endfunction() - foreach(TARGET ${TARGETS}) - string(APPEND CONTENT "\n ${TARGET}") +function(cmate_configure_generate) + # Set CMate global template variables + set(CM.HPAT "${CMATE_HEADER_PAT}") + set(CM.SPAT "${CMATE_SOURCE_PAT}") + + # Set project level template variables + set(P.NAME "${CMATE_PROJECT.name}") + string(TOUPPER "${CMATE_PROJECT.name}" P.UNAME) + set(P.VER "${CMATE_PROJECT.version}") + set(P.VER_MAJOR "${CMATE_PROJECT.version_major}") + set(P.VER_MINOR "${CMATE_PROJECT.version_minor}") + set(P.VER_PATCH "${CMATE_PROJECT.version_patch}") + set(P.NS "${CMATE_PROJECT.namespace}") + set(P.STD "${CMATE_PROJECT.std}") + + foreach(NAME ${CMATE_LIBS}) + cmate_target_name(${NAME} "lib" "TNAME") + cmate_configure_lib(${NAME} ${TNAME} "src/lib") endforeach() - string( - APPEND - CONTENT - " - EXPORT ${CMATE_PROJECT_NAME}-config - RUNTIME DESTINATION \${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION \${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION \${CMAKE_INSTALL_LIBDIR} -) + # Binaries and tests + foreach(TYPE "bin" "test") + string(TOUPPER "CMATE_${TYPE}S" LNAME) -install( - EXPORT ${CMATE_PROJECT_NAME}-config - FILE ${CMATE_PROJECT_NAME}-config.cmake - NAMESPACE ${CMATE_PROJECT_NAMESPACE}:: - DESTINATION \${CMAKE_INSTALL_LIBDIR}/cmake/${CMATE_PROJECT_NAME} -) -" - ) + if(NOT ${LNAME}) + continue() + endif() - if (IS_DIRECTORY "${CMATE_ROOT_DIR}/include") - foreach(LIB ${CMATE_LIBS}) - string( - APPEND - CONTENT - " -install( - DIRECTORY \"\${PROJECT_SOURCE_DIR}/include/${LIB}/\" - DESTINATION \${CMAKE_INSTALL_INCLUDEDIR}/${CMATE_PROJECT_NAMESPACE} -) -" + foreach(NAME ${${LNAME}}) + cmate_target_name(${NAME} ${TYPE} "TNAME") + cmake_language( + CALL "cmate_configure_${TYPE}" + ${NAME} ${TNAME} "src/${TYPE}" ) endforeach() - endif() + endforeach() - file(WRITE ${CM_FILE} ${CONTENT}) + # Top-level project + cmate_configure_project() endfunction() -function(cmate_configure_run_cmake TYPE) - string(TOLOWER ${TYPE} TDIR) - set(BUILD_DIR "${CMATE_ROOT_DIR}/build/${TDIR}") - set(STAGE_DIR "${CMATE_ROOT_DIR}/stage/${TDIR}") - - if (IS_DIRECTORY ${BUILD_DIR}) - return() - endif() - - file(MAKE_DIRECTORY ${BUILD_DIR}) - +function(cmate_configure_cmake_common_args VAR) set(ARGS "") if (EXISTS "${CMATE_ENV_DIR}") list(APPEND ARGS "-DCMAKE_PREFIX_PATH=${CMATE_ENV_DIR}") endif() - list(APPEND ARGS "-DCMAKE_INSTALL_PREFIX=${STAGE_DIR}") - list(APPEND ARGS "-DCMAKE_BUILD_TYPE=${TYPE}") + list(APPEND ARGS "-DCMAKE_INSTALL_PREFIX=${CMATE_ROOT_DIR}/stage") find_program(CMATE_CCACHE ccache) @@ -908,61 +1436,75 @@ function(cmate_configure_run_cmake TYPE) list(APPEND ARGS "-DCMAKE_CXX_COMPILER_LAUNCHER=${CMATE_CCACHE}") endif() - if(CMATE_TOOLCHAIN) - list(APPEND ARGS "--toolchain" "${CMATE_TOOLCHAIN}") + cmate_project_varname("BUILD_TESTS" BUILD_TESTS) + + if(CMATE_CONFIGURE_NO_TESTS) + list(APPEND ARGS "-D${BUILD_TESTS}=OFF") + list(APPEND ARGS "-DBUILD_TESTING=OFF") + else() + list(APPEND ARGS "-D${BUILD_TESTS}=ON") endif() - cmate_set_ninja() + set(${VAR} ${ARGS} PARENT_SCOPE) +endfunction() + +function(cmate_configure_run_cmake_multi) + cmate_configure_cmake_common_args(ARGS) - list(APPEND ARGS "-G" "Ninja") + cmate_join_escape_list(CMATE_BUILD_TYPES TYPES) + + list(APPEND ARGS "-DCMAKE_CONFIGURATION_TYPES=${TYPES}") list(APPEND ARGS "-S" "${CMATE_ROOT_DIR}") - list(APPEND ARGS "-B" "${BUILD_DIR}") + list(APPEND ARGS "-B" "${CMATE_BUILD_DIR}") + + if(CMATE_NINJA) + list(APPEND ARGS "-G" "Ninja Multi-Config") + endif() cmate_run_prog(CMD ${CMAKE_COMMAND} ${ARGS}) endfunction() -function(cmate_configure) - # Find libraries (libraries have headers) - file(GLOB LIB_INC_DIRS "${CMATE_ROOT_DIR}/include/*") - set(TARGETS "") - set(LIBS "") - set(SUBDIRS "") +function(cmate_configure_run_cmake TYPE) + cmate_configure_cmake_common_args(ARGS) - foreach(LIB_INC_DIR ${LIB_INC_DIRS}) - string(REPLACE "${CMATE_ROOT_DIR}/include/" "" NAME ${LIB_INC_DIR}) - cmate_target_name(${NAME} "lib" "TNAME") - cmate_configure_lib(${NAME} ${TNAME} "include" "src/lib") - list(APPEND TARGETS ${TNAME}) - list(APPEND LIBS ${NAME}) - list(APPEND SUBDIRS "src/lib/${NAME}") - endforeach() + list(APPEND ARGS "-DCMAKE_BUILD_TYPE=${TYPE}") + list(APPEND ARGS "-S" "${CMATE_ROOT_DIR}") + list(APPEND ARGS "-B" "${CMATE_BUILD_DIR}/${TYPE}") - cmate_setg(CMATE_LIBS "${LIBS}") + if(CMATE_TOOLCHAIN) + list(APPEND ARGS "--toolchain" "${CMATE_TOOLCHAIN}") + endif() - # Binaries and tests - foreach(TYPE bin test) - file(GLOB SRC_DIRS "${CMATE_ROOT_DIR}/src/${TYPE}/*") + cmate_run_prog(CMD ${CMAKE_COMMAND} ${ARGS}) +endfunction() - foreach(SRC_DIR ${SRC_DIRS}) - string(REPLACE "${CMATE_ROOT_DIR}/src/${TYPE}/" "" NAME ${SRC_DIR}) - cmate_target_name(${NAME} ${TYPE} "TNAME") - cmake_language( - CALL "cmate_configure_${TYPE}" - ${NAME} ${TNAME} "src/${TYPE}" - ) +function(cmate_configure) + cmate_configure_find_targets() + cmate_configure_load_targets(PREV) + cmate_configure_needed( + NEEDED + "${PREV_LIBS}" "${PREV_BINS}" "${PREV_TESTS}" + ) - if(NOT "${TYPE}" STREQUAL "test") - list(APPEND TARGETS ${TNAME}) - endif() + if(NOT NEEDED) + return() + endif() + + cmate_configure_generate() + + cmate_setg(CMATE_BUILD_TYPES "Debug;Release") - list(APPEND SUBDIRS "src/${TYPE}/${NAME}") + cmate_check_ninja() + + if(CMATE_NINJA OR WIN32) + cmate_configure_run_cmake_multi() + else() + foreach(TYPE ${CMATE_BUILD_TYPES}) + cmate_configure_run_cmake(${TYPE}) endforeach() - endforeach() + endif() - # Top-level project - cmate_configure_project("${TARGETS}" "${SUBDIRS}") - cmate_configure_run_cmake("Debug") - cmate_configure_run_cmake("Release") + cmate_configure_save_targets() endfunction() ############################################################################### # @@ -998,18 +1540,33 @@ Usage: cmate build [OPTIONS] ${CMATE_BUILD_SHORT_HELP} Options: + --debug Build in debug mode (default) --release Build in release mode" ) function(cmate_build) - cmate_set_build_type(CMATE_BUILD_RELEASE) cmate_configure() - set(ARGS "") - list(APPEND ARGS "--build" "${CMATE_BUILD_DIR}") - list(APPEND ARGS "--parallel") + cmate_set_build_types( + CMATE_BUILD_DEBUG + CMATE_BUILD_RELEASE + "Debug" + ) - cmate_run_prog(CMD ${CMAKE_COMMAND} ${ARGS}) + foreach(TYPE ${CMATE_BUILD_TYPES}) + set(ARGS "") + + if (IS_DIRECTORY "${CMATE_BUILD_DIR}/${TYPE}") + list(APPEND ARGS "--build" "${CMATE_BUILD_DIR}/${TYPE}") + else() + list(APPEND ARGS "--build" "${CMATE_BUILD_DIR}") + list(APPEND ARGS "--config" "${TYPE}") + endif() + + list(APPEND ARGS "--parallel") + + cmate_run_prog(CMD ${CMAKE_COMMAND} ${ARGS}) + endforeach() endfunction() ############################################################################### # @@ -1077,10 +1634,13 @@ Options: ) function(cmate_clean) + cmate_configure_find_targets() + set(DIRS "BUILD" "STAGE" "STATE") if(${CMATE_CLEAN_PURGE}) list(APPEND DIRS "ENV" "DEPS") + cmate_configure_clean() endif() foreach(DIR ${DIRS}) @@ -1124,7 +1684,7 @@ function(cmate_install_cmake_dep) list(APPEND ARGS "-DCMAKE_CXX_COMPILER_LAUNCHER=${CMATE_CCACHE}") endif() - cmate_set_ninja() + cmate_check_ninja() cmate_run_prog( CMD @@ -1132,6 +1692,7 @@ function(cmate_install_cmake_dep) -DCMAKE_PREFIX_PATH=${CMATE_ENV_DIR} -DCMAKE_INSTALL_PREFIX=${CMATE_ENV_DIR} -DCMAKE_BUILD_TYPE=Release + -DBUILD_TESTING=OFF -G Ninja ${ARGS} -S ${CMATE_DEP_SOURCE_DIR} -B ${CMATE_DEP_BUILD_DIR} @@ -1343,6 +1904,7 @@ Options: --verbose Verbose operation --cc=ID Compiler suite to use (overrides CMATE_CC) (e.g.: gcc, clang, gcc-10, clang-16, cl) + --no-ninja Don't use Ninja Commands: " @@ -1400,19 +1962,19 @@ function(cmate_help) message(${HELP}) endfunction() -############################################################################## -# -# Target common functions -# -############################################################################## - - ############################################################################## # # Configuration functions # ############################################################################## function(cmate_set_defaults) + # Policies + set(ME ${CMAKE_CURRENT_LIST_FILE}) + get_filename_component(MYDIR "${ME}" DIRECTORY) + get_filename_component(MYDIR "${MYDIR}/.." REALPATH) + + cmate_setg(CMATE_TMPL_DIR "${MYDIR}/templates") + get_filename_component(DIR "." ABSOLUTE) cmate_setg(CMATE_ROOT_DIR ${DIR}) @@ -1426,13 +1988,17 @@ function(cmate_set_defaults) cmate_setg(CMATE_STATE_DIR "${CMATE_HOME_DIR}/state") cmate_setg(CMATE_TOOLCHAINS_DIR "${CMATE_HOME_DIR}/toolchains") - cmate_setg(CMATE_BUILD_BASE_DIR "${CMATE_ROOT_DIR}/build") + cmate_setg(CMATE_BUILD_DIR "${CMATE_ROOT_DIR}/build") + cmate_setg(CMATE_TARGETS_FILE "${CMATE_BUILD_DIR}/cmate-targets.json") + cmate_setg(CMATE_STAGE_DIR "${CMATE_ROOT_DIR}/stage") cmate_setg(CMATE_TMP_DIR "${CMATE_HOME_DIR}/tmp") cmate_setg(CMATE_HEADER_PAT "*.hpp") cmate_setg(CMATE_SOURCE_PAT "*.[ch]pp") + + cmate_setg(CMATE_EMPTY_LINE_MARKER "@CMATE_EMPTY_LINE@") endfunction() function(cmate_set_compilers) @@ -1492,3 +2058,246 @@ if(CMAKE_SCRIPT_MODE_FILE) cmate_load_conf("${CMATE_ROOT_DIR}/${CMATE_PRJFILE}") cmate_process_cmd() endif() + +############################################################################### +# +# Template TARGETS_LINK_TXT_IN +# +############################################################################### +set( + CMATE_TARGETS_LINK_TXT_IN + [=[ +% if(${TARGET_DEPS_COUNT} GREATER 0) + +target_link_libraries( + @T.TNAME@ +% foreach(TYPE PUBLIC PRIVATE) +% if(${TARGET_${TYPE}_DEPS_COUNT} GREATER 0) + %{ ${TYPE} }% +% foreach(DEP ${TARGET_${TYPE}_DEPS}) + %{ ${DEP} }% +% endforeach() +% endif() +% endforeach() +) +% endif() +]=]) + +############################################################################### +# +# Template TARGETS_LIB_CMAKELISTS_TXT_IN +# +############################################################################### +set( + CMATE_TARGETS_LIB_CMAKELISTS_TXT_IN + [=[ +add_library(@T.TNAME@) +add_library(@P.NS@::@T.NAME@ ALIAS @T.TNAME@) + +set(@T.UTNAME@_INC_DIR "${PROJECT_SOURCE_DIR}/include/@T.NAME@") +file(GLOB_RECURSE @T.UTNAME@_HEADERS "${@T.UTNAME@_INC_DIR}/@CM.HPAT@") +list(APPEND @T.UTNAME@_ALL_SOURCES ${@T.UTNAME@_HEADERS}) + +set(@T.UTNAME@_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}") +file(GLOB_RECURSE @T.UTNAME@_SOURCES "${@T.UTNAME@_SRC_DIR}/@CM.SPAT@") +list(APPEND @T.UTNAME@_ALL_SOURCES ${@T.UTNAME@_SOURCES}) + +target_sources( + @T.TNAME@ + PRIVATE + ${@T.UTNAME@_ALL_SOURCES} +) + +target_include_directories( + @T.TNAME@ + PUBLIC + $ + $ + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) +%# +%#include + +set_target_properties( + @T.TNAME@ + PROPERTIES + CXX_STANDARD @P.STD@ + VERSION @P.VER@ + SOVERSION @P.VER_MAJOR@.@P.VER_MINOR@ + EXPORT_NAME @T.NAME@ + OUTPUT_NAME @P.NS@_@T.NAME@ +) +]=]) + +############################################################################### +# +# Template PROJECT_CMAKELISTS_TXT_IN +# +############################################################################### +set( + CMATE_PROJECT_CMAKELISTS_TXT_IN + [=[ +%#include +%#include +%# +%#include +%#include +%# +%#include +%# +%#include +%# +%#include +]=]) + +############################################################################### +# +# Template PROJECT_PKG_CMAKE_TXT_IN +# +############################################################################### +set( + CMATE_PROJECT_PKG_CMAKE_TXT_IN + [=[ +% if(${P.CM.PKG_COUNT} GREATER 0) + +% foreach(PKG ${P.CM.PKGS}) +% if(${P.CM.PKGS.${PKG}.COMP_COUNT} GREATER 0) +find_package( + %{ ${PKG} }% + CONFIG REQUIRED + COMPONENTS +% foreach(COMP ${P.CM.$PKG.COMPS}) + %{ ${COMP} }% +% endforeach() +) +% else() +find_package(%{ ${PKG} }% CONFIG REQUIRED) +% endif() +% endforeach() +% endif() +]=]) + +############################################################################### +# +# Template PROJECT_PKG_PKGCONFIG_TXT_IN +# +############################################################################### +set( + CMATE_PROJECT_PKG_PKGCONFIG_TXT_IN + [=[ +% if(${P.PC.PKG_COUNT} GREATER 0) +% foreach(PKG ${P.PC.PKGS}) +pkg_check_modules(%{ ${PKG} }% REQUIRED IMPORTED_TARGET %{ ${PKG} }%) +% endforeach() +% endif() +]=]) + +############################################################################### +# +# Template PROJECT_FOOTER_TXT_IN +# +############################################################################### +set( + CMATE_PROJECT_FOOTER_TXT_IN + [=[ +]=]) + +############################################################################### +# +# Template PROJECT_OPTIONS_TXT_IN +# +############################################################################### +set( + CMATE_PROJECT_OPTIONS_TXT_IN + [=[ + +set(${@P.UNAME@_LOCAL_DEV} OFF) + +if(${@P.UNAME@_LOCAL_DEV}) + set(@P.UNAME@_BUILD_TESTS_INIT ON) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +else() + set(@P.UNAME@_BUILD_TESTS_INIT OFF) +endif() + +option(@P.UNAME@_BUILD_TESTS "Build the unit tests." ${@P.UNAME@_BUILD_TESTS_INIT}) +]=]) + +############################################################################### +# +# Template PROJECT_HEADER_TXT_IN +# +############################################################################### +set( + CMATE_PROJECT_HEADER_TXT_IN + [=[ +cmake_minimum_required(VERSION @CMATE_CMAKE_VER@ FATAL_ERROR) + +project(@P.NAME@ VERSION @P.VER@ LANGUAGES C CXX) + +include(GNUInstallDirs) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_definitions(_CRT_SECURE_NO_WARNINGS _SCL_SECURE_NO_WARNINGS) +endif() +]=]) + +############################################################################### +# +# Template PROJECT_INSTALL_TXT_IN +# +############################################################################### +set( + CMATE_PROJECT_INSTALL_TXT_IN + [=[ + +install( + TARGETS +% foreach(TARGET ${P.TARGETS.INSTALL}) + %{ ${TARGET} }% +% endforeach() + EXPORT @P.NAME@-config + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install( + EXPORT @P.NAME@-config + FILE @P.NAME@-config.cmake + NAMESPACE @P.NS@:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/@P.NAME@ +) +% foreach(TARGET ${P.TARGETS.LIB}) + +install( + DIRECTORY "${PROJECT_SOURCE_DIR}/include/%{ ${P.TARGETS.LIB.${TARGET}.NAME} }%" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/@P.NS@ +) +% endforeach() +]=]) + +############################################################################### +# +# Template PROJECT_TARGETS_TXT_IN +# +############################################################################### +set( + CMATE_PROJECT_TARGETS_TXT_IN + [=[ + +% foreach(TYPE "LIB" "BIN") +% foreach(T ${P.TARGETS.${TYPE}}) +add_subdirectory(%{ ${P.TARGETS.${TYPE}.${T}.SUBDIR} }%) +% endforeach() +% endforeach() + +if(@P.UNAME@_BUILD_TESTS) + include(CTest) + enable_testing() +% foreach(T ${P.TARGETS.TEST}) + add_subdirectory(%{ ${P.TARGETS.TEST.${T}.SUBDIR} }%) +% endforeach() +endif() +]=]) diff --git a/cpp/src/lib/messages/CMakeLists.txt b/cpp/src/lib/messages/CMakeLists.txt index bca2c41f..5bae92cb 100644 --- a/cpp/src/lib/messages/CMakeLists.txt +++ b/cpp/src/lib/messages/CMakeLists.txt @@ -29,6 +29,7 @@ target_link_libraries( PUBLIC nlohmann_json::nlohmann_json ) + set_target_properties( cucumber_messages_lib PROPERTIES