Setting Various compilers in CMake for creating a shared library - c++

I am looking to set various compilers for different folders in my project, which should compile to a shared library.
The project structure is as follows -
/Cuda
a.cu
b.cu
c.cu
header.cuh
/SYCL
a.cpp
b.cpp
c.cpp
header.h
main.cpp
test.cpp
All the files under the Cuda folder must be compiled by nvcc, and the files under the SYCL folder by a specific compiler that is present at a path in the system. All the files outside these folders (namely main.cpp and test.cpp) are normal C++ code and use the headers present in these two folders and must be compiled with GCC.
How do I go about writing the CMake for such a project structure(which aims to be a shared lib).
Edit - The project needn't have only one dedicated CMake. My approach was as follows -
Each Folder(Cuda and SYCL) can have their dedicated CmakeLists.txt which would specify the compiler and the various flags to go with it.
A master CMake outside the folder can use the add_subdirectory command. And this is where I get stuck, I am not sure what to do next, how to link these two folders with the main and the test files.

CMake allows one compiler per language, so simply writing this is enough:
cmake_minimum_required(VERSION 3.20)
project(example LANGUAGES CXX CUDA)
add_subdirectory(Cuda)
add_subdirectory(SYCL)
You can separately set the C++ and CUDA compilers by setting CMAKE_CXX_COMPILER and CMAKE_CUDA_COMPILER at the configure command line.
$ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_COMPILER=g++ -DCMAKE_CUDA_COMPILER=nvcc
Also, I want to clear up this misconception:
Each Folder(Cuda and SYCL) can have their dedicated CmakeLists.txt which would specify the compiler and the various flags to go with it.
The CMakeLists.txt file should not attempt to specify the compiler. It's tricky to do correctly, can't always be done (especially in the add_subdirectory case) and unnecessarily restricts your ability to switch out the compiler. Maybe you have both GCC 10 and 11 installed and want to compare the two.
Similarly, you should not specify flags in the CMakeLists.txt file that aren't absolutely required to build, and you should always check the CMake documentation to see if the flags you're interested in have been abstracted for you. For instance, CMake has special handling of the C++ language standard (via target_compile_features) and CUDA separable compilation (via the CUDA_SEPARABLE_COMPILATION target property).
The best solution, as I have detailed here, is to set optional flags via the *_FLAGS* variables in a preset or toolchain.

Related

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.

Code parsing not working with CUDA, Clion and CMake

