0x00. 前言
在網(wǎng)上看別人做一些手工教程視頻姜性,經(jīng)常能看到這樣的評(píng)論:
腦子:我感覺(jué)我會(huì)了瞪慧。
手:你行你來(lái)。
之前一直通過(guò)編譯腳本去尋找代碼入口部念,感覺(jué)我已經(jīng)懂得CMake的語(yǔ)法了弃酌,直到今天寄己要寫(xiě)一個(gè)腳本去編譯一個(gè)工程才發(fā)現(xiàn)氨菇,事情并不簡(jiǎn)單:腳本并沒(méi)有按照我期望的去執(zhí)行。
此工程需要用到Protocol Buffer妓湘,因此當(dāng)代碼構(gòu)建的時(shí)候需要使用使用Protocol Buffer編譯器去編譯.proto
文件獲得對(duì)應(yīng)的生成文件查蓉。理論上,想要達(dá)到這個(gè)目的榜贴,我們只需要在CMakeLists.txt中使用add_custom_command
命令就可以可以生成對(duì)應(yīng)的構(gòu)建規(guī)則豌研。但出人意料的是,這條命令并沒(méi)有被執(zhí)行唬党,也就是說(shuō)鹃共,并沒(méi)有編譯.proto
文件的規(guī)則生成,因此當(dāng)最終使用Make去構(gòu)建工程的時(shí)候驶拱,沒(méi)能通過(guò).proto
文件得到對(duì)應(yīng)的源代碼及汉。
0x01. 踩雷
整個(gè)命令的使用如下面的代碼所示,作用就是將位${REPO_ROOT}/protobuf/onnx-operators-ml.proto
以及${REPO_ROOT}/protobuf/onnx-ml.proto
這兩個(gè)文件編譯成C++頭文件以及源文件屯烦,并存放到{REPO_ROOT}/src
目錄下坷随,其中${REPO_ROOT}
是項(xiàng)目的根目錄,例如在我的例子中為/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()
官方文檔中該命令的簽名有兩個(gè)形式驻龟,在開(kāi)源的項(xiàng)目中經(jīng)澄旅迹看到的是下面這個(gè)形式:
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
這兩個(gè)參數(shù)是必須的翁狐,也就是說(shuō)类溢,正常情況下只要正確提供了這兩個(gè)參數(shù)的值,在構(gòu)建的時(shí)候肯定會(huì)執(zhí)行這條命令生成的規(guī)則去編譯.proto
露懒。但是在我確認(rèn)提供的參數(shù)都沒(méi)問(wèn)題的情況下闯冷,這條命令依舊沒(méi)有按照預(yù)期工作。
這就非常奇怪了懈词,坦白的講蛇耀,我的例子中的命令就是從ONNX Runtime中拷貝過(guò)來(lái),只不過(guò)將一些變量的值修改成了指向我本地機(jī)器中的文件而已坎弯,它在別人的工程中能執(zhí)行纺涤,為什么到了我這就不好使了呢?把可選的參數(shù)嘗試了一遍抠忘,仍然木有結(jié)果撩炊。
我終于意識(shí)到,這樣蠻干是不行的崎脉,即便瞎貓碰上死耗子偶然嘗試對(duì)了一種組合拧咳,我依舊不知道它為什么又行了,回頭再需要編寫(xiě)其他命令的時(shí)候一樣抓瞎囚灼。還是需要去文檔中尋找答案骆膝。
好在砾淌,最終我還是從文檔中悟出了答案。
0x02. 解惑
其實(shí)在官方的文檔中一開(kāi)始就說(shuō)的很明白了谭网,只不過(guò)當(dāng)時(shí)著急汪厨,并沒(méi)有認(rèn)真看對(duì)整個(gè)命令的綜述,而是著急忙慌地去看應(yīng)該怎么去構(gòu)造每個(gè)參數(shù)的值愉择。官方文檔中是這么說(shuō)的:
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 DEPENDS
COMMAND
看到這一段話劫乱,我已經(jīng)知道在我的項(xiàng)目中為什么這個(gè)命令不好使了:只有當(dāng)構(gòu)建的目標(biāo)以add_custome_command
生成的OUTPUT文件為源代碼的情況下,add_custome_command
中指定的命令才會(huì)才會(huì)執(zhí)行锥涕。到目前為止衷戈,我并沒(méi)有在CMakeLists.txt中生成目標(biāo)文件的時(shí)候使用到諸如model-ml.pb.h, model-ml.pb.cc
這些文件,也就是說(shuō)當(dāng)構(gòu)建我的代碼的時(shí)候层坠,根本就用不到model-ml.pb.h, model-ml.pb.cc
殖妇,既然用不到,那生成它們干啥呢破花?因此“聰明”的構(gòu)建系統(tǒng)就不去執(zhí)行編譯.proto
的命令了谦趣。
我們知道,Makefile文件由一系列規(guī)則(rules)構(gòu)成座每,規(guī)則的形式如下所示:
<target> : <prerequisites>
[tab] <commands>
根據(jù)我的項(xiàng)目里CMakeLists.txt中的內(nèi)容前鹅,會(huì)生成一個(gè)Makefile文件(Ubuntu中默認(rèn)情況下),其形式大概如下:
model_tool: main.cpp onnx-ml.pb.cc
C++ -o model_tool main.cpp onnx-ml.pb.cc
onnx-ml.pb.cc: onnx-ml.proto
protoc --cpp_out ./include -I./protobuf/ ./protobuf/onnx-ml.proto
為了生成model_tool
峭梳,需要先生成onnx-ml.pb.cc
舰绘,因此需要先執(zhí)行protoc
命令。而如果我再CMakeLists.txt中并沒(méi)有將onnx-ml.pb.cc
指定為生成model_tool
的源文件之一葱椭,所生成的Makefile便會(huì)如下面所示 捂寿,此時(shí)規(guī)則1對(duì)規(guī)則2就不存在依賴(lài)關(guān)系,因此protoc
就不會(huì)執(zhí)行了孵运。
model_tool: main.cpp
C++ -o model_tool main.cpp
onnx-ml.pb.cc: onnx-ml.proto
protoc --cpp_out ./include -I./protobuf/ ./protobuf/onnx-ml.proto
想讓model_tool
對(duì)onnx-ml.pb.cc
形成依賴(lài)也很簡(jiǎn)單秦陋,只要在將onnx-ml.pb.cc
作為值傳個(gè)最終生成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}
我一開(kāi)始就是因?yàn)闆](méi)將onnx-ml.pb.cc
也列為生成model_tool
的源文件掐松,才導(dǎo)致add_custom_command
沒(méi)有效果踱侣。至于main.cpp
中是不是真的引用了onnx-ml.pb.cc
的內(nèi)容,Who care?
0x03 總結(jié)
作為總結(jié)大磺,這里展示一個(gè)小Demo,文件結(jié)構(gòu)如下:
demo/
CMakeLists.txt
main.cpp
source.txt
utils.h
其中每個(gè)文件中的內(nèi)容如下:
// 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;
}
此時(shí)探膊,如果CMakeLists.txt的內(nèi)容如下所示杠愧,則會(huì)執(zhí)行cat source.txt > test_file.cpp
這條命令生成test_file.cpp
,編譯得以通過(guò):
# CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(demo VERSION 0.1 LANGUAGES C CXX)
add_custom_command(OUTPUT test_file.cpp
COMMAND cat source.txt > test_file.cpp
DEPENDS source.txt
COMMENT "Just copy file contents")
add_executable(demo main.cpp test_file.cpp)
而如果CMakeLists.txt的內(nèi)容如下所示逞壁,則cat source.txt > test_file.cpp
便不會(huì)執(zhí)行流济,在鏈接階段就會(huì)因?yàn)槿鄙?code>test_file.cpp中的函數(shù)實(shí)現(xiàn)而失斎衤唷:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(demo VERSION 0.1 LANGUAGES C CXX)
add_custom_command(OUTPUT test_file.cpp
COMMAND cat source.txt > test_file.cpp
DEPENDS source.txt
COMMENT "Just copy file contents")
add_executable(demo main.cpp)
唯一的區(qū)別就是有沒(méi)有在add_executable
命令中指明demo
對(duì)test_file.cpp
的依賴(lài)。
歡1迎2關(guān)3注4個(gè)5人6微7信8公9眾10號(hào):愛(ài)碼士1024
0x03. References
[1] https://cmake.org/cmake/help/latest/command/add_custom_command.html