Makefiles, autotools & CMake
Sylvain Fargier (CERN)
03/02/2022
Introduction
- This document is only a brief introduction to give a gloal/overall understanding
- It is not a complete neither an exhaustive documentation
- Please refer to "References" sections for additional documentation
Introduction
-
Make:
-
Created in 1976, Still widely used
-
Three major flavors : BSD Make, GNU Make and Microsoft Nmake
-
Specified in POSIX standardization
-
-
Autotools :
-
Autoconf 1992, Automake 1994, Libtool 1996
-
Also known as « GNU build system », Tightly related to GCC collection
-
-
CMake :
-
First release in 2004, Cpack 2012
-
Meant to be cross-platform
-
Introduction
Makefiles
-
Simple Makefile example (POSIX compliant):
Makefiles : basics & examples
# Macro definition
SRCS = hello.c
# rule
hello: $(SRCS)
$(CC) $< -o $@-
More complete example:
Makefiles : basics & examples
EXE = test
SRC = test.c
OBJS = ${SRC:.c=.o}
all: ${EXE}
${EXE}: ${OBJS}
${CC} ${LDFLAGS} $< -o $@
.c.o:
${CC} ${CFLAGS} -c $< -o $@
clean:
@rm -rf ${OBJS} ${EXE}
.PHONY: cleanMakefiles: macros
Makefiles : macros
-
Macros basics :
-
Evaluated when used
-
Can be overridden when invoking make:
-
make CFLAGS="-O2"# Substitution is of the form ${NAME:patt=sub}
OBJS = ${SRC:.c=.o}
# Boths $() and ${} can be used to expand macros
${EXE}: $(OBJS)
${CC} ${LDFLAGS} $< -o $@-
Can be expanded and allows substitution:
Makefiles : macros
-
GNU extensions:
# POSIX affectation (deferred expansion)
FOO = bar
# Affect the variable if not already set (deferred exapnsion)
FOO ?= bar
# Expanded variables (immediate expansion)
FOO := bar
FOO ::= bar
# Append (deferred or immediate depending on previous affectation
# defaults to deferred)
FOO += bar
# BSD style shell function execution
FOO != echo world
FOO := $(shell echo world)- Predefined macros:
Makefiles : macros
| MACRO | DEFINITION |
|---|---|
| AR | Archiver name |
| ARFLAGS | Archiver flags |
| YACC | Parser name |
| YFLAGS | Parser flags |
| LEX | Lexer name |
| LFLAGS | Lexer flags |
| LDFLAGS | Linker flags |
| CC | Compiler |
| CFLAGS | Compiler flags |
| FC | Fortran compiler |
| FFLAGS | Fortran flags |
| MACRO (GNU) | DEFINITION |
|---|---|
| CXX | C++ compiler |
| CXXFLAGS | C++ flags |
| CPP | Preprocessor |
| CPPFLAGS | Preprocessor flags |
| LINT | Lint program |
| MAKEINFO | Texinfo converter |
| RM | Command to remove a file |
| ... |
Makefiles : rules
- Rules syntax:
- Targets:
- Most of the time targets are files to generate
- It can still be just a name and will be callable from the command-line (ex: clean)
- Prerequisites
- The target's dependencies
- Expanded when the target is evaluated
- Can be file names (this file's mod time will be automatically compared with the target)
- Commands
- Prefixed with a <tab>
- Used to generate the target(s)
Makefiles : rules
target [target...]: [prerequisite...][;command]
[<tab>command
<tab>command
...]-
Commands
-
Prefixed with a <tab>
-
Used to generate the target(s)
-
Some prefixes are available:
-
Makefiles : rules
| PREFIX | DESCRIPTION |
|---|---|
| - | Ignore errors |
| @ | Do not display command |
| + | Execute the command not regarding make execution mode (see -n, -q, -t arguments) |
-
Inference rules
-
Inference rules are rules that contains a '.'
-
Also called conversion rules
-
Used to convert files from one format to another using "suffixes" :
-
Makefiles : rules
.c.o:
${CC} ${CFLAGS} -c $< -o $@-
New suffixes can be declared using the .SUFFIXES special rule :
.SUFFIXES: .k .j-
Internal macros
-
Generated by the tool
-
Can be used in rules
-
Makefiles : rules
| MACRO | DEFINITION |
|---|---|
| $@ | Target name |
| $* | Target name without suffix |
| $< | First prerequisite |
| $? | All prerequisites newer than target |
| $+ | All prerequisites (GNU) |
| $^ | All prerequisites, duplicates removed (GNU) |
| ... |
Makefiles : exercise
- Exercise (15min) 10pts :
- Write the world's famous helloworld.c source file
- Write its associated Makefile to generate helloworld executable
- Do not rely on predefined inference rules
- Use an intermediate object file (.o)
Makefiles : exercise
-
Bonus
- Write the "clean" rule (1pt)
- Use a "template" makefile defining everything in macros (2pt)
- Write your own inference rule (2pt)
- Display how many times your project has been built (5pt)
Autotools
- Purpose :
- Check dependencies
- Manage options/conditionals
- Compile on any unix-like platform
- Standardize makefile rules, options definitions …
- Manage out of source builds
- A set of GNU utility programs :
- AutoConf : generates configure executable
- AutoMake : creates intermediate Makefiles
- Libtool : manages libraries
- Supports other tools :
- Pkg-config : handle package level dependencies
- Gettext : traduction files management
- ...
Autotools : basics & examples
- Global overview :
Autotools : basics & examples

Autotools : configure.ac
-
Example "configure.ac" :
-
The project description file
-
Autotools : configure.ac
# Project description
AC_INIT([amhello], [1.0], [bug-automake@gnu.org])
# Automake initialization (and options)
AM_INIT_AUTOMAKE([-Wall -Werror foreign])
# Check for a C compiler
AC_PROG_CC
# Generate a config.h file with options
AC_CONFIG_HEADERS([config.h])
# List of files to generate
AC_CONFIG_FILES([
Makefile
src/Makefile
])
# Do generate everything
AC_OUTPUT-
Variables, defines and options :
Autotools : configure.ac
# Define a macro in AC_CONFIG_HEADERS listed files
AC_DEFINE([EQUATION], ["$a > $b"], [Equation string.])
# This one will be set to 1
AC_DEFINE([CONDITIONAL])
# Declare an output variable
# any occurence of @VAR@ in intermediate files
# (.in files listed in AC_CONFIG_FILES) will be replaced by value
AC_SUBST(VAR, [value])
# Declare a Makefile conditional
AM_CONDITIONAL([MY_FEATURE], [test x$my_feature = xyes])
# Declaring an option
AC_ARG_ENABLE([feature], [My feature description],
[my_feature=$enableval], [my_feature=auto])
# Declaring an extrernal software choice
AC_ARG_WITH([package], [My external package description],
[m_package=$withval], [my_package=auto])-
Tests and checks (1/2) :
Autotools : configure.ac
# Check for a program :
# AC_PATH_PROG (variable, prog-to-check-for, [value-if-not-found], [path = ‘$PATH’])
AC_PATH_PROG([UIC], [uic], [no])
AS_IF([test "x$UIC" = xno], [AC_MSG_ERROR([Failed to find uic])])
# Check for a file :
AC_CHECK_FILE([/my/file], [my_file=true], [my_file=false])
# Check for a library
# setting HAVE_LIB$lib and adding the library to LIBS is default behavior
AC_CHECK_LIB([m], [atan], [AC_DEFINE([HAVE_LIBM]); LIBS="-lm $LIBS"; break])
# Check for a header
# setting HAVE_$hdr is default behavior
AC_CHECK_HEADER([math.h], [AC_DEFINE([HAVE_MATH_H])])
# Check for a type
# settings HAVE_$type is default behavior
AC_CHECK_TYPE([my_struct], [AC_DEFINE([HAVE_MY_STRUCT])])-
Tests and checks (2/2) :
Autotools : configure.ac
# A lot of other tests are also available, you can also check wether something
# compiles/link/runs :
AC_TRY_LINK(
[#include <time.h>
#ifndef tzname /* For SGI. */
extern char *tzname[]; /* RS6000 and others reject char **tzname. */
#endif],
[atoi (*tzname);],
[ac_cv_var_tzname=yes],
[ac_cv_var_tzname=no])
# Prefer AC_TRY_LINK and AC_TRY_COMPILE rather than AC_TRY_RUNAutotools : Makefile.am
-
Example "Makefile.am" :
-
The simplified Makefile
-
Autotools : Makefile.am
bin_PROGRAMS = helloworld
helloworld_SOURCES = helloworld.c-
Most of the work is done through variables
-
Makefile rules can be added in those files (should not be necessary)
-
Variables :
-
One "primary" that will be recognized by the tool
-
One or several prefixed that add some extra information
-
Autotools : Makefile.am
bin_PROGRAMS = helloworld
helloworld_SOURCES = helloworld.c-
Primaries :
-
Most used primaries are PROGRAMS, LIBRARIES, LTLIBRARIES, DATA, HEADERS, SCRIPTS, MANS ...
-
Other exists (PYTHON, lIST, JAVA ...)
-
At least one destination prefix is required (bin, lib, pkgdata ...)
-
-
Building a program :
-
Primary : PROGRAMS
-
Destinations : bin, sbin, libexec, pkglibexec
-
Autotools : Makefile.am
bin_PROGRAMS = helloworld
helloworld_SOURCES = hello.c
# Preprocessor flags
helloworld_CPPFLAGS = -DPWET
# Compiler flags
helloworld_CFLAGS = -Wall
# Link dependencies
helloworld_LDADD = $(DEP_LIBS) libhelloworld.la-
Building a static library :
-
Primary : LIBRARIES
-
Destinations : noinst, lib, pkglib
-
Autotools : Makefile.am
noinst_LIBRARIES = libhello.a
libhello_a_SOURCES = hello.c
# Link dependencies
libhello_a_LIBADD = $(DEP_LIBS)
# Compiler/Linker and other flags variables
# are the same than PROGRAMS-
Building a libtool library :
-
Will be either static or dynamic depending on destination and platform
-
Primary : LTLIBRARIES
-
Destinations : noinst, lib, pkglib, libexec
-
Autotools : Makefile.am
lib_LTLIBRARIES = libhello.la
libhello_la_SOURCES = hello.c
# Per library link flags, used to create modules
# define rpath, library version ...
libhello_la_LDFLAGS = -module
# Compiler/Linker and other flags variables
# are the same than LIBRARIES-
Installing headers :
-
Primary : HEADERS
-
Destinations : include, pkginclude
-
Other suffixes : nobase (keep subdir prefix)
-
Autotools : Makefile.am
nobase_include_HEADERS = sys/types.h-
Installing scripts :
-
Scripts are executables that doesn't need to be compiled
-
Primary : SCRIPTS
-
Destinations : bin, sbin, libexec, pkglibexec, pkgdata
-
dist_bin_SCRIPTS = my_script-
Installing data :
-
Primary : DATA
-
Destinations : data, pkgdata, sysconf, sharedstate, localstate
-
Other suffixes : dist, nobase
-
Autotools : Makefile.am
nobase_dist_pkgdata_DATA = images/vortex.pgm sounds/whirl.ogg-
Custom destination :
-
Custom destinations prefixes can be created by filling-in variables
-
imagesdir = $(pkgdatadir)/images
soundsdir = $(pkgdatadir)/sounds
dist_images_DATA = images/vortex.pgm
dist_sounds_DATA = sounds/whirl.ogg-
Subdirectories :
-
Subdirectories are recursed in depth-first mode (entered before current directory is parsed)
-
'.' can be used to change the order
-
Autotools : Makefile.am
SUBDIRS = doc src tests-
Conditionals :
-
Conditionals are declared in configure.ac using AM_CONDITIONAL
-
if TESTS
noinst_PROGRAMS = test_all
test_all_SOURCES = test_main.cc test_feature.cc
test_all_LDADD = $(CPPUNIT_LIBS)
test_all_CFLAGS = $(CPPUNIT_FLAGS)
endif
EXTRA_test_all_SOURCES = test_main.cc test_feature.ccAutotools : pkg-config
-
Purpose :
-
Standardized module description
-
Installed in “/usr/lib/pkgconfig” on any unix-like platform
-
Autotools : pkg-config
exec_prefix=${prefix}
libdir=/usr/lib64
sharedlibdir=${libdir}
includedir=${prefix}/include
Name: zlib
Description: zlib compression library
Version: 1.2.8
Requires:
Libs: -L${libdir} -L${sharedlibdir} –lz
Cflags: -I${includedir}- Contents
-
Name, description
-
Version information
-
cflags, ldflags, static/shared library information
-
extraneous information (data path, ...)
-
Autotools : usage
-
Generating "configure" and "Makefile.in" files :
Autotools : usage
# In the directory that contains configure.ac file:
autoreconf -vfi-
Generating "Makefile" files :
# In-source build:
./configure
# Out-of-source build:
mkdir build; cd build
../configure-
Enabling/disabling options :
Autotools : usage
# List available options:
./configure --help
# Activating options
./configure --enable-opt1 --disable-opt2- -with-opt3=value-
Compiling :
# Well written autotools files can use –j and run in parallel
make –j4CMake
-
Foreword : this is a training !
-
There are hands-on exercises at the end of each section
-
CMake : basics & examples
-
Requirements :
-
A working CMake (>= 3.17) and compiler installation
-
Preferably a Unix like system (Mac, Unix, Linux)
-
CMake : basics & examples
-
Recommendations :
-
No internet connection is required, referring to online documentation (or stackoverflow ...) is strongly discouraged
-
The only source of information should be (as advised in every exercise) :
-
cmake --help-command (--help-command-list)
-
cmake --help-variable (--help-variable-list)
-
cmake --help-module (--help-module-list)
-
-
- This is not a trap nor a competition
-
CMake is a Makefile generator that supports:
-
UNIX, Borland, MSYS, MinGW, NMake, Ninja, Watcom Makefiles
-
CMake : basics & examples
- But it also supports your preferred IDEs:
-
Visual Studio 6,7…12, Xcode, CodeBlocks, Eclipse CDT4, Kdevelop3, Kate, Sublime Text 2 …
-
-
And more:
-
Package bundling, installers, unit-tests …
-
-
Simple CMakeLists.txt example :
CMake : basics & examples
cmake_minimum_required(VERSION 2.8)
project(test CXX)
add_executable(test test.c)-
Generating Makefiles :
CMake : basics & examples
# out-of-source build
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
# debug build, with TESTS option turned on
cmake .. -DCMAKE_BUILD_TYPE=Debug -DTESTS=ON-
Compiling :
# building, 4 jobs in parallel
make –j4
# display usage, edit cache
make help
make edit_cache
# debug CMake scripts
make VERBOSE=1☟
CMake : basics & examples
☞ hands-on !
-
Create your first CMake project
-
write an helloworld.c (1pt)
-
write your main CMakeLists.txt generating an executable (2pt)
-
compile in a build directory (2pt)
-
CMake : usage ☞ hands-on !
-
Note: This step is a base for all incoming hands-on exercise (mandatory !)
-
Hints :
-
commands: cmake_minimum_required, project, add_executable (use cmake --help-command <cmd>)
-
-
(Possible) Solution
CMake : usage ☞ hands-on !
cmake_minimum_required(VERSION 3.17)
project(helloworld C)
add_executable(helloworld helloworld.c)mkdir build && cd build
cmake ..
make -j4
./helloworld#include <stdio.h>
int main()
{
puts("hello world !");
return 0;
}helloworld.c
CMakelists.txt
Shell commands
CMake : syntax
- CMake is a Makefile generator (keep this in mind) !
- Commands written in CMakeLists.txt are executed when invoking CMake (generation-time).
- It can also be used at build-time, being re-invoked by Make, but this is advanced usage (that we'll cover)
CMake : syntax & purpose
- There are only 2 syntactic elements in CMake files :
- variables
- commands (functions or macros)
CMake : syntax & purpose
- Functions can be regrouped in modules
- With this 3 concepts we've covered complete CMake syntax
- No worries it'll be explained in details
CMake : variables & cache
Variables are the basic unit of storage in CMake
CMake : variables & cache
# Set TEST variable to 42
set(TEST "42")
# Unset TEST
unset(TEST)-
To set/unset a variable :
-
To use a variable :
message(STATUS "TEST value is: ${TEST}")-
Variables are directory scoped by default
-
Modules inclusion (“include” function) doesn’t create a new scope
-
add_subdirectory function does create a new scope
-
Scope can be extended using “PARENT_SCOPE” option
-
CMake : variables & cache
function(test)
set(TEST "42")
set(TEST_EXTENDED "42" PARENT_SCOPE)
endfunction()
test()
message(STATUS "undefined: ${TEST}")
message(STATUS "defined: ${TEST_EXTENDED}")- Variables are also scoped by functions
- Not by macros
-
A global cache can also be used
-
Makes variables persistent and global
-
Used by “options” and most dependencies functions
-
Located in ${CMAKE_BINARY_DIR}/CMakeCache.txt
-
Can be edited using “ccmake” or “make edit_cache”
-
CMake : variables & cache
# Use FORCE to override if cache-entry already exist
# cache-entries are typed and requires a docstring
set(TEST "42" CACHE STRING "My test variable" FORCE)- To set a variable in cache :
-
Default built-in variables :
-
Complete list in man (7) cmake-variables
-
See also : cmake --help-variable CMAKE_SOURCE_DIR
-
CMake : variables & cache
| CMAKE_BINARY_DIR | The path to the top level of the build tree |
| CMAKE_SOURCE_DIR | The path to the top level of the source tree |
| CMAKE_CURRENT_SOURCE_DIR | The path to the source directory currently being processed |
| CMAKE_CURRENT_BINARY_DIR | The path to the binary directory currently being processed |
| ... |
-
Variables can also be treated as lists
-
CMake will manage lists using a ‘;’ separator in a regular string
-
Built-in functions are available to manipulate lists (list, foreach …)
-
CMake : variables & cache
# Create a 2 entry list
set(TEST "42" "44")
# Append 45 in our list
list(APPEND TEST "45")
# Iterate on the list
foreach(iter IN LISTS TEST)
message(STATUS "item ${iter}")
endforeach()-
One can also access environment variables :
CMake : variables & cache
# Print SHELL variable from env
message(STATUS "Got this from env: $ENV{SHELL}")-
Variables expansion is recursive :
set(OPT_arm "arm specific value")
set(ARCH "arm")
message(STATUS "Your opt is: ${OPT_${ARCH}}")☟
-
Advice :
-
do always quote a string when expanding a variable
-
CMake : variables & cache
☞ hands-on !
-
Be pedantic ! (5pt)
-
Add "-pedantic" to your C_FLAGS (mind the _)
-
It mustn't override CFLAGS from environment that CMake also uses !
-
CMake : variables & cache ☞ hands-on !
-
Be nice ! (2pt)
-
Display "Building <project_name>" on CMake invocation
-
-
Hints :
- variables: CMAKE_PROJECT_NAME, CMAKE_<LANG>_FLAGS (use cmake --help-variable "<var>")
- CMake lists uses ";" as a separator, gcc may not like it
-
Be organized ! (1pt)
-
Set your sources list in a variable and use it
-
-
(Possible) Solution
CMake : usage ☞ hands-on !
cmake_minimum_required(VERSION 3.17)
project(helloworld C)
message("Building ${CMAKE_PROJECT_NAME}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pedantic")
list(APPEND SRC helloworld.c)
add_executable(helloworld ${SRC})# From "build"
# Calling CMake is not needed
# it'll detect changes
# will show you more info
make VERBOSE=1
# your CFLAGS should also use env
# but value is cached
rm CMakeCache.txt
CFLAGS=-DTEST cmake ..
make VERBOSE=1CMakelists.txt
Shell commands
CMake : conditionals & loops
CMake : conditionals & loops
- No worries, there’ll be examples !
-
CMake has some constants evaluation rules:
False : 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, empty string, anything that ends with –NOTFOUND.
True : 1, ON, YES, TRUE, Y, or a non-zero number
Anything else will be tested as a variable name
- Variables are automatically expanded and tested
- The test will evaluate as true if the variable is defined and doesn’t contain one of the False values
CMake : conditionals & loops
-
Built-in conditional keywords:
-
Usual keywords are available: NOT, AND, OR
-
String comparison using STREQUAL, STRGREATER …
-
Numbers comparison using EQUAL, GREATER …
-
Versions comparison using VERSION_EQUAL …
-
- Extra keywords for specific use-cases:
- COMMAND <name> : true if name is a command, function or macro
- POLICY <name> : true if the given policy exist
- TARGET <name> : true if the given target exist
- DEFINED <name> : true if given variable is defiend (not evaluating its value)
- EXISTS, IS_DIRECTORY, IS_SYMLINK, IS_ABSOLUTE : don’t really have to explain
-
An example is worth a thousand words :
CMake : conditionals & loops
# Strings doesn't _need_ to be quoted, still those are strings
set(TEST1 True)
set(TEST2 False)
set(TEST3 "my value")
unset(TEST4)
if(TEST1 AND TEST3)
message(STATUS "this is the truth")
endif()
if(NOT TEST2 AND NOT TEST4)
message(STATUS "this is not false")
endif()-
Two examples is even better :
CMake : conditionals & loops
set(VERSION "1.3.4")
if(VERSION VERSION_LESS "1.5")
message(STATUS "Less than 1.5")
endif()
if(VERSION STREQUAL "1.3.4")
message(STATUS "Version is 1.3.4")
endif()-
One can loop on lists we saw before, or on items :
CMake : conditionals & loops
foreach(arg val1 val2 val3)
message(STATUS "arg: ${arg}")
endforeach()
# same than before but with "IN”
foreach(arg IN ITEMS val1 val2 val3)
message(STATUS "arg: ${arg}")
endforeach() set(MY_LIST val1 val2 val3)
foreach(arg IN LISTS MY_LIST)
message(STATUS "arg: ${arg}")
endforeach()☟
CMake : conditionals & loops ☞ hands-on !
-
If there's a ".git" directory in your source dir (2pt)
-
display a message
-
- If there's a TEST variable in your env (1pt)
- display a message
- Say something if you're on an UNIX system (1pt)
- Hints :
- command: if (use cmake --help-command <cmd>)
- variables: UNIX (use cmake --help-variable <variable>)
CMake : ☞ hands-on !
-
(Possible) Solution
CMake : usage ☞ hands-on !
cmake_minimum_required(VERSION 3.17)
project(helloworld C)
if(IS_DIRECTORY "${CMAKE_SOURCE_DIR}/.git")
message("This is a git repo")
endif()
if(DEFINED ENV{TEST})
message("You want me to test something ?")
endif()
if(UNIX)
message("<3")
endif()
CMakelists.txt
CMake : functions & macros
-
Functions creates a new scope, macros don't !
CMake : functions & macros
-
Both can use named or positional arguments :
-
Some special variables are available to access arguments (ARGC, ARGV{0, 1 …}, ARGN)
-
Note: macro arguments are not real variables (specific rules apply)
-
function(print message)
message(STATUS "message: ${message}")
foreach(arg IN LISTS ARGN)
message(STATUS "arg: ${arg}")
endforeach()
endfunction()
print("test" "33")CMake : functions & macros
-
A CMake built-in function exists to parse arguments
-
Getopt like
-
Used to be an external module on CMake <= 3.4
-
function(sample)
cmake_parse_arguments(local "BOOLOPT" "ONEVAL" "MULTI" ${ARGN})
if(local_BOOLOPT)
message(STATUS "BOOLOPT is enabled")
endif() message(STATUS "ONEVAL: ${local_ONEVAL}")
foreach(arg IN LISTS local_MULTI)
message(STATUS "MULTI arg: ${arg}")
endforeach()
endfunction()
sample(ONEVAL "val" MULTI "mval1" "mval2" BOOLOPT)CMake : functions & macros
-
Common usage :
-
optional arguments uses keywords <args...> syntax
-
project(test
LANGUAGES C CXX
VERSION "1.0.0"
DESCRIPTION "my awesome project"
HOMEPAGE_URL "http://xxx.nowhere")
add_library(test STATIC test.c)CMake : functions & macros
-
Builtin commands (functions or macros) :
-
Complete list in man (7) cmake-commands
-
See also : cmake --help-command execute_process
-
| Name | Description |
|---|---|
| project | declare your project |
| set, unset, list | manipulate variables |
| if, endif, foreach ... | control macros |
| add_library, add_executable | create a library or executable target |
| include_directories, link_libraries | use dependencies |
| install | install rule for files, targets, directories ... |
| execute_process | run an external command |
| ... |
☟
CMake : functions & macros
☞ hands-on !
-
Write a function that executes "date" command (3pt)
-
It must store its result in a variable (1pt)
-
Variable name is an argument (2pt)
-
Do it again with a macro !
-
Hints:
-
cmake --help-command function
-
cmake --help-command macro
-
cmake --help-command execute_process
-
CMake : ☞ hands-on !
-
(Possible) Solution
-
Note: command is ran at CMake execution time, not when calling make
-
CMake : ☞ hands-on !
cmake_minimum_required(VERSION 3.17)
project(helloworld C)
function(myfun NAME)
execute_process(
COMMAND date
OUTPUT_VARIABLE "${NAME}"
OUTPUT_STRIP_TRAILING_WHITESPACE)
set("${NAME}" "${${NAME}}" PARENT_SCOPE)
endfunction()
myfun(DATE)
message("Date is ${DATE}")
macro(mymacro NAME)
execute_process(
COMMAND date
OUTPUT_VARIABLE "${NAME}"
OUTPUT_STRIP_TRAILING_WHITESPACE)
endmacro()
mymacro(DATE2)
message("Date is ${DATE2}")CMakelists.txt
CMake : targets
-
Targets are used to declare build-time actions
-
Similar to Makefile rules : executed when calling make
-
Note: Everything else is done when calling CMake (generation-time)
-
CMake : targets
# creates a "test" target
add_executable(test test.c)-
CMake can compile executables :
- Or libraries :
# compile a static or shared library
add_library(myLib STATIC lib.c)
add_library(myLib SHARED lib.c)
# Use BUILD_SHARED_LIBS option to decide
# if it should be shared or static (defaults to static)
add_library(myLib lib.c)-
There are several built-in functions to manage targets :
CMake : targets
# link target with library or other target
target_link_libraries(myLib z pthread myOtherLib)
# include directories
target_include_directories(myLib PRIVATE ${CMAKE_SOURCE_DIR}/include)
# external include directories
target_include_directories(myLib SYSTEM PRIVATE ${MYDEP_INCLUDE_DIRS})
# any target depending on this one will also inherit the include directories
# it would do the same with INTERFACE option
target_include_directories(myLib PUBLIC ${CMAKE_SOURCE_DIR}/include)-
The same functions also exist at directory level :
-
But their use should be deprecated
-
CMake : targets
# deprecated in the docs
link_libraries(z pthread myOtherLib)
# include directories
include_directories("${CMAKE_SOURCE_DIR}/include")
# external include directories
include_directories(SYSTEM "${MYDEP_INCLUDE_DIRS}")-
Custom targets can also be created :
CMake : targets
# Custom target
add_custom_target(rpm-package
COMMAND "${CPACK_COMMAND}" -G RPM
COMMENT "Build an rpm package")
# More complex target that runs a command
add_custom_target(git_check ALL DEPENDS .git_version)
add_custom_command(
OUTPUT .git_version
COMMAND sh -c "git log --pretty=format:'' | wc -l | tr -d ' ' > .git_version"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
DEPENDS "${CMAKE_SOURCE_DIR}/.git" VERBATIM)-
Installing generated files :
CMake : targets
# install files generated with the given target
# components will be used when packaging the software
install(TARGETS myLib
LIBRARY DESTINATION "lib" COMPONENT Runtime
ARCHIVE DESTINATION "lib" COMPONENT Devel)
# install can also install files
install(FILES test.h DESTINATION include COMPONENT Devel)☟
CMake : targets ☞ hands-on !
-
Install your helloworld binary (2pt)
-
Use an intermediate static library (2pt)
-
Split your program in 3 files (helloworld.c/.h main.c)
-
-
Generate a .stamp file with current date (3pt)
-
must be callable with : make stamp
-
-
Hints :
-
commands: install, add_library, target_link_libraries, add_custom_target (use cmake --help-command <cmd>)
-
to test your install: make install DESTDIR=$(pwd)/instroot
-
CMake : targets ☞ hands-on !
-
(Possible) Solution
CMake : usage ☞ hands-on !
cmake_minimum_required(VERSION 3.17)
project(helloworld C)
set(SRCS helloworld.c helloworld.h)
add_library(worldlib STATIC ${SRCS})
add_executable(helloworld main.c)
target_link_libraries(helloworld worldlib)
add_custom_target(stamp
COMMAND sh -c "date '+%s' > .stamp"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
VERBATIM)
add_dependencies(helloworld stamp)
install(TARGETS helloworld RUNTIME)make install \
DESTDIR=$(pwd)/instroot
make stamp#include <stdio.h>
void hello()
{
puts("hello world !");
}helloworld.c
CMakelists.txt
Shell commands
#include "helloworld.h"
int main()
{
hello();
return 0;
}helloworld.h
#ifndef HELLOWORLD_H__
#define HELLOWORLD_H__
void hello();
#endifmain.c
CMake : properties
- Properties are "variables" (kind of)
- bound to a target, file or directory (or global)
- can be accessed with set_property/get_property
- or dedicated versions set_target_properties/get_target_property ...
- Most built-in commands manipulate properties (link_libraries, include_directories ...)
CMake : properties
cmake_minimum_required(VERSION 2.8)
project(helloworld C)
add_executable(helloworld main.c)
set_target_properties(helloworld PROPERTIES
COMPILE_DEFINITIONS NDEBUG)☟
CMake : properties
☞ hands-on !
-
Set your executable sources using properties (3pt)
CMake : properties ☞ hands-on !
-
Hints :
-
properties : SOURCES (use cmake --help-property <prop>)
-
commands : set_target_properties (use cmake --help-command <cmd>)
-
lists are ";" separated strings in CMake (if you have more than a file)
-
-
(Possible) Solution
CMake : usage ☞ hands-on !
-
No worries, properties are really advanced topic, one can use CMake without being a property expert !
cmake_minimum_required(VERSION 3.17)
project(helloworld C)
add_executable(helloworld)
set_target_properties(helloworld PROPERTIES
SOURCES "main.c;helloworld.c;helloworld.h")CMakelists.txt
CMake : modules
-
Modules are libraries of functions
CMake : modules
# check that the given flag works with the compiler
include(CheckCCompilerFlag)
check_c_compiler_flag("-march=ivybridge" ARCH_IVYBRIDGE_SUPPORT)
# use custom modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
include(Dependencies)
include(GitVersion)-
Modules can be used using "include" function :
-
Loaded from directories listed in CMAKE_MODULE_PATH
-
See man (7) cmake-modules for standard modules documentation
-
-
Find* modules are available for major frameworks and libraries :
-
Those modules are used by find_package function
-
CMake : modules
find_package(Qt5Core REQUIRED)
find_package(Qt5Gui REQUIRED)
# It created some standard variables
message(STATUS "Qt5Core includes: ${Qt5Core_INCLUDE_DIRS}")
message(STATUS "Qt5Core libraries: ${Qt5Core_LIBRARIES}")
# Also added convenient functions like qt5_add_resources-
Adding custom modules
CMake : modules
cmake_minimum_required(VERSION 3.17)
project(helloworld C)
# add "cmake" to the modules path
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_SOURCE_DIR}/cmake")
# filename of your "cmake/MkName.cmake" file
include(MkName)
CMakeLists.txt project file
-
CMake script mode (-P)
-
Using CMake as a build tool can be really convenient
-
A common pattern to run commands at compile-time
-
CMake : modules
if(NOT CMAKE_SCRIPT_MODE_FILE)
# adding a mkname target in project
add_custom_target(mkname
COMMAND "${CMAKE_COMMAND}"
-DNAME="${CMAKE_PROJECT_NAME}"
-P "${CMAKE_CURRENT_LIST_FILE}")
else()
# script mode
execute_process(
COMMAND echo "hello ${NAME}")
message(STATUS "Writing name file")
file(WRITE .name "name: ${NAME}")
endif()cmake/MkName.cmake (self-calling script)
cmake_minimum_required(VERSION 3.17)
project(helloworld C)
# add "cmake" to the modules path
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_SOURCE_DIR}/cmake")
# filename of your "cmake/MkName.cmake" file
include(MkName)
CMakeLists.txt project file (from previous slide)
-
More elaborate example :
-
Generating a git badge (with external dependency on shields.io)
-
CMake : modules
if(NOT CMAKE_SCRIPT_MODE_FILE)
add_custom_target(git-badge DEPENDS git_badge.svg)
add_custom_command(
OUTPUT git_badge.svg
COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_LIST_FILE}"
-DGIT_WORKDIR="${CMAKE_SOURCE_DIR}" VERBOSE=1
DEPENDS "${CMAKE_SOURCE_DIR}/.git")
else()
find_package(Git REQUIRED)
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe --dirty --long --tags --always
WORKING_DIRECTORY "${GIT_WORKDIR}"
OUTPUT_STRIP_TRAILING_WHITESPACE
OUTPUT_VARIABLE GIT_DESCRIBE)
string(REPLACE "-" "--" GIT_BADGE "${GIT_DESCRIBE}")
file(DOWNLOAD
"https://img.shields.io/badge/version-${GIT_BADGE}-informational.svg"
"${CMAKE_CURRENT_BINARY_DIR}/git_badge.svg")
message(STATUS "Git badge generated !")
endif()☟
-
Check for "-march=native" compiler flag support (1pt)
-
and add it to your CFLAGS
-
-
Generate a header at build time (5pt)
-
Containing a define with date-stamp
-
Using a custom CMake module
-
-
Hints :
-
module: CheckCCompilerFlag (use cmake --help-module <module>)
-
commands: file, execute_process, add_custom_target, add_dependencies (use cmake --help-command <cmd>)
-
Note: CMake -D options must come before -P on the command line
-
CMake : modules ☞ hands-on !
-
(Possible) Solution
CMake : modules ☞ hands-on !
cmake_minimum_required(VERSION 3.17)
project(helloworld C)
include(CheckCCompilerFlag)
check_c_compiler_flag("-march=native" MARCH_NATIVE)
if(MARCH_NATIVE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native")
endif()
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_SOURCE_DIR}/cmake")
include(GenHeader)
set(SRCS helloworld.c helloworld.h genheader.h)
add_library(worldlib STATIC ${SRCS})
target_include_directories(worldlib
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
add_executable(helloworld main.c)
target_link_libraries(helloworld worldlib)#include <stdio.h>
#include "genheader.h"
void hello()
{
puts("hello world !");
puts(DATE);
}helloworld.c
CMakelists.txt
if(NOT CMAKE_SCRIPT_MODE_FILE)
add_custom_target(genheader
COMMAND "${CMAKE_COMMAND}"
-P "${CMAKE_CURRENT_LIST_FILE}"
BYPRODUCTS genheader.h)
else()
execute_process(
COMMAND date
OUTPUT_VARIABLE DATE
OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "Writing genheader.h file")
file(WRITE genheader.h "#define DATE \"${DATE}\"")
endif()cmake/GenHeader.cmake
CMake : dependencies
-
Finding libraries and headers :
CMake : dependencies
# Looking for a library
find_library(CURL_LIBRARIES curl)
# Looking for a directory containing curl.h file, proving "curl" as a suffix hint
find_path(CURL_INCLUDE_DIRS curl.h PATH_SUFFIXES curl)
if(NOT CURL_LIBRARIES OR NOT CURL_INCLUDE_DIRS)
message(SEND_ERROR "Failed to find curl")
endif()-
Using it :
add_library(mylib STATIC mylib.c)
target_link_libraries(mylib ${CURL_LIBRARIES})
# SYSTEM so that -isystem will be used and won't pollute linters
# PRIVATE or PUBLIC depending on whereas other targets using that one
# do need the include
target_include_directories(mylib SYSTEM PRIVATE ${CURL_INCLUDE_LIBRARIES})-
Dependencies for CMake heroes !
-
CMake >= 3 recommended way
-
CMake : dependencies
find_path(SSL_INCLUDE_DIRS NAMES openssl/md5.h)
find_library(SSL_LIBRARIES NAMES ssl)
if(NOT SSL_INCLUDE_DIRS OR NOT SSL_LIBRARIES)
mesage(SEND_ERROR "Failed to find openssl")
endif()
# Creating an imported target
# UNKNOWN since we don't know if static or shared library has been found
add_library(OPENSSL UNKNOWN IMPORTED)
# Add properties to the target
set_target_properties(OPENSSL PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${SSL_INCLUDE_DIRS}"
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${SSL_LIBRARIES}")
-
Using it :
add_library(mylib STATIC mylib.c)
# will automatically add includes and others
target_link_libraries(mylib SSL)-
Using pkg-config to find packages :
CMake : dependencies
# This requires an external module
include(FindPkgConfig)
# Look for cppunit pkg-config file
pkg_check_modules(CPPUNIT cppunit REQUIRED)
# CPPUNIT_LIBRARIES and CPPUNIT_INCLUDE_DIRS variables
# are automatically created-
Finding CMake pre-bundled dependencies
-
Uses Find* modules
-
CMake : dependencies
find_package(Qt5Core REQUIRED)
find_package(Qt5Gui REQUIRED)
# It created some standard variables
message(STATUS "Qt5Core includes: ${Qt5Core_INCLUDE_DIRS}")
message(STATUS "Qt5Core libraries: ${Qt5Core_LIBRARIES}")
# Also added convenient functions like qt5_add_resources-
Custom Find* modules can be implemented
-
Automatically found by find_package when in CMAKE_MODULES_PATH
-
A FindPackageHandleStandardArgs module helper is available
-
☟
-
Write your very own FindPWET module
-
must find curl.h header
-
must link to libcurl.so library
-
for CMake-heroes : must declare an imported target
-
-
Hints :
- module: FindPackageHandleStandardArgs (use cmake --help-module <module>)
- commands: find_path, find_library, include (use cmake --help-command <cmd>)
CMake : dependencies ☞ hands-on !
-
(Possible) Solution
CMake : dependencies ☞ hands-on !
# Use different names than exported
# user can then force some parts
find_path(PWET_INCLUDE_DIR NAMES curl.h PATH_SUFFIXES curl)
find_library(PWET_LIBRARY curl)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PWET
REQUIRED_VARS PWET_LIBRARY PWET_INCLUDE_DIR)
# All or nothing
if(PWET_FOUND)
set(PWET_LIBRARIES "${PWET_LIBRARY}")
set(PWET_INCLUDE_DIRS "${PWET_INCLUDE_DIR}")
add_library(PWET UNKNOWN IMPORTED)
set_target_properties(PWET PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${PWET_INCLUDE_DIRS}"
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
IMPORTED_LOCATION "${PWET_LIBRARIES}")
endif()cmake/FindPWET.cmake
cmake_minimum_required(VERSION 3.17)
project(helloworld C)
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_SOURCE_DIR}/cmake")
find_package(PWET REQUIRED)
set(SRCS helloworld.c helloworld.h)
add_library(worldlib STATIC ${SRCS})
target_link_libraries(worldlib PWET)
add_executable(helloworld main.c)
target_link_libraries(helloworld worldlib)CMakelists.txt
CMake : CTest & CPack
- Two major modules comes along with CMake :
- CTest : to declare unit-tests and implement “make test”
- CPack : to bundle app/lib in rpm, installer …
CMake : CTest & CPack
- This training is big enough without those parts, read the docs if you’re interested !
References
-
Makefile references :
References
| Name | Link |
|---|---|
| POSIX spec | http://pubs.opengroup.org/onlinepubs/009695399/utilities/make.html |
| GNU Make | http://www.gnu.org/software/make/manual/make.html |
| BSD Make | http://www.khmere.com/freebsd_book/html/ch01.html |
| Microsoft NMake | http://msdn.microsoft.com/en-us/library/dd9y37ha.aspx |
-
Autotools references :
References
| Name | Link |
|---|---|
| Automake doc | https://www.gnu.org/software/automake/manual/automake.html |
| Autoconf doc | http://www.gnu.org/software/autoconf/manual/autoconf.html |
| General overview | http://devmanual.gentoo.org/general-concepts/autotools/ |
-
CMake references :
References
| Name | Link |
|---|---|
| Man pagews | cmake (1), cmake-commands (7), cmake-modules (7) ... |
| CMake doc | https://cmake.org/documentation/ |
Makefiles, autotools & CMake
By Sylvain fargier
Makefiles, autotools & CMake
- 690