Difference between revisions of "Working with CMake"

From Inkscape Wiki
Jump to navigation Jump to search
Line 34: Line 34:
 
Create an empty directory and open a file named 'CMakeLists.txt' in your favorite text editor.
 
Create an empty directory and open a file named 'CMakeLists.txt' in your favorite text editor.
  
     mkdir cmake-tutorial
+
     $ mkdir cmake-tutorial
     cd cmake-tutorial
+
     $ cd cmake-tutorial
     gedit CMakeLists.txt
+
     $ gedit CMakeLists.txt
  
 
Type the following lines into this CMakeLists.txt file:
 
Type the following lines into this CMakeLists.txt file:
Line 50: Line 50:
 
     -- Generating done
 
     -- Generating done
 
     -- Build files have been written to: /tmp/cmake-tutorial
 
     -- Build files have been written to: /tmp/cmake-tutorial
 +
 +
CMake will automatically recognize "CMakeLists.txt" as its config file.  CMake assumes that the current directory is where we want all our stuff built to.  The '.' argument to the cmake command tells CMake to look in the current directory for source files.
 +
  
 
== Pre-defined Variables ==
 
== Pre-defined Variables ==
Line 120: Line 123:
 
== Conditionals ==
 
== Conditionals ==
  
If statements are pretty fundamental in all languages, but particularly so for configuring software.  Here's how to do an if statement in CMake:
+
'If' statements are pretty fundamental in all languages, but particularly so for configuring software.  Here's how to do an if statement in CMake:
 +
 
 +
    set(MYVAR "foobar")
  
 
     if(MYVAR STREQUAL "foobar")
 
     if(MYVAR STREQUAL "foobar")
Line 126: Line 131:
 
     endif()
 
     endif()
  
Couple things to note - there's none of the usual ==, <, etc. boolean operators.  Instead use EQUAL and LESS for numerical comparisons, STREQUAL, and STRLESS for strings.  Other frequently used tests include EXISTS, DEFINED, AND, OR, and NOT.  MATCHES provides a handy way to do regular expression testing on strings. VERSION_EQUAL, VERSION_LESS, and VERSION_GREATER are helpful for testing release version strings. There's a bunch of tests relating to checking status on files.  For a complete list of available unary and binary tests see https://cmake.org/cmake/help/v3.0/command/if.html.
+
    if(FOOBAR)
 +
        message("Hello world!")
 +
    elseif(NOT DEFINED FOOBAR)
 +
        message("Your FOOBAR is fubar.")
 +
    else()
 +
        message("Goodbye, cruel world...")
 +
    endif()
  
 +
Couple things to note - there's none of the usual ==, <, etc. boolean operators.  Instead use EQUAL and LESS for numerical comparisons, STREQUAL, and STRLESS for strings.  Other frequently used tests include EXISTS, DEFINED, AND, OR, and NOT.  MATCHES provides a handy way to do regular expression testing on strings.  VERSION_EQUAL, VERSION_LESS, and VERSION_GREATER are helpful for testing release version strings.  There's a bunch of tests relating to checking status on files.  Also notice in the second if statement, that FOOBAR's value of "yes" is automatically recognized as a true value.  "no" is a false value.
  
 +
For a complete list of available unary and binary tests and what CMake recognizes as true and false values, see https://cmake.org/cmake/help/v3.0/command/if.html.
  
  
 
== Lists ==
 
== Lists ==
 +
 +
CMake strives to make handling lists of strings straightforward and easy.  Basically, it handles lists as semi-colon delimited strings, and automatically handles converting back and forth:
 +
 +
    set(FOOLIST a b c d e)
 +
    message("${FOOLIST}")
 +
 +
Prints out
 +
 +
    a;b;c;d;e
 +
 +
Iterating through the items can be done with a for loop:
 +
 +
    cmake_minimum_required(VERSION 2.8)
 +
    set(FOOLIST a b c d e)
 +
    foreach(v ${FOOLIST})
 +
        message("${v}")
 +
    endforeach()
 +
 +
Prints out
 +
 +
    a
 +
    b
 +
    c
 +
    d
 +
    e
 +
 +
 +
== CMake Setup ==
 +
 +
Did you notice in that last example how we had a cmake_minimum_required() call?
 +
 +
