CMake實戰教程(三)

前言

從本小節開始,後面所有的構建我們都將採用 out-of-source build 外部構建的方式去編寫構建工程代碼,構建目錄是工程目錄下的 build 目錄。

從上一篇文章我們就知道,通過aux_source_directory命令可以掃描某個目錄下的所有源碼,但是更深一層的目錄源碼就找不到了,因此不滿足我們真正工程項目的需要,畢竟真的工程會有很多個目錄的,這些目錄在的地方還不一樣,現在就來解決這個問題…

定個小目標

  • CMake自動構建多個源碼目錄下的工程

實現

首先,起碼工程中得有多個目錄對吧,按照編程習慣,我喜歡把源碼放到 src 目錄下,把頭文件放到 inc 目錄下,那麼把上一篇文章的代碼分離放開,具體如下:

jie@pc:~/github/cmake/section4$ tree
.
├── build.sh
├── CMakeLists.txt
├── inc
│   └── power.h
├── main.c
└── power
    ├── CMakeLists.txt
    └── power.c

2 directories, 6 files

很顯然,從上面的目錄結構中可以看出,有兩個CMakeLists.txt文件,一個是在根目錄下,一個是在src目錄下:

./CMakeLists.txt
./src/CMakeLists.txt

其實無論在何處,CMakeLists.txt文件都可以被 CMake 命令識別並且自動構建,但是,我們自動構建的時候,都會有一個頂層目錄CMakeLists.txt文件,它就是cmake的入口文件,而在頂層目錄下,如果某些子目錄中存在CMakeLists.txt文件,那麼 CMake 可以將頂層的環境變量傳遞到子目錄下,就好比,你在某個文件中定義了個全局變量,你在那個文件中的子函數都可以使用這個全局變量,有點類似的道理,說白了就是作用域。。。

添加子目錄

那麼爲了能人 CMake 去識別到src目錄下的./src/CMakeLists.txt文件,我們可以在頂層./CMakeLists.txt文件添加一個子目錄,用以下這個命令:

add_subdirectory(source_dir [binary_dir]
				[EXCLUDE_FROM_ALL])

add_subdirectory 這條命令的作用是爲構建添加一個子路徑。

  • source_dir 選項指定了 CMakeLists.txt 源文件和代碼文件的位置。如果 source_dir 是一個相對路徑,那麼 source_dir 選項會被解釋爲相對於當前的目錄,不過它也可以是一個絕對路徑。在 source_dir 指定路徑下的 CMakeLists.txt 將會在當前輸入文件的處理過程執行到該命令之前,立即被 CMake 處理。
  • binary_dir 選項指定了輸出文件的路徑,同樣的, binary_dir 可以相對路徑,也可以是一個絕對路徑。不過傑傑目前暫未使用到binary_dir,感興趣的兄弟姐妹可以自己去了解它。
  • 如果指定了 EXCLUDE_FROM_ALL 選項,在子路徑下的目標默認不會被包含到父路徑的 ALL 目標裏,並且也會被排除在工程文件之外,用戶必須顯式構建在子路徑下的目標。

添加路徑

include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)

同理可以使用include_directories命令將給定的路徑添加到編譯器搜索包含文件(.h 文件)的路徑列表中,這是爲了讓編譯器找到合適的頭文件。如果指定了 SYSTEM 選項,編譯器將會認爲該路徑是某種平臺上的系統包含路徑。

遍歷

在cmake中,很多變量都是以列表的形式存在,比如上面的兩個命令,添加頭文件與添加子目錄,我們可以用列表的形式添加進去,比如include_directories(dir1 dir2 dir3),那麼這些dir,那麼CMake也提供了遍歷這些列表的方法,這與C語言中的for循環都差不多,可以使用以下命令去遍歷它:

