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  depend1
                                    [ 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)构成,规则的形式如下所示:

 :  
[tab]  

根据我的项目里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 
#include 
void greeting(std::string who);

#endif // #define MY_OWN_HEADER__

// source.txt
#include 
#include 

#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

你可能感兴趣的:(CMake笔记:add_custom_command不执行)