cmake的add_custom_command及add_custom_target

cmake的add_custom_command及add_custom_target

  • 前言
  • add_custom_target
      • API
      • 測試
  • add_custom_command
    • 使用方式一:目標依賴於命令的輸出(Generating files)
      • API
      • 測試
    • 使用方式二:為目標新增一個自訂的命令(Build events)
      • API
      • 測試
  • 支援指令測試
  • 參考連結

前言

在編譯的C++專案的過程中難免會需要對檔案或資料夾做複製,刪除,移動等操作。cmake這個編譯工具便提供了一種機制,讓使用者可以在編譯的過程插入欲執行的命令。

具體方式是在CMakeLists.txt中使用add_custom_targetadd_custom_command。本文給出使用add_custom_target添加並執行命令的方式,以及使用add_custom_command的二種等價寫法。

在正式進入CMakeLists.txt之前,建議先了解一下cmake -E命令行工具。cmake -E除了可以在命令行中使用,在CMakeLists.txt中,也可以通過add_custom_targetadd_custom_command這兩個指令來呼叫執行。

add_custom_target

API

cmake - add_custom_target

add_custom_target的API如下:

add_custom_target(Name [ALL] [command1 [args1...]]
                  [COMMAND command2 [args2...] ...]
                  [DEPENDS depend depend depend ... ]
                  [BYPRODUCTS [files...]]
                  [WORKING_DIRECTORY dir]
                  [COMMENT comment]
                  [JOB_POOL job_pool]
                  [VERBATIM] [USES_TERMINAL]
                  [COMMAND_EXPAND_LISTS]
                  [SOURCES src1 [src2...]])

官方文檔說明如下:

Add a target with no output so it will always be built.
Adds a target with the given name that executes the given commands. The target has no output file and is always considered out of date even if the commands try to create a file with the name of the target. Use the add_custom_command() command to generate a file with dependencies. By default nothing depends on the custom target. Use the add_dependencies() command to add dependencies to or from other targets.

其作用是新增一個名為Name的目標,用於執行COMMAND關鍵字後給定的指令command2。該目標總是會被建構。

NAME後面有個ALL選項,開啟的話表示本目標每次都會被執行:

ALL
Indicate that this target should be added to the default build target so that it will be run every time (the command cannot be called ALL).

至於此API支援哪些指令呢?目前已測試chdir, copy, copy_directory, removeremove_directory等指令,詳見支援指令測試章節。

另外API當中還有個DEPENDS參數,可以透過這個參數來讓目標依賴於add_custom_command指令的輸出。具體可見add_custom_command的第一種使用方式。

測試

為了以下測試方便,先在CMakeLists.txt所在的目錄下新增config/config.txtlog.txt(內容隨意),新增後的目錄結構如下:

.
├── build
├── CMakeLists.txt
├── config
│   └── config.txt
└── log.txt

CMakeLists.txt內容如下:

cmake_minimum_required(VERSION 3.0)
project(test)

add_custom_target(CopyTask
  COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/config ${CMAKE_CURRENT_SOURCE_DIR}/etc
  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/log.txt ${CMAKE_CURRENT_SOURCE_DIR}/etc
)

會新增一個名為CopyTask的目標。

接著編譯,注意要用如下指令指定欲建構的目錄為當前目錄,還有欲建構的目標為CopyTask

cd build && cmake ..
cmake --build . --target CopyTask

使用make指令等價的寫法如下:

cd build && cmake ..
make CopyTask

如果出現如下輸出,表示CopyTask這個目標已被成功建構:

Scanning dependencies of target CopyTask
Built target CopyTask

這個CMakeLists.txt所做的事情如下:

copy_directory指令先把config目錄裡的內容複製到etc目錄裡,此處因為etc目錄不存在,所以會自動創建。copy指令再把log.txt複製到etc目錄裡。

build後的目錄結構如下:

.
├── CMakeLists.txt
├── config
│   └── config.txt
├── etc
│   ├── config.txt
│   └── log.txt
└── log.txt

注意此處如果不指定建構目標,只用cmake --build .make的話,那麼CopyTask並不會被建構,也就是說etc資料夾並不會被生成。

另外如果調換copy_directorycopy指令的順序,log.txt會被複製成一個成叫etc的檔案,所以copy_directory就無法再創建同名的目錄,會出現如下錯誤:

Error copying directory from "/xxx/config" to "/xxx/etc".
make[2]: *** [CMakeFiles/CopyTask.dir/build.make:62:COPY_RES] 錯誤 1
make[1]: *** [CMakeFiles/Makefile2:76:CMakeFiles/CopyTask.dir/all] 錯誤 2
make: *** [Makefile:84:all] 錯誤 2

add_custom_command

cmake - add_custom_command

add_custom_command有兩種API,分別用於Generating files及Build Events。這兩種API都需要搭配add_custom_target使用。

