《CMake實踐》筆記一:PROJECT/MESSAGE/ADD_EXECUTABLEphp
《CMake實踐》筆記二:INSTALL/CMAKE_INSTALL_PREFIXhtml
《CMake實踐》筆記三:構建靜態庫與動態庫 及 如何使用外部共享庫和頭文件linux
讀者雲,太能羅唆了,一個Hello World就折騰了兩個大節。OK,從本節開始,咱們再也不折騰Hello World了,咱們來折騰Hello World的共享庫。編程
本節的任務:bash
1、創建一個靜態庫和動態庫,提供HelloFunc函數供其餘程序編程使用,HelloFunc向終端輸出Hello World字符串。ide
2、安裝頭文件與共享庫。函數
(一)、準備工做:學習
在/backup/cmake目錄創建t3目錄,用於存放本節涉及到的工程測試
(二)、創建共享庫ui
cd /backup/cmake/t3
mkdir lib
在t3目錄下創建CMakeLists.txt,內容以下:
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
在lib目錄下創建兩個源文件hello.c與hello.h
hello.c內容以下:
#include "hello.h"
void HelloFunc()
{
printf("Hello World\n");
}
hello.h內容以下:
#ifndef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
在lib目錄下創建CMakeLists.txt,內容以下:
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
(三)、編譯共享庫
仍然採用 out-of-source 編譯的方式,按照習慣,咱們創建一個build目錄,在build目錄中
cmake ..
make
這時,你就能夠在lib目錄獲得一個libhello.so,這就是咱們指望的共享庫。
若是你要指定libhello.so生成的位置,能夠經過在主工程文件CMakeLists.txt中修改ADD_SUBDIRECTORY(lib)指令來指定一個編譯輸出位置或者在lib/CMakeLists.txt中添加SET(LIBRARY_OUTPUT_PATH <路徑>)來指定一個新的位置。這二者的區別咱們上一節已經提到了,因此,這裏再也不贅述,下面,咱們解釋一下一個新的指令ADD_LIBRARY:
ADD_LIBRARY(libname [SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL]source1 source2 ... sourceN)
你不須要寫全libhello.so,只須要填寫hello便可,cmake系統會自動爲你生成libhello.X。類型有三種:
(四)、添加靜態庫
一樣使用上面的指令,咱們在支持動態庫的基礎上再爲工程添加一個靜態庫,按照通常的習慣,靜態庫名字跟動態庫名字應該是一致的,只不事後綴是.a罷了。下面咱們用這個指令再來添加靜態庫:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
而後再在build目錄進行外部編譯,咱們會發現,靜態庫根本沒有被構建,仍然只生成了一個動態庫。由於hello做爲一個target是不能重名的,因此,靜態庫構建指令無效。
若是咱們把上面的hello修改成hello_static:
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
就能夠構建一個libhello_static.a的靜態庫了。這種結果顯示不是咱們想要的,咱們須要的是名字相同的靜態庫和動態庫,由於target名稱是惟一的,因此,咱們確定不能經過ADD_LIBRARY指令來實現了。這時候咱們須要用到另一個指令:
SET_TARGET_PROPERTIES,其基本語法是:
SET_TARGET_PROPERTIES(target1 target2 ...PROPERTIES prop1 value1prop2 value2 ...)這條指令能夠用來設置輸出的名稱,對於動態庫,還能夠用來指定動態庫版本和API版本。在本例中,咱們須要做的是向lib/CMakeLists.txt中添加一條:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
這樣,咱們就能夠同時獲得libhello.so/libhello.a兩個庫了。與他對應的指令是:
GET_TARGET_PROPERTY(VAR target property)
具體用法以下例,咱們向lib/CMakeListst.txt中添加:
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS "This is the hello_static OUTPUT_NAME:" ${OUTPUT_VALUE})若是沒有這個屬性定義,則返回 NOTFOUND。
讓咱們來檢查一下最終的構建結果,咱們發現,libhello.a已經構建完成,位於build/lib目錄中,可是libhello.so去消失了。這個問題的緣由是:cmake在構建一個新的target時,會嘗試清理掉其餘使用這個名字的庫,由於,在構建libhello.a時,就會清理掉libhello.so.爲了迴避這個問題,好比再次使用SET_TARGET_PROPERTIES定義
CLEAN_DIRECT_OUTPUT屬性。向lib/CMakeLists.txt中添加:
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
這時候,咱們再次進行構建,會發現build/lib目錄中同時生成了 libhello.so 和 libhello.a
(五)、動態庫版本號
按照規則,動態庫是應該包含一個版本號的,咱們能夠看一下系統的動態庫,通常狀況是
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2
爲了實現動態庫版本號,咱們仍然須要使用SET_TARGET_PROPERTIES指令。具體使用方法以下:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION指代動態庫版本,SOVERSION指代API版本。將上述指令加入lib/CMakeLists.txt中,從新構建看看結果。
在build/lib目錄會生成:
libhello.so.1.2
libhello.so.1->libhello.so.1.2
libhello.so ->libhello.so.1
(六)、安裝共享庫和頭文件
以上面的例子,咱們須要將libhello.a, libhello.so.x以及hello.h安裝到系統目錄,才能真正讓其餘人開發使用,在本例中咱們將hello的共享庫安裝到<prefix>/lib目錄,將hello.h安裝到<prefix>/include/hello目錄。
利用上一節瞭解到的INSTALL指令,咱們向lib/CMakeLists.txt中添加以下指令:
INSTALL(TARGETS hello hello_static
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
注意,靜態庫要使用ARCHIVE關鍵字經過:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
make install
咱們就能夠將頭文件和共享庫安裝到系統目錄/usr/lib和/usr/include/hello中了。
(七)、小結
本小節,咱們談到了:
如何經過 ADD_LIBRARY 指令構建動態庫和靜態庫。
如何經過 SET_TARGET_PROPERTIES 同時構建同名的動態庫和靜態庫。
如何經過 SET_TARGET_PROPERTIES 控制動態庫版本
最終使用上一節談到的 INSTALL 指令來安裝頭文件和動態、靜態庫。
在下一節,咱們須要編寫另外一個高級一點的Hello World來演示怎麼使用咱們已經構建的構建的共享庫libhello和外部頭文件。
如下是我跟着作的截圖,確實很方便:(這裏有點失誤,設置了個CC的環境變量,因此這裏找的是arm-linux-gcc去了。。本意是用gcc編譯的)
抱歉,本節仍然繼續折騰Hello World。
上一節咱們已經完成了libhello動態庫的構建以及安裝,本節咱們的任務很簡單:
編寫一個程序使用咱們上一節構建的共享庫。
一、準備工做:
請在/backup/cmake目錄創建t4目錄,本節全部資源將存儲在t4目錄。
二、重複之前的步驟,創建src目錄,編寫源文件main.c,內容以下:
#include <hello.h>
int main()
{
HelloFunc();
return 0;
}
編寫工程主文件CMakeLists.txt
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
編寫src/CMakeLists.txt
ADD_EXECUTABLE(main main.c)
上述工做已經嚴格按照咱們前面季節提到的內容完成了。
3、外部構建
按照習慣,仍然創建build目錄,使用cmake ..方式構建。
過程:
cmake ..
make
構建失敗,若是須要查看細節,可使用第一節提到的方法
make VERBOSE=1
來構建
錯誤輸出爲是:
/backup/cmake/t4/src/main.c:1:19: error: hello.h: 沒有那個文件或目錄
4、引入頭文件搜索路徑
hello.h位於/usr/include/hello目錄中,並無位於系統標準的頭文件路徑,(有人會說了,白癡啊,你就不會include <hello/hello.h>,同志,要這麼幹,我這一節就沒什麼可寫了,只能選擇一個glib或者libX11來寫了,這些代碼寫出來不少同志是看不懂的)
爲了讓咱們的工程可以找到hello.h頭文件,咱們須要引入一個新的指令
INCLUDE_DIRECTORIES,其完整語法爲:
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
這條指令能夠用來向工程添加多個特定的頭文件搜索路徑,路徑之間用空格分割,若是路徑中包含了空格,可使用雙引號將它括起來,默認的行爲是追加到當前的頭文件搜索路徑的後面,你能夠經過兩種方式來進行控制搜索路徑添加的方式:
(1)、CMAKE_INCLUDE_DIRECTORIES_BEFORE,經過SET這個cmake變量爲on,能夠將添加的頭文件搜索路徑放在已有路徑的前面。
(2)、經過AFTER或者BEFORE參數,也能夠控制是追加仍是置前。如今咱們在src/CMakeLists.txt中添加一個頭文件搜索路徑,方式很簡單,加入:INCLUDE_DIRECTORIES(/usr/include/hello)
進入build目錄,從新進行構建,這是找不到hello.h的錯誤已經消失,可是出現了一個新的錯誤:
main.c:(.text+0x12): undefined reference to `HelloFunc'
由於咱們並無link到共享庫libhello上。
五、爲target添加共享庫
咱們如今須要完成的任務是將目標文件連接到libhello,這裏咱們須要引入兩個新的指令
LINK_DIRECTORIES和TARGET_LINK_LIBRARIES
LINK_DIRECTORIES的所有語法是:
LINK_DIRECTORIES(directory1 directory2 ...)
這個指令很是簡單,添加非標準的共享庫搜索路徑,好比,在工程內部同時存在共享庫和可執行二進制,在編譯時就須要指定一下這些共享庫的路徑。這個例子中咱們沒有用到這個指令。
TARGET_LINK_LIBRARIES的所有語法是:
TARGET_LINK_LIBRARIES(target library1<debug | optimized> library2...)
這個指令能夠用來爲target添加須要連接的共享庫,本例中是一個可執行文件,可是一樣能夠用於爲本身編寫的共享庫添加共享庫連接。爲了解決咱們前面遇到的HelloFunc未定義錯誤,咱們須要做的是向src/CMakeLists.txt中添加以下指令:
TARGET_LINK_LIBRARIES(main hello)
也能夠寫成
TARGET_LINK_LIBRARIES(main libhello.so)
這裏的hello指的是咱們上一節構建的共享庫libhello.
進入build目錄從新進行構建。
cmake ..
make
這是咱們就獲得了一個鏈接到libhello的可執行程序main,位於build/src目錄,運行main的結果是輸出:
Hello World
讓咱們來檢查一下main的連接狀況:
ldd src/main
linux-gate.so.1 => (0xb7ee7000)
libhello.so.1 => /usr/lib/libhello.so.1 (0xb7ece000)
libc.so.6 => /lib/libc.so.6 (0xb7d77000)
/lib/ld-linux.so.2 (0xb7ee8000)
能夠清楚的看到main確實連接了共享庫libhello,並且連接的是動態庫libhello.so.1
那如何連接到靜態庫呢?
方法很簡單:
將TARGET_LINK_LIBRRARIES指令修改成:
TARGET_LINK_LIBRARIES(main libhello.a)
從新構建後再來看一下main的連接狀況
ldd src/main
linux-gate.so.1 => (0xb7fa8000)
libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
/lib/ld-linux.so.2 (0xb7fa9000)
說明,main確實連接到了靜態庫libhello.a
六、特殊的環境變量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH
務必注意,這兩個是環境變量而不是cmake變量。使用方法是要在bash中用export或者在csh中使用set命令設置或者CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。
這兩個變量主要是用來解決之前autotools工程中--extra-include-dir等參數的支持的。也就是,若是頭文件沒有存放在常規路徑(/usr/include, /usr/local/include等),則能夠經過這些變量就行彌補。咱們以本例中的hello.h爲例,它存放在/usr/include/hello目錄,因此直接查找確定是找不到的。前面咱們直接使用了絕對路徑INCLUDE_DIRECTORIES(/usr/include/hello)告訴工程這個頭文件目錄。爲了將程序更智能一點,咱們可使用CMAKE_INCLUDE_PATH來進行,使用bash的方法以下:
export CMAKE_INCLUDE_PATH=/usr/include/hello而後在頭文件中將INCLUDE_DIRECTORIES(/usr/include/hello)替換爲:
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
上述的一些指令咱們在後面會介紹。這裏簡單說明一下,FIND_PATH用來在指定路徑中搜索文件名,好比:
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include/usr/include/hello)
這裏咱們沒有指定路徑,可是,cmake仍然能夠幫咱們找到hello.h存放的路徑,就是由於咱們設置了環境變量CMAKE_INCLUDE_PATH。若是你不使用FIND_PATH,CMAKE_INCLUDE_PATH變量的設置是沒有做用的,你不能期望它會直接爲編譯器命令添加參數-I<CMAKE_INCLUDE_PATH>。以此爲例,CMAKE_LIBRARY_PATH能夠用在FIND_LIBRARY中。一樣,由於這些變量直接爲FIND_指令所使用,因此全部使用FIND_指令的cmake模塊都會受益。
七、小節
本節咱們探討了:
如何經過INCLUDE_DIRECTORIES指令加入非標準的頭文件搜索路徑。
如何經過LINK_DIRECTORIES指令加入非標準的庫文件搜索路徑。
若是經過TARGET_LINK_LIBRARIES爲庫或可執行二進制加入庫連接。
並解釋了若是連接到靜態庫。
到這裏爲止,您應該基本可使用cmake工做了,可是還有不少高級的話題沒有探討,好比編譯條件檢查、編譯器定義、平臺判斷、如何跟pkgconfig配合使用等等。
到這裏,或許你能夠理解前面講到的「cmake的使用過程其實就是學習cmake語言並編寫cmake程序的過程」,既然是「cmake語言」,天然涉及到變量、語法等。
下一節,咱們將拋開程序的話題,看看經常使用的CMAKE變量以及一些基本的控制語法規則。
未完,待續。。。。
這裏關於LINK_DIRECTORIES彷佛有個bug。基本上按照上述所寫方法,只是我沒有將測試用的hello.h和libhello.so安裝在系統的usr/lib目錄下。因而我就須要用到這個LINK_DIRECTORIES指定個人庫文件的目錄。main.c的CMakeLists.txt以下:
ADD_EXECUTABLE(main main.c)
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
TARGET_LINK_LIBRARIES(main libhello.so)
LINK_DIRECTORIES(/home/wideking/cmakeDemo/libhello/bulid)
在編譯的時候它就是找不到個人libhello.so文件。goole了一下,有人試過將ADD_EXECUTABLE和LINK_DIRECTORIES互換了下位置就好了。我也試了試,提示錯誤,必須先有main,纔能有庫的連接,確實也應該這樣。因而再在後面增長一行ADD_EXECUTABLE就變成了這樣:
ADD_EXECUTABLE(main main.c)
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
TARGET_LINK_LIBRARIES(main libhello.so)
LINK_DIRECTORIES(/home/wideking/cmakeDemo/libhello/bulid)
ADD_EXECUTABLE(main main.c)
雖然有構建的時候有warning,可是這樣才能正常的使用。我是在cmake2.6版本中測試的。不知道這個是我沒理解對仍是咋的。。。。
那個warning就是不得很少添加了一次ADD_EXECUTABLE出來的
好比目錄結構以下
project/utils
project/bin/lib
project/login/remote/control/src
project/login/remote/control/build
構造control工程
LINK_DIRECTORIES(../../../bin/lib)
這裏的相對路徑並非相對於源碼路徑(CMakeLists.txt路徑),而是相對於執行命令的路徑(build目錄),向上三層目錄結構。
src目錄下是源代碼,在build目錄下執行make,那麼這個相對路徑就是相對於build目錄。
而頭文件的路徑則是相對於源碼的路徑(CMakeLists.txt路徑)
INCLUDE_DIRECTORIES(../../utils)向上2層目錄
指定頭文件搜索路徑
INCLUDE_DIRECTORIES( "../../xxxx" ) // 注意:括號裏的相對路徑是相對CMakeLists.txt的。
指定庫的鏈接路徑
LINK_DIRECTORIES(" ../../xxxx ") // 括號裏的相對路徑是相對執行make動做的目錄爲基準的。
《CMake實踐》筆記一:PROJECT/MESSAGE/ADD_EXECUTABLE