CMake笔记:add_custom_command不执行
0x00. 前言
在網上看別人做一些手工教程視頻,經常能看到這樣的評論:
腦子:我感覺我會了。
手:你行你來。
之前一直通過編譯腳本去尋找代碼入口,感覺我已經懂得CMake的語法了,直到今天寄己要寫一個腳本去編譯一個工程才發現,事情并不簡單:腳本并沒有按照我期望的去執行。
此工程需要用到Protocol Buffer,因此當代碼構建的時候需要使用使用Protocol Buffer編譯器去編譯.proto文件獲得對應的生成文件。理論上,想要達到這個目的,我們只需要在CMakeLists.txt中使用add_custom_command命令就可以可以生成對應的構建規則。但出人意料的是,這條命令并沒有被執行,也就是說,并沒有編譯.proto文件的規則生成,因此當最終使用Make去構建工程的時候,沒能通過.proto文件得到對應的源代碼。
0x01. 踩雷
整個命令的使用如下面的代碼所示,作用就是將位${REPO_ROOT}/protobuf/onnx-operators-ml.proto以及${REPO_ROOT}/protobuf/onnx-ml.proto這兩個文件編譯成C++頭文件以及源文件,并存放到{REPO_ROOT}/src目錄下,其中${REPO_ROOT}是項目的根目錄,例如在我的例子中為/home/sunny/workspace/model-tool/:
set(PROTOBUF_PROTOC_EXECUTABLE ${REPO_ROOT}/build/third_party/protobuf/cmake/protoc)list(APPEND PROTO_FILES "${REPO_ROOT}/protobuf/onnx-operators-ml.proto""${REPO_ROOT}/protobuf/onnx-ml.proto")set(output_dir ${REPO_ROOT}/include) set(protoc_include ${REPO_ROOT}/protobuf)foreach(fil ${PROTO_FILES})get_filename_component(abs_fil ${fil} ABSOLUTE)get_filename_component(fil_we ${fil} NAME_WE)list(APPEND ${srcs_var} "${output_dir}/${fil_we}.pb.cc")list(APPEND ${hdrs_var} "${output_dir}/${fil_we}.pb.h")add_custom_command(OUTPUT "${output_dir}/${fil_we}.pb.cc""${output_dir}/${fil_we}.pb.h"COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --cpp_out ${output_dir} -I${protoc_include} ${abs_fil}DEPENDS ${abs_file}COMMENT "Running C++ protocol buffer compiler on ${fil}" VERBATIM ) endforeach()官方文檔中該命令的簽名有兩個形式,在開源的項目中經常看到的是下面這個形式:
add_custom_command(OUTPUT output1 [output2 ...]COMMAND command1 [ARGS] [args1...][COMMAND command2 [ARGS] [args2...] ...][MAIN_DEPENDENCY depend][DEPENDS [depends...]][BYPRODUCTS [files...]][IMPLICIT_DEPENDS <lang1> depend1[<lang2> depend2] ...][WORKING_DIRECTORY dir][COMMENT comment][DEPFILE depfile][JOB_POOL job_pool][VERBATIM] [APPEND] [USES_TERMINAL][COMMAND_EXPAND_LISTS])從中可以看到,只有OUTPUT以及COMMAND這兩個參數是必須的,也就是說,正常情況下只要正確提供了這兩個參數的值,在構建的時候肯定會執行這條命令生成的規則去編譯.proto。但是在我確認提供的參數都沒問題的情況下,這條命令依舊沒有按照預期工作。
這就非常奇怪了,坦白的講,我的例子中的命令就是從ONNX Runtime中拷貝過來,只不過將一些變量的值修改成了指向我本地機器中的文件而已,它在別人的工程中能執行,為什么到了我這就不好使了呢?把可選的參數嘗試了一遍,仍然木有結果。
我終于意識到,這樣蠻干是不行的,即便瞎貓碰上死耗子偶然嘗試對了一種組合,我依舊不知道它為什么又行了,回頭再需要編寫其他命令的時候一樣抓瞎。還是需要去文檔中尋找答案。
好在,最終我還是從文檔中悟出了答案。
0x02. 解惑
其實在官方的文檔中一開始就說的很明白了,只不過當時著急,并沒有認真看對整個命令的綜述,而是著急忙慌地去看應該怎么去構造每個參數的值。官方文檔中是這么說的:
This defines a command to generate specified OUTPUT file(s). A target created in the same directory (CMakeLists.txt file) that specifies any output of the custom command as a source file is given a rule to generate the file using the command at build time.……In makefile terms this creates a new target in the following form:
OUTPUT: MAIN_DEPENDENCY DEPENDSCOMMAND看到這一段話,我已經知道在我的項目中為什么這個命令不好使了:只有當構建的目標以add_custome_command生成的OUTPUT文件為源代碼的情況下,add_custome_command中指定的命令才會才會執行。到目前為止,我并沒有在CMakeLists.txt中生成目標文件的時候使用到諸如model-ml.pb.h, model-ml.pb.cc這些文件,也就是說當構建我的代碼的時候,根本就用不到model-ml.pb.h, model-ml.pb.cc,既然用不到,那生成它們干啥呢?因此“聰明”的構建系統就不去執行編譯.proto的命令了。
我們知道,Makefile文件由一系列規則(rules)構成,規則的形式如下所示:
<target> : <prerequisites> [tab] <commands>根據我的項目里CMakeLists.txt中的內容,會生成一個Makefile文件(Ubuntu中默認情況下),其形式大概如下:
model_tool: main.cpp onnx-ml.pb.ccC++ -o model_tool main.cpp onnx-ml.pb.cconnx-ml.pb.cc: onnx-ml.protoprotoc --cpp_out ./include -I./protobuf/ ./protobuf/onnx-ml.proto為了生成model_tool,需要先生成onnx-ml.pb.cc,因此需要先執行protoc命令。而如果我再CMakeLists.txt中并沒有將onnx-ml.pb.cc指定為生成model_tool的源文件之一,所生成的Makefile便會如下面所示 ,此時規則1對規則2就不存在依賴關系,因此protoc就不會執行了。
model_tool: main.cppC++ -o model_tool main.cpponnx-ml.pb.cc: onnx-ml.protoprotoc --cpp_out ./include -I./protobuf/ ./protobuf/onnx-ml.proto想讓model_tool對onnx-ml.pb.cc形成依賴也很簡單,只要在將onnx-ml.pb.cc作為值傳個最終生成model_tool的命令add_executable就行,如下所示:
list(APPEND CXX_SRCS ${REPO_ROOT}/src/main.cpp${REPO_ROOT}/include/onnx-ml.pb.cc) add_executable(model_tool ${CXX_SRCS}我一開始就是因為沒將onnx-ml.pb.cc也列為生成model_tool的源文件,才導致add_custom_command沒有效果。至于main.cpp中是不是真的引用了onnx-ml.pb.cc的內容,Who care?
0x03 總結
作為總結,這里展示一個小Demo,文件結構如下:
demo/CMakeLists.txtmain.cppsource.txtutils.h其中每個文件中的內容如下:
// main.cpp #include "utils.h"int main(int argc, char **argv) {greeting("Sunny");return 0; }// utils.h #ifndef MY_OWN_DEADER__ #define MY_OWN_HEADER__#include <iostream> #include <string> void greeting(std::string who);#endif // #define MY_OWN_HEADER__// source.txt #include <iostream> #include <string>#include "utils.h"void greeting(std::string who) {std::cout<< "Hello " << who << std::endl; }此時,如果CMakeLists.txt的內容如下所示,則會執行cat source.txt > test_file.cpp這條命令生成test_file.cpp,編譯得以通過:
# CMakeLists.txt cmake_minimum_required(VERSION 3.5)project(demo VERSION 0.1 LANGUAGES C CXX)add_custom_command(OUTPUT test_file.cppCOMMAND cat source.txt > test_file.cppDEPENDS source.txt COMMENT "Just copy file contents")add_executable(demo main.cpp test_file.cpp)而如果CMakeLists.txt的內容如下所示,則cat source.txt > test_file.cpp便不會執行,在鏈接階段就會因為缺少test_file.cpp中的函數實現而失敗:
# CMakeLists.txt cmake_minimum_required(VERSION 3.5)project(demo VERSION 0.1 LANGUAGES C CXX)add_custom_command(OUTPUT test_file.cppCOMMAND cat source.txt > test_file.cppDEPENDS source.txt COMMENT "Just copy file contents")add_executable(demo main.cpp)唯一的區別就是有沒有在add_executable命令中指明demo對test_file.cpp的依賴。
首發于個人微信公眾號【愛碼士1024】。微信掃描上方二維碼或者微信搜索【愛碼士1024】并關注,及時獲取更多最新文章!
C++ | Python | 推理引擎 | AI框架源碼,有一起玩耍的么?
0x03. References
[1] https://cmake.org/cmake/help/latest/command/add_custom_command.html
總結
以上是生活随笔為你收集整理的CMake笔记:add_custom_command不执行的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: uniapp vue3版本 引用colo
- 下一篇: CMake I add_custom_c