03/02/2022
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
Simple Makefile example (POSIX compliant):
# Macro definition
SRCS = hello.c
# rule
hello: $(SRCS)
$(CC) $< -o $@More complete example:
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: cleanMacros 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:
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)| 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 |
| ... |
target [target...]: [prerequisite...][;command]
[<tab>command
<tab>command
...]Commands
Prefixed with a <tab>
Used to generate the target(s)
Some prefixes are available:
| 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" :
.c.o:
${CC} ${CFLAGS} -c $< -o $@New suffixes can be declared using the .SUFFIXES special rule :
.SUFFIXES: .k .jInternal macros
Generated by the tool
Can be used in rules
| MACRO | DEFINITION |
|---|---|
| $@ | Target name |
| $* | Target name without suffix |
| $< | First prerequisite |
| $? | All prerequisites newer than target |
| $+ | All prerequisites (GNU) |
| $^ | All prerequisites, duplicates removed (GNU) |
| ... |
Example "configure.ac" :
The project description file
# 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_OUTPUTVariables, defines and options :
# 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) :
# 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) :
# 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_RUNExample "Makefile.am" :
The simplified Makefile
bin_PROGRAMS = helloworld
helloworld_SOURCES = helloworld.cMost 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
bin_PROGRAMS = helloworld
helloworld_SOURCES = helloworld.cPrimaries :
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
bin_PROGRAMS = helloworld
helloworld_SOURCES = hello.c
# Preprocessor flags
helloworld_CPPFLAGS = -DPWET
# Compiler flags
helloworld_CFLAGS = -Wall
# Link dependencies
helloworld_LDADD = $(DEP_LIBS) libhelloworld.laBuilding a static library :
Primary : LIBRARIES
Destinations : noinst, lib, pkglib
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 PROGRAMSBuilding a libtool library :
Will be either static or dynamic depending on destination and platform
Primary : LTLIBRARIES
Destinations : noinst, lib, pkglib, libexec
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 LIBRARIESInstalling headers :
Primary : HEADERS
Destinations : include, pkginclude
Other suffixes : nobase (keep subdir prefix)
nobase_include_HEADERS = sys/types.hInstalling scripts :
Scripts are executables that doesn't need to be compiled
Primary : SCRIPTS
Destinations : bin, sbin, libexec, pkglibexec, pkgdata
dist_bin_SCRIPTS = my_scriptInstalling data :
Primary : DATA
Destinations : data, pkgdata, sysconf, sharedstate, localstate
Other suffixes : dist, nobase
nobase_dist_pkgdata_DATA = images/vortex.pgm sounds/whirl.oggCustom 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.oggSubdirectories :
Subdirectories are recursed in depth-first mode (entered before current directory is parsed)
'.' can be used to change the order
SUBDIRS = doc src testsConditionals :
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.ccPurpose :
Standardized module description
Installed in “/usr/lib/pkgconfig” on any unix-like platform
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}Name, description
Version information
cflags, ldflags, static/shared library information
extraneous information (data path, ...)
Generating "configure" and "Makefile.in" files :
# In the directory that contains configure.ac file:
autoreconf -vfiGenerating "Makefile" files :
# In-source build:
./configure
# Out-of-source build:
mkdir build; cd build
../configureEnabling/disabling options :
# List available options:
./configure --help
# Activating options
./configure --enable-opt1 --disable-opt2- -with-opt3=valueCompiling :
# Well written autotools files can use –j and run in parallel
make –j4Foreword : this is a training !
There are hands-on exercises at the end of each section
A working CMake (>= 3.17) and compiler installation
Preferably a Unix like system (Mac, Unix, Linux)
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)
CMake is a Makefile generator that supports:
UNIX, Borland, MSYS, MinGW, NMake, Ninja, Watcom Makefiles
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_minimum_required(VERSION 2.8)
project(test CXX)
add_executable(test test.c)Generating Makefiles :
# 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=ONCompiling :
# building, 4 jobs in parallel
make –j4
# display usage, edit cache
make help
make edit_cache
# debug CMake scripts
make VERBOSE=1Create your first CMake project
write an helloworld.c (1pt)
write your main CMakeLists.txt generating an executable (2pt)
compile in a build directory (2pt)
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_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
Variables are the basic unit of storage in CMake
# 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
function(test)
set(TEST "42")
set(TEST_EXTENDED "42" PARENT_SCOPE)
endfunction()
test()
message(STATUS "undefined: ${TEST}")
message(STATUS "defined: ${TEST_EXTENDED}")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”
# 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)Default built-in variables :
Complete list in man (7) cmake-variables
See also : cmake --help-variable CMAKE_SOURCE_DIR
| 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 …)
# 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 :
# 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
Be pedantic ! (5pt)
Add "-pedantic" to your C_FLAGS (mind the _)
It mustn't override CFLAGS from environment that CMake also uses !
Be nice ! (2pt)
Display "Building <project_name>" on CMake invocation
Hints :
Be organized ! (1pt)
Set your sources list in a variable and use it
(Possible) Solution
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 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
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 …
An example is worth a thousand words :
# 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 :
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 :
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()If there's a ".git" directory in your source dir (2pt)
display a message
(Possible) Solution
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
Functions creates a new scope, macros don't !
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")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)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)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 |
| ... |
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
(Possible) Solution
Note: command is ran at CMake execution time, not when calling make
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
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)
# creates a "test" target
add_executable(test test.c)CMake can compile executables :
# 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 :
# 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
# 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 :
# 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 :
# 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)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
(Possible) Solution
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_minimum_required(VERSION 2.8)
project(helloworld C)
add_executable(helloworld main.c)
set_target_properties(helloworld PROPERTIES
COMPILE_DEFINITIONS NDEBUG)Set your executable sources using properties (3pt)
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
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
Modules are libraries of functions
# 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
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_resourcesAdding custom 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
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)
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
(Possible) Solution
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
Finding libraries and headers :
# 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
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 :
# 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 createdFinding CMake pre-bundled dependencies
Uses Find* 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_resourcesCustom 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 :
(Possible) Solution
# 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
Makefile 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 :
| 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 :
| Name | Link |
|---|---|
| Man pagews | cmake (1), cmake-commands (7), cmake-modules (7) ... |
| CMake doc | https://cmake.org/documentation/ |