cmake_minimum_required(VERSION 3.10) cmake_policy(SET CMP0043 OLD) # testing the old behavior project(Preprocess) # This test is meant both as a test and as a reference for supported # syntax on native tool command lines. # Determine the build tool being used. Not all characters can be # escaped for all build tools. This test checks all characters known # to work with each tool and documents those known to not work. if("${CMAKE_GENERATOR}" MATCHES "Xcode") set(PP_XCODE 1) endif() if("${CMAKE_GENERATOR}" MATCHES "Unix Makefiles") set(PP_UMAKE 1) endif() if("${CMAKE_GENERATOR}" MATCHES "NMake Makefiles") set(PP_NMAKE 1) endif() if("${CMAKE_GENERATOR}" MATCHES "MinGW Makefiles") set(PP_MINGW 1) endif() if("${CMAKE_GENERATOR}" MATCHES "Borland Makefiles") set(PP_BORLAND 1) endif() if("${CMAKE_GENERATOR}" MATCHES "Watcom WMake") set(PP_WATCOM 1) endif() if("${CMAKE_GENERATOR}" MATCHES "Visual Studio") set(PP_VS 1) endif() if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND "x${CMAKE_C_SIMULATE_ID}" STREQUAL "xMSVC") set(CLANG_MSVC_WINDOWS 1) endif() if(CLANG_MSVC_WINDOWS AND "x${CMAKE_C_COMPILER_FRONTEND_VARIANT}" STREQUAL "xGNU") set(CLANG_GNULIKE_WINDOWS 1) endif() # Some tests below check the PP_* variables set above. They are meant # to test the case that the build tool is at fault. Other tests below # check the compiler that will be used when the compiler is at fault # (does not work even from a command shell). #----------------------------------------------------------------------------- # Construct a C-string literal to test passing through a definition on # the command line. We configure the value into a header so it can be # checked in the executable at runtime. The semicolon is handled # specially because it needs to be escaped in the COMPILE_DEFINITIONS # property value to avoid separating definitions but the string value # must not have it escaped inside the configured header. set(STRING_EXTRA "") if(NOT BORLAND) # Borland: ; # The Borland compiler will simply not accept a non-escaped semicolon # on the command line. If it is escaped \; then the escape character # shows up in the preprocessing output too. set(SEMICOLON "\;") endif() string(APPEND STRING_EXTRA " ") if(NOT PP_BORLAND AND NOT PP_WATCOM AND NOT CLANG_GNULIKE_WINDOWS) # Borland, WMake: multiple spaces # The make tool seems to remove extra whitespace from inside # quoted strings when passing to the compiler. It does not have # trouble passing to other tools, and the compiler may be directly # invoked from the command line. string(APPEND STRING_EXTRA " ") endif() if(NOT PP_VS) # VS: , # Visual Studio will not accept a comma in the value of a definition. # The comma-separated list of PreprocessorDefinitions in the project # file seems to be parsed before the content of entries is examined. string(APPEND STRING_EXTRA ",") endif() if(NOT PP_MINGW AND NOT CLANG_GNULIKE_WINDOWS) # MinGW: & # When inside -D"FOO=\"a & b\"" MinGW make wants -D"FOO=\"a "&" b\"" # but it does not like quoted ampersand elsewhere. string(APPEND STRING_EXTRA "&") endif() if(NOT PP_MINGW AND NOT CLANG_GNULIKE_WINDOWS) # MinGW: | # When inside -D"FOO=\"a | b\"" MinGW make wants -D"FOO=\"a "|" b\"" # but it does not like quoted pipe elsewhere. string(APPEND STRING_EXTRA "|") endif() if(NOT PP_BORLAND AND NOT PP_MINGW AND NOT PP_NMAKE) # Borland, NMake, MinGW: ^ # When inside -D"FOO=\"a ^ b\"" the make tools want -D"FOO=\"a "^" b\"" # but do not like quoted carrot elsewhere. In NMake the non-quoted # syntax works when the flags are not in a make variable. string(APPEND STRING_EXTRA "^") endif() if(NOT PP_BORLAND AND NOT PP_MINGW AND NOT PP_NMAKE) # Borland, MinGW: < > # Angle-brackets have funny behavior that is hard to escape. string(APPEND STRING_EXTRA "<>") endif() set(EXPR_OP1 "/") if((NOT MSVC OR PP_NMAKE) AND NOT CMAKE_C_COMPILER_ID STREQUAL "Intel" AND NOT CLANG_MSVC_WINDOWS) # MSVC cl, Intel icl: % # When the cl compiler is invoked from the command line then % must # be written %% (to distinguish from %ENV% syntax). However cl does # not seem to accept the syntax when it is invoked from inside a # make tool (nmake, mingw32-make, etc.). Instead the argument must # be placed inside a response file. Then cl accepts it because it # parses the response file as it would the normal windows command # line. Currently only NMake supports running cl with a response # file. Supporting other make tools would require CMake to generate # response files explicitly for each object file. # # When the icl compiler is invoked from the command line then % must # be written just '%'. However nmake requires '%%' except when using # response files. Currently we have no way to affect escaping based # on whether flags go in a response file, so we just have to skip it. string(APPEND STRING_EXTRA "%") set(EXPR_OP1 "%") endif() # XL: )( # The XL compiler cannot pass unbalanced parens correctly to a tool # it launches internally. if(CMAKE_C_COMPILER_ID STREQUAL "XL") string(APPEND STRING_EXTRA "()") else() string(APPEND STRING_EXTRA ")(") endif() # General: \" # Make tools do not reliably accept \\\" syntax: # - MinGW and MSYS make tools crash with \\\" # - Borland make actually wants a mis-matched quote \\" # or $(BACKSLASH)\" where BACKSLASH is a variable set to \\ # - VS IDE gets confused about the bounds of the definition value \\\" # - NMake is okay with just \\\" # - The XL compiler does not re-escape \\\" when launching an # internal tool to do preprocessing . # - The IntelLLVM C and C++ compiler drivers do not re-escape the \\\" when # launching the underlying compiler. FIXME: this bug is expected to be fixed # in a future release. if((PP_NMAKE OR PP_UMAKE) AND NOT CMAKE_C_COMPILER_ID STREQUAL "XL" AND NOT CMAKE_C_COMPILER_ID STREQUAL "IntelLLVM" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") string(APPEND STRING_EXTRA "\\\"") endif() # General: # # MSVC will not accept a # in the value of a string definition on the # command line. The character seems to be simply replaced by an # equals =. According to "cl -help" definitions may be specified by # -DMACRO#VALUE as well as -DMACRO=VALUE. It must be implemented by a # simple search-and-replace. # # The Borland compiler will parse both # and \# as just # but the make # tool seems to want \# sometimes and not others. # # Unix make does not like # in variable settings without extra # escaping. This could probably be fixed but since MSVC does not # support it and it is not an operator it is not worthwhile. # Compose the final test string. set(STRING_VALUE "hello`~!@$*_+-=}{][:'.?/${STRING_EXTRA}world") #----------------------------------------------------------------------------- # Function-style macro command-line support: # - Borland does not support # - MSVC does not support # - Watcom does not support # - GCC supports # Too few platforms support this to bother implementing. # People can just configure headers with the macros. #----------------------------------------------------------------------------- # Construct a sample expression to pass as a macro definition. set(EXPR "x*y+!(x==(y+1*2))*f(x${EXPR_OP1}2)") if(NOT WATCOM) # Watcom does not support - or / because it parses them as options. string(APPEND EXPR " + y/x-x") endif() #----------------------------------------------------------------------------- # Inform the test if the debug configuration is getting built. string(APPEND CMAKE_C_FLAGS_DEBUG " -DPREPROCESS_DEBUG") string(APPEND CMAKE_CXX_FLAGS_DEBUG " -DPREPROCESS_DEBUG") string(APPEND CMAKE_C_FLAGS_RELEASE " -DPREPROCESS_NDEBUG") string(APPEND CMAKE_CXX_FLAGS_RELEASE " -DPREPROCESS_NDEBUG") string(APPEND CMAKE_C_FLAGS_RELWITHDEBINFO " -DPREPROCESS_NDEBUG") string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -DPREPROCESS_NDEBUG") string(APPEND CMAKE_C_FLAGS_MINSIZEREL " -DPREPROCESS_NDEBUG") string(APPEND CMAKE_CXX_FLAGS_MINSIZEREL " -DPREPROCESS_NDEBUG") # Inform the test if it built from Xcode. if(PP_XCODE) set(PREPROCESS_XCODE 1) endif() # Test old-style definitions. add_definitions(-DOLD_DEF -DOLD_EXPR=2) # Make sure old-style definitions are converted to directory property. set(OLD_DEFS_EXPECTED "OLD_DEF;OLD_EXPR=2") get_property(OLD_DEFS DIRECTORY PROPERTY COMPILE_DEFINITIONS) if(NOT "${OLD_DEFS}" STREQUAL "${OLD_DEFS_EXPECTED}") message(SEND_ERROR "add_definitions not converted to directory property!") endif() add_executable(Preprocess preprocess.c preprocess.cxx) set(FILE_PATH "${Preprocess_SOURCE_DIR}/file_def.h") set(TARGET_PATH "${Preprocess_SOURCE_DIR}/target_def.h") # Set some definition properties. foreach(c "" "_DEBUG" "_RELEASE" "_RELWITHDEBINFO" "_MINSIZEREL") set(FLAVOR "${c}") # Treat RelWithDebInfo and MinSizeRel as Release to avoid having # an exponentional matrix of inclusions and exclusions of defines if("${c}" STREQUAL "_RELWITHDEBINFO" OR "${c}" STREQUAL "_MINSIZEREL") set(FLAVOR "_RELEASE") endif() set_property( DIRECTORY . APPEND PROPERTY COMPILE_DEFINITIONS${c} "DIRECTORY_DEF${FLAVOR}" ) set_property( TARGET Preprocess PROPERTY COMPILE_DEFINITIONS${c} "TARGET_DEF${FLAVOR}" ) set_property( SOURCE preprocess.c preprocess.cxx PROPERTY COMPILE_DEFINITIONS${c} "FILE_DEF${FLAVOR}" ) endforeach() # Add definitions with values. set(DEF_TARGET_PATH "TARGET_PATH=\"${TARGET_PATH}\"") set(DEF_FILE_PATH "FILE_PATH=\"${FILE_PATH}\"") set_property( TARGET Preprocess APPEND PROPERTY COMPILE_DEFINITIONS "TARGET_STRING=\"${STRING_VALUE}${SEMICOLON}\"" "TARGET_EXPR=${EXPR}" ${DEF_TARGET_PATH} ) set_property( SOURCE preprocess.c preprocess.cxx APPEND PROPERTY COMPILE_DEFINITIONS "FILE_STRING=\"${STRING_VALUE}${SEMICOLON}\"" "FILE_EXPR=${EXPR}" ${DEF_FILE_PATH} ) # Try reading and writing the property value to ensure the string is # preserved. get_property(defs1 TARGET Preprocess PROPERTY COMPILE_DEFINITIONS) set_property(TARGET Preprocess PROPERTY COMPILE_DEFINITIONS "${defs1}") get_property(defs2 TARGET Preprocess PROPERTY COMPILE_DEFINITIONS) if(NOT "x${defs1}" STREQUAL "x${defs2}") message(FATAL_ERROR "get/set/get COMPILE_DEFINITIONS round trip failed. " "First get:\n" " ${defs1}\n" "Second get:\n" " ${defs2}") endif() # Helper target for running test manually in build tree. add_custom_target(drive COMMAND Preprocess) # Configure the header file with the desired string value. if(SEMICOLON) string(APPEND STRING_VALUE ";") endif() configure_file(${Preprocess_SOURCE_DIR}/preprocess.h.in ${Preprocess_BINARY_DIR}/preprocess.h) include_directories(${Preprocess_BINARY_DIR})