繼續完善Hello World,創建它的共享庫, 包括靜態庫和動態庫。編程
本節的任務:bash
1,創建一個靜態庫和動態庫,提供HelloFunc函數供其餘程序編程使用,HelloFuncide
向終端輸出Hello World字符串。函數
2,安裝頭文件與共享庫。學習
3, 編寫一個程序使用建立的共享庫(靜態庫和動態庫)。ui
cd /home/ccj/CMakeDemothis
mkdir t3spa
cd /backup/cmake/t3three
mkdir lib開發
在t3目錄下創建CMakeLists.txt,內容以下:
PROJECT(HELLOLIB)
# 經過在主工程文件CMakeLists.txt中修改ADD_SUBDIRECTORY (lib) 指令來指定一個編譯輸出位置;
# 指定本工程中靜態庫libhello.so生成的位置,即 build/lib;
ADD_SUBDIRECTORY(lib)
# 也能夠經過變動爲其餘的位置,如
# ADD_SUBDIRECTORY(lib lib_new)
# 則,靜態庫libhello.so生成的位置變爲 build/lib_new;
在lib目錄下創建兩個源文件hello.cpp與 hello.h
hello.cpp內容以下:
#include "hello.h"
using namespace std;
void HelloFunc(){
cout << "Hello World/n";
}
hello.h內容以下:
#ifndef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
在lib目錄下創建CMakeLists.txt,內容以下:
注意: 這裏咱們將Section 7以前的CMakeLists.txt中應有的內容提供以下:
SET (LIBHELLO_SRC hello.cpp)
# SET (LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# three kinds of libraries:
# 1. shared, i.e., dynamic library,動態庫文件擴展名常爲 "*.so";
# 2. static, i.e., static library, 靜態庫文件擴展名常爲 "*.a";
# 並且,咱們一般但願靜態庫和動態庫文件名保持一致,只是擴展名不一樣;
# 3. module, this parameter is valid only when dyld is supported;
# otherwise, will be considered as shared
# 添加動態庫,關鍵詞爲shared,你不須要寫全libhello.so,
# 只須要填寫hello便可,cmake系統會自動爲你生成 libhello.X
ADD_LIBRARY (hello SHARED ${LIBHELLO_SRC})
# 添加靜態庫,關鍵詞爲static,
# ADD_LIBRARY (hello STATIC ${LIBHELLO_SRC})
# 仍然用hello做爲target名時,是不能成功建立所需的靜態庫的,
# 由於hello做爲一個target是不能重名的, 故把上面的hello修改成hello_static
# 同理,你不須要寫全libhello_static.a
# 只須要填寫hello便可,cmake系統會自動爲你生成 libhello_static.X
ADD_LIBRARY (hello_static STATIC ${LIBHELLO_SRC})
# 按照通常的習慣,靜態庫名字跟動態庫名字應該是一致的,只是擴展名不一樣;
# 即:靜態庫名爲 libhello.a; 動態庫名爲libhello.so ;
# 因此,但願 "hello_static" 在輸出時,不是"hello_static",而是以"hello"的名字顯示,故設置以下:
SET_TARGET_PROPERTIES (hello_static PROPERTIES OUTPUT_NAME "hello")
GET_TARGET_PROPERTY (OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE (STATUS "This is the hello_static OUTPUT_NAME: " ${OUTPUT_VALUE})
# cmake在構建一個新的target時,會嘗試清理掉其餘使用這個名字的庫,
# 所以,在構建libhello.a時,就會清理掉libhello.so.
# 爲了迴避這個問題,好比再次使用SET_TARGET_PROPERTIES定義 CLEAN_DIRECT_OUTPUT屬性。
SET_TARGET_PROPERTIES (hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES (hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
# 按照規則,動態庫是應該包含一個版本號的,
# VERSION指代動態庫版本,SOVERSION指代API版本。
SET_TARGET_PROPERTIES (hello PROPERTIES VERSION 1.2 SOVERSION 1)
# 咱們須要將libhello.a, libhello.so.x以及hello.h安裝到系統目錄,才能真正讓其餘人開發使用,
# 在本例中咱們將hello的共享庫安裝到<prefix>/lib目錄;
# 將hello.h安裝<prefix>/include/hello目錄。
INSTALL (TARGETS hello hello_static LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL (FILES hello.h DESTINATION include/hello)
:
仍然採用out-of-source編譯的方式,按照習慣,咱們創建一個build目錄,在build
目錄中
cmake ..
make
這時,你就能夠在lib目錄獲得一個libhello.so,這就是咱們指望的共享庫。
若是你要指定libhello.so生成的位置,能夠經過:
這二者的區別咱們上一節已經提到了,因此,這裏再也不贅述,下面,咱們解釋一下一個新的
指令ADD_LIBRARY。
ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
你不須要寫全 libhello.so,只須要填寫hello便可,cmake系統會自動爲你生成 libhello.X
類型有三種:
EXCLUDE_FROM_ALL參數的意思是這個庫不會被默認構建,除非有其餘的組件依賴或者手動構建。
一樣使用上面的指令,咱們在支持動態庫的基礎上再爲工程添加一個靜態庫,按照通常的習
慣,靜態庫名字跟動態庫名字應該是一致的,只不事後綴是.a罷了。
下面咱們用這個指令再來添加靜態庫:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
而後再在build目錄進行外部編譯,咱們會發現,靜態庫根本沒有被構建,仍然只生成了
一個動態庫。由於hello做爲一個ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
就能夠構建一個libhello_static.a的靜態庫了。
這種結果顯示不是咱們想要的,咱們須要的是名字相同的靜態庫和動態庫,由於target名
稱是惟一的,因此,咱們確定不能經過ADD_LIBRARY指令來實現了。這時候咱們須要用到
另一個指令:
SET_TARGET_PROPERTIES,
其基本語法是:
SET_TARGET_PROPERTIES (target1 target2 ...PROPERTIES prop1 value1 prop2 value2 ...)
這條指令能夠用來設置輸出的名稱,對於動態庫,還能夠用來指定動態庫版本和API版本。
在本例中,咱們須要做的是向lib/CMakeLists.txt中添加一條:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
這樣,咱們就能夠同時獲得libhello.so 和 libhello.a兩個庫了。
與SET_TARGET_PROPERTIES對應的指令是:
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
目錄,將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/loal ..
make
sudo make install
咱們就能夠將頭文件和共享庫安裝到系統目錄/usr/local/lib和/usr/local/include/hello中了。
以下圖:
前幾個sections中,咱們已經完成了libhello靜、動態庫的構建以及安裝,本section中咱們須要
編寫一個程序使用構建好的共享庫。
)在目錄下創建目:#include "hello.h"
int main(){
HelloFunc();
return 0;
}
編寫工程主文件CMakeLists.txt
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
編寫src/CMakeLists.txt
ADD_EXECUTABLE(main main.cpp)
)外部構建cmake ..
make
構建失敗,若是須要查看細節,可使用 make VERBOSE=1來構建。
錯誤結果以下:
1
)引入頭文件搜索路徑。
hello.h位於/usr/local/include/hello目錄中,並無位於,同志,要這麼幹,我這
一節就沒什麼可寫了,只能選擇一個glib或者libX11來寫了,這些代碼寫出來不少同志
是看不懂的)
爲了讓咱們的工程可以找到hello.h頭文件,咱們須要引入一個新的指令
INCLUDE_DIRECTORIES,其完整語法爲:
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
這條指令能夠用來向工程添加多個特定的頭文件搜索路徑,路徑之間用如今咱們在src/CMakeLists.txt中添加一個頭文件搜索路徑,方式很簡單,加入:
INCLUDE_DIRECTORIES(/usr/local/include/hello)
進入build目錄,從新進行構建,這時找不到hello.h的錯誤已經消失,可是出現了一個新的錯誤:
2
main.cpp:(.text+0x5): undefined reference to `HelloFunc()'
由於咱們並無link到共享庫libhello上。
)target這個指令能夠用來爲target添加須要連接的共享庫,本例中是一個可執行文件,可是一樣能夠用於爲本身編寫的共享庫添加共享庫連接。
爲了解決咱們前面遇到的"HelloFunc()"未定義錯誤,咱們須要做的是向
src/CMakeLists.txt中添加以下指令:
也能夠寫成
進入build目錄從新進行構建。
cmake ..
make
這時咱們就獲得了一個鏈接到libhello的可執行程序main,位於build/src目錄,
運行main的結果是
讓咱們來檢查一下main的連接狀況:
$ ldd src/main
雖然了這個結果,可是沒有理解明白,到底連接什麼庫
能夠清楚的看到main確實連接了共享庫libhello,並且連接的是動態庫libhello.so.1
$ ldd src/main
雖然了這個結果,可是沒有理解明白,到底連接什麼庫
說明,main確實連接到了靜態庫libhello.a
)特殊的環境變量:和務必注意CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH這兩個是環境變量來進行,使用bash的方法以下:
而後在頭文件中將INCLUDE_DIRECTORIES(/usr/local/include/hello)替換爲:
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
上述的一些指令咱們在後面會介紹。這裏簡單說明一下,FIND_PATH用來在指定路徑中搜索文件名,好比:FIND_PATH (myHeader NAMES hello.h PATHS /usr/local/include /usr/local/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模塊都會受益。
到這裏爲止,您應該基本可使用cmake工做了,可是還有不少高級的話題沒有探討,比
如編譯條件檢查、編譯器定義、平臺判斷、如何跟pkgconfig配合使用等等。
到這裏,或許你能夠理解前面講到的"cmake的使用過程其實就是學習cmake語言,並編寫
cmake程序的過程",既然是"cmake語言",天然涉及到變量、語法等. 下一節,咱們將拋開程序的話題,看看經常使用的CMAKE變量以及一些基本的控制語法規則。