CMake 自定义构建问题

CMake custom builds issues

我正在尝试使用 CMake 构建自定义项目,其中涉及使用 emscripten 为我的 C++ 库提供 javascript 绑定。

这就是我希望我的 CMakeLists.txt 文件实现的目标

  1. 为我的文件指定源位置(完成)

  2. 设置要使用的相关编译器以及编译器标志等(完成)

  3. 使用自定义构建生成新的cpp文件(下面有详细步骤)

    • 使用自定义工具(python 脚本)生成 interface/glue.cpp
    • 创建一个新的空文件interface/glue_wrapper.cpp
    • 对于 ${my_header_files} 中的每个头文件 f#include "f" 附加到文件 interface/glue_wrapper.cpp
    • interface/glue_wrapper.cpp 中的最后一个条目应该是 #include "glue.cpp"
  4. 使用自定义构建通过以下逻辑生成我的 javascript 文件:

    • 创建一个变量 ${ALL_SOURCES} 包含上面第 1 步中列出的所有源和上面第 3 步中的 interface/glue_wrapper.cpp
    • 使用计算结果为的 COMMAND 进行编译:${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${ALL_SOURCES} interface/glue_wrapper.cpp --post-js glue.js -o output.js

我在第 3 步和第 4 步上花费了最后 7 个小时 - 但没有成功。

这是我目前所掌握的(与上面的第 3 步和第 4 步相关)

# Build Interface
ADD_CUSTOM_COMMAND(
                OUTPUT interface/glue.cpp
                COMMAND cd interface
                COMMAND python ${PLATFORM_PREFIX}/tools/webidl_binder.py ${myclasses_INTERFACE} glue
                # Need to loop through list and generate include statements ...
                #COMMAND echo "#include <glue.cpp>" > glue_wrapper.cpp
                 )

# Build JS library
ADD_CUSTOM_COMMAND(
                OUTPUT ${PROJECT_JS_DIR}/${PROJECT}.js
                COMMAND ${CMAKE_CXX_COMPILER} # Nothing seems to work anyway .... giving up finally :(
                )

我正在使用 cmake 3.2.1,并在 Ubuntu 14.0.4 上构建。我正在尝试创建 Unix MakeFiles。

我的问题是:

如何修改上面的代码片段,以实现步骤 3 和 4 中指定的所需功能?

似乎 glue_wrapper.cpp 的内容根本不依赖于构建时的值,它们完全基于 CMake 时可用的信息(my_header_files 变量的内容) .因此,您可以使用简单的 file() 命令在 CMake 时创建文件:

file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/interface/glue_wrapper.cpp)  # erase file if it exists
foreach(header IN LISTS my_header_files ITEMS glue.cpp)
  file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/interface/glue_wrapper.cpp "#include \"${header}\"\n")
endforeach()

创建 .js 库的自定义命令应该可以正常工作:

