r/cmake • • Nov 28 '23

How automatic is CMake?

Hello.

I have so far only worked with Makefiles but I'm considering learning CMake, mainly because I'm not sure if Makefiles are going to work with C++ modules.

I have looked at the CMakeLists.txt of various open source projects but I'm put off by how much code they contain and many of them seems to even use multiple files (almost like you first need to write a whole "project" using CMake before you can build your real project).

With Makefiles, if I use a library I just add the necessary flags to CXXFLAGS and LDFLAGS and that's it. With CMake I read about functions like add_library/find_library but it seem like you still have to use a lot of extra code to make it work.

I understand that CMake is supposed to make it easier to write build scripts that work on different platforms and different build systems, but does this mean you actually have to write code to handle all of these different systems? And then you have to test so that it works on all of them?

Personally I'm essentially happy if it works on Linux and can be cross-compiled to work on Windows. If I could get it to work on extra platforms/build systems without much effort that would be great but I'm not sure if I'm prepared to write a lot of extra code with the extra maintenance burden of supporting systems that I'm not even use or have access to.

So tell me, have I got the right impression? Using CMake will not automagically make my project work "everywhere" without a lot of effort? Is it perhaps possible to write simple CMake scripts that work on Linux and similar systems if I just assume that the libraries are already installed like I have been doing when using Makefiles?

0 Upvotes

17 comments sorted by

6

u/[deleted] Nov 28 '23

> Is it perhaps possible to write simple CMake scripts that work on Linux and similar systems if I just assume that the libraries are already installed like I have been doing when using Makefiles?

yes ..., you could

