130

Somehow I am totally confused by how CMake works. Every time I think that I am getting closer to understand how CMake is meant to be written, it vanishes in the next example I read. All I want to know is, how should I structure my project, so that my CMake requires the least amount of maintainance in the future. For example, I don't want to update my CMakeList.txt when I am adding a new folder in my src tree, that works exactly like all other src folders.

This is how I imagine my project's structure, but please this is only an example. If the recommended way differs, please tell me, and tell me how to do it.

myProject
    src/
        module1/
            module1.h
            module1.cpp
        module2/
            [...]
        main.cpp
    test/
        test1.cpp
    resources/
        file.png
    bin
        [execute cmake ..]

By the way, it is important that my program knows where the resources are. I would like to know the recommended way of managing resources. I do not want to access my resources with "../resources/file.png"

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
Arne
  • 7,548
  • 7
  • 45
  • 64
  • 1
    `For example I don't want to update my CMakeList.txt when I am adding a new folder in my src tree` can you give an example of IDE which collects sources automatically? –  Jan 17 '14 at 07:07
  • 8
    no ide's normally don't collect sources automatically, because they don't need to. When I add a new file or folder, I do it within the ide, and the project is updated. A build system on the other side does not notice when I change some files, so it is a desired behavior that it collects all source files automatically – Arne Jan 17 '14 at 16:26

3 Answers3

100

After some research I have now my own version of the most simple but complete CMake example. Here it is, and it tries to cover most of the basics, including resources and packaging.

One thing it does non-standard is resource handling. By default CMake wants to put them in /usr/share/, /usr/local/share/ and something equivalent on windows. I wanted to have a simple zip/tar.gz that you can extract anywhere and run. Therefore resources are loaded relative to the executable.

The basic rule to understand CMake commands is the following syntax: <function-name>(<arg1> [<arg2> ...]) without comma or semicolon. Each argument is a string. foobar(3.0) and foobar("3.0") is the same. You can set lists/variables with set(args arg1 arg2). With this variable set foobar(${args}) and foobar(arg1 arg2) are effectively the same. A non existent variable is equivalent to an empty list. A list is internally just a string with semicolons to separate the elements. Therefore a list with just one element is by definition just that element, no boxing takes place. Variables are global. Builtin functions offer some form of named arguments by the fact that they expect some ids like PUBLIC or DESTINATION in their argument list, to group the arguments. But that's not a language feature, those ids are also just strings, and parsed by the function implementation.

You can clone everything from github

cmake_minimum_required(VERSION 3.0)
project(example_project)

###############################################################################
## file globbing ##############################################################
###############################################################################