CMake has grown in functionality since it was originally first introduced, and the cmake_minimum_required() let's us specify what level of compatibility our script expects.  After all, the version of cmake installed on different users' systems is going to vary considerably so this is a very basic thing to check.
 +
 +
I don't know if there is a website somewhere that indicates what cmake functionality is available in which cmake versions.  However, cmake seems to be good about warning us when we use something that requires a specific version of cmake.  Obviously we don't want to set the version *too* recent or else users will be unable to run cmake to build Inkscape!
 +
 +
There are a variety of commands to initialize CMake with various preferences.  Usually these are all handled when you first create a CMake project, and likely you'll just cut and paste from some other project.  Here's a basic template CMakeLists.txt:
 +
 +
    cmake_minimum_required(VERSION 2.8.0)
 +
    set(CMAKE_BUILD_TYPE_INIT "Release")
 +
    project(my_project)

Revision as of 23:33, 17 January 2016

This is a developer-oriented introduction to CMake and how to modify the CMake build scripts.

We assume you already know the basics of how to use cmake to configure and build Inkscape (if not, see Inkscape's README).


Functions and Variables

cmake has its own language and syntax, but it's pretty simple. Essentially, everything is a function call.

   message("Hello world!")

Even just setting variables is a function call:

   set(MYVAR "foobar")

Function calls have parenthesis but arguments aren't separated with commas. CMake isn't strict about casing for function names, so SET() and set() are equivalent, but it is strict about variable name casing. We'll adopt the convention of keeping function names lower case, and variable names upper case.

Strings don't always have to be quoted, although it's also a good convention to follow. So, these are all equivalent:

   SET(MYVAR "foobar")
   set(MYVAR foobar)
   SET(MYVAR foobar)

Variables are referenced using a dollar sign and curly brackets:

   set(MYVAR "foobar")
   message("${MYVAR}")


Your First CMake Script

You now know enough to create a trivial cmake program.

Create an empty directory and open a file named 'CMakeLists.txt' in your favorite text editor.

   $ mkdir cmake-tutorial
   $ cd cmake-tutorial
   $ gedit CMakeLists.txt

Type the following lines into this CMakeLists.txt file:

   set(MYVAR "foobar")
   message("${MYVAR}")

Save the text file, exit your editor, and run cmake in this directory:

   $ cmake .
   foobar
   -- Configuring done
   -- Generating done
   -- Build files have been written to: /tmp/cmake-tutorial

CMake will automatically recognize "CMakeLists.txt" as its config file. CMake assumes that the current directory is where we want all our stuff built to. The '.' argument to the cmake command tells CMake to look in the current directory for source files.


Pre-defined Variables

As you can imagine, CMake provides a broad set of pre-defined variables that relate to build systems.

It has a bunch to help determine what platform the build is building for:

   UNIX
   APPLE
   WIN32
   MSVC
   etc.

and a slew of variables to keep track of directories that things should go to:

   PROJECT_SOURCE_DIR
   CMAKE_SOURCE_DIR
   CMAKE_CURRENT_SOURCE_DIR
   PROJECT_BINARY_DIR
   CMAKE_BINARY_DIR
   CMAKE_CURRENT_BINARY_DIR
   CMAKE_INSTALL_PREFIX

Let's take a closer look at these:

   message("PROJECT_NAME              ${PROJECT_NAME}")
   message("PROJECT_SOURCE_DIR        ${PROJECT_SOURCE_DIR}")
   message("CMAKE_SOURCE_DIR          ${CMAKE_SOURCE_DIR}")
   message("CMAKE_CURRENT_SOURCE_DIR  ${CMAKE_CURRENT_SOURCE_DIR}")
   message("PROJECT_BINARY_DIR        ${PROJECT_BINARY_DIR}")
   message("CMAKE_BINARY_DIR          ${CMAKE_BINARY_DIR}")
   message("CMAKE_CURRENT_BINARY_DIR  ${CMAKE_CURRENT_BINARY_DIR}")
   message("CMAKE_INSTALL_PREFIX      ${CMAKE_INSTALL_PREFIX}")

For me this prints out:

   PROJECT_NAME              Project
   PROJECT_SOURCE_DIR        /tmp/cmake-tutorial
   CMAKE_SOURCE_DIR          /tmp/cmake-tutorial
   CMAKE_CURRENT_SOURCE_DIR  /tmp/cmake-tutorial
   PROJECT_BINARY_DIR        /tmp/cmake-tutorial
   CMAKE_BINARY_DIR          /tmp/cmake-tutorial
   CMAKE_CURRENT_BINARY_DIR  /tmp/cmake-tutorial
   CMAKE_INSTALL_PREFIX      /usr/local

The 'source' vs. 'binary' variables are the same if the user is doing an in-tree build but differ if out-of-tree. Generally, if you do out-of-tree builds yourself, you can be reasonably confident your code will work either way.

The 'current' variables come into play when you have a directory hierarchy with CMakeList.txt files at various levels. Variables like CMAKE_CURRENT_SOURCE_DIR refer to the currently processing child location in the tree, whereas CMAKE_SOURCE_DIR will always reference the root of the source tree.

Also, be aware that the user is able to set their own installation prefix, so be careful about assumptions about where things will be installed.

Let's try building our example in a more complex way to see how these variables change:

   $ mkdir build install
   $ cd build && cmake .. -DCMAKE_INSTALL_PREFIX=../install
   ...
   PROJECT_NAME              Project
   PROJECT_SOURCE_DIR        /tmp/cmake-tutorial
   CMAKE_SOURCE_DIR          /tmp/cmake-tutorial
   CMAKE_CURRENT_SOURCE_DIR  /tmp/cmake-tutorial
   PROJECT_BINARY_DIR        /tmp/cmake-tutorial/build
   CMAKE_BINARY_DIR          /tmp/cmake-tutorial/build
   CMAKE_CURRENT_BINARY_DIR  /tmp/cmake-tutorial/build
   CMAKE_INSTALL_PREFIX      /tmp/cmake-tutorial/install
   ...


Conditionals

'If' statements are pretty fundamental in all languages, but particularly so for configuring software. Here's how to do an if statement in CMake:

   set(MYVAR "foobar")
   if(MYVAR STREQUAL "foobar")
       set(FOOBAR "yes")
   endif()
   if(FOOBAR)
       message("Hello world!")
   elseif(NOT DEFINED FOOBAR)
       message("Your FOOBAR is fubar.")
   else()
       message("Goodbye, cruel world...")
   endif()

Couple things to note - there's none of the usual ==, <, etc. boolean operators. Instead use EQUAL and LESS for numerical comparisons, STREQUAL, and STRLESS for strings. Other frequently used tests include EXISTS, DEFINED, AND, OR, and NOT. MATCHES provides a handy way to do regular expression testing on strings. VERSION_EQUAL, VERSION_LESS, and VERSION_GREATER are helpful for testing release version strings. There's a bunch of tests relating to checking status on files. Also notice in the second if statement, that FOOBAR's value of "yes" is automatically recognized as a true value. "no" is a false value.

For a complete list of available unary and binary tests and what CMake recognizes as true and false values, see https://cmake.org/cmake/help/v3.0/command/if.html.


Lists

CMake strives to make handling lists of strings straightforward and easy. Basically, it handles lists as semi-colon delimited strings, and automatically handles converting back and forth:

   set(FOOLIST a b c d e)
   message("${FOOLIST}")

Prints out

   a;b;c;d;e

Iterating through the items can be done with a for loop:

   cmake_minimum_required(VERSION 2.8)
   set(FOOLIST a b c d e)
   foreach(v ${FOOLIST})
       message("${v}")
   endforeach()

Prints out

   a
   b
   c
   d
   e


CMake Setup

Did you notice in that last example how we had a cmake_minimum_required() call?

CMake has grown in functionality since it was originally first introduced, and the cmake_minimum_required() let's us specify what level of compatibility our script expects. After all, the version of cmake installed on different users' systems is going to vary considerably so this is a very basic thing to check.

I don't know if there is a website somewhere that indicates what cmake functionality is available in which cmake versions. However, cmake seems to be good about warning us when we use something that requires a specific version of cmake. Obviously we don't want to set the version *too* recent or else users will be unable to run cmake to build Inkscape!

There are a variety of commands to initialize CMake with various preferences. Usually these are all handled when you first create a CMake project, and likely you'll just cut and paste from some other project. Here's a basic template CMakeLists.txt:

   cmake_minimum_required(VERSION 2.8.0)
   set(CMAKE_BUILD_TYPE_INIT "Release")
   project(my_project)