Makefile 和 CMake 之間的轉換和實作

http://mqjing.blogspot.tw/2009/09/cmake-how-to-use-cmake.html
CMake 常用變數

簡介

CMake用來產生跨平台的專案建置文件,在windows下它會生成visual studio的專案檔(.sln) codeblock eclipse,在linux下它會生成Makefile。類似工具有autotools和qmake,不過autotools太複雜,qmake只限於qt使用。

CMake用更抽象的語法來組織項目。例如用math表示數學庫,而不需要再具體指定到底是math.dll還是libmath.so。

類似功能的工具

  • SCons

    • by python
  • Apache ant

    • by java
  • 優點

    • stable
    • fast
    • 支援Code::Blocks, Xcode等
  • 缺點

    • 需學習語法

專案建置工具比較

使用

GUI

  • command line: cmake
  • curse-based TUI: ccmake
  • QT-based GUI: cmake-gui

Out-Of-Source Build

將build資料和原始碼分離

1
2
3
4
5
6
# 修改 CMakeLists.txt
mkdir build # will generate build files in this folder
cd build
cmake .. # the place that put CMakelists.txt

cmake -G "Unix Makefiles" [CMakeList.txt path] # 指定生成平台
  • 執行結束後,build資料夾會出現各項中間檔、makefile 以及執行檔

優點

  1. 可保持working directory的乾淨
  2. 只要用新的資料夾,就可以產生多個不同的build,也可以隨時刪除

語法

  • 設定變數 set(var hello)
  • 印出訊息 message(${var})
1
2
3
cmake_minimum_required (VERSION 2.6) # minimum executable version of cmake
project(ex1) # project name
add_executable(ex1 main.c) # build program ex1 from main.c

在不同的資料夾下建立執行檔

1
2
3
4
# relative destination
cmake --help-variable CMAKE_INSTALL_PREFIX
# At Install-time use DESTDIR mechanism (Unix Makefiles)
make DESTDIR=/tmp/testinstall install

CMake語法

general purpose constructs: set, unset, if, elseif, else, endif, foreach, while, break

CMake 變數

變數不分大小寫,但我習慣性用大寫

  • 變數指定

    • CMake 有許多預設變數,以改變基本的運作,用cmake --help-variables-list可以瀏覽
    • 在檔案內指定
      • set(CMAKE_INSTALL_PREFIX /home/eric/testinstall)
  • 平台相關變數

    • 作業系統
      • WIN32 True on windows systems, including win64.
      • UNIX True for UNIX and UNIX like operating systems.
      • APPLE True if running on Mac OSX.
      • CYGWIN True for cygwin.
    • 編譯器
      • MSVC True when using Microsoft Visual C
      • GNU<LANG> True if the compiler of this language is GNU
      • MINGW True if the compiler is MinGW
    • example
    1
    2
    3
    if (NOT WIN32)
    option (WITH GUESS NAME "Guess acronym name" ON)
    endif (NOT WIN32)
    • 找需要的package
    1
    2
    3
    4
    find package ( LibXml2 )
    if (LIBXML2 FOUND)
    add definitions (−DHAVE XML ${LIBXML2 DEFINITIONS })
    includedirectories( ${LIBXML2 INCLUDE DIR })
    • XXXFOUND
      - Set to false, or undefined, if we haven’t found, or don’t want to use XXX
      • XXX_INCLUDE_DIRS
        • The final set of include directories listed in one variable for use by client code
      • XXX_LIBRARIES
        • The libraries to link against to use XXX. These should include full paths
      • XXX_DEFINITIONS
        • Definitions to use when compiling code that uses XXX
      • XXX_EXECUTABLE
        • Where to find the XXX tool
      • XXX_LIBRARY_DIRS
        • Optionally, the final set of library directories listed in one variable for use by client code
      • to find an executable program
        • find_program
      • to find a library
        • find_library
      • to find any kind of file
        • find_file
      • to find a path where a file reside
        • find_path
  • file manipulation with file

    • READ, WRITE, APPEND, RENAME, REMOVE, MAKE DIRECTORY
  • advanced files operations

    • GLOB, GLOB RECURSE [file name in a path], DOWNLOAD, UPLOAD
  • working with path

    • file (TO CMAKE PATH /TO NATIVE PATH ...), get filename component
  • execute an external process (with stdout, stderr and return code retrieval): execute process

  • builtin list manipulation command

    • LENGTH, GET, APPEND, FIND, APPEND, INSERT, REMOVE ITEM, REMOVE AT, REMOVE DUPLICATES REVERSE, SORT
  • string manipulation

    • upper/lower case conversion, length, comparison, substring, regular expression match

CMake Application

  • CPack

    • CMake friend application
    • used to easily package your software
    • Package the resulting binaries with CPack
  • CTest

    • make test
    • runs systematic test with CTest and publish them with CDash
  • Wizard mode

    • cmake -i, interactive equivalent of the Normal mode
  • Command mode

    • cmake -E , command line mode which offers basic command in a portable way

專案說明

一個簡單的圍棋程式

file tree

1
2
3
4
5
6
7
├── CMakeLists.txt
├── *.h
├── *.cpp
├── previous_version
│   ├── CMakeLists.txt
│   ├── *.cpp
│   └── *.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
P?=binary
EXEC=execs/$(P)
OBJECTS=$(P).o *.o
CXXFLAGS=-pipe -std=c++11 -Wall -Wextra -Wpedantic -Wno-unknown-pragmas -g -O3 -march=native -fopenmp
LDFLAGS=-lpthread
CXX=g++

all: main
parallel: main
parallel: CXXFLAGS+=-fopenmp -DPARALLEL # add additional flags

previous:
cd previous_version; make
binary: $(OBJECTS)
g++ $(CXXFLAGS) $(OBJECTS) -flto -o $(EXEC) $(LDFLAGS)

$(P).o: *.h
$(CXX) $(CXXFLAGS) main.cpp -c -o $(P).o $(LDFLAGS)

# object files and it's dependency
board.o: mc_node.h liberty.h board.h board_constants.h ladder.h database.h strategy.h gameSetting.h
# ...

clean:
rm -rf *.o
previous_clean:
cd previous_version; make clean

CMakeLists.txt in root directory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
cmake_minimum_required(VERSION 2.6)
project(MCTS_Go9x9)