# these instructions search the directory tree when cmake is
# invoked and put all files that match the pattern in the variables 
# `sources` and `data`
file(GLOB_RECURSE sources      src/main/*.cpp src/main/*.h)
file(GLOB_RECURSE sources_test src/test/*.cpp)
file(GLOB_RECURSE data resources/*)
# you can use set(sources src/main.cpp) etc if you don't want to
# use globing to find files automatically

###############################################################################
## target definitions #########################################################
###############################################################################

# add the data to the target, so it becomes visible in some IDE
add_executable(example ${sources} ${data})

# just for example add some compiler flags
target_compile_options(example PUBLIC -std=c++1y -Wall -Wfloat-conversion)

# this lets me include files relative to the root src dir with a <> pair
target_include_directories(example PUBLIC src/main)

# this copies all resource files in the build directory
# we need this, because we want to work with paths relative to the executable
file(COPY ${data} DESTINATION resources)

###############################################################################
## dependencies ###############################################################
###############################################################################

# this defines the variables Boost_LIBRARIES that contain all library names
# that we need to link to
find_package(Boost 1.36.0 COMPONENTS filesystem system REQUIRED)

target_link_libraries(example PUBLIC
  ${Boost_LIBRARIES}
  # here you can add any library dependencies
)

###############################################################################
## testing ####################################################################
###############################################################################

# this is for our testing framework
# we don't add REQUIRED because it's just for testing
find_package(GTest)

if(GTEST_FOUND)
  add_executable(unit_tests ${sources_test} ${sources})

  # we add this define to prevent collision with the main
  # this might be better solved by not adding the source with the main to the
  # testing target
  target_compile_definitions(unit_tests PUBLIC UNIT_TESTS)

  # this allows us to use our executable as a link library
  # therefore we can inherit all compiler options and library dependencies
  set_target_properties(example PROPERTIES ENABLE_EXPORTS on)

  target_link_libraries(unit_tests PUBLIC
    ${GTEST_BOTH_LIBRARIES}
    example
  )

  target_include_directories(unit_tests PUBLIC
    ${GTEST_INCLUDE_DIRS} # doesn't do anything on Linux
  )
endif()

###############################################################################
## packaging ##################################################################
###############################################################################

# all install commands get the same destination. this allows us to use paths
# relative to the executable.
install(TARGETS example DESTINATION example_destination)
# this is basically a repeat of the file copy instruction that copies the
# resources in the build directory, but here we tell cmake that we want it
# in the package
install(DIRECTORY resources DESTINATION example_destination)

# now comes everything we need, to create a package
# there are a lot more variables you can set, and some
# you need to set for some package types, but we want to
# be minimal here
set(CPACK_PACKAGE_NAME "MyExample")
set(CPACK_PACKAGE_VERSION "1.0.0")

# we don't want to split our program up into several things
set(CPACK_MONOLITHIC_INSTALL 1)

# This must be last
include(CPack)
holzkohlengrill
  • 642
  • 17
  • 26
Arne
  • 7,548
  • 7
  • 45
  • 64
  • How does this solve your self-imposed requirement of automatically adding a new module? – André Dec 01 '15 at 10:14
  • A module is in most cases just a logical separation of the project. So by default modules are collected by `file(GLOB_RECURSE ...)`. Remember the focus is on simplicity. – Arne Dec 01 '15 at 22:01
  • How does your `file(GLOB...)` work in relation to [this](http://stackoverflow.com/a/28251332/955273) answer, where they say *`file(GLOB..)` is BAD style and the CMake Docs for file explicitly say **Note: We do not recommend using GLOB to collect a list of source files from your source tree. If no CMakeLists.txt file changes when a source is added or removed then the generated build system cannot know when to ask CMake to regenerate.**?* [official docs here](https://cmake.org/cmake/help/v3.1/command/file.html?highlight=file) – Steve Lorimer Apr 06 '16 at 15:46
  • 10
    @SteveLorimer I just disagree, that file globbing is a bad style, I think manually copying the file tree into the CMakeLists.txt is a bad style becaue it is redundant. But I know that people do disagree on this topic, therefore I left a comment in the code, where you can replace the globbing with a list that contain all source files explicitly. Search for `set(sources src/main.cpp)`. – Arne Apr 06 '16 at 17:18
  • @Arne when the official documentation explicitly says don't do this, then I'm assuming they have a valid reason for it. I'm just starting out with cmake, so I don't know enough to make my own decision one way or the other. I assume you haven't had any adverse side effects of globbing the files? Do you ever had to re-invoke cmake itself? – Steve Lorimer Apr 06 '16 at 17:44
  • 4
    @SteveLorimer yes often did I have to invoke cmake again. Every time I add something in the directory tree, I need to reinvoke cmake manually, so that the globbing get's reevaluated. If you put the files in the `CMakeLists.txt`, then a normal make (or ninja) will trigger the reinvocation of cmake, so you can't forget it. It's also a bit team friendlier, because then the teammembers also can't forget to execute cmake. But I think a makefile should not need to be touched, just because someone added a file. Write it once, and nobody should need to think about it ever again. – Arne Apr 06 '16 at 22:34
  • 4
    @SteveLorimer I also disagree with the pattern to put one CMakeLists.txt in every directory of the projects, it just scatters the configuration the project everywhere, I think one file to do it all should be enough, otherwise you loose the overview, of what is actually done in the build process. That doesn't mean there can't be subdirectories with their own CMakeLists.txt, I just think it should be an exception. – Arne Apr 06 '16 at 22:41
  • @Arne Thanks for the write-up! But Coverage and Valgrind cmake instructions are missing with respect to GTest. – uss Jul 17 '17 at 14:37
  • @sree Thanks for you feedback. The reason those instructions are missing for GTest are simply I did not think about them, and I don't really use gtest anymore so I don't know. The question would be, is it straight forward to add coverage and valgrind instructions from now on and adding it to this minimal example would just clutter it, or would it really be an improvement? Pull requests to the gihub repository are welcome. – Arne Jul 18 '17 at 12:08
  • 1
    I'm going to have to side with @SteveLorimer here. GLOBbing files is not just bad. It's right out dangerous. It'll lead to situations, where changes to the source tree will fail to get noticed, and you'll be left with stale code. Exchanging correctness for convenience is a bad deal. – IInspectable Aug 07 '17 at 15:19
  • @IInspectable well feel free to disagree. There is a comment as an example that sets the sources manually. I don't understand how "changes to the source tree will fail to get noticed" because when there is a change in the source tree the VCS normally knows about that pretty well. And even if that would be an issue, the typical "new project" doesn't start with this requirement. And when the requirement is there, globbing can still be refactored to manual resource management. – Arne Aug 08 '17 at 08:49
  • 2
    Assuming *"VCS"* is short for *"version control system"*, then that is irrelevant. The issue is not, that artifacts won't get added to source control. The issue is, that CMake will fail to re-evaluate added source files. It will not re-generate build system input files. The build system will happily stick with the outdated input files, either leading to errors (if you are lucky), or going unnoticed, if you run out of luck. GLOBbing produces a gap in the dependency calculation chain. This *is* a significant issue, and a comment doesn't appropriately acknowledge this. – IInspectable Aug 08 '17 at 09:09
  • 1
    @IInspectable Correct me if I am wrong. The only way the dependencies can get outdated are is when there are new files added or removed from the folders. New files are either added manually or by the VCS (version control system). So when you get used to always rerun cmake when new files are added or operations on the VCS had happended, all dependencies will be calculated correctly. Of course with the price to run cmake more often than necessary. – Arne Aug 08 '17 at 18:32
  • 2
    CMake and a VCS operate in complete isolation. The VCS is unaware of CMake and CMake is unaware of any VCS. There is no link between them. Unless you suggest that developers should take manual steps, taking information out of the VCS, and based on some heuristic clean and rerun CMake. That doesn't scale, obviously, and is susceptible to the fallacy that is peculiar to humans. Nope, sorry, you haven't made a convincing point for GLOBbing files so far. – IInspectable Aug 08 '17 at 18:42
  • Well I don't need to convince you. But on the other side, neither did you convince me of anything. And discussing with you seems pointless, because you don't bring up arguments. Just give me an example or use case where my system "rerun cmake whenever VCS commands have changed files, or new files have been added manually" does not work. – Arne Aug 10 '17 at 11:24
  • @IInspectable After reading the comments, may I ask if it's okay to use GLOB for header files only? (for the IDE) meaning I would do still do the .cpp files manually. – Gam Mar 30 '18 at 14:17
  • @Gam: Off the top of my head, I can't think of a reason to even have header files show up in CMakeLists.txt files. If there is a valid reason, then GLOBbing header files has the same issues as GLOBbing any other files: CMake can no longer determine the necessity to re-generate build system input files. – IInspectable Mar 30 '18 at 14:48
  • @IInspectable I thought I gave you the reason, to make them show up in the IDE. Also, in visual studio, if you search something using "Entire Solution/Current Project" since the headers are not part of the solution, it won't find anything in those headers. I can't understand why it's a problem because those header are not compiled/used unless they are included in a .cpp file (that I'm adding manually, not GLOBbing) – Gam 11 mins ago – Gam Mar 30 '18 at 21:08
  • @Gam: Try your GLOBbing approach then, add a new header (outside of Visual Studio), and observe the result. The header is missing from the generated .vcxproj file, and cannot be searched until CMake is manually instructed to rebuild the project files. Which it would have done automatically (and only as far as strictly required), had you added the header file to your CMakeLists.txt file. – IInspectable Mar 30 '18 at 21:12
  • @IInspectable The automatically part is not that "automatic". Both approaches require you to deal with cmake in 2 manual steps. Create header file, run cmake or your approach: add X header files manually to your CMakeLists.txt (hoping it's only one) and ensuring you type it properly since windows is not case sensitive but linux is. I'm assuming you already know which way is faster and simpler to not get wrong. When everything is cached cmake is fast at generating project files and visual studio gently tells you that the solution has changed and asks you if you want to reload it... – Gam Mar 30 '18 at 22:29
  • @Gam: If you already have all the answers, why even ask? Yes, obviously, adding a file to a build system is a manual process. Sneaking it by your build system may or may not hurt, but I wouldn't take chances. And frankly, I haven't heard of any developer saying: "Nah, I'm not using `#include`-directives anymore. It's just way too demanding, to get the casing right." If you find yourself in that, however, you may as well GLOB *everything*. If you are worried about getting the casing wrong with your headers, you will get the casing wrong with your source files just as much. – IInspectable Mar 31 '18 at 07:18
  • I also would like to state that, for the readers of the project, having a CMakeLists of 2000 lines where 1800 are just references to source files isn't exactly a pleasant reading. – JoaoBapt Sep 12 '20 at 14:56
42

The most basic but complete example can be found in the CMake tutorial :

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

For your project example you may have:

cmake_minimum_required (VERSION 2.6)
project (MyProject)
add_executable(myexec src/module1/module1.cpp src/module2/module2.cpp src/main.cpp)
add_executable(mytest test1.cpp)

For your additional question, one way to go is again in the tutorial: create a configurable header file that you include in your code. For this, make a file configuration.h.in with the following contents:

#define RESOURCES_PATH "@RESOURCES_PATH@"

Then in your CMakeLists.txt add:

set(RESOURCES_PATH "${PROJECT_SOURCE_DIR}/resources/"
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/configuration.h.in"
  "${PROJECT_BINARY_DIR}/configuration.h"
)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

Finally, where you need the path in your code, you can do:

#include "configuration.h"

...

string resourcePath = string(RESOURCE_PATH) + "file.png";
Kevin
  • 14,269
  • 7
  • 44
  • 64
sgvd
  • 3,669
  • 17
  • 29
  • thank you very much, especially for the RESOURCE_PATH, somehow I did not get that the configure_file is what I was looking for. But you added all files from the project manually, is there a better way way to simply define a pattern in which all files are added from the src tree? – Arne Jan 16 '14 at 16:01
  • See Dieter's answer, but also my comments on why you shouldn't use it. If you really want to automate it, a better approach may be to write a script that you can run to regenerate the list of source files (or use a cmake aware IDE that does this for you; I am not familiar with any). – sgvd Jan 16 '14 at 16:37
  • 3
    @sgvd `string resourcePath = string(RESOURCE_PATH) + "file.png"` IMHO it's a bad idea to hardcode **absolute** path to source directory. What if you need to install your project? –  Jan 17 '14 at 07:13
  • The point is that it is not hard coded as such, it is determined at configuration time. You would indeed have to do something a bit smarter to set `RESOURCE_PATH` in your `CMakeLists.txt`, perhaps using `CMAKE_INSTALL_PREFIX` if building a Release version, or add the install path as another configured definition and test both at run time. But it is not hardcoded as in that you have to actually change your code when moving things arounf. – sgvd Jan 17 '14 at 09:57
  • 2
    I know automatically gathering sources sounds nice, but it can lead to all sorts of complications. See this question from a while ago for a brief discussion: http://stackoverflow.com/q/10914607/1401351. – Peter Jan 17 '14 at 14:42
  • I've summed it up and not gathering automatically all sources leads to more complications, for example on cpp file is missing in the cmake, but it is clearly in the project folder which leads in the end to undefined references that you have to track. – Arne Jan 17 '14 at 18:10
  • 2
    You get exactly the same error if you don't run cmake; adding files by hand takes one second one time, running cmake at every compile takes one second every time; you actually break a feature of cmake; somebody who works on the same project and pulls in your changes would do: runs make -> get undefined references -> hopefully remember to rerun cmake, or files bug with you -> runs cmake -> runs make successfully, whereas if you add file by hand he does: runs make successfully -> spends time with family. Sum that up, don't be lazy, and spare yourself and others a head ache in the future. – sgvd Jan 18 '14 at 13:18
  • yea maybe the real problem is C++, but that is off topic now. – Arne Jan 18 '14 at 22:00
  • @sgvd, Thanks for the write-up! But Coverage and Valgrind cmake instructions are missing with respect to GTest. – uss Jul 17 '17 at 14:52
4

Here I write a most simple but complete CMakeLists.txt files sample.

Source Code

  1. Tutorials from hello world to cross platform Android/iOS/Web/Desktop.
  2. Each platform I released a sample application.
  3. The 08-cross_platform file struct is verified by my work
  4. It may be not perfect but useful & best practice for a team on my own

After that, I offered document for the details.

If you have any questions, you can contact me and I'd like to explain it.

MinamiTouma
  • 381
  • 2
  • 10