Modular C++ Project Build with CMake - c++

I'm trying to find a way to build a big modular C++ project with CMake.
The structure of the project is the following:
--project_root
--src
--folder_1
--source_1.h
--source_1.cc
--test_source_1.cc // file containing a main with unit tests
--folder_2
--source_2.h
--source_2.cc
--test_source_2.cc // file containing a main with unit tests
--folder_3
...
And so on.
Each folder represent a project module and each module might depend on other modules, so for example source_1.h may include source_2.h.
Every module folder may also contains a test file so the whole project will have multiple executables.
How can I build the whole project with CMake? How should I write my CMakeLists.txt file?
Thank you a lot.

There are many, many examples out there of how to structure CMake projects for C++, many of which are referenced by the tutorial #user2485710 suggested in his comment, so I'm not going to go super in-depth here, but I'll at least give you a good starting point based on the way you want to lay out you folder structure.
The nice thing about CMake is that it can essentially do a tree-decent using the add_subdirectory command. This lets us easily divide up our CMake code to only do what is required at any specific directory level. In otherwords, each CMakeLists.txt file should only do the minimal amount of work needed to properly set up things at the current depth in the directory tree. In you example, your CMake tree might look like this:
--project_root
--src
--CMakeLists.txt
--folder_1
--CMakeLists.txt
--source_1.h
--source_1.cc
--test_source_1.cc // file containing a main with unit tests
--folder_2
--CMakeLists.txt
--source_2.h
--source_2.cc
--test_source_2.cc // file containing a main with unit tests
...
In src/CMakeLists.txt you do all of your project-level initialization, I.E. find_package, setting up your include-path, etc. Then you simply add the following at the end:
add_subdirectory(folder_1)
add_subdirectory(folder_2)
...
This tells CMake that it should look in those folders for additional stuff to do. Now in src/folder_1/CMakeLists.txt, we do the actual work of whatever combination of add_executable and add_library you need to properly build source_1.cc and test_source_1.cc, and likewise in src/folder_2/CMakeLists.txt for source_2.cc, etc.
The other nice thing is that any CMake variables you set higher up the tree are propagated down through add_subdirectory. So, for example, in src/CMakeLists.txt you can check for some sort of 'build unit-test' flag and set the CMake variable there, and then all you have to do in the other CMakeLists.txt files is check for that variable. This can also be super useful to do if you have a project where CMake is dynamically generating header files for you based on checking environment variables for path-names and the like.

If the structure of the project is well-regulated, you could write custom macros or function of cmake to define the modules and their dependencies.
The cmake scripts in OpenCV project is a good reference:
/libs/opencv-2.4.8/sources/
|+cmake/
|+doc/
|~modules/
| |+core/
| | |+doc/
| | |+include/
| | |+perf/
| | |+src/
| | |+test/
| | `-CMakeLists.txt
| |~imgproc/
| | |+doc/
| | |+include/
| | |+perf/
| | |+src/
| | |+test/
| | `-CMakeLists.txt
| |+ml/
| |+...
| |-CMakeLists.txt
|-CMakeLists.txt
root/modules/imgproc/CMakeLists.txt
set(the_description "Image Processing")
ocv_define_module(imgproc opencv_core)

You will need a CMakeLists.txt in each folder where building will occur.
project() is used to set the name of your overall project.
add_subdirectory() is used to command the configuration to process the CMakeLists.txt in that directory.
add_executable() is used to create an executable from included sources.
add_library() is used to create a static or dynamic library that can be added to executables or libraries as a dependency.
Using a build script (.bat, .cmd, or .sh) will allow you to automate some of the cmake process, such as setting up an out-of-source configuration or build.
You should look up the documentation for these commands on the cmake website, https://cmake.org/cmake/help/latest/

Related

compiler can't find header file on pre-build stage with target_include_directories