foreach(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endforeach(loop_var)

注意,這個命令需要配合 endforeach 命令使用,endforeach 表示結束遍歷
所有的 foreach 和與之匹配的 endforeach 命令之間的命令,會根據list的每個變量都執行一遍,在每次迭代中,循環變量${loop_var}將會被設置爲 list 中的當前變量值。

爲了演示,我就將foreach命令去變量指定的INCDIRS(雖然只有一個變量,不過不影響演示),把INCDIRS列表的內容一個個添加到路徑中:

# 頭文件
set(INCDIRS "inc")

# 添加頭文件目錄
foreach(incdir ${INCDIRS})
    include_directories(${incdir})
endforeach()

當然,我在這提到遍歷並不是因爲必須要使用這個命令,而是爲後續的文章做鋪墊。

構建庫

如果一個工程中有非常多的源碼,那麼總不能將所有的源碼都使用add_executable命令去生成可執行文件是不是,我們可以讓這個可執行文件去依賴庫,靜態庫,動態庫都可以,那麼在這裏就簡單說說add_executable命令依賴靜態庫生成可執行文件,那麼問題來了,靜態庫從哪來?

很顯然,靜態庫是我們的一些源碼文件生成的,在生成靜態庫後,再通過靜態庫去構建可執行文件,那麼就用到了CMake中的add_library命令:

add_library(<name> [STATIC | SHARED | MODULE]
			[EXCLUDE_FROM_ALL]
			source1 source2 ... sourceN)

它的作用就是使用指定的源文件向工程中添加一個庫。添加一個名爲<name>的庫文件,<name>在一個工程的全局域內必須是唯一的,這個庫文件將會根據命令裏列出的源文件(source1 source2 ... sourceN)來創建。待構建的庫文件的實際文件名根據對應平臺的命名約定來構造(比如 可以是lib<name>.a 或者<name>.lib)。通過指定STATICSHARED,或者 MODULE 參數用來指定要創建的庫的類型。

  • STATIC 庫是目標文件的歸檔文件,在鏈接其它目標的時候使用。
  • SHARED 庫會被動態鏈接,在運行時被加載。
  • MODULE 庫是不會被鏈接到其它目標中的插件,但是可能會在運行時使用 dlopen-系列的函數動態鏈接。如果沒有類型被顯式指定,這個選項將會根據變量 BUILD_SHARED_LIBS 的當前值是否爲真決定是 STATIC 還是 SHARED
# 查找當前目錄下的所有源文件
# 並將名稱保存到 DIR_LIB_SRCS 變量
file(GLOB_RECURSE DIR_LIB_SRCS *.c)
file(GLOB_RECURSE DIR_LIB_HDRS *.h)

# 生成鏈接庫
add_library(${LIB_NAME} ${DIR_LIB_SRCS} ${DIR_LIB_HDRS})

當然,爲了我們的CMakeLists.txt文件有更好的兼容性,也不必每次都去寫對應的庫文件名字<name>,傑傑自己會使用正則表達式去解析當前目錄的名字,並以這個目錄名字去作爲庫的命名,正則表達式如下:

# 正則表達式得到當前目錄名字作爲提供給上一層的庫名字
string(REGEX REPLACE ".*/(.*)" "\\1" LIB_NAME ${CMAKE_CURRENT_SOURCE_DIR}) 

工程demo

頂層./CMakeLists.txt文件內容:

# CMake 最低版本號要求
cmake_minimum_required(VERSION 2.8)

# 項目信息
project(targets)

# 設置可執行文件目標
set(TARGETS "targets")

# 子目錄
set(SUBDIRS "power")

# 頭文件
set(INCDIRS "inc")

# 指定編譯器
set(CMAKE_C_COMPILER "gcc")

#判斷編譯器類型,如果是gcc編譯器,則在編譯選項中加入c++11支持
if(CMAKE_COMPILER_IS_GNUCXX)
    set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
endif(CMAKE_COMPILER_IS_GNUCXX)

#指定編譯類型
SET(CMAKE_BUILE_TYPE "RELEASE")

# 查找當前目錄下的所有源文件
# 並將名稱保存到 DIR_SRCS 變量
aux_source_directory(. DIR_SRCS)

# 指定生成目標
add_executable(${TARGETS} ${DIR_SRCS})

# 添加頭文件目錄
foreach(incdir ${INCDIRS})
    include_directories(${incdir})
endforeach()


# 添加子目錄
foreach(subdir ${SUBDIRS})
    add_subdirectory(${subdir})
endforeach()

# 添加鏈接庫
target_link_libraries(${TARGETS} ${SUBDIRS})

src目錄下的./src/CMakeLists.txt文件內容:

# 查找當前目錄下的所有源文件
# 並將名稱保存到 DIR_LIB_SRCS 變量
file(GLOB_RECURSE DIR_LIB_SRCS *.c)
file(GLOB_RECURSE DIR_LIB_HDRS *.h)

# 正則表達式得到當前目錄名字作爲提供給上一層的庫名字
string(REGEX REPLACE ".*/(.*)" "\\1" LIB_NAME ${CMAKE_CURRENT_SOURCE_DIR}) 

# 生成鏈接庫
add_library(${LIB_NAME} ${DIR_LIB_SRCS} ${DIR_LIB_HDRS})

最後直接運行./build去構建工程與編譯工程即可

代碼下載

https://github.com/jiejieTop/cmake

未完待續…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章