前言
做第一個linux項目時,Makefile是一行行敲入的,第二個項目後,開始使用cmake。至於爲何選擇cmake,倒不是覺得它有什麼好,僅僅是因爲當時項目組中的一個linux前輩向我們推薦了這個。經過一番研究之後,並在項目中使用,現將使用經驗總結一下,供大家參考。
學習一項新知識的時候,最好是從sample開始。cmake官方網站就給出了一個簡單的例子。在開始之前,還是先安裝cmake程序,在ubuntu下非常簡單,輸入以下命令即可:
$
sudo apt-get install cmake
下面來看看sample中的內容。sample包含一個主目錄,下面兩個子目錄: Demo和Hello。其中Hello包含程序庫代碼,Demo中爲可執行程序,需要連接Hello中的程序庫。總共有三個CMakeLists.txt文件,每個目錄下一個。主目錄下的CMakeLists.txt文件內容爲:
# The name of our project is "HELLO". CMakeLists files in this project can # refer to the root source directory of the project as ${HELLO_SOURCE_DIR} and # to the root binary directory of the project as ${HELLO_BINARY_DIR}. project (HELLO) # Recurse into the "Hello" and "Demo" subdirectories. This does not actually # cause another cmake executable to run. The same process will walk through # the project's entire directory structure. add_subdirectory (Hello) add_subdirectory (Demo)
Hello目錄下的CMakeLists.txt文件爲:
# Create a library called "Hello" which includes the source file "hello.cxx". # The extension is already found. Any number of sources could be listed here. add_library (Hello hello.cxx)
最後,Demo包含如下CMakeLists.txt文件:
# Make sure the compiler can find include files from our Hello library. include_directories (${HELLO_SOURCE_DIR}/Hello) # Make sure the linker can find the Hello library once it is built. link_directories (${HELLO_BINARY_DIR}/Hello) # Add executable called "helloDemo" that is built from the source files # "demo.cxx" and "demo_b.cxx". The extensions are automatically found. add_executable (helloDemo demo.cxx demo_b.cxx) # Link the executable to the Hello library. target_link_libraries (helloDemo Hello)
CMake在主目錄執行時,會處理該目錄下CMakeLists.txt文件,然後進入到子目錄,處理子目錄下的CMakeLists.txt.
從字面上看,我們差不多可以理解這三個文件的涵義。第一個CMakeLists.txt文件指定包含Hello和Demo兩個子目錄。第二個文件則指定生成Hello庫文件,第三個文件則是生成一個可執行文件helloDemo,另外兩個附加語句則用來指明頭文件路徑以及所要鏈接的庫。
乍一看,爲了編譯一個程序,需要寫三個CMakeLists.txt文件,相對於原來只需寫一個Makefile文件,不是更復雜了嗎?事實上不盡然,雖然要寫三個CMakeLists文件,但每個文件都非常簡單,總共算起來,並不比一個Makefile文件多多少。更重要的是,這其中隱含着linux哲學:分而治之。每個模塊自行編寫配置文件,只負責自己份內的事務,所以可擴展性好。在進行大型項目開發,就可以體現出優勢了。
CMakeLists.txt相當於定義了一套生成Makefile文件的規則,下面就可以生成Makefile文件了,命令如下:
$
cmake .
注:.表示當前目錄,如果CMakeLists.txt不在當前目錄,請在cmake後面指定。在主目錄下和Demo、Hello子目錄下均會生成一個Makefile文件,有了這個文件,我們就可以敲入make編譯目標程序了。
總結起來,使用CMake編譯應用程序的流程爲:
CMake語法非常簡單,包含註釋、命令和空格。以#開頭的行爲註釋行,命令則由命令名、括號及以空格進行分隔的參數組成。命令可以是諸如add_library這樣的內置命令,也可以是子定義的宏或者函數。CMake的輸入是主目錄下的CMakeLists.txt文件,該文件可以使用include或者add_directory命令添加其它的輸入文件。
命令的形式如下:
command (args ...)
其中<I>command</I>爲命令名,<B>args</B>爲空格分隔的參數列表,如果參數中包含空格,使用雙引號引起來。命令不區分大小寫。
lists and strings. CMake的基本數據類型爲字符串,字符串又可以組成list類型,有兩種方式:一種通過分號分隔,一種通過空格分隔。比如以下例子給VAR賦了同樣的值:
set(VAR a;b;c) set(VAR a b c)
字符串列表主要用於foreach進行迭代,有些命令也用於對list進行處理。
CMake支持字符串和list類型的簡單變量,變量以${VAR}形式引用。多個參數可以用set命令組成一個list,命令將展開list,例如:
set(Foo a b c) command(${Foo})
等價於
command(a b c)
如果你希望將list當作一個參數傳遞給命令,就應該用雙引號把list引起來,如command("${Foo}")等價於command("a b c")
寫CMakeLists.txt文件就象寫一個簡單的程序,CMake提供了三種流程控制結構:
- 條件語句if
# some_command will be called if the variable's value is not: # empty, 0, N, NO, OFF, FALSE, NOTFOUND, or -NOTFOUND. if(var) some_command(...) endif(var)
- 循環結構
set(VAR a b c) # loop over a, b,c with the variable f foreach(f ${VAR}) some_command(${f}) endforeach(f)
- 宏和函數,函數在2.6及以上版本才支持,函數和宏的區別在於函數中可定義局部變量,而宏定義的變量都是全局變量。
# define a macro hello macro(hello MESSAGE) message(${MESSAGE}) endmacro(hello) # call the macro with the string "hello world" hello("hello world") # define a function hello function(hello MESSAGE) message(${MESSAGE}) endfunction(hello)
CMake Tutorial.
CMake官方網站:www.cmake.org.
在前面一篇文章中,我們從一個sample入手,瞭解了CMake的基本用法和語法。但這個例子與實際開發還有一段距離,主要存在以下幾點問題:
- 生成的二進制程序和源程序混在一起
- 使用gcc進程程序編譯,而不是使用交叉編譯工具
- 爲指定編譯選項,通常會生成debug版本供調試用,release版本用於發佈
在本章,我們將sample程序逐步改造,解決上述問題。
一個項目,通常包含若干子模塊。比如上一篇的sample,我們可以認爲它包含兩個子模塊,Hello爲程序庫,Demo爲主程序。很少有項目會把目標二進制文件和源程序放在一起的,通常會建立一個bin目錄,存放生成的二進制文件,發佈程序則放在release。根據我在項目開發中的習慣,將目錄結構修改如下:
CMakeSample |--- release |--- doc |--- lib |--- source |--- include |--- bin |--- Hello |--- Demo
其中,release存放程序發佈相關文件,包括程序文件、腳本、參數等。doc包含項目開發中的相關文檔,如設計說明以及通過doxgen等工具從代碼中生成的文檔。lib存放項目中使用的第三方庫,項目中自己編寫的庫不放在此目錄,應該作爲項目的一個模塊放在source目錄下。include包含整個項目中使用的公共頭文件,如果子模塊中的頭文件僅在該子模塊類使用,不需放到include目錄。bin目錄存放編譯後的調試版本代碼。其它的子目錄則爲各模塊的代碼及頭文件。
按照以上目錄結構,將Hello下的hello.h移到include目錄,因爲這個頭文件被Demo模塊包含。這個sample中未使用第三方庫,所以暫時爲空。
從上文中我們知道,通過set語句可以自定義變量,然而,CMake還包含大量的內置變量,這些變量和自定義變量的用法沒有區別,下面就列出一些常用的變量:
- CMAKE_C_COMPILER
指定C編譯器,通常,CMake運行時能夠自動檢測C語言編譯器。進行嵌入式系統開發時,通常需要設置此變量,指定交叉編譯器。
- CMAKE_CXX_COMPILER
指定C++編譯器
- CMAKE_C_FLAGS
指定編譯C文件時編譯選項,比如-g指定產生調試信息。也可以通過add_definitions命令添加編譯選項。
- EXECUTABLE_OUTPUT_PATH
指定可執行文件存放的路徑。
- LIBRARY_OUTPUT_PATH
指定庫文件放置的路徑
除了內置變量,我們還可以通過命令來修改編譯選項,現將一些常用的命令列出來:
- include_directories
指定頭文件的搜索路徑,相當於指定gcc編譯器的-I參數
- link_directories
動態鏈接庫或靜態鏈接庫的搜索路徑,相當於指>定gcc的-L參數
- add_subdirectory
包含子目錄,當工程包含多個子目錄時,此命令有用
- add_definitions
添加編譯參數,比如add_definitions(-DDEBUG)將在gcc命令行添加DEBUG宏定義
- add_executable
編譯可執行程序
- target_link_libraries
指定鏈接庫,相同於指定-l參數