Preferred Cmake Project Structure

preferred cmake project structure

First I have to admit that I agree with @Tsyvarev. Your CMake environment should fit to your processes/workflow and should take project sizes and team structure into account. Or generally speaking the environment CMake will be used in. And this tends to be - in a positive way - very alive.

So this part of your question is difficult to answer and I'll concentrate on the technical part:

  1. CMake has to know the location of the dependencies - relative or absolute - by

    • having a monolithic source tree (the one you don't want anymore)

      • CMake share library with multiple executables
      • CMake: How to setup Source, Library and CMakeLists.txt dependencies?
    • a common directory location for includes/libraries/binaries

      • Custom Directory for CMake Library Output
      • cmake install not installing libraries on windows
    • getting the paths via config files/variable definitions

      • How can I get cmake to find my alternative boost installation?
      • How to add_custom_command() for the CMake build process itself?
    • using registration in or installation from a database provided on the host

      • Making cmake library accessible by other cmake packages automatically
      • cmake wont run build_command in ExternalProject_Add correctly
  2. To keep your CMake files as simple as possible I would recommend to group your CMake code into separate dedicated files:

    • Prefer toolchain files over if(SomeCompiler) statements
    • Move common/repeating code parts as function() bodies into a shared CMake include file
    • Move complex non-target specific code parts into their own (CMake) script files

Example Code

Since you have specifically asked for the find_package() variant, taking Use CMake-enabled libraries in your CMake project and the things listed above:

MyCommonCode.cmake

cmake_policy(SET CMP0022 NEW)

function(my_export_target _target _include_dir)
file(
WRITE "${CMAKE_CURRENT_BINARY_DIR}/${_target}Config.cmake"
"
include(\"\$\{CMAKE_CURRENT_LIST_DIR\}/${_target}Targets.cmake\")
set_property(
TARGET ${_target}
APPEND PROPERTY
INTERFACE_INCLUDE_DIRECTORIES \"${_include_dir}\"
)
"
)

export(
TARGETS ${_target}
FILE "${CMAKE_CURRENT_BINARY_DIR}/${_target}Targets.cmake"
EXPORT_LINK_INTERFACE_LIBRARIES
)
export(PACKAGE ${_target})
endfunction(my_export_target)

C/CMakeLists.txt

include(MyCommonCode.cmake)
...
my_export_target(C "${CMAKE_CURRENT_SOURCE_DIR}/include")

B/CMakeLists.txt

include(MyCommonCode.cmake)

find_package(C REQUIRED)
...
target_link_libraries(B C)
my_export_target(B "${CMAKE_CURRENT_SOURCE_DIR}/include")

A/CMakeLists.txt

include(MyCommonCode.cmake)

find_package(B REQUIRED)
...
target_link_libraries(A B)

This keeps all 3 build environments separate, only sharing the relatively static MyCommonCode.cmake file. So in this approach I have so far not covered your first point, but would recommend the use of a external script to chain/trigger your build steps for A/B/C.

Is there a better way to structure a C project with cmake?

Is there any better way to expose libraries to linking?

No, this seems fine.

You might however want to reconsider the granularity at which you create static libraries. For example, if all applications except the tests will only ever use Module1 and Module2 in combination, you might want to merge them into a single library target. Sure, the tests will link against parts of the component that they do not use, but that is a small price to pay for the decrease in build complexity.

I'm not compiling "main" yet, what would be the right configuration
for that?

There's nothing wrong with adding it to the src/CMakeLists.txt as well:

add_executable(my_main main.c)
target_link_libraries(my_main Module1.o Module2.o)

is add_definitions the right way to add flags to the compiler?

It can be used for that purpose, but might not be ideal.

Newer CMake scripts should prefer the target_compile_options command for this purpose. The only disadvantage here is that if you want to reuse the same compile options for all targets in your projects, you also have to do the same target_compile_options call for each of those. See below for tips on how to resolve that.

How can I make this structure more DRY?

First of all, unlike most program code, redundancy is often not that big an issue in build system code. The notable thing to look out for here is stuff that gets in the way of maintainability. Getting back to the common compiler options from before: Should you ever want to change those flags in the future, it is likely that you want to change them for every target. Here it makes sense to centralize the knowledge about the options: Either introduce a function at the top-level that sets the option for a given target, or store the options to a global variable.

In either case you will have to write one line per target to get the option, but it will not generate any maintenance overhead after that. As an added bonus, should you actually need to change the option for only one target in the future, you still have the flexibility to do so.

Still, take care not to overengineer things. A build system should first and foremost get things done.

If the easiest way to set it up means you copy/paste a lot, go for it! If during maintenance later it turns out that you have some real unnecessary redundancies, you can always refactor.

The sooner you accept the fact that your CMake scripts will never be as pretty as your program code, the better ;)

One small nitpick at the end: Avoid giving your target names extensions. That is, instead of

add_library(Libtap.o tap.c)

consider

add_library(Libtap tap.c)

CMake will automatically append the correct file ending depending on the target platform anyway.

How do I set up a CMake project with subfolders?

Can you please tip me if this project/cmake structure is best practice or not?

There are none, or endless, best practices, and every day someone invents a new one. Especially as to how to structure your project, which is unrelated to CMake. Structure it in a way that you want, and you judge is the best. Your structure seems completely fine.

Look a look at endless google results. What's a good directory structure for larger C++ projects using Makefile? and http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1204r0.html project.

As for CMake you can take a look at the ultimate https://github.com/friendlyanon/cmake-init , https://github.com/cmake-lint/cmake-lint , https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1 .

where i should use link_directories() and target_include_directories().

Generally you should prefer target stuff, i.e. target_link_directories over non-target.

Use target_include_directories to add a path to #include <thishere> search path.

Use target_link_directories to add a library path to the search path target_link_libraries(... this_library_here). Usually you want to use add_library(... IMPORTED), then find_library, instead of target_link_directories. See man ld.

In your project there are no external shared .so nor static .a libraries. I see no reason to use link_directories at all.

how can I keep my codebase tidy and compile my project?

Well, you can work hard and cleanup your project often. Remember about regular exercise, sleep and to eat healthy.


Instead of set(CMAKE_CXX_STANDARD 11) prefer target_set_properties(. .. CXX_STANDARD 11).

How should I organize my directory structure when using CMake with C++ inheritance?

If your intention is to build two libraries, and an executable, then that is what your structure achieves. I would however recommend thinking why you are doing that, and perhaps consider whether it would be simpler to instead have both classes in a single library.

If I keep extending this, say make another class that inherits from Derived, then I have to make a new CMakeLists.txt file then link and include against these files. I don't have a problem doing this, but it does not seem very maintainable for larger projects

No, you don't have to make a new CMakeLists.txt unless you add a new target. You don't necessarily need more than a single target for a project.

A target (be it executable or a library) is not limited to having only one translation unit.

My personal preference for small project is three (or two) targets: A library containing all functionality, a main executable with int main() that does nothing except invoke the library (if the project is an executable rather than just the library), and a test executable with int main() from a test framework. The library can optionally be split to smaller pieces if it has parts that are reusable in other projects.


set(CMAKE_CXX_STANDARD 11)

prefer target specific properties instead:

set_target_properties(
Base PROPERTIES
CXX_STANDARD 11

# use unless you also support older standards
CXX_STANDARD_REQUIRED ON
)

The dependees will inherit the property, but you can set it explicitly in each for potentially lesser chance of breakage in case you later decide to not use the base library.

How to properly organize a CMake project with multiple subdirectories?

You used find_library yet you did not use ${MyLib} variable. Consult find_library documentation.

You used link_directories, yet it's local to the directory it was used in. Upper directories are unaffected.

Use an IMPORTED library or an INTERFACE library to link with .a file. See https://stackoverflow.com/a/10550334/9072753 and https://stackoverflow.com/a/41909627/9072753 answers. If it's one library only, I think prefer IMPORTED. Move target_include_directories(Source ...) to library/CMakeLists.txt and instead of Source add include directories to your library, it's conceptually the library include directory.

I recommend, subjective, depens: move target_include_directories(Source.. inside source/CMakeList.txt. I.e. keep all Source related stuff inside source/CMakeLists.txt. And maybe add EXCLUDE_FROM_ALL to add_subdirectory(library).

Use set_target_properties instead of set(CMAKE_CXX_STANDARD. Overall, prefer to solely use *target* specific stuff instead of set(...).

Directory Structure for Library CMake Projects

If you want to access your headers like this:

#include <matrix.hpp>

then you need to include directories. I suggest you the next:

INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/Hello/includes)

Your mistake is that you never defines Hello_INCLUDE_DIRS variable in global scope, only in local. So your include do nothing. CMAKE_SOURCE_DIR is always defined and safe to use for includes. Thus you need either define Hello_INCLUDE_DIRS in global scope or use INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/Hello/includes).

Usage is up to the developer as always. I prefer the structure:

+ Project Root
+-> Build
+-> Documentation
+-> Include
+-> Source
+-> Test
+ CMakeLists.txt

Then I can preset in root CMakeLists.txt next useful variables:

SET(Project_Include_Dir         "${CMAKE_SOURCE_DIR}/Include")
SET(Project_Source_Dir "${CMAKE_SOURCE_DIR}/Source")


Related Topics



Leave a reply



Submit