DEV Community

Cover image for Learning CMake 2: working with targets
Israel Blancas
Israel Blancas

Posted on • Updated on

Learning CMake 2: working with targets

In the previous post, we learnt how to build a really basic application. For that, we created a CMake target using the add_executable CMake macro. During this post, I will explain some extra things can we do with targets.

Compiler definitions

Sometimes you will need to build software that is using definitions. Example:

#include <stdio.h>
int main()
{
   #ifdef AWESOME
      char *message = "YOU ARE AWESOME!\n";
   #else
      char *message = "Sorry, you are not awesome\n";
   #endif
   printf("%s", message);

   return 0;
}
Enter fullscreen mode Exit fullscreen mode

This code is using the AWESOME definition. If we provide to the compiler the definition AWESOME the final application will print "YOU ARE AWESOME!". Otherwise, it will print "Sorry, you are not awesome". How can we handle this situation from CMake?

cmake_minimum_required(VERSION 3.5)
project(example)

add_executable(my_app
    "${CMAKE_CURRENT_SOURCE_DIR}/myapp.c"
)

target_compile_definitions(my_app
    PRIVATE
        AWESOME
)
Enter fullscreen mode Exit fullscreen mode

As before, we created one target (that will build an executable) using the source files. Then, we provide the definitions needed by that target using the target_compile_definitions.

There are other methods to the compiler calls like add_definitions but they are discouraged. Why? Using target_compile_definitions we will apply the definitions just to the desired targets and add_definitions will ad them to all the targets.

The PRIVATEkeyword references to the visibility of the definition. There are 3 visibility modes:

  • PRIVATE: used when the definitions will be applied just for the current target.
  • INTERFACE: used when the definitions need to be applied to targets that link against the given target.
  • PUBLIC: it is the union between the INTERFACE and PRIVATE vibilities. It will use the given definitions to build the current target and also targets that link against the current target.

INTERFACE and PUBLIC visibility modes are useful for libraries.

Include directories

Now, let's imagine we need to build a software where the folder structure is this one:

.
├── include
│   └── awesome.h
└── src
    └── myapp.c
Enter fullscreen mode Exit fullscreen mode

The file src/myapp.c contains:

#include "awesome.h"

int main()
{
   print_awesome();
   return 0;
}
Enter fullscreen mode Exit fullscreen mode

And include/awesome.h:

#ifndef _awesome_h
#define _awesome_h

#include <stdio.h>

void print_awesome() {
    printf("AWESOME MESSAGE\n");
}

#endif
Enter fullscreen mode Exit fullscreen mode

We need to add the include directory as part of the build of the application. How can this be done? It is really similar to how we add a compiler definition:

cmake_minimum_required(VERSION 3.5)
project(example)

add_executable(my_app
    "${CMAKE_CURRENT_SOURCE_DIR}/src/myapp.c"
)

target_include_directories(my_app
    PRIVATE
        "${CMAKE_CURRENT_SOURCE_DIR}/include"
)
Enter fullscreen mode Exit fullscreen mode

The visibility parameters has the same meaning than in the case of target_compile_definitions.

Target properties

The macros shown during the previous sections will modify the target properties. You can access these properties using the get_target_property CMake macro. Also, you can set some of these properties with the set_target_properties CMake macro.

You will find the complete list of properties in this link. Some of then can be modified and others only will apply depending on different situations like the operating system or the generator used to create the build system.

Pay special attention to these properties where the token <CONFIG> is present: these variables will be used in multiconfiguration generators: depending on the configuration used to build or generate the build system. For instance, the property OUTPUT_NAME_<CONFIG> is used to the base name for output files created for an executable or library target. It can exists as OUTPUT_NAME_RELEASE and OUTPUT_NAME_DEBUG

Recap

What did we learn?

  • How to work with targets
  • The use of target_compile_definitions and target_include_directories
    • How the visibility works for these CMake macros
  • How to get and modify the target properties values

Top comments (3)

Collapse
 
raghav profile image
Raghav Jha • Edited

Hello Israel Blancas
Can you explain more on CMake and also on Make. I think 4 Series on CMake is not enough. Your explanation is simple and clear. Can you add more series in CMake and Make it will good for new developers.

Thanks for the blog.

Collapse
 
bhupesh profile image
Bhupesh Varshney 👾 • Edited

I have been waiting for a CMake series
and here it is 👌
would you convert it to a DEV Blog Series?

Collapse
 
iblancasa profile image
Israel Blancas

Hi! I am almost new in dev.to and I didn't know about the "series" feature. Yes, I'll convert these posts to a "DEV Blog Series" :)

Thanks for your comment.