I have a project divided in modules, here is a dummy example:
root
CMakeLists.txt
modules
utils
CMakeLists.txt
src
util_file.cpp
cuda
CMakeLists.txt
src
cuda_file.cu
If I edit the cuda_file.cu with CLion, all the symbols are unresolved (even the includes from standard library) by CLion. All the code completion/creation features are then of course gone (among other things). The problem seems to be that whenever you create a library or an executable with only CUDA files, Clion becomes stupid and doesn't parse or resolve anything anymore.
There is two workarounds I've found but they are not friendly or "clean" to use :
add an empty .cpp file to the directory and add it to the add_library() CMake line.
switch to another library or executable target that has .cpp files (like utils in my dummy example). But then when you want to compile or execute you have to switch again to cuda target (or some subtarget like test_cuda for test units) and then switch back again to continue coding or debugging, etc...
Here is the CMakeLists.txt from the cuda module with the workaround:
cmake_minimum_required(VERSION 3.5)
message(STATUS "Configuring module cuda")
# Build module static library
FILE(GLOB CUDA_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
FILE(GLOB CUDA_CU_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cu)
FILE(GLOB CUDA_CU_HDRS
${CMAKE_CURRENT_SOURCE_DIR}/include/*.cuh)
cuda_compile(cuda_objs ${CUDA_CU_SRCS} ${CUDA_CU_HDRS})
add_library(cuda STATIC ${CUDA_SRCS} ${cuda_objs})
# because only .cu files, help cmake detect C++ language
set_target_properties(cuda PROPERTIES LINKER_LANGUAGE CXX)
Is there a way to avoid CLion derping when resolving links to other headers and libraries ?
I've already added .cu and .cuh files as C/C++ code in CLion options and tried using JETBRAINS_IDE define option as explained in another similar post, but those two problems are not the same.
It seems like without the intervention of Jetbrains to add official CUDA support, the most I could get out of the combo CLion + CMake + CUDA was achieved by:
adding .cu and .cuh as C++ files in CLion. This allows Clion to recognize cuda code as C++ code and color it correctly.
adding an empty dummy .cpp file to the cuda source directory if it is only filled by .cu files (one of my "dirty" hacks from my question). I could not find better. This allows Clion to not completely derp. A simple thing like recognizing cstdio doesn't work without this "hack" and CLion is basically an enhanced notepad.
using when possible CMake 3.8+ that officially supports CUDA as a language and use the new "cuda aware" add_library() instead of the old macro defined cuda_add_library(). This can avoid problems in the future in case of deprecation.
in the CMakeLists of the cuda module (or main CMakeLists if only one), include the path to the include directory of cuda to allow Clion to "see" the cuda headers. CLion can then propose to you to include them so that CLion resolves correctly CUDA API calls like cudaMalloc() or cudaFree(). This is only needed for CLion as the CUDA compiler doesn't need this includes to compile properly (cuda.h, cuda_runtime.h, ...).
use this answer to create a "clion helper" header file, so that it doesn't derp on symbols like __device__ or __global__.
I think that if Jetbrains starts adding support for CUDA, not also will it remove the need to add this dummy file, but it will probably also resolve all other things listed.
Here is the link to the nvidia blog with examples about the official cuda language support in CMake and new "cuda aware" add_library() : https://devblogs.nvidia.com/parallelforall/building-cuda-applications-cmake/
As of the version 2020.1 of CLion, CUDA projects are now officially supported
https://blog.jetbrains.com/clion/2020/04/clion-2020-1-cuda-clang-embedded/

How to make cmake select the "right" library

Where multiple possible versions of a library exist, is there a way of ensuring cmake picks the "right" one?
For example: centOS7 comes with gcc 4.5.2, and includes /usr/lib64/libgfortran.so.1. I have compiled gcc 6.2.0 in /usr/local, and this includes /usr/local/gcc-6.2/libgfortran.so.3
However,
find_package(Gfortran REQUIRED)
Always finds the system gfortran. My "FindGfortran.cmake" file contains:
find_library(Gfortran_LIBRARY
NAMES gfortran
PATHS /usr/local/gcc-6.2/lib64
)
And I've set tried running cmake with:
LD_RUN_PATH='/usr/local/gcc-6.2/lib64' LDFLAGS='-L/usr/local/gcc-6.2/lib64' CXXFLAGS='-L/usr/local/gcc-6.2/lib64' cmake ..
But whatever I do, cmake picks up the /usr/lib64/libgfortran.so.1 version (and then my code fails because it links, correctly, against a different library which is in turn linked against libgfortran.so.3).
I have a "workaround" in that I can edit my CMakeLists.txt file to include the line:
set (Gfortran_LIBRARIES /usr/local/gcc-6.2/lib64/libgfortran.so.3)
But this is rather rubbish, and means that I need to maintain different CMakeLists.txt files on different machines (so I can't commit the file to SVN) which seems to defy the point of using cmake.
There must be something stupid I'm doing wrong - can anyone advise?

How do I write system-independent code when there are paths involved?

Say I am creating a project that uses a certain library and I have to provide the path for that library while linking. In the command line or makefile I might have:
g++ ... -L/path/to/mylibrary
I'm also going to send this project to someone else who wants to use it. The path on their system might not necessarily be the same as mine. They could be using a different file path all together.
How do I make sure that the path to the library works for both my computer and the recipient of my project?
This is the role of a build system or build configuration tool. There are many of those around. The main one is probably CMake as it has a very extensive feature set, cross-platform, and widely adopted. There are others like Boost.Jam, autoconf, and others.
The way that these tools will be used is that they have automated scripts for looking into the file-system and finding the headers or libraries that you need, i.e., the dependencies required to compile your code. They can also be used to do all sorts of other fancy things, like checking what features the OS supports and reconfiguring the build as a consequence of that. But the point is, you don't hard-code any file-paths into the build configuration, everything is either relative to your source folder or it is found automatically by the build script.
Here is an example CMake file for a project that uses Boost:
cmake_minimum_required (VERSION 2.8)
project (ExampleWithBoost)
find_package(Boost 1.46 COMPONENTS thread program_options filesystem REQUIRED)
# Add the boost directory to the include paths:
include_directories(SYSTEM ${Boost_INCLUDE_DIR})
# Add the boost library directory to the link paths:
link_directories(${Boost_LIBRARY_DIRS})
# Add an executable target (for compilation):
add_executable(example_with_boost example_with_boost.cpp)
# Add boost libraries to the linking on the target:
target_link_libraries(example_with_boost ${Boost_LIBRARIES})
The find_package cmake function is simply a special script (specialized for Boost, and installed with CMake) that finds the latest version of boost (with some minimal version) installed on the system, and it does so based on the file-name patterns that the library uses. You can also write your own equivalents of find_package, or even your own package finders, using the functions that CMake provides for searching the file system for certain file-name patterns (e.g., regular expressions).
As you see, the build configuration file above only refer directly to your source files, like "example_with_boost.cpp", and it's only relative to the source folder. If you do things right, the configuration scripts will work on virtually any system and any OS that CMake supports (and that the libraries you depend on support). This is how most major cross-platform projects work, and when you understand how to work with these systems, it's very powerful and very easy to use (in general, far easier to use and trouble-free than build configurations that you do by point-and-click within IDE menus like in Visual Studio).
You can use premake that generates cross platform makefiles: Visual Studio, Gcc and others
http://industriousone.com/what-premake
CMake is another alternative
http://www.cmake.org/
I'm not sure if there's a single universal way of doing this, but people often provide different config files and let the main Makefile detect which one to include: linux.make, darwin.make, cygwin.make etc.
there are also various tools like CMake that allow to automate this, but all in all it's just scripting that hides the system-dependency from the developer.

Building CUDA object files using cmake

I got the following setup. I'm going to extend a framework written in C++ using MPI and other Stuff using CUDA. The Project uses cmake for building. I would like to avoid using a library for my extensions and build object files from my cuda sources. Afterwards I would like to link these object object files and some other files compiled with other compilers.
Does anyone have a clue on hwo to achieve that?
I had a look at http://code.google.com/p/cudpp/wiki/BuildingCUDPPwithCMake for getting an overview on how to use CUDA with cmake but this solution uses a library as well.
It is possible to compile object files with the CUDA support that comes with newer versions of cmake. You use the cuda_compile command. See below.
# CMakeLists.txt for G4CU project
project(test-cuda-thrust-gdb)
# required cmake version
cmake_minimum_required(VERSION 2.8)
# packages
find_package(CUDA)
# nvcc flags
set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS};-gencode arch=compute_20,code=sm_20)
cuda_compile(HELPER_O helper.cu)
cuda_compile(DRIVER_O driver.cu OPTIONS -G)
cuda_add_executable(driver ${HELPER_O} ${DRIVER_O})
If you need more information have a look at the FindCUDA.cmake file.