使用方式一:目標依賴於命令的輸出(Generating files)

API

以下為add_custom_command的第一種API,用於Generating files:

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])
The first signature is for adding a custom command to produce an output.
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.

其作用是新增一個或多個命令command1(,command2…),並生成一個輸出output1

測試

編寫如下CMakeLists.txt

cmake_minimum_required(VERSION 3.0)
project(test)

add_custom_command(OUTPUT COPY_RES
  COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/config ${CMAKE_CURRENT_SOURCE_DIR}/etc
  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/log.txt ${CMAKE_CURRENT_SOURCE_DIR}/etc
)

add_custom_target(CopyTask ALL DEPENDS COPY_RES)

add_custom_command會輸出COPY_RESadd_custom_target則會生成一個依賴於COPY_RES的目標。如此一來,便可以透過運行CopyTask間接地執行COPY_RES

以上寫法等價於在add_custom_target的測試章節看到的寫法。

編譯(編譯之前記得刪掉剛剛生成的etc目錄):

cd build && cmake ..
make CopyTask

結果與剛剛在add_custom_target處看到的一致。

另外一個值得注意的地方是:這裡add_custom_commandadd_custom_target的順序不重要,可以對調。在torch/CMakeLists.txt中就是先add_custom_targetadd_custom_command

add_custom_target(torch_python_stubs DEPENDS
    "${TORCH_SRC_DIR}/_C/__init__.pyi"
    "${TORCH_SRC_DIR}/_C/_VariableFunctions.pyi"
    "${TORCH_SRC_DIR}/nn/functional.pyi"
    "${TORCH_SRC_DIR}/utils/data/datapipes/datapipe.pyi"
)
# ...
add_custom_command(
    OUTPUT
    "${TORCH_SRC_DIR}/_C/__init__.pyi"
    "${TORCH_SRC_DIR}/_C/_VariableFunctions.pyi"
    "${TORCH_SRC_DIR}/nn/functional.pyi"
    COMMAND
    "${PYTHON_EXECUTABLE}" -mtools.pyi.gen_pyi
      --native-functions-path "aten/src/ATen/native/native_functions.yaml"
      --tags-path "aten/src/ATen/native/tags.yaml"
      --deprecated-functions-path "tools/autograd/deprecated.yaml"
    DEPENDS
      "${TORCH_SRC_DIR}/_C/__init__.pyi.in"
      "${TORCH_SRC_DIR}/_C/_VariableFunctions.pyi.in"
      "${TORCH_SRC_DIR}/nn/functional.pyi.in"
      "${TORCH_ROOT}/aten/src/ATen/native/native_functions.yaml"
      "${TORCH_ROOT}/aten/src/ATen/native/tags.yaml"
      "${TORCH_ROOT}/tools/autograd/deprecated.yaml"
      ${pyi_python}
      ${autograd_python}
      ${torchgen_python}
    WORKING_DIRECTORY
    "${TORCH_ROOT}"
)

使用方式二:為目標新增一個自訂的命令(Build events)

API

以下為add_custom_command的第二種API,用於Build events:

add_custom_command(TARGET 
                   PRE_BUILD | PRE_LINK | POST_BUILD
                   COMMAND command1 [ARGS] [args1...]
                   [COMMAND command2 [ARGS] [args2...] ...]
                   [BYPRODUCTS [files...]]
                   [WORKING_DIRECTORY dir]
                   [COMMENT comment]
                   [VERBATIM] [USES_TERMINAL]
                   [COMMAND_EXPAND_LISTS])

為目標(如函式庫或執行檔)新增一個命令。可以透過PRE_BUILD | PRE_LINK | POST_BUILD自由決定要在建構目標之前或之後執行命令。

測試

先新增一個空的target再用add_custom_command為它新增一個自訂的命令:

cmake_minimum_required(VERSION 3.0)
project(test)

add_custom_target(CopyTask)

add_custom_command(TARGET CopyTask
  POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/config ${CMAKE_CURRENT_SOURCE_DIR}/etc
  COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/log.txt ${CMAKE_CURRENT_SOURCE_DIR}/etc
)

編譯後可以得到與剛剛兩種方法同樣的結果:

cmake ..
make CopyTask

上面add_custom_command的兩種寫法等價於之前看到的只使用add_custom_target的用法。那麼add_custom_command存在的意義為何?參考add_custom_command第二個signature的文檔:

The second signature adds a custom command to a target such as a library or executable. This is useful for performing an operation before or after building the target. The command becomes part of the target and will only execute when the target itself is built. If the target is already built, the command will not execute.
This defines a new command that will be associated with building the specified . The  must be defined in the current directory; targets defined in other directories may not be specified.

When the command will happen is determined by which of the following is specified:

