CMake Introduction 2

 

一.第一次嘗試結果:
我將源碼目錄建爲src,編譯目錄建爲build.
然後在src下建立main,用於放main相關的文件,再在src下建立lib1,用於放一個小庫。
Magic Happens like this:
(1)main和lib1中的CMakeLists.txt,只需要寫上和Build Target相關的command。這裏是ADD_LIBRARY()或ADD_EXECUTABLE(),另外因爲main要鏈接lib1庫,所以要添加Build Flags(Options)相關的:TARGET_LINK_LIBRARIES()。
(2)然後在main和lib1的同級目錄,即src下,建立工程的總的CMakeLists.txt。這裏面就放SUBDIRS()以及和Build Flags(Options)相關的就好。Build Flags(Options)可用INCLUDE_DIRECTORIES()來將lib1寫入,這樣,main中的程序要用lib1庫,就只需要 寫"lib.h"就好,而不需要給出路徑。然後,不用寫LINK_DIRECTORIES(),可能有些版本要寫。但我這個版本,不用給出工程中生成的庫 的路徑,就可以鏈接。

還有一個Magic是,只需要一遍cmake,之後如果源文件或CMakeLists.txt做了更改,都不必要cmake,直接make,它會看情況rerun cmake.

我習慣於:公共的東西,放在top的CMakeLists.txt裏,各個庫和執行文件相關的放在各個目錄的CMakeLists.txt裏,比如各個庫怎麼裝。top的CMakeLists.txt的例子:
PROJECT(helloworld)
SUBDIRS(main lib1 lib2)
#設置工程需要的頭文件路徑(包括工程內的)
INCLUDE_DIRECTORIES(./lib1 ./lib2 ${CMAKE_BINARY_DIR}/config)
#設置用戶選項
OPTION(HELLO1 "Using Lib1" ON)
#將config.h.in轉爲config.h
CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/config/config.h.in ${CMAKE_BINARY_DIR}/config/config.h)
#設置工程變量
SET(CMAKE_INSTALL_PREFIX /home/evan/local)


二. 小結:
1、基本命令:
- Build Targets:
SET()
SUBDIRS()
ADD_LIBRARY()
ADD_EXECUTABLE()
PROJECT()

- Build Flags and Options:
INCLUDE_DIRECTORIES()
LINK_DIRECTORIES()
TARGET_LINK_LIBRARIES()

