1

I'm new to CMake so I apologize if my question turns out to be a noob one.

I'm trying to set up a project in C++ with a directory structure similar to what Maven would create if I was coding in Java (src directory and build directory):

root
├── build (where to build)
├── CMakeLists.txt (main one)
├── compile_commands.json -> ./build/compile_commands.json
├── doc
├── Doxyfile
└── src
    ├── CMakeLists.txt
    ├── common
    │   └── Terminal.hpp
    ├── fsa
    │   ├── CMakeLists.txt
    │   ├── Machine.cpp
    │   ├── Machine.hpp
    │   └── MachineState.hpp
    └── main.cpp

I don't know how to properly set CMake so to recognize, compile and link all the files. In particular, I think I should use (a mix of) add_subdirectory(), add_executable(), link_directories(), target_link_libraries(), add_library() and target_include_directories(), but I'm not sure I got how.

I provide later my CMakeLists.txt files, but when I configure and compile, I get:

/usr/bin/ld: fsa/libfsalib.a(Machine.cpp.o): in function `Machine::addState(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, bool)':
Machine.cpp:(.text+0xd1): undefined reference to `MachineState::MachineState(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool)'
collect2: error: ld returned 1 exit status
make[2]: *** [src/CMakeFiles/elr1.dir/build.make:98: src/elr1] Error 1
make[1]: *** [CMakeFiles/Makefile2:115: src/CMakeFiles/elr1.dir/all] Error 2
make: *** [Makefile:91: all] Error 2

What do I do wrong? EDIT: turned out it was a very stupid mistake of mine, I forgot to add an implementation. However, some questions remain:

  • Can you please tip me if this project/cmake structure is best practice or not?
  • I dind't get the where i should use link_directories() and target_include_directories().
  • More in general, how can I keep my codebase tidy and compile my project? Thanks in advance

My commands are

to configure: "cmake -S /path_to_root_project -B /path_to_root_project/build -D CMAKE_EXPORT_COMPILE_COMMANDS=ON"
to compile: "cmake --build /path_to_root_project/build"

root_project/CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)

# set the project name
project(elr1 VERSION 0.1)

set(CMAKE_CXX_STANDARD 11)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

add_subdirectory(src)

root_project/src/CMakeLists.txt:

add_subdirectory(fsa)

# add the executable
add_executable(elr1 main.cpp)

link_directories(fsa)

target_link_libraries(elr1 #target in which link
  fsalib #library name
  )

root_project/src/fsa/CMakeLists.txt:

add_library(fsalib #name
   Machine.cpp #files
   MachineState.hpp
   )

 target_include_directories(fsalib PUBLIC
  ${CMAKE_CURRENT_SOURCE_DIR}
  )
  • So where is `MachineState::MachineState(std::string, bool)` _defined_? Is there no `MachineState.cpp`? – KamilCuk Dec 29 '21 at 15:38
  • You're right, there wasn't, my mistake. Now the error went away. My bad! I was so focused on structuring the project that I forgot it **could be** a sincere linker error. I'll edit my question, but the question about CMake commands and best practice remains. – Alessandro Bertulli Dec 29 '21 at 15:56
  • 1
    "the question about CMake commands and best practice remains." - There is no "best practice" in organizing project. In CMake you can use almost any layout of project files. – Tsyvarev Dec 29 '21 at 15:58
  • There might not be best, but there are good ones, such as not creating folders unless you absolutely must, while lettingone `CMakeLists.txt` to handle ONLY a single folder and only ever using `add_subdirectory` on deeper ones instead of referencing a bunch of files from separate places. Example: `project/tests/CMakeLists.txt`, `project/CMakeLists.txt` can now add an option to optionally `add_subdirectory(tests)` which leads to test build generator either running or completely ignored without even being parsed by CMake. –  Dec 29 '21 at 17:14
  • `(target_)link_directories()` should only be used in rare circumstances. It adds directories for the linker to search for library files. For linking targets built by the same project, this is unnecessary; for external projects that provide an cmake import script, this isn't necessary either. For external libs that do not provide cmake configuration files this may be an option, but in general I prefer creating an imported target instead... – fabian Dec 29 '21 at 18:16
  • @Sahsahae why using subfolders is a bad idea? I would say that using `add_subdirectoriy()` is in fact better, since it automatically execute the `CMakeLists.txt` of that directory (?), thus achievingmodularity – Alessandro Bertulli Dec 30 '21 at 18:46
  • @AlessandroBertulli nowhere did I write that it is a bad idea. –  Dec 30 '21 at 19:58
  • @Sahsahae I'm sorry, maybe I misinterpreted. You said "but there are good ones, such as not creating folders unless you absolutely must". Can you elaborate that further, please? – Alessandro Bertulli Dec 31 '21 at 11:30
  • @fabian could you please explain it a little further? I thought `target_link_libraries` was the go-to choice when building little, self-contained projects. was I wrong? – Alessandro Bertulli Jan 03 '22 at 15:29
  • @AlessandroBertulli I'm not criticising the use of `target_link_libraries`; `link_directories` (whether target-specific or not) combined with the use of the library name tells the linker "here's one directory where you may find the lib", but you've got very limited control, which lib is actually linked, if there are multiple libs with the name to be found in different link dirs. Therefore my preferrence is the use of absolute paths which can be specified for imported targets and imported targets also allow you to add info about other required properties, like include directories. – fabian Jan 03 '22 at 17:44

1 Answers1

1

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).

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thanks a lot! Just a minor thing: in the footnote, did you mean `set_target_properties`? – Alessandro Bertulli Jan 03 '22 at 14:47
  • Moreover @KamilCuk, would you say that `find_library` is better than `target_link_directories`? If yes, why? I'm sorry if I seem pedantic, but I find the CMake documentation not providing really best practices. – Alessandro Bertulli Jan 03 '22 at 15:27
  • `did you mean set_target_properties?` Yes `find_library is better than target_link_directories?` To make sure that the library is really there, and you link with the proper one. It's rarely that you need to import like 20 libraries - usually it's one single third party lib. – KamilCuk Jan 03 '22 at 16:30
  • thanks a lot, it makes sense. It is best practice when dealing with imported libraries. In my project, I use it only to modularize compilation: actually, all of the sources are written by me, so I don't have a library to "import". I simpy create a library for every subdirectory, and then link everything to the main executable. In that case, I assume `target_link_libraries` is the best way to go, and that I don't even need `target_link_directories`, since the location is managed by CMake. – Alessandro Bertulli Jan 03 '22 at 17:34