if(QuickCppLibUtilsIncluded) return() endif() set(QuickCppLibUtilsIncluded ON) set(QuickCppLibCMakePath "${CMAKE_CURRENT_LIST_DIR}") find_package(Git) if(NOT GIT_FOUND) message(WARNING "WARNING: The quickcpplib infrastructure is very tightly integrated with git" " and requires it to be available. Proceeding anyway.") endif() # Returns a path with forward slashes replaced with backslashes on WIN32 function(NativisePath outvar) if(WIN32) string(REPLACE "/" "\\" new ${ARGN}) else() set(new ${ARGN}) endif() set(${outvar} ${new} PARENT_SCOPE) endfunction() # Simulate a target_link_options as cmake is missing such a thing function(_target_link_options target_name visibility) if(NOT visibility MATCHES "PRIVATE") message(FATAL_ERROR "FATAL: _target_link_options() used with non-PRIVATE visibility") endif() if(COMMAND target_link_options) target_link_options(${target_name} ${visibility} ${ARGN}) else() # Convert args to a string string(REPLACE ";" " " props "${ARGN}") get_target_property(oldprops ${target_name} LINK_FLAGS) if(oldprops MATCHES "NOTFOUND") set(oldprops) endif() set_target_properties(${target_name} PROPERTIES LINK_FLAGS "${oldprops} ${props}" ) endif() endfunction() # Add generator expressions to appendvar expanding at build time any remaining parameters # if the is true at build time function(expand_at_build_if condition appendvar) set(ret ${${appendvar}}) set(items ${ARGN}) separate_arguments(items) foreach(item ${items}) list(APPEND ret $<${condition}:${item}>) endforeach() set(${appendvar} ${ret} PARENT_SCOPE) endfunction() # Emulate list(FILTER list INCLUDE|EXCLUDE REGEX regex) on cmake < 3.6 function(list_filter listname op regexqualifer regex) list(FILTER ${ARGV}) set(${listname} ${${listname}} PARENT_SCOPE) endfunction() # Escape a string into a regex matching that string function(escape_string_into_regex outvar) string(REGEX REPLACE "(\\^|\\$|\\.|\\[|\\]|\\*|\\+|\\?|\\(|\\)|\\\\)" "\\\\\\1" out ${ARGN}) set(${outvar} ${out} PARENT_SCOPE) endfunction() # Indents a message by a global variable amount of whitespace function(indented_message type) message(${type} "${MESSAGE_INDENT}" ${ARGN}) endfunction() # Executes an external process, fatal erroring if it fails function(checked_execute_process desc) execute_process(${ARGN} RESULT_VARIABLE result OUTPUT_VARIABLE out ERROR_VARIABLE errout ) if(NOT result EQUAL 0) message(FATAL_ERROR "FATAL: ${desc} failed with error '${result}'\n\nstdout was: ${out}\n\nstderr was: ${errout}") endif() #message("stdout was: ${out}\n\nstderr was: ${errout}") endfunction() # Determines if a git repo has changed function(git_repo_changed dir outvar) if(NOT GIT_FOUND) set(${outvar} TRUE PARENT_SCOPE) return() endif() execute_process(COMMAND "${GIT_EXECUTABLE}" status --porcelain WORKING_DIRECTORY "${dir}" OUTPUT_VARIABLE status RESULT_VARIABLE result OUTPUT_STRIP_TRAILING_WHITESPACE ) if(NOT result EQUAL 0) message(FATAL_ERROR "FATAL: git status failed with error '${result}'") endif() # message("${status}") if(status STREQUAL "") set(${outvar} FALSE PARENT_SCOPE) else() set(${outvar} TRUE PARENT_SCOPE) endif() endfunction() # Gets the committed SHA in the index for some entry function(git_repo_get_entry_sha dir entry outvar) if(NOT GIT_FOUND) message(FATAL_ERROR "FATAL: Git executable not found") endif() # git ls-files -s produces entries of the format: # 100644 e10ce7c26311e43f337b1f3929450e1804059adf 0 test/test.vcxproj execute_process(COMMAND "${GIT_EXECUTABLE}" ls-files -s WORKING_DIRECTORY "${dir}" OUTPUT_VARIABLE status RESULT_VARIABLE result ) if(NOT result EQUAL 0) message(FATAL_ERROR "FATAL: git ls-files failed with error '${result}'") endif() escape_string_into_regex(entry "${entry}") if(status MATCHES "([0-9]+) ([0-9a-f]+) ([0-9]+)\t(${entry})\n") #set(cacheinfo "${CMAKE_MATCH_1}") set(sha "${CMAKE_MATCH_2}") #set(size "${CMAKE_MATCH_3}") #set(mentry "${CMAKE_MATCH_4}") set(${outvar} "${sha}" PARENT_SCOPE) else() unset(${outvar} PARENT_SCOPE) endif() endfunction() # Determines what git revision SHA some path is currently on # unsetting outvar if not a git repository function(git_revision_from_path path outsha outtimestamp) set(gitdir "${path}/.git") unset(${outsha} PARENT_SCOPE) unset(${outtimestamp} PARENT_SCOPE) if(NOT EXISTS "${gitdir}") return() endif() # Are you a submodule? if(NOT IS_DIRECTORY "${gitdir}") file(READ "${gitdir}" pathtogitdir) # This will have the form: # gitdir: ../../../../.git/modules/include/boost/afio/boost-lite # gitdir: /home/paul/tmp/cget/cget/build/tmp-48d80d9e2c734b86800806772ac60260/boost.outcome/include/boost/outcome/boost-lite/.git string(SUBSTRING "${pathtogitdir}" 8 -1 pathtogitdir) string(STRIP "${pathtogitdir}" pathtogitdir) if("${pathtogitdir}" MATCHES "\.\./") set(gitdir "${path}/${pathtogitdir}") else() set(gitdir "${pathtogitdir}") endif() endif() # Read .git/HEAD and the SHA and timestamp #indented_message(STATUS "gitdir is ${gitdir}") file(READ "${gitdir}/HEAD" HEAD) string(SUBSTRING "${HEAD}" 5 -1 HEAD) string(STRIP "${HEAD}" HEAD) #indented_message(STATUS "head is '${HEAD}'") if(EXISTS "${gitdir}/${HEAD}") file(READ "${gitdir}/${HEAD}" HEADSHA) string(STRIP "${HEADSHA}" HEADSHA) file(TIMESTAMP "${gitdir}/${HEAD}" HEADSTAMP "%Y-%m-%d %H:%M:%S +00:00" UTC) #indented_message(STATUS "Last commit was ${HEADSHA} at ${HEADSTAMP}") string(SUBSTRING "${HEADSHA}" 0 8 HEADUNIQUE) set(${outsha} ${HEADSHA} PARENT_SCOPE) set(${outtimestamp} ${HEADSTAMP} PARENT_SCOPE) endif() endfunction() # We expect a header file with macros like # #define BOOST_AFIO_VERSION_MAJOR 2 # # The first macros with _MAJOR, _MINOR, _PATCH and _REVISION at their end are parsed function(ParseProjectVersionFromHpp hppfile outvar) file(READ ${hppfile} HPPFILE) string(REGEX MATCH "#[ \t]*define[ \t].*_MAJOR[ \t]+([0-9]+)" MAJORVER "${HPPFILE}") set(MAJORVER ${CMAKE_MATCH_1}) string(REGEX MATCH "#[ \t]*define[ \t].*_MINOR[ \t]+([0-9]+)" MINORVER "${HPPFILE}") set(MINORVER ${CMAKE_MATCH_1}) string(REGEX MATCH "#[ \t]*define[ \t].*_PATCH[ \t]+([0-9]+)" PATCHVER "${HPPFILE}") set(PATCHVER ${CMAKE_MATCH_1}) string(REGEX MATCH "#[ \t]*define[ \t].*_REVISION[ \t]+([0-9]+)" REVISIONVER "${HPPFILE}") set(REVISIONVER ${CMAKE_MATCH_1}) set(${outvar} ${MAJORVER}.${MINORVER}.${PATCHVER}.${REVISIONVER} PARENT_SCOPE) endfunction() # We expect a header file like this: # // Comment # #define BOOST_AFIO_PREVIOUS_COMMIT_REF x # #define BOOST_AFIO_PREVIOUS_COMMIT_DATE "x" # #define BOOST_AFIO_PREVIOUS_COMMIT_UNIQUE x # Lines 2, 3 and 4 need their ending rewritten function(UpdateRevisionHppFromGit hppfile) #set(temphppfile "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}_revision.hpp") git_revision_from_path("${CMAKE_CURRENT_SOURCE_DIR}" HEADSHA HEADSTAMP) if(DEFINED HEADSHA) string(SUBSTRING "${HEADSHA}" 0 8 HEADUNIQUE) file(READ "${hppfile}" HPPFILE) string(REGEX MATCH "(.*\n.* )([a-f0-9]+)([\r\n]+.* \")(.*)(\"[\r\n]+.* )([a-f0-9]+)([\r\n]+.*)" txt1 "${HPPFILE}") set(txt1 "${CMAKE_MATCH_1}") set(OLDSHA "${CMAKE_MATCH_2}") set(txt2 "${CMAKE_MATCH_3}") set(OLDSTAMP "${CMAKE_MATCH_4}") set(txt3 "${CMAKE_MATCH_5}") set(OLDUNIQUE "${CMAKE_MATCH_6}") set(txt4 "${CMAKE_MATCH_7}") set(HPPFILE2 "${txt1}${HEADSHA}${txt2}${HEADSTAMP}${txt3}${HEADUNIQUE}${txt4}") if(NOT "${HPPFILE}" STREQUAL "${HPPFILE2}") file(WRITE "${hppfile}" "${HPPFILE2}") endif() endif() endfunction() # Apply OpenMP to a given target. Add REQUIRED to make it mandatory. function(target_uses_openmp target) find_package(OpenMP) if(OPENMP_FOUND) if(MSVC AND CLANG OR APPLE) # Currently doesn't work elseif(MSVC) target_compile_options(${target} PRIVATE ${OpenMP_CXX_FLAGS}) return() else() target_compile_options(${target} PRIVATE ${OpenMP_CXX_FLAGS}) set_target_properties(${target} PROPERTIES LINK_FLAGS -fopenmp) return() endif() endif() list(FIND ARGN "REQUIRED" required_idx) if(${required_idx} GREATER -1) indented_message(FATAL_ERROR "FATAL: Target ${target} requires OpenMP") endif() endfunction() # Preprocess a file function(add_partial_preprocess target outfile depend infile) if(EXISTS "${QuickCppLibCMakePath}/../pcpp/pcpp/pcmd.py") add_custom_command(OUTPUT "${outfile}" COMMAND "${PYTHON_EXECUTABLE}" "${QuickCppLibCMakePath}/../pcpp/pcpp/pcmd.py" -o "${outfile}" "${infile}" ${ARGN} DEPENDS "${depend}" COMMENT "Preprocessing ${infile} into ${outfile} ..." ) add_custom_target(${target} DEPENDS "${outfile}") else() indented_message(WARNING "WARNING: '${QuickCppLibCMakePath}/../pcpp/pcpp/pcmd.py' was not found, cannot do partial preprocessing") add_custom_target(${target}) endif() endfunction() # Have cmake download, build, and install some git repo function(download_build_install) cmake_parse_arguments(DBI "" "NAME;DESTINATION;INSTALL_PREFIX;GIT_REPOSITORY;GIT_TAG" "CMAKE_ARGS;EXTERNALPROJECT_ARGS" ${ARGN}) configure_file("${QuickCppLibCMakePath}/DownloadBuildInstall.cmake.in" "${DBI_DESTINATION}/CMakeLists.txt" @ONLY) checked_execute_process("Configure download, build and install of ${DBI_NAME} with ${DBI_CMAKE_ARGS}" COMMAND "${CMAKE_COMMAND}" . WORKING_DIRECTORY "${DBI_DESTINATION}" ) checked_execute_process("Build download, build and install of ${DBI_NAME} with ${DBI_CMAKE_ARGS}" COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY "${DBI_DESTINATION}" ) checked_execute_process("Install download, build and install of ${DBI_NAME} with ${DBI_CMAKE_ARGS}" COMMAND "${CMAKE_COMMAND}" --install . WORKING_DIRECTORY "${DBI_DESTINATION}" ) endfunction() # Finds a quickcpplib library # # QUIET: print no messages # REQUIRED: fail if not found # LOCAL: always git clone a local edition instead of using cmake package edition # INBUILD: add the dependency as a part of this build instead of using the installed edition # INCLUDE_ALL: don't use EXCLUDE_FROM_ALL in add_subdirectory() # GIT_REPOSITORY: git repository to clone if library not found # GIT_TAG: git branch or tag or SHA to clone # # quickcpplib libraries can be located via these means in order of preference: # # 1. Only if "../.quickcpplib_use_siblings" exists, "../${libraryname}". These are always INBUILD. # # 2. If not LOCAL, find_package(${libraryname}). # # 3. ExternalProject_Add() to CMAKE_BINARY_DIR followed by find_package(${libraryname} NO_DEFAULT_PATH). function(find_quickcpplib_library libraryname) if(NOT PROJECT_NAME) message(FATAL_ERROR "FATAL: find_quickcpplib_library() must only be called after a project()") endif() get_filename_component(boostishdir "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) if(IS_DIRECTORY "${boostishdir}/.quickcpplib_use_siblings" AND NOT QUICKCPPLIB_DISABLE_SIBLINGS) set(siblingenabled ON) else() set(siblingenabled OFF) endif() cmake_parse_arguments(FINDLIB "QUIET;REQUIRED;LOCAL;INBUILD;INCLUDE_ALL;IS_HEADER_ONLY" "GIT_REPOSITORY;GIT_TAG" "" ${ARGN}) if(FINDLIB_KEYWORDS_MISSING_VALUES) message(FATAL_ERROR "FATAL: No values given for keywords ${FINDLIB_KEYWORDS_MISSING_VALUES}") endif() if(FINDLIB_UNPARSED_ARGUMENTS) message(FATAL_ERROR "FATAL: Unrecognised arguments ${FINDLIB_UNPARSED_ARGUMENTS}") endif() if(NOT FINDLIB_GIT_REPOSITORY) message(FATAL_ERROR "FATAL: GIT_REPOSITORY must be specified!") endif() set(FINDLIB_LOCAL_PATH) if(NOT ${libraryname}_FOUND) get_property(${libraryname}_FOUND GLOBAL PROPERTY ${libraryname}_FOUND) endif() if(NOT DEFINED QUICKCPPLIB_ROOT_BINARY_DIR) set(QUICKCPPLIB_ROOT_BINARY_DIR "${CMAKE_BINARY_DIR}") endif() if(NOT ${libraryname}_FOUND OR QUICKCPPLIB_REFRESH_FIND_LIBRARY_${libraryname}) set(${libraryname}_FOUND FALSE) # Prefer sibling editions of dependencies if(siblingenabled AND EXISTS "${boostishdir}/${libraryname}/.quickcpplib") set(FINDLIB_LOCAL_PATH "${boostishdir}/${libraryname}") set(${libraryname}_FOUND TRUE) elseif(NOT FINDLIB_LOCAL) find_package(${libraryname} QUIET CONFIG) if(NOT ${libraryname}_FOUND) indented_message(STATUS "Missing dependency ${libraryname} is NOT installed in cmake packages!") endif() endif() if(NOT ${libraryname}_FOUND) if(FINDLIB_INBUILD AND EXISTS "${QUICKCPPLIB_ROOT_BINARY_DIR}/${libraryname}") set(FINDLIB_LOCAL_PATH "${QUICKCPPLIB_ROOT_BINARY_DIR}/${libraryname}") set(${libraryname}_FOUND TRUE) else() find_package(${libraryname} QUIET CONFIG NO_DEFAULT_PATH PATHS "${QUICKCPPLIB_ROOT_BINARY_DIR}/${libraryname}") indented_message(STATUS "Missing dependency ${libraryname} is NOT found at ${QUICKCPPLIB_ROOT_BINARY_DIR}/${libraryname}!") endif() endif() if(NOT ${libraryname}_FOUND) foreach(config Debug Release RelWithDebInfo MinSizeRel) indented_message(STATUS "Superbuilding missing dependency ${libraryname} with config ${config}, this may take a while ...") set(cmakeargs "-DCMAKE_BUILD_TYPE=${config} -G \"${CMAKE_GENERATOR}\" -DBUILD_TESTING=OFF \"-DQUICKCPPLIB_ROOT_BINARY_DIR=${QUICKCPPLIB_ROOT_BINARY_DIR}\"") if(DEFINED CMAKE_TOOLCHAIN_FILE) set(cmakeargs "${cmakeargs} \"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}\"") endif() if(FINDLIB_GIT_TAG STREQUAL "master" OR FINDLIB_GIT_TAG STREQUAL "develop") set(extraargs "GIT_SHALLOW 1;GIT_SUBMODULES \"\"") else() set(extraargs "GIT_SUBMODULES \"\"") endif() #indented_message(STATUS "DEBUG: download_build_install() QUICKCPPLIB_ROOT_BINARY_DIR = '${QUICKCPPLIB_ROOT_BINARY_DIR}'") #indented_message(STATUS "DEBUG: download_build_install() cmakeargs = '${cmakeargs}'") download_build_install(NAME ${libraryname} CMAKE_ARGS ${cmakeargs} EXTERNALPROJECT_ARGS ${extraargs} GIT_REPOSITORY "${FINDLIB_GIT_REPOSITORY}" GIT_TAG "${FINDLIB_GIT_TAG}" DESTINATION "${QUICKCPPLIB_ROOT_BINARY_DIR}/${libraryname}" INSTALL_PREFIX "${QUICKCPPLIB_ROOT_BINARY_DIR}/install" ) if(FINDLIB_IS_HEADER_ONLY) break() endif() endforeach() if(FINDLIB_INBUILD) set(FINDLIB_LOCAL_PATH "${QUICKCPPLIB_ROOT_BINARY_DIR}/${libraryname}") set(${libraryname}_FOUND TRUE) else() include(GNUInstallDirs) # Normally, the following ${libraryname}_DIR would not be needed if # PATHS "${QUICKCPPLIB_ROOT_BINARY_DIR}/${libraryname}" were provided. # However, when CMAKE_SYSROOT is set, the paths provided by the PATHS option are assumed to be system paths # even when they are in the build directory, and get rewritten into the sysroot. list(APPEND CMAKE_PREFIX_PATH "${QUICKCPPLIB_ROOT_BINARY_DIR}/install") set(${libraryname}_DIR "${QUICKCPPLIB_ROOT_BINARY_DIR}/install/${CMAKE_INSTALL_LIBDIR}/cmake/${libraryname}") # The Android NDK toolchain file appears to replace both CMAKE_PREFIX_PATH and CMAKE_FIND_ROOT_PATH, # this for now appears to work. Comes with lots of gotchas however :( if(ANDROID) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) endif() find_package(${libraryname} CONFIG REQUIRED NO_DEFAULT_PATH) endif() endif() if(FINDLIB_LOCAL_PATH) set(MESSAGE_INDENT "${MESSAGE_INDENT} ") if(FINDLIB_INCLUDE_ALL) add_subdirectory("${FINDLIB_LOCAL_PATH}" "${QUICKCPPLIB_ROOT_BINARY_DIR}/${libraryname}_sibling" ) else() add_subdirectory("${FINDLIB_LOCAL_PATH}" "${QUICKCPPLIB_ROOT_BINARY_DIR}/${libraryname}_sibling" EXCLUDE_FROM_ALL ) endif() set(${libraryname}_DIR "${FINDLIB_LOCAL_PATH}") set(${libraryname}_FOUND TRUE) # Reset policies after using add_subdirectory() which usually means a cmake_minimum_required() # was called which resets policies to default include(QuickCppLibPolicies) endif() endif() if(${libraryname}_FOUND) set_property(GLOBAL PROPERTY ${libraryname}_FOUND TRUE) set_property(GLOBAL PROPERTY ${libraryname}_DIR "${${libraryname}_DIR}") set_property(GLOBAL PROPERTY ${libraryname}_DEPENDENCIES "${${PROJECT_NAME}_DEPENDENCIES};${libraryname}") else() if(NOT FINDLIB_QUIET) indented_message(WARNING "WARNING: quickcpplib library ${libraryname} depended upon by ${PROJECT_NAMESPACE}${PROJECT_NAME} not found") indented_message(STATUS "Tried: ") if(siblingenabled) indented_message(STATUS " ${boostishdir}/${libraryname}/.quickcpplib") endif() indented_message(STATUS " ${QUICKCPPLIB_ROOT_BINARY_DIR}/${libraryname}/.quickcpplib") if(NOT siblingenabled) indented_message(STATUS " (sibling library use disabled due to lack of ${boostishdir}/.quickcpplib_use_siblings)") endif() if(NOT FINDLIB_LOCAL) indented_message(STATUS " find_package(${libraryname})") endif() endif() if(FINDLIB_REQUIRED) indented_message(FATAL_ERROR "FATAL: quickcpplib library ${libraryname} required by ${PROJECT_NAMESPACE}${PROJECT_NAME} not found") endif() endif() endfunction() # Configures a CTest script with a sensible set of defaults # for doing a configure, build, test and submission run macro(CONFIGURE_CTEST_SCRIPT_FOR_CDASH projectname bindir) set(CTEST_PROJECT_NAME "${projectname}") set(CTEST_SOURCE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") set(CTEST_BINARY_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${bindir}") set(CTEST_CONFIGURATION_TYPE Release) if(WIN32) if(NOT DEFINED CTEST_CMAKE_GENERATOR) # TODO Figure out how to use winclang via adding in -T v140_clang_c2 set(CTEST_CMAKE_GENERATOR "Visual Studio 16 2019") set(CTEST_CMAKE_GENERATOR_PLATFORM "x64") endif() set(CTEST_CMAKE_CI_BIN_DIR "${bindir}/bin/${CTEST_CONFIGURATION_TYPE}") set(CTEST_SITE $ENV{COMPUTERNAME}) else() if(NOT DEFINED CTEST_CMAKE_GENERATOR) set(CTEST_CMAKE_GENERATOR "Unix Makefiles") endif() set(CTEST_CMAKE_CI_BIN_DIR "${bindir}/bin") set(CTEST_SITE $ENV{NAME}) endif() set(CTEST_BUILD_NAME "${CMAKE_SYSTEM}-${CMAKE_SYSTEM_PROCESSOR}") find_program(CTEST_PYTHON_COMMAND NAMES python) endmacro() function(merge_junit_results_into_ctest_xml) if(NOT DEFINED CTEST_PYTHON_COMMAND) message(FATAL_ERROR "Please call the macro CONFIGURE_CTEST_SCRIPT_FOR_CDASH() to configure the ctest environment first") endif() # Merge all the junit XML files from the testing into one junit XML file execute_process(COMMAND "${CTEST_PYTHON_COMMAND}" "${CTEST_QUICKCPPLIB_SCRIPTS}/merge_junit_results.py" "${CTEST_BINARY_DIRECTORY}/merged_junit_results.xml" "${CTEST_CMAKE_CI_BIN_DIR}/*.junit.xml" RESULT_VARIABLE result ) message(STATUS "Merging junit XML results into a single junit XML returned with status ${result}") # Figure out where this iteration's Test.xml lives file(READ "${CTEST_BINARY_DIRECTORY}/Testing/TAG" tag_file) string(REGEX MATCH "[^\n]*" xml_dir "${tag_file}") set(CTEST_XML_DIR "${CTEST_BINARY_DIRECTORY}/Testing/${xml_dir}") # Add the combined junit XML file into our Test.xml execute_process(COMMAND "${CTEST_PYTHON_COMMAND}" "${CTEST_QUICKCPPLIB_SCRIPTS}/add_junit_results_to_ctest.py" "${CTEST_XML_DIR}/Test.xml" "${CTEST_BINARY_DIRECTORY}/merged_junit_results.xml" RESULT_VARIABLE result ) message(STATUS "Merging junit XML results into the CTest XML returned with status ${result}") endfunction() # Figures out whether this compiler support C++ Concepts, and if so # applies the appropriate config to the targets specified to enable them function(apply_cxx_concepts_to visibility) include(CheckCXXSourceCompiles) # Do we have the Concepts TS? function(CheckCXXHasConcepts iter) set(CMAKE_REQUIRED_FLAGS ${ARGN}) if(CMAKE_CXX_STANDARD) if(MSVC) if(CMAKE_CXX_STANDARD EQUAL 20) list(APPEND CMAKE_REQUIRED_FLAGS /std:c++latest) else() list(APPEND CMAKE_REQUIRED_FLAGS /std:c++${CMAKE_CXX_STANDARD}) endif() elseif(CLANG OR GCC) list(APPEND CMAKE_REQUIRED_FLAGS -std=c++${CMAKE_CXX_STANDARD}) endif() endif() check_cxx_source_compiles(" #if !defined(_MSC_VER) && !defined(__clang__) && (__GNUC__ < 9 || __cplusplus < 202000L) #define OUTCOME_GCC6_CONCEPT_BOOL bool #else #define OUTCOME_GCC6_CONCEPT_BOOL #endif namespace detail { template concept OUTCOME_GCC6_CONCEPT_BOOL SameHelper = true; template concept OUTCOME_GCC6_CONCEPT_BOOL same_as = detail::SameHelper &&detail::SameHelper; } // namespace detail template concept OUTCOME_GCC6_CONCEPT_BOOL ValueOrNone = requires(U a) { { a.has_value() } ->detail::same_as; {a.value()}; }; int main() { return 0; } " CXX_HAS_CONCEPTS${iter}) set(CXX_HAS_CONCEPTS${iter} ${CXX_HAS_CONCEPTS${iter}} PARENT_SCOPE) endfunction() set(CXX_CONCEPTS_FLAGS "deduce" CACHE STRING "The flags to enable C++ Concepts for this compiler") if(CXX_CONCEPTS_FLAGS STREQUAL "deduce") set(HAVE_CONCEPTS 0) CheckCXXHasConcepts(_BY_DEFAULT) if(CXX_HAS_CONCEPTS_BY_DEFAULT) set(HAVE_CONCEPTS 1) set(CXX_CONCEPTS_FLAGS "" CACHE STRING "The flags to enable C++ Concepts for this compiler" FORCE) set(CXX_HAS_CONCEPTS "1" CACHE STRING "Whether this compiler supports C++ Concepts or not" FORCE) endif() if(NOT HAVE_CONCEPTS AND NOT "REQUIRE_STD_CONCEPTS" IN_LIST ARGN) if(CLANG) CheckCXXHasConcepts(_CLANG -fconcepts-ts) if(CXX_HAS_CONCEPTS_CLANG) set(CXX_CONCEPTS_FLAGS "-fconcepts-ts" CACHE STRING "The flags to enable C++ Concepts for this compiler" FORCE) set(CXX_HAS_CONCEPTS "2" CACHE STRING "Whether this compiler supports C++ Concepts or not" FORCE) set(HAVE_CONCEPTS 2) endif() elseif(GCC) CheckCXXHasConcepts(_GCC -fconcepts) if(CXX_HAS_CONCEPTS_GCC) set(CXX_CONCEPTS_FLAGS "-fconcepts" CACHE STRING "The flags to enable C++ Concepts for this compiler" FORCE) set(CXX_HAS_CONCEPTS "2" CACHE STRING "Whether this compiler supports C++ Concepts or not" FORCE) set(HAVE_CONCEPTS 2) endif() endif() endif() if(NOT HAVE_CONCEPTS) set(CXX_CONCEPTS_FLAGS "unsupported" CACHE STRING "The flags to enable C++ Concepts for this compiler" FORCE) set(CXX_HAS_CONCEPTS "0" CACHE STRING "Whether this compiler supports C++ Concepts or not" FORCE) endif() endif() if(CXX_CONCEPTS_FLAGS AND NOT CXX_CONCEPTS_FLAGS STREQUAL "unsupported") foreach(target ${ARGN}) if(NOT target STREQUAL "REQUIRE_STD_CONCEPTS") target_compile_options(${target} ${visibility} ${CXX_CONCEPTS_FLAGS}) endif() endforeach() endif() endfunction() # Figures out whether this compiler support C++ Coroutines, and if so # applies the appropriate config to the targets specified to enable them function(apply_cxx_coroutines_to visibility) include(CheckCXXSourceCompiles) # Do we have the Coroutines TS? function(CheckCXXHasCoroutines iter) set(CMAKE_REQUIRED_FLAGS ${ARGN}) if(CMAKE_CXX_STANDARD) if(MSVC) if(CMAKE_CXX_STANDARD EQUAL 20) list(APPEND CMAKE_REQUIRED_FLAGS /std:c++latest) else() list(APPEND CMAKE_REQUIRED_FLAGS /std:c++${CMAKE_CXX_STANDARD}) endif() elseif(CLANG OR GCC) list(APPEND CMAKE_REQUIRED_FLAGS -std=c++${CMAKE_CXX_STANDARD}) endif() endif() check_cxx_source_compiles(" #include #if __has_include() && (!defined(_MSC_VER) || _HAS_CXX20) #include using std::suspend_never; #elif __has_include() #include using std::experimental::suspend_never; #endif class resumable{ public: struct promise_type { resumable get_return_object() { return {}; } auto initial_suspend() { return suspend_never(); } auto final_suspend() noexcept { return suspend_never(); } void return_value(int) { } void unhandled_exception() {} }; bool resume() { return true; } int get() { return 0; } }; resumable g() { co_return 0; } int main() { return g().get(); } " CXX_HAS_COROUTINES${iter}) set(CXX_HAS_COROUTINES${iter} ${CXX_HAS_COROUTINES${iter}} PARENT_SCOPE) endfunction() set(CXX_COROUTINES_FLAGS "deduce" CACHE STRING "The flags to enable C++ Coroutines for this compiler") set(CXX_COROUTINES_LINKER_FLAGS "" CACHE STRING "The linker flags to enable C++ Coroutines for this compiler") if(CXX_COROUTINES_FLAGS STREQUAL "deduce") set(HAVE_COROUTINES 0) CheckCXXHasCoroutines(_BY_DEFAULT) if(CXX_HAS_COROUTINES_BY_DEFAULT) set(HAVE_COROUTINES 1) set(CXX_COROUTINES_FLAGS "" CACHE STRING "The flags to enable C++ Coroutines for this compiler" FORCE) set(CXX_HAS_COROUTINES "1" CACHE STRING "Whether this compiler has C++ Coroutines or not" FORCE) endif() if(NOT HAVE_COROUTINES) if(MSVC) CheckCXXHasCoroutines(_MSVC_STD20 "/std:c++latest") if(CXX_HAS_COROUTINES_MSVC_STD20) set(CXX_COROUTINES_FLAGS "/std:c++latest" CACHE STRING "The flags to enable C++ Coroutines for this compiler" FORCE) set(CXX_HAS_COROUTINES "1" CACHE STRING "Whether this compiler has C++ Coroutines or not" FORCE) set(HAVE_COROUTINES 1) endif() if(NOT HAVE_COROUTINES) CheckCXXHasCoroutines(_MSVC_AWAIT "/EHsc /await") if(CXX_HAS_COROUTINES_MSVC_AWAIT) set(CXX_COROUTINES_FLAGS "/await" CACHE STRING "The flags to enable C++ Coroutines for this compiler" FORCE) set(CXX_HAS_COROUTINES "2" CACHE STRING "Whether this compiler has C++ Coroutines or not" FORCE) set(HAVE_COROUTINES 2) endif() endif() endif() if(NOT HAVE_COROUTINES AND (CLANG OR GCC)) CheckCXXHasCoroutines(_WITH_FLAG "-fcoroutines") if(CXX_HAS_COROUTINES_WITH_FLAG) set(CXX_COROUTINES_FLAGS "-fcoroutines" CACHE STRING "The flags to enable C++ Coroutines for this compiler" FORCE) set(CXX_HAS_COROUTINES "2" CACHE STRING "Whether this compiler has C++ Coroutines or not" FORCE) set(HAVE_COROUTINES 2) endif() CheckCXXHasCoroutines(_WITH_FLAG_TS "-fcoroutines-ts") if(CXX_HAS_COROUTINES_WITH_FLAG_TS) set(CXX_COROUTINES_FLAGS "-fcoroutines-ts" CACHE STRING "The flags to enable C++ Coroutines for this compiler" FORCE) set(CXX_HAS_COROUTINES "2" CACHE STRING "Whether this compiler has C++ Coroutines or not" FORCE) set(HAVE_COROUTINES 2) endif() endif() endif() if(NOT HAVE_COROUTINES) set(CXX_COROUTINES_FLAGS "unsupported" CACHE STRING "The flags to enable C++ Coroutines for this compiler" FORCE) set(CXX_HAS_COROUTINES "0" CACHE STRING "Whether this compiler has C++ Coroutines or not" FORCE) endif() endif() if(CXX_COROUTINES_FLAGS AND NOT CXX_COROUTINES_FLAGS STREQUAL "unsupported") foreach(target ${ARGN}) target_compile_options(${target} ${visibility} ${CXX_COROUTINES_FLAGS}) if(CXX_COROUTINES_LINKER_FLAGS) _target_link_options(${target} ${visibility} ${CXX_COROUTINES_LINKER_FLAGS}) endif() endforeach() endif() endfunction() function(ensure_git_subrepo path url) if(NOT EXISTS "${path}") if(NOT GIT_FOUND) message(WARNING "WARNING: git not found, so cannot ensure git subrepo ${path} is up to date.") return() endif() message(STATUS "NOTE: Due to missing ${path}, running ${GIT_EXECUTABLE} submodule update --init --recursive --depth 1 --jobs 8 ...") execute_process(COMMAND "${GIT_EXECUTABLE}" submodule update --init --recursive --depth 1 --jobs 8 WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE retcode ) if(retcode OR NOT EXISTS "${path}") # Try pulling repo directly from github get_filename_component(path "${path}" DIRECTORY) file(REMOVE_RECURSE "${path}") get_filename_component(path "${path}" DIRECTORY) message(WARNING "WARNING: git submodule update failed with code ${retcode}, trying a direct git clone ...") execute_process(COMMAND "${GIT_EXECUTABLE}" clone --recurse-submodules --depth 1 --jobs 8 --shallow-submodules ${url} ${ARGN} WORKING_DIRECTORY "${path}" RESULT_VARIABLE retcode ) if(retcode) message(FATAL_ERROR "FATAL: git clone failed with code ${retcode}") endif() endif() endif() endfunction()