#.rst: # WriteCompilerDetectionHeader # ---------------------------- # # This module provides the function write_compiler_detection_header(). # # The ``WRITE_COMPILER_DETECTION_HEADER`` function can be used to generate # a file suitable for preprocessor inclusion which contains macros to be # used in source code:: # # write_compiler_detection_header( # FILE # PREFIX # [OUTPUT_FILES_VAR OUTPUT_DIR ] # COMPILERS [...] # FEATURES [...] # [VERSION ] # [PROLOG ] # [EPILOG ] # ) # # The ``write_compiler_detection_header`` function generates the # file ```` with macros which all have the prefix ````. # # By default, all content is written directly to the ````. The # ``OUTPUT_FILES_VAR`` may be specified to cause the compiler-specific # content to be written to separate files. The separate files are then # available in the ```` and may be consumed by the caller # for installation for example. The ``OUTPUT_DIR`` specifies a relative # path from the main ```` to the compiler-specific files. For example: # # .. code-block:: cmake # # write_compiler_detection_header( # FILE climbingstats_compiler_detection.h # PREFIX ClimbingStats # OUTPUT_FILES_VAR support_files # OUTPUT_DIR compilers # COMPILERS GNU Clang MSVC # FEATURES cxx_variadic_templates # ) # install(FILES # ${CMAKE_CURRENT_BINARY_DIR}/climbingstats_compiler_detection.h # DESTINATION include # ) # install(FILES # ${support_files} # DESTINATION include/compilers # ) # # # ``VERSION`` may be used to specify the API version to be generated. # Future versions of CMake may introduce alternative APIs. A given # API is selected by any ```` value greater than or equal # to the version of CMake that introduced the given API and less # than the version of CMake that introduced its succeeding API. # The value of the :variable:`CMAKE_MINIMUM_REQUIRED_VERSION` # variable is used if no explicit version is specified. # (As of CMake version |release| there is only one API version.) # # ``PROLOG`` may be specified as text content to write at the start of the # header. ``EPILOG`` may be specified as text content to write at the end # of the header # # At least one ```` and one ```` must be listed. Compilers # which are known to CMake, but not specified are detected and a preprocessor # ``#error`` is generated for them. A preprocessor macro matching # ``_COMPILER_IS_`` is generated for each compiler # known to CMake to contain the value ``0`` or ``1``. # # Possible compiler identifiers are documented with the # :variable:`CMAKE__COMPILER_ID` variable. # Available features in this version of CMake are listed in the # :prop_gbl:`CMAKE_C_KNOWN_FEATURES` and # :prop_gbl:`CMAKE_CXX_KNOWN_FEATURES` global properties. # # See the :manual:`cmake-compile-features(7)` manual for information on # compile features. # # Feature Test Macros # =================== # # For each compiler, a preprocessor macro is generated matching # ``_COMPILER_IS_`` which has the content either ``0`` # or ``1``, depending on the compiler in use. Preprocessor macros for # compiler version components are generated matching # ``_COMPILER_VERSION_MAJOR`` ``_COMPILER_VERSION_MINOR`` # and ``_COMPILER_VERSION_PATCH`` containing decimal values # for the corresponding compiler version components, if defined. # # A preprocessor test is generated based on the compiler version # denoting whether each feature is enabled. A preprocessor macro # matching ``_COMPILER_``, where ```` is the # upper-case ```` name, is generated to contain the value # ``0`` or ``1`` depending on whether the compiler in use supports the # feature: # # .. code-block:: cmake # # write_compiler_detection_header( # FILE climbingstats_compiler_detection.h # PREFIX ClimbingStats # COMPILERS GNU Clang AppleClang MSVC # FEATURES cxx_variadic_templates # ) # # .. code-block:: c++ # # #if ClimbingStats_COMPILER_CXX_VARIADIC_TEMPLATES # template # void someInterface(T t...) { /* ... */ } # #else # // Compatibility versions # template # void someInterface(T1 t1) { /* ... */ } # template # void someInterface(T1 t1, T2 t2) { /* ... */ } # template # void someInterface(T1 t1, T2 t2, T3 t3) { /* ... */ } # #endif # # Symbol Macros # ============= # # Some additional symbol-defines are created for particular features for # use as symbols which may be conditionally defined empty: # # .. code-block:: c++ # # class MyClass ClimbingStats_FINAL # { # ClimbingStats_CONSTEXPR int someInterface() { return 42; } # }; # # The ``ClimbingStats_FINAL`` macro will expand to ``final`` if the # compiler (and its flags) support the ``cxx_final`` feature, and the # ``ClimbingStats_CONSTEXPR`` macro will expand to ``constexpr`` # if ``cxx_constexpr`` is supported. # # The following features generate corresponding symbol defines: # # ========================== =================================== ================= # Feature Define Symbol # ========================== =================================== ================= # ``c_restrict`` ``_RESTRICT`` ``restrict`` # ``cxx_constexpr`` ``_CONSTEXPR`` ``constexpr`` # ``cxx_deleted_functions`` ``_DELETED_FUNCTION`` ``= delete`` # ``cxx_extern_templates`` ``_EXTERN_TEMPLATE`` ``extern`` # ``cxx_final`` ``_FINAL`` ``final`` # ``cxx_noexcept`` ``_NOEXCEPT`` ``noexcept`` # ``cxx_noexcept`` ``_NOEXCEPT_EXPR(X)`` ``noexcept(X)`` # ``cxx_override`` ``_OVERRIDE`` ``override`` # ========================== =================================== ================= # # Compatibility Implementation Macros # =================================== # # Some features are suitable for wrapping in a macro with a backward # compatibility implementation if the compiler does not support the feature. # # When the ``cxx_static_assert`` feature is not provided by the compiler, # a compatibility implementation is available via the # ``_STATIC_ASSERT(COND)`` and # ``_STATIC_ASSERT_MSG(COND, MSG)`` function-like macros. The macros # expand to ``static_assert`` where that compiler feature is available, and # to a compatibility implementation otherwise. In the first form, the # condition is stringified in the message field of ``static_assert``. In # the second form, the message ``MSG`` is passed to the message field of # ``static_assert``, or ignored if using the backward compatibility # implementation. # # The ``cxx_attribute_deprecated`` feature provides a macro definition # ``_DEPRECATED``, which expands to either the standard # ``[[deprecated]]`` attribute or a compiler-specific decorator such # as ``__attribute__((__deprecated__))`` used by GNU compilers. # # The ``cxx_alignas`` feature provides a macro definition # ``_ALIGNAS`` which expands to either the standard ``alignas`` # decorator or a compiler-specific decorator such as # ``__attribute__ ((__aligned__))`` used by GNU compilers. # # The ``cxx_alignof`` feature provides a macro definition # ``_ALIGNOF`` which expands to either the standard ``alignof`` # decorator or a compiler-specific decorator such as ``__alignof__`` # used by GNU compilers. # # ============================= ================================ ===================== # Feature Define Symbol # ============================= ================================ ===================== # ``cxx_alignas`` ``_ALIGNAS`` ``alignas`` # ``cxx_alignof`` ``_ALIGNOF`` ``alignof`` # ``cxx_nullptr`` ``_NULLPTR`` ``nullptr`` # ``cxx_static_assert`` ``_STATIC_ASSERT`` ``static_assert`` # ``cxx_static_assert`` ``_STATIC_ASSERT_MSG`` ``static_assert`` # ``cxx_attribute_deprecated`` ``_DEPRECATED`` ``[[deprecated]]`` # ``cxx_attribute_deprecated`` ``_DEPRECATED_MSG`` ``[[deprecated]]`` # ``cxx_thread_local`` ``_THREAD_LOCAL`` ``thread_local`` # ============================= ================================ ===================== # # A use-case which arises with such deprecation macros is the deprecation # of an entire library. In that case, all public API in the library may # be decorated with the ``_DEPRECATED`` macro. This results in # very noisy build output when building the library itself, so the macro # may be may be defined to empty in that case when building the deprecated # library: # # .. code-block:: cmake # # add_library(compat_support ${srcs}) # target_compile_definitions(compat_support # PRIVATE # CompatSupport_DEPRECATED= # ) #============================================================================= # Copyright 2014 Stephen Kelly # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) include(${CMAKE_CURRENT_LIST_DIR}/CMakeParseArguments.cmake) include(${CMAKE_CURRENT_LIST_DIR}/CMakeCompilerIdDetection.cmake) function(_load_compiler_variables CompilerId lang) include("${CMAKE_ROOT}/Modules/Compiler/${CompilerId}-${lang}-FeatureTests.cmake" OPTIONAL) set(_cmake_oldestSupported_${CompilerId} ${_cmake_oldestSupported} PARENT_SCOPE) foreach(feature ${ARGN}) set(_cmake_feature_test_${CompilerId}_${feature} ${_cmake_feature_test_${feature}} PARENT_SCOPE) endforeach() include("${CMAKE_ROOT}/Modules/Compiler/${CompilerId}-DetermineCompiler.cmake" OPTIONAL) set(_compiler_id_version_compute_${CompilerId} ${_compiler_id_version_compute} PARENT_SCOPE) endfunction() function(write_compiler_detection_header file_keyword file_arg prefix_keyword prefix_arg ) if (NOT file_keyword STREQUAL FILE) message(FATAL_ERROR "write_compiler_detection_header: FILE parameter missing.") endif() if (NOT prefix_keyword STREQUAL PREFIX) message(FATAL_ERROR "write_compiler_detection_header: PREFIX parameter missing.") endif() set(options) set(oneValueArgs VERSION EPILOG PROLOG OUTPUT_FILES_VAR OUTPUT_DIR) set(multiValueArgs COMPILERS FEATURES) cmake_parse_arguments(_WCD "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if (NOT _WCD_COMPILERS) message(FATAL_ERROR "Invalid arguments. write_compiler_detection_header requires at least one compiler.") endif() if (NOT _WCD_FEATURES) message(FATAL_ERROR "Invalid arguments. write_compiler_detection_header requires at least one feature.") endif() if(_WCD_UNPARSED_ARGUMENTS) message(FATAL_ERROR "Unparsed arguments: ${_WCD_UNPARSED_ARGUMENTS}") endif() if (prefix_arg STREQUAL "") message(FATAL_ERROR "A prefix must be specified") endif() string(MAKE_C_IDENTIFIER ${prefix_arg} cleaned_prefix) if (NOT prefix_arg STREQUAL cleaned_prefix) message(FATAL_ERROR "The prefix must be a valid C identifier.") endif() if(NOT _WCD_VERSION) set(_WCD_VERSION ${CMAKE_MINIMUM_REQUIRED_VERSION}) endif() set(_min_version 3.1.0) # Version which introduced this function if (_WCD_VERSION VERSION_LESS _min_version) set(err "VERSION compatibility for write_compiler_detection_header is set to ${_WCD_VERSION}, which is too low.") set(err "${err} It must be set to at least ${_min_version}. ") set(err "${err} Either set the VERSION parameter to the write_compiler_detection_header function, or update") set(err "${err} your minimum required CMake version with the cmake_minimum_required command.") message(FATAL_ERROR "${err}") endif() if(_WCD_OUTPUT_FILES_VAR) if(NOT _WCD_OUTPUT_DIR) message(FATAL_ERROR "If OUTPUT_FILES_VAR is specified, then OUTPUT_DIR must also be specified.") endif() endif() if(_WCD_OUTPUT_DIR) if(NOT _WCD_OUTPUT_FILES_VAR) message(FATAL_ERROR "If OUTPUT_DIR is specified, then OUTPUT_FILES_VAR must also be specified.") endif() get_filename_component(main_file_dir ${file_arg} DIRECTORY) if (NOT IS_ABSOLUTE ${main_file_dir}) set(main_file_dir "${CMAKE_CURRENT_BINARY_DIR}/${main_file_dir}") endif() if (NOT IS_ABSOLUTE ${_WCD_OUTPUT_DIR}) set(_WCD_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/${_WCD_OUTPUT_DIR}") endif() get_filename_component(out_file_dir ${_WCD_OUTPUT_DIR} ABSOLUTE) string(FIND ${out_file_dir} ${main_file_dir} idx) if (NOT idx EQUAL 0) message(FATAL_ERROR "The compiler-specific output directory must be within the same directory as the main file.") endif() if (main_file_dir STREQUAL out_file_dir) unset(_WCD_OUTPUT_DIR) else() string(REPLACE "${main_file_dir}/" "" _WCD_OUTPUT_DIR "${out_file_dir}/") endif() endif() set(compilers GNU Clang AppleClang MSVC ) set(_hex_compilers ADSP Borland Embarcadero SunPro) foreach(_comp ${_WCD_COMPILERS}) list(FIND compilers ${_comp} idx) if (idx EQUAL -1) message(FATAL_ERROR "Unsupported compiler ${_comp}.") endif() if (NOT _need_hex_conversion) list(FIND _hex_compilers ${_comp} idx) if (NOT idx EQUAL -1) set(_need_hex_conversion TRUE) endif() endif() endforeach() set(file_content " // This is a generated file. Do not edit! #ifndef ${prefix_arg}_COMPILER_DETECTION_H #define ${prefix_arg}_COMPILER_DETECTION_H ") if (_WCD_PROLOG) set(file_content "${file_content}\n${_WCD_PROLOG}\n") endif() if (_need_hex_conversion) set(file_content "${file_content} #define ${prefix_arg}_DEC(X) (X) #define ${prefix_arg}_HEX(X) ( \\ ((X)>>28 & 0xF) * 10000000 + \\ ((X)>>24 & 0xF) * 1000000 + \\ ((X)>>20 & 0xF) * 100000 + \\ ((X)>>16 & 0xF) * 10000 + \\ ((X)>>12 & 0xF) * 1000 + \\ ((X)>>8 & 0xF) * 100 + \\ ((X)>>4 & 0xF) * 10 + \\ ((X) & 0xF) \\ )\n") endif() foreach(feature ${_WCD_FEATURES}) if (feature MATCHES "^cxx_") list(APPEND _langs CXX) list(APPEND CXX_features ${feature}) elseif (feature MATCHES "^c_") list(APPEND _langs C) list(APPEND C_features ${feature}) else() message(FATAL_ERROR "Unsupported feature ${feature}.") endif() endforeach() list(REMOVE_DUPLICATES _langs) if(_WCD_OUTPUT_FILES_VAR) get_filename_component(main_file_name ${file_arg} NAME) set(compiler_file_content_ "#ifndef ${prefix_arg}_COMPILER_DETECTION_H # error This file may only be included from ${main_file_name} #endif\n") endif() foreach(_lang ${_langs}) get_property(known_features GLOBAL PROPERTY CMAKE_${_lang}_KNOWN_FEATURES) foreach(feature ${${_lang}_features}) list(FIND known_features ${feature} idx) if (idx EQUAL -1) message(FATAL_ERROR "Unsupported feature ${feature}.") endif() endforeach() if(_lang STREQUAL CXX) set(file_content "${file_content}\n#ifdef __cplusplus\n") else() set(file_content "${file_content}\n#ifndef __cplusplus\n") endif() compiler_id_detection(ID_CONTENT ${_lang} PREFIX ${prefix_arg}_ ID_DEFINE ) set(file_content "${file_content}${ID_CONTENT}\n") set(pp_if "if") foreach(compiler ${_WCD_COMPILERS}) _load_compiler_variables(${compiler} ${_lang} ${${_lang}_features}) set(file_content "${file_content}\n# ${pp_if} ${prefix_arg}_COMPILER_IS_${compiler}\n") if(_WCD_OUTPUT_FILES_VAR) set(compile_file_name "${_WCD_OUTPUT_DIR}${prefix_arg}_COMPILER_INFO_${compiler}.h") set(file_content "${file_content}\n# include \"${compile_file_name}\"\n") endif() if(_WCD_OUTPUT_FILES_VAR) set(compiler_file_content compiler_file_content_${compiler}) else() set(compiler_file_content file_content) endif() set(${compiler_file_content} "${${compiler_file_content}} # if !(${_cmake_oldestSupported_${compiler}}) # error Unsupported compiler version # endif\n") set(PREFIX ${prefix_arg}_) if (_need_hex_conversion) set(MACRO_DEC ${prefix_arg}_DEC) set(MACRO_HEX ${prefix_arg}_HEX) else() set(MACRO_DEC) set(MACRO_HEX) endif() string(CONFIGURE "${_compiler_id_version_compute_${compiler}}" VERSION_BLOCK @ONLY) set(${compiler_file_content} "${${compiler_file_content}}${VERSION_BLOCK}\n") set(PREFIX) set(MACRO_DEC) set(MACRO_HEX) set(pp_if "elif") foreach(feature ${${_lang}_features}) string(TOUPPER ${feature} feature_upper) set(feature_PP "COMPILER_${feature_upper}") set(_define_item "\n# define ${prefix_arg}_${feature_PP} 0\n") if (_cmake_feature_test_${compiler}_${feature} STREQUAL "1") set(_define_item "\n# define ${prefix_arg}_${feature_PP} 1\n") elseif (_cmake_feature_test_${compiler}_${feature}) set(_define_item "\n# define ${prefix_arg}_${feature_PP} 0\n") set(_define_item "\n# if ${_cmake_feature_test_${compiler}_${feature}}\n# define ${prefix_arg}_${feature_PP} 1\n# else${_define_item}# endif\n") endif() set(${compiler_file_content} "${${compiler_file_content}}${_define_item}") endforeach() endforeach() if(pp_if STREQUAL "elif") set(file_content "${file_content} # else # error Unsupported compiler # endif\n") endif() foreach(feature ${${_lang}_features}) string(TOUPPER ${feature} feature_upper) set(feature_PP "COMPILER_${feature_upper}") set(def_name ${prefix_arg}_${feature_PP}) if (feature STREQUAL c_restrict) set(def_value "${prefix_arg}_RESTRICT") set(file_content "${file_content} # if ${def_name} # define ${def_value} restrict # else # define ${def_value} # endif \n") endif() if (feature STREQUAL cxx_constexpr) set(def_value "${prefix_arg}_CONSTEXPR") set(file_content "${file_content} # if ${def_name} # define ${def_value} constexpr # else # define ${def_value} # endif \n") endif() if (feature STREQUAL cxx_final) set(def_value "${prefix_arg}_FINAL") set(file_content "${file_content} # if ${def_name} # define ${def_value} final # else # define ${def_value} # endif \n") endif() if (feature STREQUAL cxx_override) set(def_value "${prefix_arg}_OVERRIDE") set(file_content "${file_content} # if ${def_name} # define ${def_value} override # else # define ${def_value} # endif \n") endif() if (feature STREQUAL cxx_static_assert) set(def_value "${prefix_arg}_STATIC_ASSERT(X)") set(def_value_msg "${prefix_arg}_STATIC_ASSERT_MSG(X, MSG)") set(static_assert_struct "template struct ${prefix_arg}StaticAssert;\ntemplate<> struct ${prefix_arg}StaticAssert{};\n") set(def_standard "# define ${def_value} static_assert(X, #X)\n# define ${def_value_msg} static_assert(X, MSG)") set(def_alternative "${static_assert_struct}# define ${def_value} sizeof(${prefix_arg}StaticAssert)\n# define ${def_value_msg} sizeof(${prefix_arg}StaticAssert)") set(file_content "${file_content}# if ${def_name}\n${def_standard}\n# else\n${def_alternative}\n# endif\n\n") endif() if (feature STREQUAL cxx_alignas) set(def_value "${prefix_arg}_ALIGNAS(X)") set(file_content "${file_content} # if ${def_name} # define ${def_value} alignas(X) # elif ${prefix_arg}_COMPILER_IS_GNU || ${prefix_arg}_COMPILER_IS_Clang || ${prefix_arg}_COMPILER_IS_AppleClang # define ${def_value} __attribute__ ((__aligned__(X))) # else # define ${def_value} # endif \n") endif() if (feature STREQUAL cxx_alignof) set(def_value "${prefix_arg}_ALIGNOF(X)") set(file_content "${file_content} # if ${def_name} # define ${def_value} alignof(X) # elif ${prefix_arg}_COMPILER_IS_GNU || ${prefix_arg}_COMPILER_IS_Clang || ${prefix_arg}_COMPILER_IS_AppleClang # define ${def_value} __alignof__(X) # endif \n") endif() if (feature STREQUAL cxx_deleted_functions) set(def_value "${prefix_arg}_DELETED_FUNCTION") set(file_content "${file_content} # if ${def_name} # define ${def_value} = delete # else # define ${def_value} # endif \n") endif() if (feature STREQUAL cxx_extern_templates) set(def_value "${prefix_arg}_EXTERN_TEMPLATE") set(file_content "${file_content} # if ${def_name} # define ${def_value} extern # else # define ${def_value} # endif \n") endif() if (feature STREQUAL cxx_noexcept) set(def_value "${prefix_arg}_NOEXCEPT") set(file_content "${file_content} # if ${def_name} # define ${def_value} noexcept # define ${def_value}_EXPR(X) noexcept(X) # else # define ${def_value} # define ${def_value}_EXPR(X) # endif \n") endif() if (feature STREQUAL cxx_nullptr) set(def_value "${prefix_arg}_NULLPTR") set(file_content "${file_content} # if ${def_name} # define ${def_value} nullptr # else # define ${def_value} static_cast(0) # endif \n") endif() if (feature STREQUAL cxx_thread_local) set(def_value "${prefix_arg}_THREAD_LOCAL") set(file_content "${file_content} # if ${def_name} # define ${def_value} thread_local # elif ${prefix_arg}_COMPILER_IS_GNU || ${prefix_arg}_COMPILER_IS_Clang || ${prefix_arg}_COMPILER_IS_AppleClang # define ${def_value} __thread # elif ${prefix_arg}_COMPILER_IS_MSVC # define ${def_value} __declspec(thread) # else // ${def_value} not defined for this configuration. # endif \n") endif() if (feature STREQUAL cxx_attribute_deprecated) set(def_name ${prefix_arg}_${feature_PP}) set(def_value "${prefix_arg}_DEPRECATED") set(file_content "${file_content} # ifndef ${def_value} # if ${def_name} # define ${def_value} [[deprecated]] # define ${def_value}_MSG(MSG) [[deprecated(MSG)]] # elif ${prefix_arg}_COMPILER_IS_GNU || ${prefix_arg}_COMPILER_IS_Clang # define ${def_value} __attribute__((__deprecated__)) # define ${def_value}_MSG(MSG) __attribute__((__deprecated__(MSG))) # elif ${prefix_arg}_COMPILER_IS_MSVC # define ${def_value} __declspec(deprecated) # define ${def_value}_MSG(MSG) __declspec(deprecated(MSG)) # else # define ${def_value} # define ${def_value}_MSG(MSG) # endif # endif \n") endif() endforeach() set(file_content "${file_content}#endif\n") endforeach() if(_WCD_OUTPUT_FILES_VAR) foreach(compiler ${_WCD_COMPILERS}) set(CMAKE_CONFIGURABLE_FILE_CONTENT "${compiler_file_content_}") set(CMAKE_CONFIGURABLE_FILE_CONTENT "${CMAKE_CONFIGURABLE_FILE_CONTENT}${compiler_file_content_${compiler}}") set(compile_file_name "${_WCD_OUTPUT_DIR}${prefix_arg}_COMPILER_INFO_${compiler}.h") set(full_path "${main_file_dir}/${compile_file_name}") list(APPEND ${_WCD_OUTPUT_FILES_VAR} ${full_path}) configure_file("${CMAKE_ROOT}/Modules/CMakeConfigurableFile.in" "${full_path}" @ONLY ) endforeach() set(${_WCD_OUTPUT_FILES_VAR} ${${_WCD_OUTPUT_FILES_VAR}} PARENT_SCOPE) endif() if (_WCD_EPILOG) set(file_content "${file_content}\n${_WCD_EPILOG}\n") endif() set(file_content "${file_content}\n#endif") set(CMAKE_CONFIGURABLE_FILE_CONTENT ${file_content}) configure_file("${CMAKE_ROOT}/Modules/CMakeConfigurableFile.in" "${file_arg}" @ONLY ) endfunction()