- Flow Control Constructs
IF的例子:
IF(UNIX)
IF(APPLE)
 

   SET(GUI "Cocoa"
ELSE(APPLE)
    SET(GUI "X11"
ENDIF(APPLE)
ELSE(UNIX)
IF(WIN32)
    SET(GUI "Win32"
ELSE(WIN32)
    SET(GUI "Unknown"
ENDIF(WIN32)
ENDIF(UNIX)

MESSAGE的例子:
MESSAGE("GUI system is ${GUI}")

FOREACH例子:就是可以簡化對多個命令的調用,相當於bash的for語句。
SET(SOURCES source1 source2 source3)
FOREACH(source ${SOURCES})
ADD_EXECUTABLE(${source} ${source}.c)
ENDFOREACH(source)

- Useful Variables:
最常用的:
CMAKE_BINARY_DIR: build的top目錄
CMAKE_SOURCE_DIR: src的top目錄
CMAKE_INSTALL_PREFIX: 安裝路徑的前綴
CMAKE_BUILD_TYPE: 如Release, Debug等。
EXECUTABLE_OUTPUT_PATH:
LIBRARY_OUTPUT_PATH:

見CMake Useful Variables文檔(html)

2、條件編譯:
(1)給用戶提供選項:
3 steps:
* 取好宏的名字,比如LINK_LIB1
* 在top的CMakeLists.txt中寫上OPTION(LINK_LIB1 "Using Lib1" ON)命令。當然寫在top的CMakeLists.txt是因爲爲了方便查看。
* 在用宏的文件的CMakeLists.txt中寫上
IF(LINK_LIB1)
    SET_SOURCE_FILES_PROPERTIES(main.cpp COMPILE_FLAGS -DLINK_LIB1)
ENDIF(LINK_LIB1)
其中,SET_SOURCE_.... 是對main.cpp這個文件,設置其編譯選項。
你也可以用ADD_DEFINITIONS(-DLINK_LIB1),這樣的話,所有的文件在編譯時都會加上這個選項。
就這三步就好了!!!
然後,想控制選項的打開關閉,可以在命令行敲:
cmake -DLINK_LIB1:BOOL=OFF src/ (默認是打開,因爲你寫了個ON在那裏)

其他輔助的命令: (但這個不常用,而是用cmake提供的modules代替,如CheckIncludeFile.cmake,可見man)
FIND_PATH(PYTHON_INCLUDE_PATH Python.h
/usr/include
/usr/local/include)
表明你取了個變量名:PYTHON_INCLUDE_PATH,用於存放搜索Python.h的結果。
(2)檢測不同平臺,頭文件,庫等:
不同平臺:IF(WIN32) IF(UNIX) 都是預先定義好的。可直接使用。
頭文件: 這個可加載cmake的modules:
    INCLUDE(${CMAKE_ROOT}/Modules/CheckIncludeFiles.cmake)
     然後使用時,命令名和那個modules的名字一樣,HAVE_UNISTD_H爲自己定義的變量
    CHECK_INCLUDE_FILES("unistd.h" HAVE_UNISTD_H)
    之後,就可以向編譯器傳遞變量HAVE_UNISTD_H了,如,這樣使用:
    IF(HAVE_UNISTD_H)
        ADD_DEFINITIONS(-DHAVE_UNISTD_H)
    ENDIF(HAVE_UNISTD_H)
    當然,你的源文件需要含有檢測HAVE_UNISTD_H的宏。
庫:其他的檢測,如庫,函數,符號等存在與否,都可以加載相應的modules,見CMake: How To Write Platform Checks.末尾。
(3)更好的辦法:
看看上面(1)(2)兩點,他們提供的特性,就是autotools提供的兩大基本特性。但爲了完成這種條件編譯,方法是給編譯器傳遞-D標誌(-D標誌 這種特性爲大多數編譯器所支持)。而還有一種方法,是autotools用的,就是生成一個config.h文件,然後讓需要條件編譯的源文件包含這個頭 文件。即,所有-D標誌,現在在config.h中了,不用向編譯器傳遞了。
這樣,就不要用ADD_DEFINITIONS()之類的命令了,cmake會根據OPTION(),CHECK_INCLUDE_FILES()等命令,自動填好config.h。
方法如下:
* 自己找個地方,寫config.h文件(任何名字都行),裏面內容:
#ifndef CONFIG_H
#define CONFIG_H
#cmakedefine LINK_LIB1
#endif
其中,LINK_LIB1會被自動換爲如 #define LINK_LIB1 或 #undefine ..
當然,這個頭文件,你需要添加到 INCLUDE_DIRECTORIES() 中去。
* 在CMakeLists.txt(我的是在top的那個裏面)加上CONFIGURE_FILE()命令,見man。
只是注意,兩個路徑,都要是full path。第一個路徑爲包括config.h.in的全路徑,第二個路徑爲生成的config.h的全路徑,一般習慣是放在你的build目錄下去。所 以,第一個路徑可以使用系統預定義的變量:CMAKE_SOURCE_DIR來代替你的src的全路徑,再加上你的子路徑如/config /config.h.in即可,第二個路徑可以使用 CMAKE_BINARY_DIR,指代的是build的全路徑,再加上你的子路徑,如/config/config.h即可。當然,這第二個路徑要放到 INCLUDE_DIRECTORIES(),才能讓你的程序使用到。
* 這樣就可以了!!如OPTION裏面定義的值,只要你寫到config.h中去了,就會被CONFIGURE_FILE()命令轉換爲正常的#define或#undefine.
其他的IF(LINK_LIB1)等,仍然可以使用,這樣,你可以根據用戶的選擇,來用TARGET_LINK_LIBRARIES()鏈接不同的庫。

注:當然,如果是用OPTION()命令接受用戶的選擇,那麼用戶只能在cmake -DLINK_LIB1:BOOL=OFF src/ 這裏設置。也就是說,如果你把這個包給用戶的話,用戶要熟悉用cmake這樣來設置。或者使用你寫的或別人寫的wrapper,如ccmake.


3. 如何在使用一個庫時,加上-I, -L, -l 和 -D
-I, -L, -l和-D是傳遞給編譯器的主要參數,意義很明顯。
cmake提供的衆多module中,主要有兩個可以幹這事情:

(1)UsePkgConfig模塊: 背後使用的是系統的pkg-config
pkg-config的好處,太明顯了,如編譯和鏈接gtk程序:
gcc helloworld.c `pkg-config --cflags --libs gtk+-2.0`
它的原理就是把prefix/lib/pkgconfig目錄下的,你指定名字的xx.pc文件中的信息讀出來給你,你要--libs,就給你-l,查看.pc就知道了。得到-I通過--cflags,要鏈接的庫-l通過--libs,省得你手敲的麻煩,你不信,試試pkg-config命令,它找了一大堆頭文件路徑和庫名出來。
cmake提供了module UsePkgConfig來對pkg-config支持,具體可man,如編譯鏈接gtk可這樣:

假設編譯的執行文件是helloworld
ADD_EXECUTABLE(helloworld b.c)

加上gtk的支持可以這樣:
INCLUDE(UsePkgConfig)
PKGCONFIG(gtk+-2.0 includedir libdir linkflags cflags)

然後,PKGCONFIG中的變量就被填上了,就可以用cmake告知編譯器參數的如下四條命令:
INCLUDE_DIRECTORIES(${includedir}) #-I。
LINK_DIRECTORIES(${libdir}) #-L
TARGET_LINK_LIBRARIES(helloworld ${linkflags}) #-l
ADD_DEFINITIONS(${cflags}) #-D

注意:
a. INCLUDE 和 INCLUDE_DIRECTORIES 是不一樣的,前者是cmake用來包含入其他的cmake lists文件或是cmake的模塊文件,而後者是用於傳給編譯器頭文件路徑的參數。
b. 由於wxWidgets不採用普通的pkg-config,而是自己帶了一個wx-config,所以,沒有普通的xx.pc文件可供UsePkgConfig使用,所以,應該採取FIND_PACKAGE模塊,見下。

(2)FIND_PACKAGE
如你寫FIND_PACKAGE(wxWidgets),它就去調用module中的FindwxWidgets.cmake這個模塊,然後你可以得到幾個變量,man下,並搜索FindwxWidgets,就知道可以得到哪些變量,就知道怎麼用了。這個FindwxWidgets.cmake 就是使用了wx-config!而不是pkg-config,所以,看起來FIND_PACKAGE比UsePkgConfig靈活。

基本模式是:
FIND_PACKAGE(wxWidgets REQUIRED)
IF(wxWidgets_FOUND)
    INCLUDE(${wxWidgets_USE_FILE}) #這個變量是便利的-D -I... 如果要分開,看man吧。
    TARGET_LINK_LIBRARIES(rose ${wxWidgets_LIBRARIES})
ENDIF(wxWidgets_FOUND)


4. INSTALL命令:
這個命令可以使得cmake生成的Makefile文件含有install目標。
- INSTALL(FILES 源文件,資源文件等 DESTINATION 路徑)
- INSTALL(PROGRAMS 腳本等非二進制的但可執行文件 DESTINATION 路徑)
- INSTALL(TARGETS 二進制程序 動態庫 靜態庫
    RUNTIME DESTINATION 路徑
    LIBRARY DESTINATION 路徑
    ARCHIVE DESTINATION 路徑)
例子:
INSTALL(TARGETS myExe mySharedLib myStaticLib
    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib/static)
意思是:這裏有一些Targets,然後cmake會自動檢查是屬於RUNTIME,LIBRARY還是ARCHIVE,然後執行按RUNTIME/LIBRARY/ARCHIVE後面的屬性來。
- INSTALL(DIRECTORY ......)
這個比較方便的安裝整個目錄,比如資源文件目錄等,很方便。暫時不用。

注意:
(1)路徑一般填寫相對路徑: 即,之前你應該填好系統預定義的變量:CMAKE_INSTALL_PREFIX,然後,比如你寫bin,則就裝到${CMAKE_INSTALL_PREFIX}/bin下了。如果你在這裏寫的是絕對路徑(在linux下即以/開頭),則就按絕對路徑處理了。
(2)對RUNTIME/LIBRARY/ARCHIVE的說明:
RUNTIME: 可執行文件,DLL形式動態庫的DLL部分
LIBRARY: 模塊庫,非DLL形式的動態庫
ARCHIVE: 靜態庫,DLL形式動態庫的Import Lib部分。
(3)裝好的庫,要讓工具找到:
動態庫:將你裝的路徑放入/etc/ld.so.conf(man ldconfig就知道了),然後ldconfig一下。程序運行時才能夠動態鏈接上。但它不影響編譯鏈接時!!,如果不在cmake中指定鏈接目錄,我還不知道怎麼做。
靜態庫:暫時不知道怎樣調整gcc的默認鏈接目錄。
(4)PERMISSIONS,你要加的話可以加。


5. 交叉編譯:
從cmake 2.6開始支持,我的系統上暫時是2.4。
見CMake Cross Compiling.htm。比如你裝了mingw,然後就有一些編譯和鏈接等工具如i386-mingw-gcc等,就可以使用cmake 2.6的一些命令,來指定這些工具,當然還有一些所需要的頭文件和庫,都可以用它來指定。
暫不看它。

6. 和make的區別:
(1)命令行參數寫法:
make的參數寫法 make CXXFLAGS="-Dxx -Ixx"
cmake的參數寫法 cmake -Dxx:xx=xx -Ixxx

說明:關於autotools工具鏈: autoconf, automake & libtool 不想深究。
只想瞭解到這種地步:
1. autoconf: 提供了對不同平臺做檢測(庫,頭文件,編譯器等),和提供選項給用戶進行按需編譯 這兩項主要功能,來使得生產config.h文件。用戶的程序需要包含這個文件,並自己設置#ifdef這類的宏來準備條件編譯,這樣,不同平臺上的編譯,就可以“自動的”(auto)進行了。
用例:
(1)不同平臺做檢測(庫,頭文件,編譯器等):顯然可能存在好多可用的功能相同的庫,也可能某個平臺上暫時沒有裝庫,那麼用戶可以要求 configure去檢測,並把檢測結果放入config.h,然後用戶的源代碼裏因爲已經預備好了條件編譯,就可以根據實際情況“自動的”進行編譯。
(2)提供選項給用戶進行按需編譯:顯然用戶可能不想編譯所有的組件,他可以選擇一些功能編譯進去,這樣,開發者就需要自己寫些m4宏(就是shell腳本),然後加入autoconf,這樣最後生成的configure文件,就可以接可以收用戶的選項了。
可以看出,這兩個主要功能都是和“條件編譯”相結合的。
2. automake:這個就是爲了簡化Makefile的編寫,你只需要寫Makefile.am,這個的語法相對於Makefile簡單一點。
3. libtool:這個目的是提供一個統一的命令行接口,來在不同的平臺編譯出“靜態和動態庫”,這個工具確實有用,因爲你要認識到,不同的編譯器,相同編譯器的不同版本,不同平臺在安裝庫後需要做的配置等等,都是不同的。

cmake的好處就是:不用學這麼多的工具,他就是一個工具,用簡單的語法提供相當多的特性。而且,速度快得多,生成makfile文件小得多。
發佈了23 篇原創文章 · 獲贊 7 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章