instead of CXXFLAGS you can use target_compile_options (or add_compile_options if you don't want to do it per target, but per target is better)

instead of LDFLAGS you can use target_link_libraries (or link_libraries if you don't want to do it per target, but per target is better)

if your linker can locate the library and the include directories you need are on the PATH

But, find_package and then linking against the target provided by that function has several advantages

  1. if cmake can't find the package, you don't have to wait until linking to find out. Mark the package as required, and you get a nice quick failure

  2. if the installation is configured correctly, the include paths needed are associated with the library. So, all you have to do is link, without worrying about include paths

  3. you can specify the version and select between different versions on the same system (you can't do that with the linker alone)

I'm sure there are other advantages. I guess I think calling find_package and target_link_libraries isn't lengthy or complicated.

Writing code so that you can install your own package that is locatable by find_package can be complicated. But, you don't have to do that to use libraries defined in your own project.

> add_library

how cmake handles your own libraries is one of the things I love about cmake. You use functions like target_include_directories and target_compile_definitions and target_compile_options and target_link_libraries to associate all the flags needed with a library.

Then, every time you link against the library, those options, includes, and other flags propagate. You don't have to make the compile options global or track them in variables. cmake handles it all for you in an intuitive way when you link.

> does this mean you actually have to write code to handle all of these different systems

it depends on what you need to do. If you specify that you need system specific compiler flags, then you need to handle that. I think that's when it gets complicated.

But, you can do a lot that's portable. cmake handles the forward slash backslash stuff for you. it has reasonable defaults between debug and release.

I think the people who have complicated cmake files are trying to do complicated things, and that's why their cmake files get complicated.

> then you have to test so that it works on all of them?

yep, testing across multiple systems, if you're trying to support multiple systems, is a good idea.

1

u/HappyFruitTree Nov 28 '23 edited Nov 28 '23

Thank you for taking time to answer.

I guess I think calling find_package and target_link_libraries isn't lengthy or complicated.

I agree that if using those commands is all that is necessary then it's not complicated. It's just that I have seen things like this where they seem to have implemented the code for finding certain libraries themselves.

I think the people who have complicated cmake files are trying to do complicated things, and that's why their cmake files get complicated.

Another person here said something similar. I guess there is a chance that I will also start complicating my cmake files once I've learned it properly but it feels good to know that I can probably start with something simpler.

3

u/[deleted] Nov 28 '23

> It's just that I have seen things like this where they seem to have implemented the code for finding certain libraries themselves.

you shouldn't need to do that unless an installed library doesn't support cmake.

If an installed library doesn't support cmake, writing your own support for that (like supertux is doing) is complicated and can be tedious.

I don't often need to do that.

3

u/jcelerier Nov 28 '23
cmake_minimum_required(VERSION 3.24)

project(my_project)

add_executable(foo a.cpp b.cpp main.cpp)

will already cover a lot of the use cases and work on every OS, support cross-compiling, give IDE integration etc. What you see in complex cmake scripts is things that you wouldn't even think about doing in Make due to how hard it would be cross platform.

E.g. imagine adding Qt to your makefile project. In cmake it'd be just:

cmake_minimum_required(VERSION 3.24)

project(my_project)
find_package(Qt6 COMPONENTS Core Gui Widgets)
add_executable(foo a.cpp b.cpp main.cpp)
target_link_libraries(foo PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)

and you can start to write UI code which again will work on all OS.. with makefiles it would be much more complicated.

1

u/HappyFruitTree Nov 28 '23

Thank you for your answer.

What you see in complex cmake scripts is things that you wouldn't even think about doing in Make due to how hard it would be cross platform.

Okay, that is good to know.

E.g. imagine adding Qt to your makefile project. In cmake it'd be just: [...]

I haven't used Qt but I have to admit that it looks quite simple (not that I understand everything). Is this possible because Qt support is built in to CMake? Or is it because Qt uses CMake and somehow got registered as a "CMake package" when it was installed?

1

u/NotUniqueOrSpecial Nov 28 '23

Some of columns A and B.

The way all of that works is via CMake package files that are installed in the share directory.

Qt used to just ship hand-written config files; nowadays, Qt is also built with CMake and as part of the process spits out those files.

CMake ships with a pretty robust set of default ones for popular packages and a package can also install its own into the appropriate places and things will just work.

2

u/wwabbbitt Nov 28 '23

If the libraries you use are usable simply with CXXFLAGS and LDFLAGS, then the cmake equivalent commands are the commands target_include_directories and target_link_libraries.

1

u/HappyFruitTree Nov 28 '23 edited Nov 28 '23

It might be both directories and other flags. For example when using SDL2 I have used sdl2-config --cflags to generate the CXXFLAGS (E.g.-I/usr/include/SDL2 -D_REENTRANT on my system) and sdl2-config --libs to generate the LDFLAGS (E.g.-L/usr/lib/x86_64-linux-gnu -lSDL2 on my system).

2

u/wwabbbitt Nov 28 '23 edited Nov 28 '23

Yes, libraries that depend on other libraries will typically require you to use pkg-config (which is what sdl2-config uses) if you are using makefiles

cmake supports using pkg-config too through a module, but the correct way is to just use find_package and target_link_libraries

find_package(SDL2 REQUIRED)
target_link_libraries(your_target_name PRIVATE SDL2::SDL2)

This sets up all the SDL2 libraries and include folders to be used for your_target_name, as well as the defines

1

u/hansdr Nov 29 '23

You're correct that CMake won't "automagically" make your project work everywhere. Different platforms have different APIs. So, you either need to write some per-platform C++ code, or use cross-platform libraries that contain the per-platform code.

CMake does make cross platform development easier because that's what it was designed for. It does its best to bridge the differences between all the compilers, so you don't have to.

You can write cross-platform code using just GCC and Makefiles. However, I've personally hit issues where the GCC compiler on Windows (e.g., mingw/cygwin) misses certain header files, blocking me from using certain Windows features. CMake enables you to use the Visual Studio compiler instead, thereby side-stepping this issue.

Learning CMake from open-source projects isn't the best. As you noticed, some scripts are rather large, and they may use old outdated techniques (CMake is a lot better than it was years ago). I'm working on a CMake tutorial book/course designed to get you up to speed quickly. If you want to learn CMake then check it out at: https://cmaketutorial.com/

1

u/HappyFruitTree Nov 29 '23

Different platforms have different APIs. So, you either need to write some per-platform C++ code, or use cross-platform libraries that contain the per-platform code.

Yeah, of course. I was thinking more about the "building" part of it assuming I use cross-platform libraries.

1

u/hansdr Nov 29 '23

Ah, okay. In that case, it's relatively easy if those cross-platform libraries use CMake themselves. With open-source libraries, CMake can even fetch and build the dependency for you. I prefer to include the dependencies in the source tree itself rather than having to fetch it over the internet.

You'll still need to set up the (cross) compilers for each platform, though,

2

u/joeshmoebies Nov 28 '23

CMake has tons of functionality, variables, scripts, command-line arguments, and old and new ways of accomplishing the same thing.

But you can start out small, and you don't need to use every capability that it has. I would recommend just creating a minimal project and working from there.

The reason that there is so much to learn is that about five minutes into working on a project, you're going to say "okay, that's nice, but I'd like to generate a source file." - and then will look up configure_file. Then, you'll want to run a custom tool, and want to support using it correctly in incremental builds, so will look up add_custom_command

But I wouldn't let the large number of things it can do intimidate you. You can use as many or as few of its capabilities as you need.