CMake: add conditional compiler flags into Visual Studio project - c++

Visual Studio allows to select either the cl compiler or the clang-cl compiler to build projects -- these are called toolsets. These two compilers have different sets of flags, and in particular different flags for disabling warnings. Flags for one compiler produces errors on the other.
This problem can be solved in Visual Studio for both compilers at the same time by defining compiler flags conditionally based on the used toolset. Official documentation for that here.
I use CMake to generate the Visual Studio projects. How can I make CMake add such conditional flags for the generated Visual Studio projects?

You can use CMAKE_CXX_COMPILER_ID and CMAKE_CXX_SIMULATE_ID with your favourite way of handling compilers (if-else or generator expressions)
Output for -T ClangCL (Visual Studio 2019):
message(STATUS ${CMAKE_CXX_COMPILER_ID}) // Clang
message(STATUS ${CMAKE_CXX_SIMULATE_ID}) // MSVC
Output with no toolkit (Visual Studio 2019):
message(STATUS ${CMAKE_CXX_COMPILER_ID}) // MSVC
message(STATUS ${CMAKE_CXX_SIMULATE_ID}) // <empty>

Essentially a more modern approach to the earlier question here, you can use an if-statement to check the compiler type, and set compile flags based on that:
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# Disable a Clang warning type.
target_compile_options(MyLib PRIVATE -Wno-unused-variable)
elseif(MSVC)
# Disable a MSVC warning type.
target_compile_options(MyLib PRIVATE /wd4101)
endif()
For putting this into a single expression, you can use CMake generator expressions (which are evaluated at the CMake buildsystem generation stage):
target_compile_options(MyLib PRIVATE
"$<IF:$<STREQUAL:${CMAKE_CXX_COMPILER_ID},Clang>,-Wno-unused-variable,/wd4101>"
)
For reference, here is a list of all of the clang warnings types.

Related

Generate preprocessor definitions based on CMAKE_CONFIGURATION_TYPES

I want to generate a Visual Studio project with two configurations using cmake. I want cmake to define different preprocessor symbols for these configurations.
I generate the project with the following command
cmake -B intermediate/cmake -G "Visual Studio 16 2019" -T v142 -DCMAKE_GENERATOR_PLATFORM=x64
In my CMakeLists.txt I define configurations:
if(CMAKE_CONFIGURATION_TYPES)
set(CMAKE_CONFIGURATION_TYPES Debug Release)
endif()
Now, how do I define the preprocessor definitions per configuration? The quick search advises against using if(CMAKE_BUILD_TYPE STREQUAL "Release") because it doesn't work for multiconfiguration generators.
The conventional way to handle configuration-specific details in a multi-configuration generator is through the use of generator expressions.
Generator expressions allow you to specify symbols, values, flags, etc that will only be expanded at generation time based on the current state of the generator (such as the current build configuration).
For example, you can define custom pre-processor definitions with the -D flag with target_compile_definitions:
target_compile_definitions(MyTarget PRIVATE
$<$<CONFIG:Debug>:DEBUG_ONLY=1>
$<$<CONFIG:Release>:RELEASE_ONLY=2>
FOO=3
)
(This example is for PRIVATE definitions. Replace this with PUBLIC or INTERFACE as needed.)
This adds -DDEBUG_ONLY=1 to MyTarget for Debug builds, -DRELEASE_ONLY=2 for Release builds, and -DFOO=3 for all builds.
Also see this relevant/similar question: Cmake: Specifiy config specific settings for multi-config cmake project

CMake won't select the correct C++ compiler [duplicate]

