【cmake系列使用教程】
這個系列的文章翻譯自官方cmake教程:cmake tutorial。
示例程式地址:github.com/rangaofei/t…
不會僅僅停留在官方教程。本人作為一個安卓開發者,實在是沒有linux c程式開發經驗,望大佬們海涵。教程是在macos下完成,大部分linux我也測試過,有特殊說明的我會標註出來。本教程基於cmake-3.10.2,同時認為你已經安裝好cmake。
簡介
cmake中的file使用也很簡單,與c語言的檔案io相似。file命令屬於指令碼命令,可以解除安裝指令碼中。後邊講到的aux_source_directory
屬於工程命令,不能用在指令碼中。
首先來說一個我在編寫程式的時候遇到的問題,在學習apue的過程中,有許多例項小程式,因為IDE用的是Clion,所以需要在cmake指令碼中生成執行檔案,最開始的時候是簡單的新增add_executable
命令來不斷的增加新的程式構建,隨著學習的深入,手動新增太麻煩了,於是寫了一個簡單的指令碼來半自動構建程式,目錄結構如下:
apue git:(master) ✗ cd src
➜ src git:(master) ✗ tree
.
├── CmakeLists.txt
├── part1
│ ├── CmakeLists.txt
│ ├── copytest.c
│ ├── groupid.c
│ ├── mycopy.c
│ ├── myerror.c
│ ├── myls.c
│ ├── myshell.c
│ ├── mystdcopy.c
│ ├── newshell.c
│ └── processid.c
├── part11
│ ├── CmakeLists.txt
│ ├── pthread1.c
│ ├── pthread2.c
│ ├── pthread3.c
│ └── pthread4.c
├── part3
│ ├── CmakeLists.txt
│ ├── holetest.c
│ └── seektest.c
├── unp_1
│ ├── CMakeLists.txt
│ ├── daytimetcpcli.c
│ ├── daytimetcpcliv6.c
│ └── daytimetcpsrv.c
└── unp_3
├── CMakeLists.txt
└── byteorder.c
複製程式碼
src是apue的原始碼目錄,包含多個章節對應的資料夾和一個主cmakelist檔案,章節資料夾下是具體的c和h檔案和一個cmakelist檔案,看一下part3章節資料夾中的cmakelist檔案:
AUX_SOURCE_DIRECTORY(. PART_THREE)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
FOREACH (FILE ${PART_THREE})
MESSAGE(STATUS ${FILE})
STRING(REPLACE "./" "" LIB_NAME ${FILE})
STRING(REPLACE ".c" "" LIB_NAME ${LIB_NAME})
add_executable(${LIB_NAME} ${FILE})
target_link_libraries(${LIB_NAME} apue.a)
ENDFOREACH ()
複製程式碼
這段程式碼首先將當前章節的所有檔案收入到PART_THREE變數中,以list形式儲存,下邊兩行程式碼設定了可執行檔案和庫檔案的儲存路徑,然後FOREACHE迴圈中輸出了所有的可執行檔案和連結庫,可執行檔名稱是用.c檔案用正則替換無用資訊後生成的。
然後在主cmakelist中新增add_subdirectory(part3)
後直接執行即可。這也是今天要講的主要內容。
檔案寫入與追加
file(WRITE <filename> <content>...)
file(APPEND <filename> <content>...)
複製程式碼
將content的內容寫入filename中,假如檔案不存在則會建立檔案,假如filename中包含路徑,則相應的資料夾也會被建立。WRITE會擦出檔案內容,重新寫入content,APPEND會在檔案末尾追加內容。寫一個最簡單的測試:
file(WRITE test.txt "this is a test to wirte\n")
file(APPEND test/test.txt "this is a test to append")
file(APPEND test.txt "this is a test to append")
複製程式碼
此時目錄結構如下:
.
└── write.cmake
0 directories, 1 file
複製程式碼
執行該指令碼後:
➜ Stepfile git:(master) ✗ cmake -P write.cmake
➜ Stepfile git:(master) ✗ tree
.
├── test
│ └── test.txt
├── test.txt
└── write.cmake
1 directory, 3 files
複製程式碼
前邊介紹過configure_file這個命令,是用來在構建工程時替換檔案內容的,注意一下區別。
檔案的讀取
file(READ <filename> <variable>
[OFFSET <offset>] [LIMIT <max-in>] [HEX])
複製程式碼
這個也比較簡單: 將filename檔案中的內容讀取到variable總,可以指定OFFSET的值,也就是開始讀取的位置,指定LISTMI的值,讀取的長度,HEX是否以16進位制形式讀取。
file(STRINGS <filename> <variable> [<options>...])
複製程式碼
類似於讀取字元碼,而不讀取位元組碼。這個命令會將filename中的字串讀取到variable中,並且variable是一個list,每個元素儲存每行的內容。二進位制檔案不會被讀取,並且換行符會被忽略。舉個例子,我們剛才寫入的test.txt
的檔案內容是:
this is a test to wirte
this is a test to append
have tab #這個是我手動新增的
複製程式碼
我們讀取這個檔案並列印結果,編寫string.cmake
檔案如下:
file(STRINGS test.txt strings)
foreach(str IN LISTS strings)
message(STATUS ${str})
endforeach(str)
複製程式碼
因為結果會用list儲存,所以用foreach迴圈來檢視結果:
-- this is a test to wirte
-- this is a test to append
-- have tab
複製程式碼
關於一些選項,用的不太多:
OPTION | 說明 |
---|---|
LENGTH_MAXIMUM | 讀取字元的最大個數 |
LENGTH_MINIMUM | 讀取的字元的最少個數 |
LIMIT_COUNT | 提取的不同字元的最大數量 |
LIMIT_INPUT | 限制讀取的最大位元組 |
LIMIT_OUTPUT | 限制寫入變數的最大位元組 |
NEWLINE_CONSUME | 不忽略換行符 |
NO_HEX_CONVERSION | 不需要自動轉換為16進位制 |
REGEX | 提取匹配正規表示式的字串 |
ENCODING | 重新編碼UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE |
檔案的hash碼
file(<HASH> <filename> <variable>)
複製程式碼
利用這個命令可以提取出檔案的hash碼
MD5,SHA1,SHA224,SHA256,SHA384,SHA512,SHA3_224,SHA3_256,SHA3_384,SHA3_512
複製程式碼
如果看過我的bomebrew教程可應該知道,在生成formula.rb
檔案的時候需要填寫打包好的檔案的SHA256來驗證下載檔案的完整性,所以可以利用這個寫一個簡單的指令碼來輸出hash值,寫一個簡單的例子吧:
file(SHA256 test.txt hash)
message(STATUS ${hash})
複製程式碼
-- f9bb70f1a2036a73f611858d01a8fb498efc7c83568faf0c74e5a52037492702
複製程式碼
收集檔案
file(GLOB <variable>
[LIST_DIRECTORIES true|false] [RELATIVE <path>]
[<globbing-expressions>...])
file(GLOB_RECURSE <variable> [FOLLOW_SYMLINKS]
[LIST_DIRECTORIES true|false] [RELATIVE <path>]
[<globbing-expressions>...])
複製程式碼
兩個命令,首先講一下第一個GLOB:
GLOB命令將所有匹配<globbing-expressions>
(可選,假如不寫,毛都匹配不到)的檔案挑選出來,預設以字典順序排序。
file(GLOB files *)
foreach(file IN LISTS files)
message(STATUS ${file})
endforeach(file)
複製程式碼
這段程式碼的意思是挑選出當前檔案下的所有檔案,然後列印:
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/filelist.cmake
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/hash.cmake
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/string.cmake
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/test
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/test.txt
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/write.cmake
複製程式碼
其實我這個資料夾下的內容如下:
.
├── filelist.cmake
├── hash.cmake
├── string.cmake
├── test
│ └── test.txt
├── test.txt
└── write.cmake
1 directory, 6 files
複製程式碼
test是一個資料夾,但是在指令碼中輸出了這個資料夾。假如我們不想要這個資料夾,我們可以通過LIST_DIRECTORIES
設定為false即可(預設為true),修改第一行程式碼如下:
file(GLOB files LIST_DIRECTORIES false *)
複製程式碼
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/filelist.cmake
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/hash.cmake
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/string.cmake
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/test.txt
-- /Users/rangaofei/Documents/program/tutorial/Stepfile/write.cmake
複製程式碼
這次只輸出了檔案,而資料夾沒有在裡邊,假如我們不需要絕對路徑,只需要相對某個資料夾的路徑,則可以通過設定RELATIVE
的值來設定。
將檔案修改如下:
set(CUR ${CMAKE_CURRENT_SOURCE_DIR})
file(GLOB files LIST_DIRECTORIES false RELATIVE ${CUR}/.. *)
foreach(file IN LISTS files)
message(STATUS ${file})
endforeach(file)
複製程式碼
我們設定了CUR為當前的資料夾,然後設定相對路徑為當前資料夾的上級資料夾,而我的當前資料夾名稱為Stepfile,則輸出會包含當前資料夾的名字+檔名字:
-- Stepfile/filelist.cmake
-- Stepfile/hash.cmake
-- Stepfile/string.cmake
-- Stepfile/test.txt
-- Stepfile/write.cmake
複製程式碼
就是這麼蛋疼。還要說一下這個蛋疼的偽正則匹配,一般檔案是夠用的。
*.cxx - 匹配所有的cxx結尾的檔案
*.vt? - 匹配所有的vta,...,vtz等檔案
f[3-5].txt - 匹配f3.txt, f4.txt, f5.txt這三個檔案
複製程式碼
cmake官方不推薦使用GLOB來收集檔案,因為在工程或者模組中的CMakeLists.txt檔案未更改而用file搜尋的資料夾下有檔案的刪除或者增加,cmake構建並不會知曉,而是使用舊的list。
再來講一下第二個,GLOB_CURSE:
這個命令是用來列出所有子資料夾中的檔案和當前所有檔案,具體深度多少我也不知道。用法基本同上,只是多了一個FOLLOW_SYMLINKS
可選項。2.6.1版本之前對於連結的資料夾同樣會列出所有的連結過去的資料夾下的檔案,因為這樣會引起一些麻煩,所以在以後的版本中去掉了這個屬性,而是將連結當做一個檔案,不會列出連結到的資料夾下的檔案。假如需要列出,則新增FOLLOW_SYMLINKS
引數即可。
cmake_minimum_required(VERSION 3.6)
# if(POLICY CMP0009)
# cmake_policy(SET CMP0009 NEW)
# endif()
set(CUR ${CMAKE_CURRENT_SOURCE_DIR})
file(GLOB_RECURSE files FOLLOW_SYMLINKS LIST_DIRECTORIES true RELATIVE ${CUR}/.. *)
foreach(file IN LISTS files)
message(STATUS ${file})
endforeach(file)
複製程式碼
這段程式碼將會列出當前所有檔案、子資料夾中的檔案以及連結中的檔案。
關於AUX_SOURCE_DIRECTORY
aux_source_directory(<dir> <variable>)
複製程式碼
注意這個命令不能用於script中,他是project命令。
尋找dir資料夾下所有的原始檔,存入variable中。這個命令與之前的命令有所區別,因為它只會蒐集當前設定語言的檔案,cmake預設的設定語言是c/cxx,則會收集到的檔案只有這些語言能識別的檔案,比如在step中新增如下程式碼
aux_source_directory(./ SRCLIST)
foreach(file IN LISTS SRCLIST)
message(STATUS ${file})
endforeach(file)
複製程式碼
當前目錄結構如下
.
├── CMakeLists.txt
├── TutorialConfig.h.in
├── build
└── tutorial.cxx
複製程式碼
看一下輸出了什麼
-- ./tutorial.cxx
複製程式碼
只有一個檔案被假如list中了。
檔案的操作
file(RENAME <oldname> <newname>)
複製程式碼
重新命名檔案或者資料夾
file(REMOVE [<files>...])
file(REMOVE_RECURSE [<files>...])
複製程式碼
刪除指定的檔案,REMOVE_RECURSE
則會刪除檔案和資料夾,假如不存在,不會丟擲錯誤。
file(MAKE_DIRECTORY [<directories>...])
複製程式碼
遞迴建立檔案,包括路徑中的資料夾
file(RELATIVE_PATH <variable> <directory> <file>)
複製程式碼
計算file相對於directory的相對路徑,存入variable中。類似於前邊的收集檔案。
file(TO_CMAKE_PATH "<path>" <variable>)
file(TO_NATIVE_PATH "<path>" <variable>)
複製程式碼
在cmake路徑和本地路徑之間相互轉換。cmake路徑使用的是/
file(DOWNLOAD <url> <file> [<options>...])
file(UPLOAD <file> <url> [<options>...])
複製程式碼
這兩個命令真是讓我的菊花緊到極致了。第一個是從url下載檔案命名為file,第二個是將本地檔案file上傳至url。 以下的option適用於這兩個命令
引數 | 說明 |
---|---|
INACTIVITY_TIMEOUT | 超時時間 |
LOG | 將日誌寫入變數中 |
SHOW_PROGRESS | 顯示進度 |
STATUS | a;b 形式,a是返回的狀態碼,b是錯誤程式碼,假如沒錯誤,b是0(鬼知道,我沒試) |
TIMEOUT | 連線超時時間 |
USERPWD : | 使用者名稱和密碼 |
HTTPHEADER | http請求頭 |
EXPECTED_HASH ALGO= | 驗證演算法(適用於下載) |
file(TIMESTAMP <filename> <variable> [<format>] [UTC])
複製程式碼
將filename檔案的時間戳儲存在varibale中。
file(GENERATE OUTPUT output-file
<INPUT input-file|CONTENT content>
[CONDITION expression])
複製程式碼
這個命令不能用於script,只能在project中才有作用。
在教程四中我們介紹過用add_custom_command
方式在構建時新增檔案,現在講的file方式,基本類似,INPUT和CONTENT必須要選一個,前者是以檔案為內容,後者是以字串為內容。在3.10版本之後,INPUT使用的是相對當前資料夾的路徑,OUTPUT使用的是生成的資料夾的路徑。另外,只有當condition為真得時候才會執行生成檔案,並且表示式的值必須返回0或者1。假如設定的最小版本小於3.10,徐志明CMP0070,設定為new,則與3.10相同,設定為old,則是相對路徑,不測試old了,因為以後不會用這個。
在step1中cdCMakeLists.txt中新增
if(POLICY CMP0070)
cmake_policy(SET CMP0070 NEW)
endif()
file(GENERATE OUTPUT out.txt
CONTENT "java"
)
複製程式碼
在build資料夾中確實生成了out.txt
,且內容是java。
還有兩個命令:
file(<COPY|INSTALL> <files>... DESTINATION <dir>
[FILE_PERMISSIONS <permissions>...]
[DIRECTORY_PERMISSIONS <permissions>...]
[NO_SOURCE_PERMISSIONS] [USE_SOURCE_PERMISSIONS]
[FILES_MATCHING]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS <permissions>...]] [...])
複製程式碼
copy和install檔案到指定目標資料夾。
輸入的檔案是相對於當前資料夾的相對路徑,而目標資料夾則是相對於構建資料夾的相對路徑。
COPY可保留輸入檔案時間戳,並優化檔案(如果在目標資料夾已經存在相同的檔案且時間戳相同)。除非給出顯式許可權或NO_SOURCE_PERMISSIONS
,否則將保留許可權(預設值為USE_SOURCE_PERMISSIONS
)。
INSTALL與COPY基本相同,但是會輸出install日誌,並且會預設使用NO_SOURCE_PERMISSIONS
許可權。
file(LOCK <path> [DIRECTORY] [RELEASE]
[GUARD <FUNCTION|FILE|PROCESS>]
[RESULT_VARIABLE <variable>]
[TIMEOUT <seconds>])
複製程式碼
類似於java中的同步鎖。
假如指定了[DIRECTORY]選項則鎖定<path>/cmake.lock
檔案,否則鎖定<path>
檔案。GUARD用來確定作用域,預設是PROCESS,其他兩個看意思也能明白。RELEASE用來顯式的解鎖。
假如沒有指定TIMEOUT選項,則系統會一直等到檔案被上鎖或者發生致命的錯誤;假如設定為0則會立即執行lock操作並返回狀態碼;假如設定不為0,則會等待相應的時間(以秒為單位)來上鎖。
假如沒有設定RESULT_VARIABLE
,則任何錯誤都會阻止程式執行。設定的話結果會儲存到varobale中,成功是0,錯誤會儲存錯誤資訊。
請注意,鎖是建議性的 - 不能保證其他程式會尊重此鎖,即鎖定同步兩個或多個共享某些可修改資源的CMake例項。 應用於DIRECTORY選項的類似邏輯 - 鎖定父目錄不會阻止其他LOCK命令鎖定任何子目錄或檔案。
試圖鎖定檔案兩次是不允許的。 如果它們不存在,任何中間目錄和檔案本身都將被建立。 RELEASE操作中忽略GUARD和TIMEOUT選項。