CMake 編寫

在linux底下除了make(makefile), 就是使用cmake(CMakeLists.txt)來編譯程式或套件(packages). 這邊用範例的方式來記錄用法


Example 1: 簡易的hello world專案 [Top]

簡易的hello world範例, 編譯一個執行檔, 以下為project底下的檔案列表

.
├── CMakeLists.txt
├── build
└── hello_world.c

CMakeLists.txt裡面填寫所要編譯相關規則.
build是一個空目錄, 進去此目錄後, 下達cmake相關指令後, 把編譯的檔案放置此處

以下為CMakeLists.txt內容

1
2
3
4
5
# recommend to add these two lines
cmake_minimum_required(VERSION 2.8.9)
project(hello_world)

add_executable(hello_world hello_world.c)

行2是規定所需要的最低cmake版本.
行3是專案名稱.
行5則是編譯一個名稱為hello_world執行檔,其source檔案為hello_world.c

#編譯
$ mkdir build ; cd build; cmake ..; make
#執行程式
$ ./hello_world
hello world!

Example 2: 多層階層的專案(hierarchical CMakeLists.txt) [Top]

階層的CMakeLists.txt範例

.
├── CMakeLists.txt
├── src1
│   ├── CMakeLists.txt
│   └── foo.cpp
└── src2
    ├── CMakeLists.txt
    └── bar.cpp

此專案底下有兩個小程式src1與src2,藉由最上層的CMakeLists.txt去執行兩個程式裡的CMakeLists.txt. 藉此個別編譯出對應的程式

以下為3個CMakeLists.txt個別的內容.

1
2
3
4
5
6
7
# top CMakeKLists.txt
cmake_minimum_required(VERSION 3.8.1)
project (hierarchical-cmakelists)
message("===> start to build.... " ${PROJECT_NAME})

add_subdirectory(src1)
add_subdirectory(src2)

主要行7~8使用add_subdirectory把這兩個目錄加入到程序. cmake便會依序進入目錄裡去尋找CMakeLists.txt來編譯

1
2
3
4
# CMakeKLists.txt in src1
cmake_minimum_required(VERSION 3.8.1)
project (foo)
add_executable(foo foo.cpp)
1
2
3
4
# CMakeKLists.txt in src2
cmake_minimum_required(VERSION 3.8.1)
project (bar)
add_executable(bar bar.cpp)

src1與src2的內容跟hello world專案裡的CMakeLists.txt並無太大差異

#編譯
$ mkdir build/; cd build/; cmake ..; make

#執行
$ ./src1/foo; ./src2/bar
Foo!
Bar !

Example 3: 建立靜態與動態程式庫(static and dynamic libraries) [Top]

建立static library與share library, 並撰寫使用此函式庫的測試程式

.
├── CMakeLists.txt
├── lib
│   ├── foo.cpp
│   └── foo.h
└── main.cpp

專案底下有一個lib資料夾,用來產生static library與share library. 而main.cpp則是使用此lib的測試程式

底下為CMakeLists.txt內容

 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 3.8.1)
project (static-lib-and-dynamic-lib)
set(CMAKE_BUILD_TYPE Release)

# include headers
include_directories(lib)
# source files
set(SOURCES "lib/foo.cpp")

# share library
add_library(foo-d SHARED ${SOURCES})
# static library
add_library(foo-s STATIC ${SOURCES})

# unit test for libraries
# way 1: add to each
#add_compile_options(-D_TEST_)
#add_executable(foo-test lib/foo.cpp)
# way 2: only available with this project
add_executable(foo-test lib/foo.cpp)
target_compile_options(
 foo-test
 PRIVATE -D_TEST_)

# excutable with share library
add_executable(main-d main.cpp)
target_link_libraries(main-d foo-d)
# excutable with static library
add_executable(main-s main.cpp)
target_link_libraries(main-s foo-s)

行1~3為CMakeLists.txt的基本設置.
行6主要是設置header files所在的目錄. 由於我這邊是放在lib目錄. 所以include_directories(lib)
行8是設定編譯此library所需的source file有哪些
行11與行13使用add_library編譯出library. 其根據${SOURCE}變數的檔案去產生對應的library類型.
行16~18與行19~24作用相同. 主要編譯出lib的unit test程式. 因為main()函數被我用#ifdef _TEST_的方式包起來, 所以編譯的時候需夾帶 -D_TEST_.
行25~30個別編譯使用這些lib的測試程式.

#編譯
$ mkdir build; cd build; cmake ..; make

#執行測試
 echo "===> unit test"; ./foo-test;  echo "===> use lib test"; ./main-d ; ./main-s
===> unit test
TEST: Call foo()...
foo: This is foo!
===> use lib test
Main: Call foo()...
foo: This is foo!
Main: Call foo()...
foo: This is foo!

Example 4: 使用外部函式庫 [Top]

查找系統上所需的package(library).這邊使用(1)cmake內建的find_package. (2) pkg-config的方式去查找. (3)使用搜尋檔案的方式

.
├── CMakeLists.txt
├── build
└── externallib.cpp

目錄底下的externally.cpp會使用libcurl作為測試

以下為CMakeLists.txt內容

 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# recommend to add these two lines
cmake_minimum_required(VERSION 3.8.1)
project(external_libs)

