I'm currently working on using cmake to build some projects, with the main platforms being Visual C++, MinGW GCC and Linux GCC. When building with GCC, I need to specify the -Wno-invalid-offsetof compiler option.
My current fix is as follows...
if ( "${CMAKE_GENERATOR}" MATCHES "^Visual Studio"
OR "${CMAKE_GENERATOR}" MATCHES "^NMake"
)
set (CPPLIB_COMPILER_OPTS "")
else ()
set (CPPLIB_COMPILER_OPTS "-Wno-invalid-offsetof")
endif ()
...
set_target_properties(sh_core PROPERTIES COMPILE_FLAGS "${CPPLIB_COMPILER_OPTS}")
# repeated for all targets
This works, but assuming that all generators other than the visual studio ones will build with gcc is obviously unsafe. For a start, there are IIRC generators for Borland compilers. More importantly, using make doesn't always mean using gcc.
Other compilers I'm likely to use are llvm-gcc and clang. Fortunately, I think even clang supports gcc-compatible options. But this logic is only good for as long as the relevant code is never released.
Cmake appears to check for available compilers and generate a makefile specifically for that compiler (raising the question - why not at least have the option of building the project directly, without the need for a middle-man like make?).
That being the case, I was hoping to be able to test directly for gcc in my CMakeLists.txt files. So far, though, I can't find an appropriate variable to test or any other obvious solution.
Is this possible?
To create a portable build system, it is best to not test for platforms, but to test for features.
Instead of testing "if Windows then do this", test for "if the -Wno-invalid-offsetof flag works then use it". You can do that with the CheckCCompilerFlag module, for example:
include(CheckCCompilerFlag)
check_c_compiler_flag(-Wno-invalid-offsetof HAS_NO_INVALID_OFFSETOF)
if (HAS_NO_INVALID_OFFSETOF)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-invalid-offsetof")
endif()
For C++ there is a similar CheckCXXCompilerFlag with a check_cxx_compiler_flag(flag var) command.
Related
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.
If you build C++14 code with G++ and libstdc++, there's a library named libstdc++fs, which is separate from the rest of libstdc++, and contains the code for std::experimental::filesystem. If you don't link against it, you'll get undefined references.
The "trick" I'm using for overcoming this right now is:
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(CXX_FILESYSTEM_LIBRARIES "stdc++fs")
endif()
and later:
target_link_libraries(my_target PUBLIC ${CXX_FILESYSTEM_LIBRARIES})
but - I don't like having to place this code in every project I work on. Is there a simpler or more standard idiom I could use? Some way this will all happen implicitly perhaps, with some CMake behind-the-scences magic?
tl;dr: Nothing right now, wait for a newer CMake version
As #Pedro graciously points out, this is a known problem, and there is an open issue about it at KitWare's GitLab site for CMake:
Portable linking for C++17 std::filesystem
If using CMAKE_CXX_STANDARD=17 and std::filesystem, GCC requires linking of an extra library: stdc++fs. ... If C++17 is enabled, would it be worth automatically linking to stdc++fs for GCC versions which require this? Likewise for any quirks in other compilers or libraries.
The KitWare issue is about C++17, for which apparently you still need the separate extra library (i.e. it's not just because of the "experimentality" in C++14). Hopefully we'll see some traction on this matter - but
Note: If you're experiencing this problem with C++17's std::filesystem, you're in luck - that code is built into libstdc++ beginning with GCC 9, so if you're using g++ 9 or later, and std::filesystem, you should no longer experience this problem.
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.
I would like Cmake to use Intel C++ Compiler only if it is present. I found this snippet on here, but, as the name suggests, it forces Cmake to use Intel compiler. And will complain if not present. Period.
include(CMakeForceCompiler)
CMAKE_FORCE_C_COMPILER(icc "Intel C Compiler")
CMAKE_FORCE_CXX_COMPILER(icpc "Intel C++ Compiler")
Is there any way to achieve this kind of behaviour (ideally only displaying a warning to the user if not present)?
This is not possible from inside CMake.
CMake expects the user to setup and select the correct toolchain. Then on the first configure run CMake performs a couple of tests to verify that the selected compiler is present and works as expected. This check can only be performed once per configure, so a clean switch of compilers from within CMake always requires wiping the CMakeCache.txt and restarting with a fresh configure run.
You will need an external script that selects the correct compiler and then invokes CMake with the correct parameters.
Here is how I do it:
find_program (INTEL_COMPILER icc)
if (INTEL_COMPILER)
include (CMakeForceCompiler)
cmake_force_c_compiler (icc "Intel C Compiler")
cmake_force_cxx_compiler (icpc "Intel C++ Compiler")
endif (INTEL_COMPILER)
So here, CMake doesn't say anything if you don't have Intel compiler, but you could easily add a message("You don't have icc in your PATH") in the else() branch!
I'm wondering how i can make a portable build system (step-by-step), i currently use cmake because it was easy to set up in the first place, with only one arch target, but now that i have to package the library I'm developing I'm wondering how is the best way to make it portable for arch I'm testing.
I know I need a config.h to define things depending on the arch but I don't know how automatic this can be.
Any other way to have a build system are warmly welcome!
You can just use CMake, it's pretty straightforward.
You need these things:
First, means to find out the configuration specifics. For example, if you know that some function is named differently on some platform, you can use TRY_COMPILE to discover that:
TRY_COMPILE(HAVE_ALTERNATIVE_FUNC
${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}/alternative_function_test.cpp
CMAKE_FLAGS -DINCLUDE_DIRECTORIES=xxx
)
where alternative_function_test.cpp is a file in your source directory that compiles only with the alternative definition.
This will define variable HAVE_ALTERNATIVE_FUNC if the compile succeeds.
Second, you need to make this definition affect your sources. Either you can add it to compile flags
IF(HAVE_TR1_RANDOM)
ADD_DEFINITIONS(-DHAVE_TR1_RANDOM)
ENDIF(HAVE_TR1_RANDOM)
or you can make a config.h file. Create config.h.in with the following line
#cmakedefine HAVE_ALTERNATIVE_FUNCS
and create a config.h file by this line in CMakeLists.txt (see CONFIGURE_FILE)
CONFIGURE_FILE(config.h.in config.h #ONLY)
the #cmakedefine will be translated to #define or #undef depending on the CMake variable.
BTW, for testing edianness, see this mail
I have been using the GNU autoconf/automake toolchain which has worked well for me so far. I am only really focussed on Linux/x86 (and 64bit) and the Mac, which is important if you are building on a PowerPC, due to endian issues.
With autoconf you can check the host platform with the macro:
AC_CANONICAL_HOST
And check the endianness using:
AC_C_BIGENDIAN
Autoconf will then add definitions to config.h which you can use in your code.
I am not certain (have never tried) how well the GNU autotools work on Windows, so if Windows is one of your targets then you may be better off finding similar functionality with your existing cmake build system.
For a good primer on the autotools, have a look here:
http://www.freesoftwaremagazine.com/books/autotools_a_guide_to_autoconf_automake_libtool