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

I am starting a new C project using CMake, so I created a directory structure very similar to the ones I use in Python (my "main" language). Although it compiles correctly, I'm not sure if I am doing it right. This is the current structure:

. β”œβ”€β”€ CMakeLists.txt β”œβ”€β”€ dist β”‚  └── # project will be built here, 'cmake ..' β”œβ”€β”€ extras β”‚  β”œβ”€β”€ CMakeLists.txt β”‚  β”œβ”€β”€ extra1 β”‚ β”‚  β”œβ”€β”€ CMakeLists.txt β”‚ β”‚  β”œβ”€β”€ extra1.h β”‚ β”‚  └── extra1.c β”‚  └── extra2 β”‚   β”œβ”€β”€ CMakeLists.txt β”‚   β”œβ”€β”€ extra2.h β”‚   └── extra2.c β”œβ”€β”€ src β”‚  β”œβ”€β”€ CMakeLists.txt β”‚  β”œβ”€β”€ main.c β”‚  β”œβ”€β”€ module1.h β”‚  β”œβ”€β”€ module1.c β”‚  β”œβ”€β”€ module2.h β”‚  └── module2.c └── test β”œβ”€β”€ CMakeLists.txt β”œβ”€β”€ test_module1.c └── test_module2.c 

Since all the files are distributed between several directories, I had to find a way to find the libraries that are present in extras , and those that I need to test in src . So these are my CMakeLists':

./CMakeLists.txt

 cmake_minimum_required(VERSION 2.8) project(MyProject) add_definitions(-Wall -std=c99) # I don't know really why I need this set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/dist) add_subdirectory(src) add_subdirectory(test) add_subdirectory(extras) enable_testing() add_test(NAME DoTestModule1 COMMAND TestModule1) add_test(NAME DoTestModule2 COMMAND TestModule2) 

./SRC/CMakeLists.txt

 macro(make_library target source) add_library(${target} ${source}) target_include_directories(${target} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) endmacro(make_library) make_library(Module1.o module1.c) make_library(Module2.o module2.c) 

./test/CMakeLists.txt

 macro(make_test target source library) add_executable(${target} ${source}) target_link_libraries(${target} Libtap.o ${library}) target_include_directories(${target} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) endmacro(make_test) make_test(TestModule1 test_module1.c Module1.o) make_test(TestModule2 test_module2.c Module2.o) 

./extra/CMakeLists.txt

 # Hopefully you'll never need to change this file foreach(subdir ${SUBDIRS}) add_subdirectory(${subdir}) endforeach() 

(Finally) ./ extras / libtap / CMakeLists.txt

 add_library(Libtap.o tap.c) target_include_directories(Libtap.o PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 

So now the question is : the reason I'm worried is that this setting will create a β€œpublic” library for every file I use, including additional libraries (which are not meant to be shared). If I have 10 libraries in src , 4 dependencies in extras (including libtap, which I use for testing) and at least as many test files, then I get 24 compiled artifacts.

  • Is there a better way to expose libraries for links?
  • I am not compiling the β€œmain” yet, what would be the correct configuration for this?
  • is add_definitions correct way to add flags to the compiler?
  • How can I make this structure DRY?
+5
source share
1 answer

Is there a better way to open libraries for links?

No, that seems beautiful.

However, you may need to reconsider the granularity with which you create static libraries. For example, if all applications except tests use only Module1 and Module2 in combination, you can combine them into one library target. Of course, the tests will contact parts of the component that they don’t use, but this is a small price to pay to reduce assembly complexity.

I am not compiling the "main" yet, what would be the correct configuration for this?

There is nothing wrong with adding it to src/CMakeLists.txt :

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

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

It may be used for this purpose, but may not be ideal.

Newer CMake scripts should use target_compile_options for this purpose. The only drawback here is that if you want to reuse the same compilation options for all the goals in your projects, you also need to make the same target_compile_options call for each of them. See below for tips on how to resolve this.

How can I make this structure DRY?

First of all, unlike most program codes, redundancy is often not a big problem in the system build code. A wonderful thing to pay attention to is things that interfere with maintainability. Returning to the general compiler options: before you want to change these flags in the future, it is likely that you want to change them for each purpose. It makes sense to centralize knowledge of options: either enter function at the top level, which sets an option for a given target, or save the parameters of a global variable.

In any case, you will need to write one line for each purpose in order to get this option, but after that it will not generate unnecessary maintenance costs. As an added bonus, if you really need to change the option for just one purpose in the future, you still have the opportunity to do so.

However, be careful not to overload things. The build system should do everything first.

If the easiest way is to set it up, it means you copy / paste a lot, follow it! If during servicing later it turns out that you have real unnecessary redundancies, you can always reorganize.

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

One little nitpick at the end: Avoid providing extensions to your target names. That is, instead of

add_library(Libtap.o tap.c)

consider

add_library(Libtap tap.c)

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

+1
source

Source: https://habr.com/ru/post/1202349/


All Articles