message("CMAKE_ROOT => " ${CMAKE_ROOT})
message("CMAKE_MODULE_PATH => " ${CMAKE_MODULE_PATH})
add_executable(externallib externallib.cpp)

# way - use find package to get information of package
if(0)
 find_package(CURL REQUIRED)
 if(CURL_FOUND)
  include_directories(${CURL_INCLUDE_DIR})
  target_link_libraries(externallib ${CURL_LIBRARY})
  message("curl found => " ${CURL_FOUND})
  message("curl header => " ${CURL_INCLUDE_DIR})
  message("curl Library => " ${CURL_LIBRARY})
  message("curl Libraries => " ${CURL_LIBRARIES})
 else() # or else(CURL_FOUND)
  message(FATAL_ERROR "curl not found!")
 endif() # or endif(CURL_FOUND)
endif()

# way - use find libray to get library link
if(1)
 IF (APPLE)
  #find_path(CURL_TEST_INCLUDE_DIR NAMES curl/curl.h)
  # Additional search locations can be specified after the PATHS argument.
  find_path(CURL_TEST_INCLUDE_DIR NAMES curl.h PATHS /usr/include/curl)
  find_library(CURL_TEST_LIB curl)
  message("header -> ${CURL_TEST_INCLUDE_DIR}")
  message("lib -> ${CURL_TEST_LIB}")
  target_link_libraries(externallib ${CURL_TEST_LIB})
 ENDIF (APPLE)
endif()

# way - use pkg-config
if(0)
 message("PKG_CONFIG_FOUND => " ${PKG_CONFIG_FOUND})
 message("PKG_CONFIG_EXECUTABLE => " ${PKG_CONFIG_EXECUTABLE})
 message("PKG_CONFIG_VERSION_STRING => " ${PKG_CONFIG_VERSION_STRING})
 find_package(PKGCONFIG)
 message("PKG_CONFIG_FOUND => " ${PKG_CONFIG_FOUND})
 message("PKG_CONFIG_EXECUTABLE => " ${PKG_CONFIG_EXECUTABLE})
 message("PKG_CONFIG_VERSION_STRING => " ${PKG_CONFIG_VERSION_STRING})
 # use find_package(PKGCONFIG) before use pkg-config module
 pkg_check_modules(LIBPCRE libpcre)
 message("LIBPCRE found => " ${LIBPCRE_FOUND})
 message("LIBPCRE Libraries => " ${LIBPCRE_LIBRARIES})
 message("LIBPCRE Library dirs => " ${LIBPCRE_LIBRARY_DIR})
 message("LIBPCRE LDFLAGS => " ${LIBPCRE_LDFLAGS})
 message("LIBPCRE LIBPCRE_LDFLAGS_OTHER => " ${LIBPCRE_LDFLAGS_OTHER})
 message("LIBPCRE LIBPCRE_INCLUDE_DIRS => " ${LIBPCRE_INCLUDE_DIRS})
 message("LIBPCRE LIBPCRE_CFLAGS => " ${LIBPCRE_CFLAGS})
 message("LIBPCRE LIBPCRE_CFLAGS_OTHER => " ${LIBPCRE_CFLAGS_OTHER})

 pkg_check_modules (RUBY ruby-2.3)
 pkg_search_module(RUBY ruby-2.3 ruby)
 message("ruby => " ${RUBY_FOUND})
endif()

行1~3為基本的project設置
行5~6輸出make module的資訊
行7則是編譯出目標的執行檔

行10~22則是使用find_package的方式去尋找libcurl.
行13~18則是把找到的package資訊,加入到編譯選項並顯示出來. include_directories加入header files路徑. target_link_libraries加入所需連接(link)的library.

行25~35則是指定明確路徑(目錄)的方式去尋找libcurl.
行29使用find_path去尋找curl.h是否存在於/usr/include/curl. 並把找到檔案的目錄存在CURL_TEST_INCLUDE_DIR
行30使用find_library去尋找curl函式庫.並把資訊存放於CURL_TEST_LIB.

行38~60則是使用pkg-config的module. 這邊因為libcurl不存在於pkg-config裡面. 所以使用 libpcre來做範例
行42先尋找是否有pkg-config模組. 此行在使用pkg-config之前必須先使用.
行47使用pkg_check_modules來尋找libpcre.

#編譯
$ mkdir build; cd build; cmake ..; make

#執行結果. 此範例是curl的範例程式
$ ./externallib
<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style type="text/css">
    body {
        background-color: #f0f0f2;
        margin: 0;
        padding: 0;
        font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

    }
    div {
        width: 600px;
        margin: 5em auto;
        padding: 50px;
        background-color: #fff;
        border-radius: 1em;
    }
    a:link, a:visited {
        color: #38488f;
        text-decoration: none;
    }
    @media (max-width: 700px) {
        body {
            background-color: #fff;
        }
        div {
            width: auto;
            margin: 0 auto;
            border-radius: 0;
            padding: 1em;
        }
    }
    </style>
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is established to be used for illustrative examples in documents. You may use this
    domain in examples without prior coordination or asking for permission.</p>
    <p><a href="http://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
We received Content-Type: text/html

留言

這個網誌中的熱門文章

yocto recipe : (1) 撰寫 recipe

yocto recipe : (2) 撰寫 bbappend

yocto recipe : (3) 使用 External Source 來編譯軟體