用CMake在建置時複製檔案到輸出執行檔的目錄

CMake有指令file(COPY …)可以用來copy檔案,可是如果將此指令寫在CMakeLists.txt中,該指令只會在configuration階段複製檔案。
有時我們會希望每次建置、make時都copy一次檔案。

自己遇到的使用案例 : 更新shader source file

OpenGL的shader是在Runtime用OpenGL的API將source code交給drive編譯的,因此在runtime需要有檔案提供shader的source。
這類檔案就像DLL一樣當程式啟動時需要能找到並載入,將檔案跟執行檔放在同一個目錄之下是最簡單的做法。

因此我的需求是要cmake每次都將source tree(原始碼目錄)中的shader file複製到build tree(建置目錄)中執行檔的目錄。

對於純粹想建置專案的人來說,把file(COPY …)寫在CMakeLists.txt中是OK的,因為檔案有copy到build tree就好。

但是對於專案開發者來說,需要能在每次建置時都從source tree複製shader到build tree中,以確保使用最新版本的shader file執行Debug。當然也可以手動,比較麻煩就是了。

如前所述,只將複製指令寫在CMakeLists.txt是不夠的。

利用add_custom_target或add_custom_command安插建置步驟,使任務在建置時執行

上StackOverflow找到的相關解答:

大致上,這方法額外寫個CMake檔案並利用add_custom_target安插執行此cmake檔案的步驟,該cmake腳本內的指令就是file(COPY …)

若想讓特定事件在每次建置時發生可利用這類方法

更好的方法

S.O.上找到的方法需要額外的cmake file,並使用script processing mode執行。我發現cmake除了processing mode也有command mode。該模式透過加入flag “-E” 切換:

cmake已經為我們準備了需多常用的指令,接下來只要如法炮製讓cmake在建置時觸發cmake的copy command就行了:

1
2
3
4
5
6
7
8
add_custom_target(
# 自訂的目標名稱
copy_shader_files
# 執行copy指令 cmake -E copy <file>... destination
${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/shaders/** ${CMAKE_BINARY_DIR}
)
# 用add_dependencies將自訂目標列為建置目標的dependency
add_dependencies(main copy_shader_files)

建立自訂目標後cmake會在指定的建置系統中建置對應的目標,如果建置系統為Unix make可以用以下指令執行copy指令:

問題: Xcode或Visual Studio的build type目錄

若你是使用Unix make或許到目前已經功德圓滿了,建置系統會在每次建置執行檔時幫我們複製shader。可是我遇到問題了。

Xcode或Visual Studio這類IDE內建build type而不同build type的執行檔通常會放在不同目錄裡:

Oh no..輸出的執行檔main在Debug下,而我們的shaders卻在Debug之外。可見CMAKE_SOURCE_DIR變數指向的位置跟實際執行檔輸出的位置是可能有出入的。

執行檔會輸出到哪是IDE決定的,而IDE的專案是CMake的Generator產生的,因此除非將路徑寫死在CMakeLists.txt,否則輸出路徑是generator決定的。

shader被複製到哪裡並不重要,只要他跟輸出的執行擋在同一個目錄即可。

對於Generating階段才做的決定cmake有一類像placeholder的特殊變數(`$’開頭,很像變數)代表generator給出的值。

使用 $> 代表目標檔案的目錄。

我的執行檔建置目標稱為main,因此 $ 指向main目標產生的檔案的所在目錄,也就是執行檔所在的目錄。

可是,單純的修改先前的code會得到cyclic depedencies錯誤。

建置目標的depdencies為我們的copy_shader_files目標,而要決定檔案目的地的卻又是建置目標,造成循環相依性。

add_custom_command

別灰心,除了add_custom_target,我們還可以透過add_custom_command叫cmake幫我們做事。只是add_custom_command不會在建置系統中建立任務或目標,沒辦法手動啟動,對於建置系統來說就是建置執行檔的一個環節。

1
2
3
4
5
6
7
8
9
add_custom_command(
# 指令依附的目標
TARGET main
# 自訂指令的執行時機
POST_BUILD
# COMMAND關鍵字後為執行指令
COMMAND
${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/shaders/** $<TARGET_FILE_DIR:main>
)

add_custom_command的語法稍微不同,不過指令依然是cmake的command mode。

目前這段code可以滿足我的需求: 在每次建置時把檔案複製到執行檔的目錄中。