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 DEPENDS
        COMMAND

看到这一段话,我已经知道在我的项目中为什么这个命令不好使了:只有当构建的目标以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.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,因此需要先执行protoc命令。而如果我再CMakeLists.txt中并没有将onnx-ml.pb.cc指定为生成model_tool的源文件之一,所生成的Makefile便会如下面所示 ,此时规则1对规则2就不存在依赖关系,因此protoc就不会执行了。

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_toolonnx-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.txt
  main.cpp
  source.txt
  utils.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.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的内容如下所示,则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.cpp
                    COMMAND cat source.txt > test_file.cpp
                    DEPENDS source.txt 
                    COMMENT "Just copy file contents")

add_executable(demo main.cpp)

唯一的区别就是有没有在add_executable命令中指明demotest_file.cpp的依赖。

欢1迎2关3注4个5人6微7信8公9众10号:爱码士1024

0x03. References

[1] https://cmake.org/cmake/help/latest/command/add_custom_command.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容