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_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.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命令中指明demotest_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不执行相关推荐

  1. CMake中add_custom_command的使用

    CMake中的add_custom_command命令用于将自定义构建规则添加到生成的构建系统(Add a custom build rule to the generated build syste ...

  2. CMake I add_custom_command命令详解(构建)

    目录 一.add_custom_command 1.执行有输出文件的自定义操作 2.执行没有输出的自定义操作 二.应用 1.执行有输出文件的自定义操作 2.执行没有输出的自定义操作 CMake提供了三 ...

  3. [python教程入门学习]python学习笔记(CMD执行文件并传入参数)

    本文章向大家介绍python学习笔记(CMD执行文件并传入参数),主要包括python学习笔记(CMD执行文件并传入参数)使用实例.应用技巧.基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋 ...

  4. cmake笔记(1)

    本文是cmake使用过程中的简单笔记,写作过程中大量参考了<CMake Practice中文版1>,如果需要快速从零开始学习cmake,推荐阅读CMake Practice.本人使用cma ...

  5. 【CMake】cmake的add_custom_command和add_custom_target指令

    在很多时候,需要在cmake中创建一些目标,如clean.copy等等,这就需要通过add_custom_target来指定.同时,add_custom_command可以用来完成对add_custo ...

  6. 昼猫笔记 JavaScript -- 异步执行 | 定时器真的定时执行?

      本篇主要内容:异步.定时器引发的思考 预计阅读时间:8分钟 了解 我们都知道在js中定时器有两种  setInterval()  . setTimeout()   setInterval() :按 ...

  7. [PYTHON] 核心编程笔记(14.Python执行环境)

    在python中有多种运行外部程序的方法,比如,运行操作系统命令或另外的python脚本,或执行一个磁盘上的文件,或通过网络来运行文件 在特定执行场景包括: 在当前脚本继续运行 创建和管理子进程 执行 ...

  8. CMake笔记2 构建动态链接库

    新建一个文件夹,然后创建如下的目录: . ├── build ├── CMakeLists.txt └── lib├── CMakeLists.txt├── hello.cpp└── hello.h ...

  9. CMake笔记1 构建基本的项目

    最基础的同级文件操作 首先,创建CMakeLists.txt,这是CMake处理的基本文件.首先给出只有一个文件的最简单的情况: PROJECT (HELLO) SET(SRC_LIST main.c ...

最新文章

  1. 最全面的Python重点知识汇总,建议收藏!
  2. SAP MM 史上最偷懒的盘点事务代码MI10
  3. Scala Actor,receive不断接收消息,react复用线程,结合case class的actor,Future使用,使用Actor进行wordCount
  4. linux ubuntu 安装安卓,借助ubuntu电脑,给安卓手机安装整套ubuntu14.04
  5. [Leetcode][JAVA][第912题][排序算法]
  6. python中osgeo库使用教程链接
  7. Win11任务栏空白怎么办 Win11任务栏空白解决办法
  8. Linux 编程和系统管理新手入门
  9. GraphQL —— 接口类型
  10. mysql optimize 参数查看_Mysql High Performance:Optimize Setting
  11. 剑指offer——面试题42-2:翻转单词顺序
  12. [OGRE]最小ogre程序的流程
  13. 学术论文英文催稿模板
  14. YIT-CTF—社工类
  15. U盘插入电脑有声音,无盘符,只显示安全删除硬件并弹出媒体
  16. 勒索病毒威胁的解决方案
  17. 【二、八、十、十六】进制转换详解
  18. 防ddos-shell
  19. 12 个在线学习 Linux 技能网站
  20. Webpack中的文件指纹

热门文章

  1. 设计,看上去很美 wayfarer
  2. 《校园墙》小程序可行性分析
  3. 【Makefile】strip
  4. 小老板巧用信用卡 透支妙获现金流
  5. 代码量?项目经验?面试官你到底要看程序员哪一点
  6. UEdit 使用总结
  7. 苹果笔记本python开发第一个程序_Xcode的第一个mac程序
  8. ucsd大学音乐计算机,音乐留学│综合名校UCSD音乐制作专业详解!
  9. windows系统下载合集
  10. 使用three.js/webgl开发智慧城市场景的一些总结