PRE_BUILD
On Visual Studio Generators, run before any other rules are executed within the target. On other generators, run just before PRE_LINK commands.

PRE_LINK
Run after sources have been compiled but before linking the binary or running the librarian or archiver tool of a static library. This is not defined for targets created by the add_custom_target() command.

POST_BUILD
Run after all other rules within the target have been executed.

Projects should always specify one of the above three keywords when using the TARGET form. For backward compatibility reasons, POST_BUILD is assumed if no such keyword is given, but projects should explicitly provide one of the keywords to make clear the behavior they expect.

看起來是不是將預設的POST_BUILD修改成其它常數就能展現出add_custom_command的價值呢?

  • 改成PRE_BUILD:在Linux平台下結果並無不同。

  • 改成PRE_LINK:如文檔所說,PRE_LINK選項對於由add_custom_target建立的目標是沒有定義的。實測的結果亦是如此:在make CopyTask後並不會創建etc資料夾。

所以目前在Linux下看不出來使用add_custom_command的必要。

支援指令測試

編寫一個CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.0)
project(test)

add_custom_target(TestTask
  COMMAND ${CMAKE_COMMAND} -E chdir .. ls
  COMMAND ${CMAKE_COMMAND} -E copy ../CMakeLists.txt .
  COMMAND ${CMAKE_COMMAND} -E copy_directory CMakeFiles/TestTask.dir/ CMakeFiles/CMakeTmp/
  COMMAND ${CMAKE_COMMAND} -E remove CMakeCache.txt
  COMMAND ${CMAKE_COMMAND} -E remove_directory CMakeFiles/TestTask.dir
)

創建一個build目錄,在該目錄中:

rm -rf * && cmake ..

輸出如下:

Scanning dependencies of target TestTask
build  CMakeLists.txt  config  etc  log.txt
Built target TestTask

表示已建構一個名為TestTask的目標。

當前的目錄結構如下:

.
├── CMakeCache.txt
├── CMakeFiles
│   ├── 3.16.3
│   │   ├── CMakeCCompiler.cmake
│   │   ├── CMakeCXXCompiler.cmake
│   │   ├── CMakeDetermineCompilerABI_C.bin
│   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   ├── CMakeSystem.cmake
│   │   ├── CompilerIdC
│   │   │   ├── a.out
│   │   │   ├── CMakeCCompilerId.c
│   │   │   └── tmp
│   │   └── CompilerIdCXX
│   │       ├── a.out
│   │       ├── CMakeCXXCompilerId.cpp
│   │       └── tmp
│   ├── cmake.check_cache
│   ├── CMakeDirectoryInformation.cmake
│   ├── CMakeOutput.log
│   ├── CMakeRuleHashes.txt
│   ├── CMakeTmp
│   ├── Makefile2
│   ├── Makefile.cmake
│   ├── progress.marks
│   ├── TargetDirectories.txt
│   └── TestTask.dir
│       ├── build.make
│       ├── cmake_clean.cmake
│       ├── DependInfo.cmake
│       └── progress.make
├── cmake_install.cmake
└── Makefile

8 directories, 24 files

接著透過make TestTask運行剛剛建構出來的目標TestTask,運行後目錄結構如下:

.
├── CMakeFiles
│   ├── 3.16.3
│   │   ├── CMakeCCompiler.cmake
│   │   ├── CMakeCXXCompiler.cmake
│   │   ├── CMakeDetermineCompilerABI_C.bin
│   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   ├── CMakeSystem.cmake
│   │   ├── CompilerIdC
│   │   │   ├── a.out
│   │   │   ├── CMakeCCompilerId.c
│   │   │   └── tmp
│   │   └── CompilerIdCXX
│   │       ├── a.out
│   │       ├── CMakeCXXCompilerId.cpp
│   │       └── tmp
│   ├── cmake.check_cache
│   ├── CMakeDirectoryInformation.cmake
│   ├── CMakeOutput.log
│   ├── CMakeRuleHashes.txt
│   ├── CMakeTmp
│   │   ├── build.make
│   │   ├── cmake_clean.cmake
│   │   ├── DependInfo.cmake
│   │   ├── depend.internal
│   │   ├── depend.make
│   │   └── progress.make
│   ├── Makefile2
│   ├── Makefile.cmake
│   ├── progress.marks
│   └── TargetDirectories.txt
├── cmake_install.cmake
├── CMakeLists.txt
└── Makefile

7 directories, 26 files

可以看到目錄中多了CMakeLists.txtCMakeFiles/CMakeTmp目錄中多了原來在CMakeFiles/TestTask.dir裡的東西,CMakeFilesCMakeFiles/TestTask.dir則被刪除了。

參考連結

【CMake】cmake的add_custom_command和add_custom_target指令

cmake:添加自定义操作

你可能感兴趣的:(c++,cmake,linux)