add_custom_command(
  OUTPUT ${PROJECT_JS_DIR}/${PROJECT}.js
  COMMAND ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${ALL_SOURCES} interface/glue_wrapper.cpp --post-js glue.js -o output.js
  DEPENDS ${ALL_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/interface/glue_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/interface/glue.cpp
  COMMENT "Building ${PROJECT}.js"
  VERBATIM
)

与任何 CMake 自定义命令一样,只有在某些内容取决于其输出时才会将其包含在构建中(我怀疑这就是您的方法失败的原因)。所以你应该添加一个自定义目标来驱动命令:

add_custom_target(
  JsLibrary ALL
  DEPENDS ${PROJECT_JS_DIR}/${PROJECT}.js
  COMMENT "Building JsLibrary"
)

这应该是所有必要的。

作为旁注,请注意 add_custom_command 有一个 WORKING_DIRECTORY 参数,您应该使用它来代替 COMMAND cd.


自定义命令和自定义目标之间的 CMake 关系可能需要一些时间才能完全理解,因此我将尝试解释上面代码中发生的事情。

自定义命令

命令 add_custom_command(OUTPUT x ...) 创建生成输出的构建规则。基本上,这告诉 CMake:

If somebody ever needs the file x, here is how you create it.

该命令本身不会向生成的构建系统添加任何内容它仅向 CMake 提供有关如何创建文件的信息。

调用的各个组成部分是:

add_custom_command(
  OUTPUT ${PROJECT_JS_DIR}/${PROJECT}.js

此自定义命令生成的一个或多个文件。它说:"the custom command produces these files."

  COMMAND ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${ALL_SOURCES} interface/glue_wrapper.cpp --post-js glue.js -o output.js

COMMAND参数引入了要执行的命令的命令行。它说:"this is what you must do to produce the files listed in OUTPUT."

  DEPENDS ${ALL_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/interface/glue_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/interface/glue.cpp

DEPENDS部分介绍了命令的依赖关系(先决条件)。它后面的每一项都是一个文件,它是命令的依赖项。它说:"if any of these files is missing, or if any of these files is newer than any of the output files, this command must be re-run."

特别注意对${CMAKE_CURRENT_BINARY_DIR}/interface/glue.cpp的依赖,我稍后再回来。

  COMMENT "Building ${PROJECT}.js"

这纯粹是文档——它将在自定义命令执行(=构建)时打印出来。

  VERBATIM
)

这告诉 CMake 正确转义 COMMAND 部分中的任何特殊字符,以便 shell 将执行命令。基本上,除非您确定有理由不这样做,否则请始终将其放入自定义命令中。

自定义目标

正如我在上面提到的,CMake 仅在某些内容请求其输出时才将自定义命令添加到构建系统。普通目标(即库或可执行文件)可以通过在其源文件中列出输出文件来做到这一点。这在自定义命令生成 C++ 源文件(例如来自 IDL 定义)的情况下很典型。

自定义命令还可以在其 DEPENDS 部分中列出另一个自定义命令的输出,这会创建所需的依赖项。但是,只有在某处请求 "master" 命令的输出时,才会再次包含两者。

如果生成的文件实际上是最终产品而不只是普通目标的源文件,则必须在某处指定对它的显式依赖以确保生成它.这就是自定义目标的用武之地。它是一个目标(就像可执行文件或库),因此它将始终存在于构建系统中。当使用基于 makefile 的生成器时,自定义目标只是一个额外的规则。下面分析一下我在上面的回答里放的那个:

add_custom_target(
  JsLibrary

JsLibrary 只是目标的符号名称。它可以是任何你想要的。这是您将在命令行中键入以构建 .js 文件的名称:> make JsLibrary.

ALL

默认情况下,自定义目标 不是 make all 调用的 all 目标的一部分;你必须明确地make他们。添加 ALL 参数使自定义目标成为 make all 的一部分,我假设您会在这里想要它。

DEPENDS ${PROJECT_JS_DIR}/${PROJECT}.js

这是关键的一行,也是我们首先创建自定义目标的原因。这告诉 CMake 自定义目标取决于生成的文件。现在 CMake 发现该文件是构建系统的一部分(即自定义命令 JsLibrary)所需要的,并查看它是否知道如何创建这样的文件。它找到自定义命令,并确保将正确的规则添加到生成的构建系统中。

  COMMENT "Building JsLibrary"
)

这又是纯粹的文档——每次创建目标时都会打印它(即使它的所有依赖项都是最新的,因此不会进行进一步处理)。

总结

JsLibrary 将包含在构建系统中,因为它是自定义目标并且始终包含自定义目标。它将成为 make all 的一部分,因为我们在创建它时指定了 ALL

JsLibrary 依赖于 ${PROJECT_JS_DIR}/${PROJECT}.js,因此自定义命令创建的规则将包含在构建系统中,并在每次构建 JsLibrary 时进行检查。如果过时,它将被执行。

${PROJECT_JS_DIR}/${PROJECT}.js 又取决于 ${CMAKE_CURRENT_BINARY_DIR}/interface/glue.cpp,因为这是在创建 .js 的自定义命令的 DEPENDS 部分中指定的。因此,创建 glue.cpp 的自定义命令中描述的规则也将包含在构建系统中,并且一切都按预期工作。