# set default output file name
SET(BINARYNAME "binary" CACHE STRING "default binary file's name = binary" FORCE)
# set output directory
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/execs
# compile parameters
set(CMAKE_CXX_COMPILER g++)
message("compiler: ${CMAKE_CXX_COMPILER}")
set(CMAKE_CXX_FLAGS "-pipe -std=c++11 -Wall -Wextra -Wpedantic -Wno-unknown-pragmas -g -O3 -march=native -fopenmp")
message("cxxflags: ${CMAKE_CXX_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "-flto")

set(THREADS_PREFER_PTHREAD_FLAG ON) # pthread
find_package(Threads REQUIRED) # pthread

set(SRC_FILES *.cpp)
message("source files: ${SRC_FILES}")

# each executable is a target in GNU makefile
add_executable(${BINARYNAME} ${SRC_FILES})
target_link_libraries(${BINARYNAME} Threads::Threads) # pthread

# parallel
add_executable(parallel ${SRC_FILES})
target_compile_options(parallel PUBLIC -fopenmp -DPARALLEL) # add additional compile parameters

# we have another CMakeLists.txt in this directory, still follow the same method to generate
add_subdirectory(previous_version)

Makefile 和 CMake 的語法比較

makefile cmake
COMPILE_FLAGS = -c -m32 -O3 -fPIC -w -DSOMETHING -Wall -I src/sdk/core SET( COMPILE_FLAGS "-c -m32 -O3 -fPIC -w DSOMETHING -Wall" ) INCLUDE_DIRECTORIES( src/sdk/core )
1
2
3
4
5
#Makefile
ifdef STATIC
# Do something
else
# Do something elseendif
1
2
3
4
5
6
7
# CMake
OPTION(STATIC "Brief description" ON)
IF( STATIC )
# Do something
ELSE()
# Do something else
ENDIF()

using libraries
call find_package with the name of your library, which will invoke a library search script from your cmake module path. This script (which is also written in CMake) will attempt to detect the location of the library's header and lib files and store them in a couple of CMake variables that can then be passed to the according CMake commands like include_directories and target_link_libraries.

There are two problems with this approach: First, you need a search script. Fortunately, CMake ships with search scripts for Pthreads, Boost and a couple of others, but if you are using a more exotic library, you might have to write the search script yourself, which is kind of an arcane experience at first...

並不會刪除自身產出的快取和中間檔

The following Makefile builds an executable named prog from the sources prog1.c, prog2.c, prog3.c and main.c. prog is linked against libmystatlib.a and libmydynlib.so which are both also built from source. Additionally, prog uses the library libstuff.a in stuff/lib and its header in stuff/include. The Makefile by default builds a release target, but offers also a debug target:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#Makefile
CC = gcc
CPP = g++
RANLIB = ar rcs
RELEASE = -c -O3
DEBUG = -c -g -D_DEBUG
INCDIR = -I./stuff/include
LIBDIR = -L./stuff/lib -L.
LIBS = -lstuff -lmystatlib -lmydynlib
CFLAGS = $(RELEASE)

PROGOBJS = prog1.o prog2.o prog3.o

prog: main.o $(PROGOBJS) mystatlib mydynlib
$(CC) main.o $(PROGOBJS) $(LIBDIR) $(LIBS) -o prog
debug: CFLAGS=$(DEBUG)
debug: prog

mystatlib: mystatlib.o
$(RANLIB) libmystatlib.a mystatlib.o
mydynlib: mydynlib.o
$(CPP) -shared mydynlib.o -o libmydynlib.so

%.o: %.c
$(CC) $(CFLAGS) $(INCDIR) $< -o $@
%.o: %.cpp
$(CPP) $(CFLAGS) $(INCDIR) -fPIC $< -o $@
Here is a CMakeLists.txtthat does (almost) exactly the same, with some comments to underline the similarities to the Makefile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#CMakeLists.txt
cmake_minimum_required(VERSION 2.8) # stuff not directly
project(example) # related to building

include_directories(${CMAKE_SOURCE_DIR}/stuff/include) # -I flags for compiler
link_directories(${CMAKE_SOURCE_DIR}/stuff/lib) # -L flags for linker

set(PROGSRC prog1.c prog2.c prog3.c) # define variable

add_executable(prog main.c ${PROGSRC}) # define executable target prog, specify sources
target_link_libraries(prog mystatlib mydynlib stuff) # -l flags for linking prog target

add_library(mystatlib STATIC mystatlib.c) # define static library target mystatlib, specify sources

add_library(mydynlib SHARED mydynlib.cpp) # define shared library target mydynlib, specify sources
#extra flags for linking mydynlib
set_target_properties(mydynlib PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
#alternatively:
#set_target_properties(mydynlib PROPERTIES COMPILE_FLAGS "-fPIC")

In this simple example, the most important differences are:

CMake recognizes which compilers to use for which kind of source. Also, it invokes the right sequence of commands for each type of target. Therefore, there is no explicit specification of commands like $(CC)..., $(RANLIB)... and so on.
All usual compiler/linker flags dealing with inclusion of header files, libraries, etc. are replaced by platform independent / build system independent commands.
Debugging flags are included by either setting the variable CMAKE_BUILD_TYPE to "Debug", or by passing it to CMake when invoking the program: cmake -DCMAKE_BUILD_TYPE:STRING=Debug.
CMake offers also the platform independent inclusion of the '-fPIC' flag (via the POSITION_INDEPENDENT_CODE property) and many others. Still, more obscure settings can be implemented by hand in CMake just as well as in a Makefile (by using COMPILE_FLAGS and similar properties). Of course CMake really starts to shine when third party libraries (like OpenGL) are included in a portable manner.
The build process has one step if you use a Makefile, namely typing
make at the command line. For CMake, there are two steps: First, you need to setup your build environment (either by typing cmake <source_dir> in your build directory or by running some GUI client). This creates a Makefile or something equivalent, depending on the build system of your choice (e.g. make on Unixes or VC++ or MinGW + Msys on Windows). The build system can be passed to CMake as a parameter; however, CMake makes reasonable default choices depending on your system configuration. Second, you perform the actual build in the selected build system.
Sources and build instructions are available at https://github.com/rhoelzel/make_cmake.

設定變數範例

      set(MYLIBPATH "C:\\Documents and
              Settings\\Jing\\Desktop\\opencv_bin\\lib\\release\\")

使用變數範例

      target_link_libraries(${name} ${MYLIBPATH}cv200.lib)

建立 macro 範例

       MACRO(MY_DEFINE_EXAMPLE name srcs)
             add_executable(${name} ${srcs})
       ENDMACRO(MY_DEFINE_EXAMPLE)

使用 macro 範例

       MY_DEFINE_EXAMPLE(demo     demo.cpp)

存取環境變數的範例

         MESSAGE("$ENV{PATH}")

加入 Post Build 的範例

add_custom_command(
        TARGET ${MyPluginTarget}
        POST_BUILD
        COMMAND copy
                       ${CMAKE_CFG_INTDIR}\\${MyPluginTarget}.dll
                       "C:\\Program Files\\Mozilla Firefox\\
                         plugins\\${MyPluginTarget}.dll"
    )

若你的執行檔是 Windows 程式, 請加入 Win32
add_executable(demo WIN32 ${SOURCE})

MFC 設定

1
2
3
4
5
6
7
8
set(CMAKE_MFC_FLAG 2)
set_target_properties(MyApp PROPERTIES
COMPILE_DEFINITIONS
_AFXDLL, _UNICODE, UNICODE,
_BIND_TO_CURRENT_CRT_VERSION,
_BIND_TO_CURRENT_MFC_VERSION
LINK_FLAGS
" /ENTRY:\"wWinMainCRTStartup\" ")

add_executable(demo WIN32 ${SOURCE})

設定 source code 群組範例

1
2
3
4
5
6
7
8
9
10
# ex1:
file(GLOB_RECURSE myRESOURCE_FILE 「*.def」 「*.rc」)
source_group(Resource FILES ${myRESOURCE_FILE })
# ex2:
source_group(abc_group REGULAR_EXPRESSION "abc.*")
source_group(123_group REGULAR_EXPRESSION "123.*")
set(SOURCE abc.cpp abc.h 123.cpp 123.h abc.rc main.cpp main.h
resource.h stdafx.cpp stdafx.h
./res/123.ico ./res/123.rc2)
add_executable(demo WIN32 ${SOURCE})

建立 dll 範例

1
add_library( target SHARED a.c b.c);

#console 與 windows (/SUBSYSTEM:CONSOLE or /SUBSYSTEM:WINDOWS)
a. 如果你的程式是 Windows.
=> 則 add_executable(demo WIN32 ${SOURCE})
b. 如果是 console, 則
=> 則 add_executable(demo ${SOURCE})

好用的變數 (more)
PROJECT_SOURCE_DIR: 包含 project 命令的 CMakeFile.txt 完整路徑. (ex: c:\abc)

1
set(PROJECT_HOME ${PROJECT_SOURCE_DIR}\\..\\)

Reference

  • make makefile cmake qmake都是什麼,有什麼區別?-玟清的回答-知乎
  • CMake筆記
  • Eric Noulard - CMake tutorial
  • CMake 入門 - 維基教科書,自由的教學讀本
  • stackoverflow - Cmake vs make sample codes?
  • stackoverflow - cmake CFLAGS CXXFLAGS modification
  • stackoverflow - Converting old makefile to CMake
  • stackoverflow - Creating CMakeLists file from existing Makefile