CMake用法詳解(轉)


2020年12月20日13:26:16

原文是pdf,我轉成TXT了

 


CMake 相關


一.第一次嘗試結果:

我將源碼目錄建為 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 的模塊文件,而后者是用於傳給編譯器頭文件路徑的參數。

-由於 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,你要加的話可以加。

 

4.交叉編譯:

從 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 簡單一點。

-libtool:這個目的是提供一個統一的命令行接口,來在不同的平台編譯出“靜態和動態庫”,這個工具確實有用,因為你要認識到,不同的編譯器,相同編譯器的不同版本,不同平台在安裝庫后需要做的配置等等,都是不同的。


cmake 的好處就是:不用學這么多的工具,他就是一個工具,用簡單的語法提供相當多的特性。而且,速度快得多,生成 makfile 文件小得多


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM