參考資料地址:https://github.com/Akagi201/learning-cmake/blob/master/docs/cmake-practice.pdf
一、靜態庫與動態庫構建
本小節目標如下:
- 建立一個靜態庫和動態庫,提供HelloFunc函數供其他程序編程使用,HelloFunc向終端輸出Hello World字符串
- 安裝頭文件與共享庫
1. 建立工作目錄t3
mkdir -p /backup/cmake/t3
2. 建立共享庫目錄
cd /backup/cmake/t3
mkdir lib
在t3工程目錄下建立CMakeLists.txt:
// /backup/cmake/t3/CMakeLists.txt
1 PROJECT(HELLOLIB) 2 ADD_SUBDIRECTORY(lib)
在lib目錄下建立源文件hello.c與hello.h:
//hello.c
1 #include "hello.h" 2 void HelloFunc() 3 { 4 printf("Hello World\n"); 5 }
//hello.h
1 #ifndef HELLO_H 2 #define HELLO_H 3 #include <stdio.h> 4 void HelloFunc(); 5 #endif
在lib子目錄下建立CMakeLists.txt:
// /backup/cmake/t3/CMakeLists.txt
1 SET(LIBHELLO_SRC hello.c) 2 ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
3. 編譯共享庫
mkdir build + cd build
cmake .. + make //在lib目錄生成共享庫libhello.so
//可修改工程目錄t3中的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)
注:
(1)無需寫全libhello.so,只需要將libname設置為hello即可,cmake系統會自動生成libhello.X
(2)庫類型有三種:
- SHARED:動態庫
- STATIC: 靜態庫
- MODULE:在使用dyld的系統中有效,如果不支持dyld則被視為SHARED
(3)EXCLUDE_FROM_ALL參數的意思是這個庫不會被默認構建,除非有其他的組件依賴或者手工構建
4. 添加靜態庫(lib/CMakeLists.txt)
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) //靜態庫和動態庫名字一致,生成后綴為.a
添加上述命令並重新進行外部編譯后,仍然僅生成動態庫,並沒有構建靜態庫。原因:hello作為一個target是不能重名的,所以,靜態庫構建指令無效。
(1)解決方案一:ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
構建了libhello_static.a的靜態庫;不足:靜態庫和動態庫名字不同
(2)解決方案二:采用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兩個庫。
補充:對應的GET_TARGET_PROPERTY指令
GET_TARGET_PROPERTY(VAR target property)
具體用法:向lib/CMakeLists.txt添加
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS “This is the hello_static OUTPUT_NAME: ” ${OUTPUT_VALUE}) //如果未定義該屬性,則返回NOTFOUND
(3)問題:檢查最終的構建結果,會發現build/lib目錄中存在libhello.a,但是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庫
5. 動態庫版本號:使用SET_TARGET_PROPERTIES指令
在lib/CMakeLists.txt中加入:SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
構建結果會在build/lib目錄生成:
libhello.so.1.2
libhello.so.1 -> libhello.so.1.2
libhello.so -> libhello.so.1
6. 安裝共享庫和頭文件(將libhello.a, libhello.so.x以及hello.h安裝到系統目錄,以供其他人開發使用)
在lib/CMakeLists.txt中添加如下指令:
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
注:靜態庫要使用ARCHIVE關鍵字
執行cd build + cmake -DCMAKE_INSTALL_PREFIX=/usr .. + make + make install 將頭文件和共享庫安裝到系統目錄/usr/lib和/usr/include/hello目錄中
二、使用外部共享庫和頭文件
1. 創建工作目錄
mkdir -p /bakcup/cmake/t4
2. 建立src子目錄 mkdir src,編寫源文件main.c:
//main.c
1 #include<hello.h> 2 int main() 3 { 4 HelloFunc(); 5 return 0; 6 }
編寫工程目錄CMakeLists.txt:
// /backup/cmake/t4/CMakeLists
1 PROJECT(NEWHELLO) 2 ADD_SUBDIRECTORY(src)
編寫src/CMakeLists.txt:
// /backup/cmake/t4/src/CMakeLists
1 ADD_EXECUTABLE(main main.c)
3. 外部構建
mkdir build + cd build + cmake .. + make VERBOSE=1
構建失敗,錯誤輸出為:/backup/cmake/t4/src/main.c:1:19: error: hello.h: 沒有那個文件或目錄
原因:找不到頭文件
4. 引入頭文件搜索路徑(上一節的hello.h位於/usr/include/hello目錄中,並沒有位於系統標准的頭文件路徑)
解決方法:采用INCLUDE_DIRECTORIES指令
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
用來向工程添加多個特定的頭文件搜索路徑,路徑之間用空格分割,如果路徑中包含了空格,可以使用雙引號將它括起來,默認的行為是追加到當前的頭文件搜索路徑的后面,可以通過兩種方式來進行控制搜索路徑添加的方式:
(1)通過SET指令設置cmake變量CMAKE_INCLUDE_DIRECTORIES_BEFORE為on,將添加的頭文件搜索路徑放在已有路徑的前面
(2)通過上述指令的AFTER或者BEFORE參數,控制是追加還是置前
因此,在src/CMakeLists.txt中添加頭文件搜索路徑:INCLUDE_DIRECTORIES(/usr/include/hello)
重新進入build構建,仍然會失敗,新的錯誤為:main.c:(.text+0x12): undefined reference to `HelloFunc'
原因:目標文件沒有link到共享庫libhello上
5. 為target添加共享庫
解決方法:LINK_DIRECTORIES和TARGET_LINK_LIBRARIES指令
LINK_DIRECTORIES(directory1 directory2 ...) //添加非標准的共享庫搜索路徑
TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 ...) //添加鏈接,為target添加需要鏈接的共享庫或可執行二進制文件,本例中是一個可執行文件,也可以用於為自己編寫的共享庫添加共享庫鏈接
因此,在src/CMakeLists.txt中添加如下指令:TARGET_LINK_LIBRARIES(main hello) 或者 TARGET_LINK_LIBRARIES(main libhello.so)
重新進入build構建,得到了一個連接到libhello的可執行程序main,位於build/src目錄
查看main的鏈接情況:ldd src/main //ldd:list dynamic dependencies,列出動態庫依賴關系
linux-gate.so.1 => (0xb7ee7000)
libhello.so.1 => /usr/lib/libhello.so.1 (0xb7ece000) //鏈接動態庫libhello.so.1
libc.so.6 => /lib/libc.so.6 (0xb7d77000)
/lib/ld-linux.so.2 (0xb7ee8000)
如何鏈接到靜態庫?修改鏈接指令為:TARGET_LINK_LIBRARIES(main libhello.a)
ldd src/main:
linux-gate.so.1 => (0xb7fa8000)
libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
/lib/ld-linux.so.2 (0xb7fa9000)
結果無動態庫,表明確實鏈接到靜態庫
6. 特殊的環境變量CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH(非cmake變量)
用於在bash中用export或者在csh中使用set命令設置或者CMAKE_INCLUDE_PATH=/home/include cmake ..等方式;主要用於彌補頭文件沒有存放在常規路徑的搜索情況
例如前面我們直接使用了絕對路徑INCLUDE_DIRECTORIES(/usr/include/hello)告訴工程這個頭文件目錄。為了將程序更智能一點,我們可以使用CMAKE_INCLUDE_PATH來進行,使用bash的方法如下:
export CMAKE_INCLUDE_PATH=/usr/include/hello
然后在src/CMakeLists.txt中將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_INCLUDE_PATH;相應的FIND_LIBRARY可以使用CMAKE_LIBRARY_PATH變量,所有使用FIND_指令的cmake模塊都可以使用上述環境變量。