I would like to use the IAR compiler. I noticed CMake has already have a bunch of files about this compiler:
https://github.com/jevinskie/cmake/blob/master/Modules/Compiler/IAR.cmake
From what I read the common solution is to specify manually ALL the toolchain in my CMakeLists.txt:
set(CMAKE_C_COMPILER iccarm)
set(CMAKE_CPP_COMPILER iccarm)
How CMake can link these definitions with `Modules/Compiler/IAR.cmake"?
I thought I would just have to do
include("Modules/Compiler/IAR.cmake")
What is the correct way to specify my IAR compiler?
When I do
cmake .
It still tries to use gcc instead of my IAR compiler. Why?
To select a specific compiler, you have several solutions, as exaplained in CMake wiki:
Method 1: use environment variables
For C and C++, set the CC and CXX environment variables. This method is not guaranteed to work for all generators. (Specifically, if you are trying to set Xcode's GCC_VERSION, this method confuses Xcode.)
For example:
CC=gcc-4.2 CXX=/usr/bin/g++-4.2 cmake -G "Your Generator" path/to/your/source
Method 2: use cmake -D
Set the appropriate CMAKE_FOO_COMPILER variable(s) to a valid compiler name or full path on the command-line using cmake -D.
For example:
cmake -G "Your Generator" -D CMAKE_C_COMPILER=gcc-4.2 -D CMAKE_CXX_COMPILER=g++-4.2 path/to/your/source
Method 3 (avoid): use set()
Set the appropriate CMAKE_FOO_COMPILER variable(s) to a valid compiler name or full path in a list file using set(). This must be done before any language is set (ie: before any project() or enable_language() command).
For example:
set(CMAKE_C_COMPILER "gcc-4.2")
set(CMAKE_CXX_COMPILER "/usr/bin/g++-4.2")
project("YourProjectName")
The wiki doesn't provide reason why 3rd method should be avoided...
I see more and more people who set CMAKE_C_COMPILER and other compiler-related variables in the CMakeLists.txt after the project call and wonder why this approach breaks sometimes.
What happens actually
When CMake executes the project() call, it looks for a default compiler executable and determines the way for use it: default compiler flags, default linker flags, compile features, etc.
And CMake stores path to that default compiler executable in the CMAKE_C_COMPILER variable.
When one sets CMAKE_C_COMPILER variable after the project() call, this only changes the compiler executable: default flags, features all remains set for the default compiler.
AS RESULT: When the project is built, a build system calls the project-specified compiler executable but with parameters suitable for the default compiler.
As one could guess, this approach would work only when one replaces a default compiler with a highly compatible one. E.g. replacement of gcc with clang could work sometimes.
This approach will never work for replacement of cl compiler (used in Visual Studio) with gcc one. Nor this will work when replacing a native compiler with a cross-compiler.
What to do
Never set a compiler in CMakeLists.txt.
If you want, e.g., to use clang instead of defaulted gcc, then either:
Pass -DCMAKE_C_COMPILER=<compiler> to cmake when configure the project. That way CMake will use this compiler instead of default one and on the project() call it will adjust all flags for the specified compiler.
Set CC environment variable (CXX for C++ compiler). CMake checks this variable when selects a default compiler.
(Only in rare cases) Set CMAKE_C_COMPILER variable before the project() call. This approach is similar to the first one, but makes the project less flexible.
If the ways above do not work
If on setting CMAKE_C_COMPILER in the command line CMake errors that a compiler cannot "compile a simple project", then something wrong in your environment.. or you specify a compiler incompatible for chosen generator or platform.
Examples:
Visual Studio generators work with cl compiler but cannot work with gcc.
A MinGW compiler usually requires MinGW Makefiles generator.
Incompatible generator cannot be fixed in CMakeLists.txt. One need to pass the proper -G option to the cmake executable (or select the proper generator in CMake GUI).
Cross-compiling
Cross-compiling usually requires setting CMAKE_SYSTEM_NAME variable, and this setting should normally be done in the toolchain file. That toolchain file is also responsible for set a compiler.
Setting CMAKE_SYSTEM_NAME in the CMakeLists.txt is almost always an error.
You need to create a toolchain file, and use the CmakeForceCompiler module.
Here is an example toolchain file for bare-metal ARM development with IAR:
include(CMakeForceCompiler)
set(CMAKE_SYSTEM_NAME Generic) # Or name of your OS if you have one
set(CMAKE_SYSTEM_PROCESSOR arm) # Or whatever
set(CMAKE_CROSSCOMPILING 1)
set(CMAKE_C_COMPILER iccarm) # Change the arm suffix if appropriate
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) # Required to make the previous line work for a target that requires a custom linker file
The last line is necessary because CMake will try to compile a test program with the compiler to make sure it works and to get some version information from preprocessor defines. Without this line, CMake will use add_executable() for the test program, and you will get the error "The C compiler "XXX" is not able to compile a simple test program." This is because the test program fails to link, as it doesn't have your custom linker file (I'm assuming bare-metal development since this is what IAR is usually used for). This line tells CMake to use add_library() instead, which makes the test succeed without the linker file. Source of this workaround: this CMake mailing list post.
Then, assuming that your toolchain file is named iar-toolchain.cmake, invoke CMake like this:
cmake -DCMAKE_TOOLCHAIN_FILE=iar-toolchain.cmake .
You can call cmake like this:
cmake -DCMAKE_C_COMPILER=iccarm ...
or
cmake -DCMAKE_CXX_COMPILER=...
If you don't want to use your PC's standard compiler, you have to give CMake the path to the compiler. You do this via environment variables, a toolchain file or direct definitions in the CMake command line (see e.g. CMake Error at CMakeLists.txt:30 (project): No CMAKE_C_COMPILER could be found).
Putting the compiler's name/path into your CMakeLists.txt would stop your project from being cross-platform.
CMake does check for the compiler ids by compiling special C/C++ files. So no need to manually include from Module/Compiler or Module/Platform.
This will be automatically done by CMake based on its compiler and platform checks.
References
CMake: In which Order are Files parsed (Cache, Toolchain, …)?
CMake GitLab Commit: Add support files for C, C++ and ASM for the IAR toolchain.
IAR Systems recently published a basic CMake tutorial with examples under their GitHub profile.
I like the the idea of a generic toolchain file which works seamlessly for both Windows and Linux compilers using find_program().
The following snippet will be used for when using C and can be used similarly for CXX:
# IAR C Compiler
find_program(CMAKE_C_COMPILER
NAMES icc${CMAKE_SYSTEM_PROCESSOR}
PATHS ${TOOLKIT}
"$ENV{ProgramFiles}/IAR Systems/*"
"$ENV{ProgramFiles\(x86\)}/IAR Systems/*"
/opt/iarsystems/bx${CMAKE_SYSTEM_PROCESSOR}
PATH_SUFFIXES bin ${CMAKE_SYSTEM_PROCESSOR}/bin
REQUIRED )
For ASM, I initially got puzzled with the NAMES but then I realized that the toolchain file was made that way for working with old Assemblers shipped with XLINK:
find_program(CMAKE_ASM_COMPILER
NAMES iasm${CMAKE_SYSTEM_PROCESSOR} a${CMAKE_SYSTEM_PROCESSOR}
PATHS ${TOOLKIT}
"$ENV{PROGRAMFILES}/IAR Systems/*"
"$ENV{ProgramFiles\(x86\)}/IAR Systems/*"
/opt/iarsystems/bx${CMAKE_SYSTEM_PROCESSOR}
PATH_SUFFIXES bin ${CMAKE_SYSTEM_PROCESSOR}/bin
REQUIRED )
Also, take a look at the full toolchain file. It will work automatically for "Arm" when the tools are installed on their default locations, otherwise it is just about updating the TOOLKIT variable and the compilers for all the supported languages should adjust automatically.
If your wanting to specify a compiler in cmake then just do ...
cmake_minimum_required(VERSION 3.22)
set(CMAKE_C_COMPILER "clang")
set(CMAKE_CXX_COMPILER "clang++")
Options 1 is only used if you want to specify what compiler you want to use as default for everything that you might compile on your computer. And I don't even think it would work on windows.
Option 2 would be used if you only want to use a different temporarily.
Option 3 is used if that's the compiler that should be used for that particular project. Also option 3 would be the most cross compatible.

Compiling C++17 using CLion, CMake and the VS2017 compiler

When creating a new CLion project and selecting the C++17 language standard the C++17 code will not compile.
The initial CMakeLists.txt file is as follows:
cmake_minimum_required(VERSION 3.8)
project(optional2)
set(CMAKE_CXX_STANDARD 17)
set(SOURCE_FILES main.cpp)
add_executable(optional2 ${SOURCE_FILES})
To compile the C++17 code I had to add the following line to the CMakeLists.txt file:
add_compile_options(/std:c++latest)
Is this the correct way (and/or only way?) to add this compile option in CMAKE / CLion?
The C++ compiler in use is the vs2017 cl.exe with nmake.exe on a Windows 10 workstation running CLion 2017.2.
The CMAKE_CXX_STANDARD variable is used to initialize the CXX_STANDARD property.
From the property documentation:
For compilers that have no notion of a standard level, such as MSVC,
this has no effect.
For Visual Studio 2017 with plain CMake, the canonical way for C++17 (the default is C++14) is:
target_compile_options(optional2 PRIVATE /std:c++latest)

How to enable /std:c++17 in VS2017 with CMake

I'm trying to add the /std:c++17 compiler flag to VS2017 with CMake. I'm using the "modern" cross-platform way so far:
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) # -std=c++11 instead of -std=gnu++11
set(MY_CXX_COMPILE_FEATURES cxx_generic_lambdas cxx_range_for cxx_strong_enums)
add_library(mylib INTERFACE)
target_compile_features(mylib INTERFACE ${MY_CXX_COMPILE_FEATURES})
This adds /std:c++14 in VS2017 (which might be the default anyway?).
However I'm having trouble switching this to C++17 (i.e. having it add /std:c++17). If I just add it manually, I get the not-so-nice warning because both flags are present:
1>cl : Command line warning D9025: overriding '/std:c++14' with '/std:c++17'
I've tried set(CMAKE_CXX_STANDARD 17) but it has no effect, in fact the CMake documentation mentions that CMAKE_CXX_STANDARD has no effect on VS anyway.
As for adding a C++17 feature to target_compile_features, it doesn't seem like there are any yet (even in CMake-3.9.0-rc5), and even if there were, I'm specifically only using std::optional from C++17, and there's no target_compile_features flags for library features like std::optional.
So my question is, what's the best (or least ugly) way to do this with CMake? And in a way so it'll also work for gcc and clang? I'm happy to use a very recent CMake version (3.8 or 3.9). I prefer it to be "nice" and not manually looping through CXX_COMPILE_FLAGS and removing the string "/std:c++14" or some hack like that.
(Edit: It can also be the VS/std:c++latest switch - whichever is possible. Both work for the purpose.)
Turning my comment into an answer
The CMake team is working on it for VS2017 (as for July 2017, for upcoming CMake version 3.10):
CMake: MSVC standard version switches
Those flags seem to be rather new switches (as related to the date of this question):
VS 2017 15.3 preview now supports /std:c++17
So for Visual Studio you have to "manually" replace or append the compiler switches until CMake officially does support it.
Here is a code snippet that I've tested for std:c++latest (which is already supported e.g. in my CMake 3.8.0 version):
if (MSVC_VERSION GREATER_EQUAL "1900")
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("/std:c++latest" _cpp_latest_flag_supported)
if (_cpp_latest_flag_supported)
add_compile_options("/std:c++latest")
endif()
endif()
For CLang and GNU the support was merged into the main source code branch begin of 2017 and is part of CMake version 3.8 and above:
CMake: Features: Add support for C++ 17 language standard
CMake versions higher than 3.10 support MSVC C++ standard switches for MSVC versions newer than 19.0.24215. If either of the version requirements are not met, then they have no effect.
The only portable approach, to ensuring your program is compiled with the correct C++ standard mode on Visual Studio, is to require at least CMake 3.10, set the target property CXX_STANDARD to your desired value and CXX_STANDARD_REQUIRED to ON.
Example usage:
set_property(TARGET my_target PROPERTY CXX_STANDARD 17)
set_property(TARGET my_target PROPERTY CXX_STANDARD_REQUIRED ON)
You can add the following line to your CmakeLists.txt file
set(GCC_COMPILE_FLAGS "${GCC_COMPILE_FLAGS} /std:c++17)

Add compiler flag for Visual Studio 2005 with CMake 2.8.1

Probably an easy beginners question: I want to add the compiler flag /EHsc to my project and tried both
SET_TARGET_PROPERTIES(name_of_my_project PROPERTIES COMPILER_FLAGS "/EHsc")
and
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
in my CMakeLists.txt. With both, CMake generates a Visual Studio Solution without complaining.
EDIT: initially I asked why both versions did not work. Well, because I am an idiot and made an error elsewhere so that none of the two lines where ever processed. Still, I'm wondering if both versions are equivalent.
They are equivalent on a project that has just 1 target.
SET_TARGET_PROPERTIES sets the compiler flag for the named target (which in your case happens to also be the name of the project). If you have more than one target, then the other targets will not have the "/EHsc" flag set.
Note that you want to use COMPILE_FLAGS rather than COMPILER_FLAGS, thus making the correct line:
SET_TARGET_PROPERTIES(name_of_my_project PROPERTIES COMPILE_FLAGS "/EHsc")
SET(CMAKE_CXX_FLAGS ...) will set the C++ flags for all targets in the current directory, as well as in any sub-directories. So you can use this to set global C++ flags that apply to all of your targets, i.e. the libraries and executables defined in the project.