參考:
But what is a convolution?
https://www.youtube.com/watch?v=KuXjwB4LzSA
參考:
But what is a convolution?
https://www.youtube.com/watch?v=KuXjwB4LzSA
參考:
論文
https://arxiv.org/pdf/1502.03167.pdf
Training Deep Neural Networks with Batch Normalization
https://zaffnet.github.io/batch-normalization
反向傳播Computational Graphs
Calculus on Computational Graphs: Backpropagation
https://colah.github.io/posts/2015-08-Backprop/
Neural Network Batch Normalization Fusion
https://leimao.github.io/blog/Neural-Network-Batch-Normalization-Fusion/
http://d2l.ai/chapter_convolutional-modern/batch-norm.html
FUSING CONVOLUTION AND BATCH NORM USING CUSTOM FUNCTION
https://pytorch.org/tutorials/intermediate/custom_function_conv_bn_tutorial.html
參考:
設定.gdbinit
https://interrupt.memfault.com/blog/using-pypi-packages-with-gdb
GDB python modules
https://sourceware.org/gdb/onlinedocs/gdb/Python.html#Python
撰寫GDB pretty printer
https://undo.io/resources/gdb-watchpoint/debugging-pretty-printers-gdb-part2/
GDB array to numpy
https://github.com/TorosFanny/gdb_numpy/blob/master/gdb_numpy.py
本篇文章修改參考這篇文章製作範例和教學,首先我們先寫一個有bug的程式factorial.c
如果要給GDB除錯,一定要加上-g
選項
1 | gcc -g <any other flags e.g. -Wall> -o <file> <file.c> |
在終端機輸入gdb和執行檔名稱即可開啟gdb session
1 | gdb <program_name> |
用run指令執行程式。
1 | (gdb) run |
如果有程式有接收參數,可以直接放在run後面
1 | (gdb) run <arg1> <arg2> ... <arg n> |
如果要用檔案作為輸入,可以用<
1 | (gdb) run < <data> |
用start指令執行程式,並且在main函式的第一行停下來。
1 | (gdb) start |
如果有程式有接收參數,可以直接放在run後面
1 | (gdb) start <arg1> <arg2> ... <arg n> |
如果要用檔案作為輸入,可以用<
1 | (gdb) start < <data> |
離開GDB可以用quit指令
1 | (gdb) quit |
以下面程式為範例
1 | //This program calculates and prints out the factorials of 5 and 17 |
用以下指令編譯
1 | gcc -Wall -g -o factorial factorial.c |
接下來開啟GDB session
1 | gdb factorial |
在GDB session中執行程式,會發現程式輸出錯誤的值
1 | (gdb) run |
如果要結束GDB session指令可以用quit。
1 | (gdb) quit |
如果要放置中斷點在特定檔案的某一行,可以用break <filename>:<line number>
指令
1 | (gdb) break <filename>:<line number> |
你也可以直接指定要放在哪一個function(gdb) break <filename>:<function>
1 | (gdb) break <filename>:<function> |
要列出全部的中斷點可以用以下指令
1 | (gdb) info break |
如果要刪除中斷點可以先用info break
所有中斷點的號碼再刪除
1 | (gdb) delete <breakpoint number> |
接著前面的例子,我們懷疑程式的第15行有錯誤,因此在GDB session中把中斷點放在第15行並且執行程式
1 | (gdb) break 15 |
我們應該會看到類似以下的輸出,你可以看到GDB輸出我們的第15行程式,代表現在程式停在第15行。
注意!!這時候程式並還沒有執行第15行。GDB告訴我們的是他下一行將要執行的程式。
1 | [Thread debugging using libthread_db enabled] |
印出變數
1 | (gdb) print <variable_name> |
印出當前所有的local variable,除了輸入函式的參數以外。
1 | (gdb) info locals |
印出輸入給函式的參數
1 | (gdb) info args |
接續前面的範例,們想查看程式運作到中斷點時變數的值。首先查看f和n的值
1 | (gdb) print f |
我們也可以用info locals
來查看所有區域變數。
1 | (gdb) info locals |
當程式在中斷點停下後,利用step
, next
and continue
可以控制程式運作以便我們逐行觀察程式運作的情形。
執行下一行程式,如果下一行程式是呼叫function,gdb就會進到function裡面。
1 | (gdb) step |
執行下一行程式,與step不同的是,如果下一行是function,則將function執行完而不進入function。
1 | (gdb) next |
執行程式直到碰到下一個中斷點
1 | (gdb) continue |
印出function call stack
1 | (gdb) where |
印出目前所在的行數以及前後各兩行。
1 | (gdb) list |
接續前面的範例。我們想要查看我們的factorial函式如何運作的。因此用step指令進入factorial函式。
1 | (gdb) step |
接下來我們想一步一步看看factorial函式如和運作的
1 | (gdb) next |
除了不斷輸入重複的指令,你也可以直接按Enter,GDB會重複你上一個指令。
接下來我們預期執行到這裡,i
和f
應該要等於1
1 | (gdb) print i |
如果我們想查看目前在程式哪一行,可以用where
指令來印出call stack
1 | (gdb) where |
如果要印出目前行數前後的程式可以用list
1 | (gdb) list |
我們也可以用continue
指令和中斷點來加速除錯,首先先下一個中斷點在第28行。
1 | (gdb) break 28 |
接下來用continue
指令直接跳到這個中斷點
1 | (gdb) continue |
然後依次印出所有區域變數
1 | info locals |
我們不斷重複這個動作,可以發現前面都還運作正常,直到i=13時,答案開始出出錯了,如果繼續執行會發現答案越來越小,甚至變成負的。這個錯誤原因是int這個資料型態無法儲存這麼大的值,我們必須使用更大的資料型態才能儲存。
call Stack是由stack frames所組成。stack frames是用來儲存呼叫函式的時候函式的區域變數。如果函式內又呼叫另一個函式,新的一個stack frames會被放當前函式的stack frames的上面。當一個函式完成後,就會移除一個stack frames。
印出call stack並且包含檔名和行數。
往上移動stack一層frame
1 | (gdb) up |
往下移動stack一層frame
1 | (gdb) down |
移動到指定的frame
1 | (gdb) frame <frame_number> |
如果錯誤出現在函式庫的程式碼,這時候用call stack來debug就會很有用,我們可以利用call stack來尋找我們的程式在什麼地方出錯。用下面範例corrupted_linked_list.c
來講解。
1 | //Makes a linked list of length 7 and prints it out |
{:file=’corrupted_linked_list.c’}
首先我們編譯並且執行程式,我們會發現程式進入無窮迴圈,於是我們強制程式停下來。
1 | $ gcc -g -o corrupted_linked_list corrupted_linked_list.c |
我們預期程式的輸出應該像下面這樣
1 | ./corrupted_linked_list |
為了要了解程式到底錯在哪裡,我們在GDB session裡面執行程式。並且中斷程式
1 | $ gdb corrupted_linked_list |
中斷後我們可以用where指令看一下目前所在的位置,輸出會類似如下
1 | (gdb) where |
可以看到程式被中斷在標準函式庫的程式,不過我們想看一看輸入函式庫的參數是什麼。因此我們可以用up
指令從frame 0 移動到 frame 1。或者我們直接用frame
指令移動到我們要的地方
1 | (gdb) frame 7 |
首先我們先看一下區域變數
1 | (gdb) info locals |
可以用ptype
指令查看curr的型態,可以發現他是一個node struct的指針。
1 | (gdb) ptype curr |
我們dereference查看一下內容
1 | (gdb) print *curr |
也可以查看其他的內容
1 | (gdb) print *(curr->next) |
程式當機當下程式的狀態對於除錯十分有幫助,我們可以利用core dump
檔來記錄這些狀態。對於一些不定時發生的錯誤,這些除錯資訊就十分珍貴了。
首先查看Core Dumps記錄功能有沒有被開啟,如果回傳0代表沒有打開Core Dumps記錄功能
1 | ulimit -c |
用以下指令設定打開Core Dumps記錄功能
1 | ulimit -c unlimited |
通常Core Dumps檔產生的位置會記路在/proc/sys/kernel/core_pattern
這個設定,用以下指令查看Core Dumps檔的位置。
當Core Dumps紀錄功能打開後,如果程式遇到Segmentation fault
的錯誤,就會產生Core Dump檔。
要查看Core Dump檔可以用以下指令。注意當我們利用Core Dump檔來除錯的時候,程式實際上並沒有在運作,所以step
, next
和 continue
這些指令這時候是沒有功能的。
1 | gdb <binary-file> <core-dump-file> |
利用下面範例broken_linked_list.c
將說明如何使用Core Dump除錯。
1 | //Makes a linked list of length 7 and prints it out |
{: file=’broken_linked_list.c’}
編譯並且執行程式,可以看到程式出現Segmentation fault (core dumped)
錯誤
1 | $ gcc -g -o broken_linked_list broken_linked_list.c |
接下來讀取Core Dump檔
1 | gdb broken_linked_list core |
GDB將會顯示程式出錯的位置
1 | Program terminated with signal SIGSEGV, Segmentation fault. |
從這裡可以知道我們在第51行正在嘗試存取一個非法的記憶體位置。因此我們可以推測可能是curr的位址不正確或者是data是不可以讀取的位址。於是先嘗試印出curr的值
1 | (gdb) print curr |
可以看到curr是一個NULL址針。接下來我們在印出當前狀況的區域變數
1 | (gdb) info locals |
可以看到i是7,也就是現在是第8次執行for迴圈,但是我們的Linking list只有7個節點,而且依照我們的建立Linking list的方式,第8個節點會是NULL址針,所以程式會出錯。
我們在檢查一下Linking list本身是否有問題。
1 | (gdb) print *list |
可以看到Linking list的位址沒問題,因此可以更加確定問題就是迴圈多執行了一次。我們可以用list指令看一下程式現在所在的位置。
1 | (gdb) list |
如果單純使用GDB的指令,有些變數就會變得難以查看,例如如果想要查看linked list的所有成員就會變得很麻煩。而GDB提供讓使用者自定義指令讓我們可以容易議處想要的結果。
GEB啟動時會載入User Initialization File所記錄的指令,你可以建立一個新的User Initialization File,他的檔名是.gdbinit
,放置的位置是home directory
1 | ~/.gdbinit |
建立之後在檔案內加入下面指令,如此一來就可以在每一個專案下各自建立專屬的initialization file .gdbinit
專案自己的initialization file位在專案的跟目錄下,使用者可以自訂義指令或是GDB啟動的行為。
1 | ~/<file_path>/.gdbinit |
例如你的專案想要每次使用GDB的時候都會放一個breakpoint在某一個function,你就可以在.gdbinit
寫入下面這行。
1 | break <function_name> |
你可以用以下命令定義一個自訂義命令
1 | define <command> |
你可以用以下命令為自訂義命令增加註解
1 | document <command> |
如果要查看命令說明可以用以下命令
1 | (gdb) help <command> |
如果需要傳入參數給自訂義命令可以用以下方式
1 | (gdb) <command> <arg0> <arg1> <arg2> ... |
如果要在指令內使用參數可以用以下方式
1 | $argc |
可以用以下方式定義
1 | $<variable_name> |
用以下指令設定變數
1 | set $<variable_name> = <value_or_expression> |
用以下方式可以做if聲明
1 | if <condition> |
1 | while <condition> |
GDB的print語法跟c十分相似
1 | printf "<format string>", <arg0>, <arg1>, ... |
使用者自訂義命令的用法跟內建指令的用法相同。
1 | (gdb) <command> <arg0> <arg1> <arg2> ... |
下面將以除錯Linked list作為範例linked_list.c
1 | //Makes a linked list of length 7 and prints it out |
{:file=’linked_list.c’}
首先先把下面這行加入~/.gdbinit
1 | set auto-load safe-path / |
接下來撰寫自訂義命令,在專案根目錄也建立一個.gdbinit
1 | define p_generic_list |
{:file=’.gdbinit’}
在命令裡面,我們建立一個變數來儲存第一個參數($arg0),也就是linked list的指針
1 | set var $n = $arg0 |
接下來印出 linked list的內容。
1 | print *($n) |
接下來把把linked list的指針指向下一個元素
1 | set var $n = $n->next |
運行結果如下
1 | $ gcc -g -o linked_list linked_list.c |
watch可以監看一個變數,每當這個變數數值改變的時候就暫停程式。display 則是每當程式停止的時候顯示變數。
使用以下指令設定想要監看的變數
1 | (gdb) watch <variable_name> |
查看Watchpoints的方式跟查看breakpoints的方式一樣
1 | (gdb) info breakpoints |
使用以下指令移除Watchpoints
1 | (gdb) disable <watchpoint_number> |
使用以下指令設定display
1 | (gdb) display expression |
查看所有display
1 | (gdb) info display |
用以下指令移除display
1 | (gdb) delete display <display_number> |
以下將以計算階乘的程式factorial.c
來做示範。程式在計算階層
1 | //This program calculates and prints out the factorials of 5 and 17 |
接著編譯程式並且用gdb讀取
1 | $ gcc -g -o factorial factorial.c |
首先先設定中斷點,可以看到n=5的時候晟是正常運作,讓嘗試繼續執行讓n=17
1 | (gdb) br factorial |
接下來設定watch和display,我們希望i初始化之後再設定watch和display,
1 | (gdb) n |
然後設定watch和display
1 | (gdb) watch f |
然後我們就可以觀察程式計算
1 | (gdb) c |
我們可以觀察到當n=13的時候程式就開始出錯。
利用conditional breakpoints可以讓程式達到特定條件的時候才停下來。
建立一個中斷點
如同前面所教的方法先建立一個中斷點。
1 | (gdb) break <file_name> : <line_number> |
查看所有中斷點
下面指令可以查看目前已經設定過的中斷點。
1 | (gdb) info breakpoints |
設定條件
首先我們必須要知道中斷點的編號,並且用以下指令設定條件
1 | (gdb) condition <breakpoint_number> condition |
移除中斷點的停止條件
如果想要移除中斷點的停止條件,可以用以下指令
1 | (gdb) condition <breakpoint_number> |
下面將繼續沿用階層計算程式factorial.c
來示範conditional breakpoints
1 | //This program calculates and prints out the factorials of 5 and 17 |
{:file=’factorial.c’}
1 | $ gcc -g -o factorial factorial.c |
我們已經知道程式在i <= 5 之前都正常運作,所以我們不必在確認i <= 5 之前的輸出結果。因此我們設定的條件是 i > 5。
1 | $ gdb factorial |
接下來就可以執行程式並且觀察不同i之下的輸出變化
1 | (gdb) r |
我們可以發現i=13之後數值就開始出現異常了。
1 | gcc -Wl,-t your_program.c -o your_program > ld_output.txt |
1 | info share |
set stop-on-solib-events 1
https://jasonblog.github.io/note/gdb/li_yong_gdb_jin_xing_shared_library_de_chu_cuo.html
參考:
https://www.cse.unsw.edu.au/~learn/debugging/modules/gdb_basic_use/
參考:
介紹resident memory 和 allocated memory
https://pythonspeed.com/articles/measuring-memory-python/
https://senliuy.gitbook.io/advanced-deep-learning/chapter1/yolo9000-better-faster-stronger
目前Github上的YOLOv2已經被修改過,和論文上的描述並不一樣,必須參考最原始的cfg,而且必須要是voc的版本。
https://github.com/pjreddie/darknet/blob/c6afc7ff1499fbbe64069e1843d7929bd7ae2eaa/cfg/yolo_voc.cfg
{: .prompt-tip }
論文的第三章Faster提到Darknet-19是一個classification model,他是YOLOv2的基礎。
在Training for detection提到YOLOv2是刪掉Darknet-19的最後一個conv layers(以及他後面的其他layer),並且用3個3 x 3 x 1024 conv layer 最後搭配 1 x 1 x 類別數量的conv layer。
We modify this network for detection by removing the last convolutional layer and instead adding on three 3 × 3 convolutional layers with 1024 filters each followed by a final 1 × 1 convolutional layer with the number of outputs we need for detection.
region layer的biases就是anchors的值
activation為linear時沒有做任何事 https://github.com/AlexeyAB/darknet/blob/0faed3e60e52f742bbef43b83f6be51dd30f373e/src/gemm.c#L2337
問題:
在pytorch有momentum,在darknet有沒有?
下面以圖片作為輸入來舉例
https://kikaben.com/yolo-v2-yolo9000/
參考:
yolov2架構講解(架構圖1x1 conv的地方有錯)
https://kikaben.com/yolo-v2-yolo9000/
完整yolov2實作(高參考價值)
https://github.com/Tencent/ObjectDetection-OneStageDet
https://zhuanlan.zhihu.com/p/45039781
http://yuenshome.space/timeline/2018-11/yolov2-region-source-code/
https://github.com/tztztztztz/yolov2.pytorch
https://github.com/gwinndr/YOLOv4-Pytorch/tree/master/model/layers
https://github.com/ruiminshen/yolo2-pytorch/blob/master/model/yolo2.py
韓文Darknet介紹
https://jjeamin.github.io/darknet_book/part3_src/batchnorm_layer.html
{: w=”700” h=”200” }
parse_convolutional
可以發現if(pad) padding = size/2;
,也就是如果cfg的padding=1,padding的大小就是$$\lfloor {kernel \ size \over 2} \rfloor$$ 取整數。所以第一層的padding是$$\lfloor {7 \over 2} \rfloor$$取整數3。make_convolutional_layer
呼叫的convolutional_out_width
,就可以看到詳細計算。conv的輸出尺寸是$$\lfloor{ {輸入長(寬) + 2 \times padding - kernel \ 長(寬)} \over stride}\rfloor + 1 = \lfloor{447 \over 2}\rfloor + 1 = 224$$
接下來是2 x 2 Maxpool layer,輸出尺寸為112,輸出channel是64
!!不過如果直接對照圖看,會發現圖上寫112 x 112 x 192,這很可能是論文的圖寫錯了,因為如果用darknet幫你計算每一層的輸出的話應該是如下圖。
{: w=”700” h=”200” }
1 x 1 的conv稱為reduction layers
下面以圖片作為輸入來舉例
darknet的conv layer程式碼疑問:
1 | for(i = 0; i < l.nweights; ++i) l.weights[i] = scale*rand_normal(); #209 |
h:輸入高
w:輸入寬
c:輸入channel
n:輸出channel
size:kernel size
nweights = (c / groups) * n * size * size
src -> parser.c -> load_convolutional_weights
史丹佛cs231n的這張圖解釋得非常清楚,權重的總數為
輸入channel x kernel 寬 x kernel 高 x 輸出channel
參考:
Dive into Deep learning
https://d2l.ai/chapter_convolutional-neural-networks/padding-and-strides.html#stride
CS231
https://cs231n.github.io/convolutional-networks/#comp
Deep learning
http://neuralnetworksanddeeplearning.com
caculate weights number : Convolution Demo
https://cs231n.github.io/convolutional-networks/
Batch-Normalization
https://towardsdatascience.com/batch-normalization-in-3-levels-of-understanding-14c2da90a338
Locally Connected Layers說明
https://www.cs.toronto.edu/~lczhang/aps360_20191/lec/w03/convnet.html
Understanding Convolutions
http://colah.github.io/posts/2014-07-Understanding-Convolutions/
local connected layer
http://sintesis.ugto.mx/WintemplaWeb/01Neural%20Lab/09Convolutional%20NN/10Locally%20Connected/index.htm
local connected layer 實作
https://github.com/pytorch/pytorch/pull/1583/files
local connected layer 實作
https://github.com/nsoul97/yolov1_pytorch/blob/main/code/model.py
local connected layer 實作
https://github.com/pytorch/pytorch/issues/499
GDB
https://cotonne.github.io/gdb/2019/07/14/gdb-beginner-guide.html
GDB https://condor.depaul.edu/glancast/373class/docs/gdb.html
Math Syntax
https://www.rapidtables.com/math/symbols/Basic_Math_Symbols.html
GDB指令:
p sizeof(array)/sizeof(*array) ptype array 印出array長度
印出指標array的長度
p *l.biases@10 印出array的前10個元素
下面指令將會下載本文所需的範例
1 | git clone https://gitlab.com/CLIUtils/modern-cmake.git |
add_library(MyLibExample simple_lib.cpp simple_lib.hpp)
增加了一個名為MyLibExample
的target
並且將會編譯一個MyLibExample
libraryadd_executable(MyExample simple_example.cpp)
增加了一個名為MyExample
的target
並且將會編譯一個MyExample
的執行檔target_link_libraries(MyExample PRIVATE MyLibExample)
連接了MyLibExample
和MyExample
這兩個target
,注意你必須先製作target
才能夠連接他們接下來你可以用以下指令建置專案並且編譯。CMake會產生一個build
資料夾,在裡面你應該會看到libMyLibExample.a
library和MyExample
執行檔。你可以查看CMakeCache.txt
看有哪些cache variable
,接下來用./build/MyExample
執行MyExample
執行檔
1 | cmake -S . -B build |
1 | set(VAR a b v) |
這個寫法會讓VAR變成一個list,並且擁有a b c三個元素,因此如果你的路徑像這樣,你的路徑就會被拆成兩段。
1 | set(MY_DIR "/path/with spaces/") |
解決方法就是在${MY_DIR}
外面再包一個引號
1 | set(MY_DIR "/path/with spaces/") |
雖然message
指令也可以印出變數,不過你有更好的選擇,cmake_print_properties
、cmake_print_variables
,記得要先include(CMakePrintHelpers)
。如此一來你可以更加容易的印出target
的屬性
1 | include(CMakePrintHelpers) |
--trace-source
讓你可以指定只要看你想看的CMakeLists.txt
,--trace-expand
變數全部展開,例如原本是
1 | add_library(simple_lib ${SOURCES} ${HEADERS} ) |
加了--trace-expand
變成
1 | add_library(simple_lib simple_lib.c simple_lib.h ) |
下載這個範例,並且試看看cmake -S . -B build --trace-source=CMakeLists.txt
、 cmake -S . -B build --trace-source=CMakeLists.txt --trace-expand
有什麼不一樣
1 | git clone git@github.com:hsf-training/hsf-training-cmake-webpage.git |
沿用上面的範例,在這個範例你可以看到第16行有一個find_library(MATH_LIBRARY m)
1 | git clone git@github.com:hsf-training/hsf-training-cmake-webpage.git |
試看看cmake -S . -B build --debug-find
。記得要先清除build
資料夾否會出現debug訊息。你也可以用CMAKE_FIND_DEBUG_MODE來針對你想要debug的find_…模組來除錯
1 | set(CMAKE_FIND_DEBUG_MODE TRUE) |
如果你想要執行C++ debugger,你會需要設定很多flag,CMake提供四種build types來幫你設定這些flag。
1 | cd hsf-training-cmake-webpage/code/04-debug |
1 | # GDB |
1 | find_package(MyPackage 1.2) |
這個命令會首先尋找CMAKE_MODULE_PATH這份路徑清單,在這些路徑底下尋找FindMyPackage.cmake
這個檔案,注意他是直接把find_package
第一個參數MyPackage
產生出FindMyPackage.cmake
這個搜尋目標,所以如果我們寫成find_package(OpenCV 3)
,那搜尋目標就是FindOpenCV.cmake
。
如果找不到FindMyPackage.cmake
他就會接著尋找MyPackageConfig.cmake
,如果MyPackage_DIR
存在的話也會搜尋這個路徑。
在CMake3.12+之後,如果你的套件不是安裝在系統預設路徑,你可以設定環境變數<PackageName>_ROOT
讓CMake搜尋。以下以Bash命令設定環境變數為例
1 | export HDF5_ROOT=$HOME/software/hdf5-1.12.0 |
或者是設定CMAKE環境變數,以下以Bash命令設定環境變數為例
1 | export CMAKE_PREFIX_PATH=$HOME/software/hdf5-1.12.0:$HOME/software/boost-1.74.0:$CMAKE_PREFIX_PATH |
這是舊方法(MODULE
方法),這裡有CMake提供的FindPackage清單
這是由package開發者所提供,簡而言之如果你是package開發者,你應該提供<package>Config.cmake
並且自行維護。
參考:
https://hsf-training.github.io/hsf-training-cmake-webpage/08-debugging/index.html
https://hsf-training.github.io/hsf-training-cmake-webpage/09-findingpackages/index.html
現代CMake觀念
https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/
CMake結合PkgConfig
https://stackoverflow.com/a/74038236
在第一篇我們已經學會如何編譯單一檔案的C++程式,接下來將介紹一些CMake的觀念
在CMake有兩種方式建立target
,target
的名稱必須是唯一的
target
,並且命名輸出的執行檔為myexampletarget
,並且命名輸出的函式庫為myexampletarget
很像程式裡面物件的概念,他擁有許多屬性,例如SOURCES在這個範例他會擁有simple.cpp這個檔案,target
所有的屬性可以在這裡查到有些C++函式庫只有標頭檔(例如函式庫Eigen),這種情況下你沒辦法真正的輸出一個library檔,但是他又被製作成target
讓其他target
使用。這種函式庫我們叫做interface libraries
而再CMake我們可以用下面指令把他製作成target
。注意到跟前面不同的是他只需要設為INTERFACE
屬性而且他不需要輸入*.cpp檔。
1 | add_library(some_header_only_lib INTERFACE) |
另一種狀況是你要直接使用預先編譯好的pre-built library,這種情況你也不會有*.cpp可以給CMake編譯。這種library在CMake裡我們稱為imported library,我們可以用關鍵字IMPORTED
來告訴CMake。
1 | add_library(some_library STATIC IMPORTED) |
一旦你有了target
就可以用target_link_libraries連接所需要的東西,連接的時候有PUBLIC, PRIVATE, 和 INTERFACE三種屬性可以選擇,他有點像程式語言中的存取控制。如果TargetB引用TargetA,則TargetA的PUBLIC屬性都會傳遞給TargetB。
target_include_directories(TargetA PRIVATE mydir)
連接TargetA和mydir資料夾屬性為PRIVATE,這時候TargetA的INCLUDE_DIRECTORIES屬性就會包含mydir資料夾target_include_directories(TargetA INTERFACE mydir)
連接TargetA和mydir資料夾屬性為INTERFACE,這時候TargetA的INTERFACE_INCLUDE_DIRECTORIES屬性就會包含mydir資料夾target_include_directories(TargetA PUBLIC mydir)
連接TargetA和mydir資料夾屬性為PUBLIC,則INCLUDE_DIRECTORIES
INTERFACE_INCLUDE_DIRECTORIES
都會包含mydir資料夾為了方便起見,接下來的範例會直接執行example.cmake
檔,而不是建立一個CMakeLists.txt
。首先你需要先建立一個example.cmake
,如果要執行example.cmake
可以利用CMake的 -p 選項。這樣可以節省去許多編譯設定,也比較容易實驗
1 | # Assuming you have a file called example.cmake: |
在example.cmake
輸入以下指令,然後執行cmake -P example.cmake
就可以看到終端機輸出你的變數。在這裡set指令設定變數,message指令印出變數,這裡我們使用的是STATUS
message,還有其他的狀態你可以到官網查看
1 | set(MY_VARIABLE "I am a variable") |
{: file=’example.cmake’}
cached variables在CMake十分重要,通常我們會用CMake圖形介面或是CMake命令介面設定許多cached variables,這些變數都會被寫到CMakeCache.txt
檔案裡面。當你執行CMake的時候CMake會先讀取這些Cached variables。下面這個範例會設定一個Cached variables,不過因為我們用-P
執行*.cmake,所以不會真的輸出一個CMakeCache.txt
,你可以參考上一篇的範例觀察CMake如何產生CMakeCache.txt
。
1 | set(MY_VARIABLE "I am a variable") |
{: file=’example.cmake’}
而因為Cached variables幾乎都是可以讓使用者設定的選項,所以有一個更方便的指令option
1 | option(MY_OPTION "On or off" OFF) |
$ENV{my_env}
來取得環境變數my_env
的值,你也可以利用if(DEFINED ENV{my_env})
來檢查my_env
這個環境變數是不是有被設定(注意這個指令沒有$
)target
的屬性其實也是一種變數,你可以利用get_property和set_property,或者是get_target_properties和set_target_properties來查看和設定target
屬性,除此之外還有CMake本身的屬性,可以從cmake-properties這份清單查詢。你已經知道target
的屬性可以控制target
的行為,如果你仔細觀察會發現,有許多target
屬性(例如CXX_EXTENSIONS)會有一個對應的CMake變數,並且以CMAKE_
為開頭(例如CMAKE_CXX_EXTENSIONS),這些CMake變數是用來初始化對應的target
屬性用的。因此你可以先設定這些CMake變數,這樣就可以快速設定target
對應屬性的初始值。
CMake有一些方便的glob指令可以用來處理string。
1 | file(GLOB OUTPUT_VAR *.cxx) |
在這裡要提到一個很重要的flagCONFIGURE_DEPENDS
(CMake 3.12+),如果沒有這個flag則在重新執行建置的時候,cmake不會再次用glob去搜尋,如此一來如果這些被搜尋的資料夾放入的新的檔案也不會被CMake發現。因此如果你想要在每一次建置專案的時候都重新搜尋,記得加上這個flag
1 | file(GLOB OUTPUT_VAR *.cxx CONFIGURE_DEPENDS) |
參考:
CMake Fundamentals Part 4
https://jeremimucha.com/2021/02/cmake-fundamentals-part4/
Modern CMake is like inheritance
https://kubasejdak.com/modern-cmake-is-like-inheritance
CMake doc : Importing Libraries
https://cmake.org/cmake/help/latest/guide/importing-exporting/index.html#importing-libraries
https://cliutils.gitlab.io/modern-cmake/chapters/basics.html
https://hsf-training.github.io/hsf-training-cmake-webpage/04-targets/index.html
https://hsf-training.github.io/hsf-training-cmake-webpage/05-variables/index.html
Ubuntu22.04安裝Vagrant
1 | wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg |
安裝VirtualBox
在/etc/apt/sources.list
最下面加上這一行
1 | deb [arch=amd64 signed-by=/usr/share/keyrings/oracle-virtualbox-2016.gpg] https://download.virtualbox.org/virtualbox/debian jammy contrib |
然後下載和註冊public key
1 | wget -O- https://www.virtualbox.org/download/oracle_vbox_2016.asc | sudo gpg --dearmor --yes --output /usr/share/keyrings/oracle-virtualbox-2016.gpg |
顯示fingerprint,應該要為B9F8 D658 297A F3EF C18D 5CDF A2F6 83C5 2980 AECF
1 | gpg --show-keys --with-fingerprint /usr/share/keyrings/oracle-virtualbox-2016.gpg |
最後安裝
1 | sudo apt-get update |
1 | vagrant init ubuntu/focal64 |
https://developer.hashicorp.com/vagrant/tutorials/getting-started/getting-started-synced-folders
參考:
安裝VirtualBox
https://www.virtualbox.org/wiki/Linux_Downloads
顯示gpg fingerprint
https://superuser.com/a/1747762