Chapter 2

Basic CMake Syntax

Designing correct CMake configuration files is easy. The process is base on a series of files, called CMakeLists.txt (to which will refer as CMakeLists in this book), inside each directory in the project. The files constain the commands describing the process of construction of the project in CMake simple script language.

Cmake works with a command based procedural script language, in which each command is translated into a series of scripts commands spcialized in the targeted platform. The CMake commands have a basic structure of the form:

command( args... )

When command is the name of the function we try to call and args are a white-space separated list of values which are passed to the function at the moment of execution. If one of the arguments we are trying to pass contains a white space, the entire argument should be passed as a string between double quotes.

Unlike most programming languages, CMake is case insensitive, which implies that the following variants, showing some combinations of capital and lower case letters, are considered correct and the same function.

command( args... )
COMMAND( args... )
Command( args... )
CoMmand( args... )

Comments

Comments in CMake start with the charater #, all the characters after it are considered part fo the comment. Below is shown how comments can appear inside a CMakeLists file

# This is a comment which covers an entire line
command( args... )  # This line comment works simmilar to comments in the Python Language

Variable declaration and access

Variable declaration in CMake is done wising the command

set( <Variable name> values... )

Where values is a white space separated list, and can represent a single value or a list of values. For example to set the variable number_cpu with the single value 2, we use the function:

set( number_cpu 2 )

If we want to set the variable version to the list of values 1 0 2 that could represent the version 1.0.2 of our software ( Could also be stored as single value 1.0.2) we use the function:

set( version 1 0 2 )

Note: It's not possible to set multiples variables at the same time using the function set() as of CMake 3.0.2, meaning the next line doesn't work as we intended:

# Sets the value *"version 2 1 0 2"* in the variable *number_cpu*
set( number_cpu version 2 1 0 2)

To access the value of a variable previously set, we use the sintax:

${ <Variable Name> }

For example to access the number cpus and the version of our program that we set previously we use:

${ number_cpu }
${ version }

The variables can be used as arguments for functions and they get expanded before the function is executed. For example in the next piece of code the variable project_name is set at the begining of the CMakeLists and is used later in the program inside other function:

# Begining of the CMakeLists
set( project_name "CMake Guide" )

...

command( ${ project_name } )

Environment Variables

A project that is built in multiple platforms will require sooner or later access to specific values and configurations of them. In that case, CMake allows access to environment variables and Windows registry values.

The syntax to access environment values and Windows registry values are:

# Accesing environmental values is easy, you only require the name
# of the variable as is listed in your operating system
$ENV{ <Variable name> }

# Accessing Windows registries is more complicated, the path of
# the registry value is required as is listed in Windows. Notice
# the square brackets and the semicolon
[ <Registry path>;key ]

For example, if you want to access the current shell used in the operating system the code would be the following:

# For Linux, Mac Os and Unix
$END{ SHELL }

# For Windows
[ HKEY_LOCAL_MACHINE \SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon;Shell ]

Conditional syntax

One of the great points of CMake is that it allows the construction of native build files based on specific conditions, which means that there must be a form to test if a condition is fulfilled.

Conditional statements allows for the test of conditions in the platform and the selection of functions to be executed depending on the result of the test allowing the creation of more general build files which function in a wider range of uses cases.

The syntax used for an if condition is:

if( condition )
    command_1( args... )
else()
    command_2( args... )
endif()

CMake basic special functions

CMake has a set of special functions which are unique to the language designed to help the tool to build your project, the most basic ones are listed below:

  1. project: Indicates the name of the resulting workspace
project( <Project Name> )
  1. add_executable: Adds an executable target to the build process and associates the files from which the binary would be generated
add_executable( <Binary Name> files... )
  1. cmake_minimum_required: Ensures that the CMake version used to build the current project is greater than or equal to the one required as a param. If the current version of CMake is lower than that required it will stop processing the project and report an error.
    cmake_minimum_required( <Version> )
    

Basic Syntax Example

To merge everything stated in the chapter together and to show how a basic CMake program should look like, we'll develop a simple program ( we are supposing the user is in Linux, but windows shouldn't be different in such a basic use of the tool ).Let suppose we have the following C++ program

Note: The source code of the following example and all the other examples in the book can be found in the directory examples of the main project in github

//
// Hello.h
//
#ifndef HELLO_H
#define HELLO_H

#include<string>
std::string helloWorld() {
    // This function may seem like an overkill for such a simple example,
    // but it will be necessary for future examples
    return std::string("Hello World")
}

#endif
//
// Hello.cpp
//
#include "Hello.h"
#include<iostream>

int main(int argc, char** argv) {

    std::cout << helloWorld() << std::endl;
    return 0;
}

In the same directory where we have created our file Hello.cpp, we are going to create a file called CMakeLists.txt used by CMake to create the Makefile ( Nmake or Visual Basic project files in Windows ).

# CMakeLists.txt

# We are using version 3.0.2 of CMake for this example
cmake_minimum_required( VERSION 3.0.2 )

# Workspace is HelloWorld, at the end you should see a 
# HelloWorld directory witht the project on it
project( HelloWorld )

set( HELLO_HEADERS Hello.h )
set( HELLO_SRCS Hello.cpp )

# Link the sources with the final binary. The name of the binary
# is going to be "hello"
add_executable( hello ${HELLO_SRCS} ${HELLO_HEADERS} )

Finally we can run the following command in the linux command line ( The command and flags used are explained in the future in other chapters ):

$ cmake CMakeLists.txt

The result of the command should look something similar to the text presented below, where everything between "< >" could be different depending on the computer where you are building the project:

-- The C compiler identification is GNU <C compiler version>
-- The CXX compiler identification is GNU <Cpp compiler version>
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: <Path where the build files were generated>

If you view your directory, you will notice the some directories and files were created, in future chapters will learn how to move those files to specific directories instead of having them inside the main directory, but right now is not important, two of them are important for us at the moment cmake_install.cmake and Makefile (If you are in Linux, Nmake or Visual Studio project if your are in Windows).

The files were generated by CMake (as you may notice by how complex the generated Makefile for such a simple project) and are used in the next step to finish the construction of our project. cmake_install.cmake will be used by the cmake tool to execute the Makefiles to generate the final version of our binary. Running the command

$ cmake --build .

The flag '--build' tells cmake to run all the makefiles and create all the binary outputs in the directory that we specify ( '.' at the moment which means current directory )

Should generate the next output

Scanning dependencies of target hello
[100%] Building CXX object CMakeFiles/hello.dir/Hello.cpp.o
Linking CXX executable hello
[100%] Built target hello

Now in the directory we should se a binary named "hello", which we can be executed

$ ./hello
Hello World

results matching ""

    No results matching ""