loading...
Cover image for Learning CMake 2: working with targets

Learning CMake 2: working with targets

iblancasa profile image Israel Blancas Updated on ・3 min read

Learning CMake (4 Part Series)

1) Learning CMake 1: a really quick introduction to CMake 2) Learning CMake 2: working with targets 3) Learning CMake 3: creating custom targets 4) Learning CMake 4: finding things

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;
}

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
)

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

The file src/myapp.c contains:

#include "awesome.h"

int main()
{
   print_awesome();
   return 0;
}

And include/awesome.h:

#ifndef _awesome_h
#define _awesome_h

#include <stdio.h>

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

#endif

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

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

Learning CMake (4 Part Series)

1) Learning CMake 1: a really quick introduction to CMake 2) Learning CMake 2: working with targets 3) Learning CMake 3: creating custom targets 4) Learning CMake 4: finding things

Posted on Oct 14 '19 by:

iblancasa profile

Israel Blancas

@iblancasa

Software Engineer at Real Time Innovations. Lead organizer at Google Developer Group Granada.

Discussion

markdown guide
 

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

 

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.