Language server (clangd) says he can't find lib.h when I trying to include header in lib_gtests.cpp : #include "lib.h".
But when I compile everything is OK and lib_gtests can find a header file.
I may miss something in CMake or I should use an IDE because they are more "smart" and are able to find these pre-build dependencies
project
|
|-------> src
| |----> main.cpp
| |----> lib
| |----> lib.cpp
| |----> lib.h
|
|
|-------> tests
| |----> lib
| |----> lib_gtests.cpp
|
|
|---->CMakeLists.txt
CMakeLists.txt
add_executable(unit_tests
tests/lib/lib_gtests.cpp
)
target_link_libraries(unit_tests PRIVATE lib)
target_include_directories(unit_tests PRIVATE ${CMAKE_SOURCE_DIR}/src/lib)
sorry for a dumb question, I'm new to C++ and CMake
and sorry for my English in advance
and could I ask for an advice for sources on CMake (not the official docs, they're scary)
use an IDE (it'll make all these included directories on compile stage visible for a language server by itself, I guess this is why everything works fine for me)
or, if you really want to use some text editor with a language server (for instance NeoVim, as I do. Actually, it's such a pain in the butt but I like this) use ccls as a language server and tell CMake to create compile_commands.json file and create a link to this file in your project directory.
cmake -Bbuild -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=YES
ln -s build/compile_commands .

cmake shared lib with Qt

I created two default projects with QtCreator v6.0.1: MyApp and MyLib for Linux(Ubuntu).
I changed the folders structure to
MyApp
├──build
│ ├──Debug
| └──Release
├──src
├──libs
| └──MyLib
| ├──build
| ├──src
| ├──include
| └──CMakeLists.txt
├──include
├──src
└──CMakeLists.txt
What kind of changes I need to make with both of CMakeLists.txt to make MyLib as shared library and use it in MyApp?
First of all, as already mentioned, you'll need to add the MyLib directory with add_subdirectory in the top-level CMakeLists.txt:
...
add_subdirectory(libs/MyLib)
...
Then, defining a shared/dynamic library with CMake is pretty straight forward. You use add_library providing the name of the library, the source files and some flags. By default it will create a static library, but with the keyword SHARED you can change that:
add_library(MyLib SHARED
MySource1.cpp
MySource2.cpp
...
)
Have a look here: https://cmake.org/cmake/help/latest/command/add_library.html
By the way, all generated code (including the library) will end up in the one and only build directory (which you either specify with cmake -B $BUILD_DIR or which is the working directory for running cmake). So you won't have an additional one in your libs/MyLib folder.

Cmake not handling header files build dependencies if build dir is a subdirectory of PROJECT_SOURCE_DIR?

The accepted answer of this question correctly states that cmake does not handle C++ header-files build dependencies in case cmake's build directory is under cmake's PROJECT_SOURCE_DIR. PROJECT_SOURCE_DIR is assigned its value when the cmake project() macro is used, therefore it would be the "top" directory for such a project structure using project(source):
top
|
|--CmakeLists.txt
|--source
|--build
As far as I know, this is the typical structure of a cmake project, so I find it surprising that cmake does not handle this structure. Instead, such a structure would be required in order to handle the header file build dependencies correctly:
top
|
|--source
|--CMakeLists.txt
|--build
Is this the expected behavior of cmake? Or is it a bug which should be reported?

Cmake recompiles everything each time I add a new source subfolder

I have a project tree organized as the following:
MyProjects/ - build - project1 - CMakeLists.txt
| | project2 - CMakeLists.txt
|
| src - project1 - Project1Class1.h
| Project1Class1.cpp
| Project1Class2.h
| Project1Class2.cpp
| more subdirectories ...
project2 - Project2Class1.h
| Project2Class1.cpp
| more subdirectories ...
Imagine that project2 depends on project1. Then project2 uses directly project1 files and does not use a static or dynamic project1 library.
Then project2/CMakeLists.txt finds out the project1 and project2 source files and includes them through a GLOB_RECURSE :
file(
GLOB_RECURSE
source_files
../../project1/
../../project2/
)
This is working in the sense that it correctly builds my projects.
Each time I add a new source file in a new folder, in e.g. file MyNewFolder/myTest.cpp in src/project2/, and type
~/MyProjects/build/project2/$ cmake .
~/MyProjects/build/project2/$ make
Then the file is correctly taken into account by cmake. However, my problem is that every file is recompiled again.
Same thing when I change a source file in project1 and try to compile project2.
Note that I considerably simplified what really is in my CMakeLists.txt. So my question is: based on what I explained about it, is this behavior what CMake is expected to do? And if yes what is the rationale behind it and what should I do for make to only compile the new file? I was not able to find any documentation about it on the internet.
Note: Feel free to discuss the overall source build files organization. Notice that I wanted to keep my build configuration separated from the src/ folder.
Edit: I found this which explains why GLOB and GLOB_RECURSE prevent it to work.
Edit 2: Even with no GLOB, the compilation is done from the begining is other cases (see this question)
You are observing known side effect of file(GLOB_RECURSE ...). I'm not sure why exactly this is happening, but to avoid this most CMake-based projects lists their sources explicitly:
set(source_files
../../project1/Project1Class1.cpp
../../project1/Project1Class2.cpp
...
../../project2/Project2Class1.cpp
../../project2/Project2Class1.cpp
...
)

How to extract part of project based on GNU Build System?

When I move some subdirectory away from project managed by "./configure" and all that things, it tries to get to some "../../configure.ac" and other things and is not easily buildable.
How to extract part of such project and make it independent?
There is two ways to deal with this, create a separate auto-tools build process or do away with the auto-tools and hand code or make a new Makefile.
myprojectfoo
|
+-- src
|
+-- man
|
+-- messages
|
+-- lib
|
+-- include
|
+-- others
Have a look at the illustration above, for a fictitious project called myprojectfoo and is using auto-tools to build a binary called foo. The top-level directory i.e. myprojectfoo will have configure.ac, Makefile.am and Makefile.in, in the subdirectories there would be at least Makefile.am and Makefile.in. The auto-tools will create and execute the make commands to build the project.
Now, from what I'm understanding in what you are trying to do:
myprojectfoo
| \ /
+-- sXc
| / \
+-- man
|
+-- messages
|
+-- lib
| \ /
+-- incXude
| / \
+-- others
You want to take out the src subdirectory and it's include's also. Then in that case, it would be easier to create a separate Makefile (read - no auto-tools) build.. in that case, it would be easier.
The best way I can think of it is, you will have to make that decision ultimately, how big is the subset of the project's sources you want to extract, once you go ahead with that, remove all references to Makefile.am, Makefile.in... and borrow an existing simple Makefile template to build it and invoke it like this
make -f MyMakefile
OR
If you want to build a separate project using that subset using auto-tools:
Create a bare-bones Makefile.am as shown below.
Create a bare-bones configure.ac as shown below...
Run autoscan on the source to pick out the dependencies, add the results of the output file 'configure.scan' to the configure.ac
Run automake (Do this once!)
Run autoconf then. It may complain about missing files such as INSTALL, COPYING etc
Then any subsequent changes to configure.ac, run autoreconf after that, which will execute automake, autoconf, and other supporting auto-tools programs.
Taking a sample of the Makefile.am for Linux...
SUBDIRS = src include
ACLOCAL_AMFLAGS = -I m4
Taking a sample of the configure.ac for Linux...
AC_PREREQ(2.63)
AC_INIT([mysubsetprojectfoo], [0.1a], [foo#bar.baz])
AC_CONFIG_AUX_DIR([build-aux])
AM_INIT_AUTOMAKE([-Wall -Werror])
AM_GNU_GETTEXT_VERSION([0.17])
AM_GNU_GETTEXT([external])
AM_CFLAGS=
# Checks for programs.
AC_HEADER_STDC
AC_PROG_CC
AC_ARG_ENABLE([debug],
[ --enable-debug Turn on debugging],
[case "${enableval}" in
yes) debug=true ;;
no) debug=false ;;
*) AC_MSG_ERROR([bad value ${enableval} for --enable-debug]) ;;
esac],[debug=false])
AM_CONDITIONAL([DEBUG], [test x$debug = xtrue])
# Checks for libraries.
AC_CHECK_LIB([mylib], [mylib_function], [:])
if test "$mylib" = :; then
AC_MSG_ERROR([MyLib is missing.\
This can be downloaded from 'http://www.foo.baz'])
fi
AC_CONFIG_HEADERS([config.h])
# Checks for header files.
# FROM running 'autoscan' on the source directory
AC_CHECK_HEADERS([arpa/inet.h fcntl.h libintl.h locale.h netinet/in.h stdlib.h string.h sys/ioctl.h sys/socket.h syslog.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_C_INLINE
AC_C_CONST
AC_TYPE_SIGNAL
AC_TYPE_PID_T
AC_TYPE_UID_T
AC_TYPE_SIZE_T
# Checks for library functions.
AC_FUNC_FORK
AC_FUNC_MALLOC
AC_CHECK_FUNCS([atexit inet_ntoa memset regcomp socket strdup strerror])
AC_CONFIG_FILES([Makefile src/Makefile include/Makefile])
AC_OUTPUT
The commands for the auto-tools, is top of my head and I may have missed something..feel free to point out by placing a comment on this at the bottom of this post and it will be amended accordingly.