在編譯的C++專案的過程中難免會需要對檔案或資料夾做複製,刪除,移動等操作。cmake這個編譯工具便提供了一種機制,讓使用者可以在編譯的過程插入欲執行的命令。
具體方式是在CMakeLists.txt
中使用add_custom_target
及add_custom_command
。本文給出使用add_custom_target
添加並執行命令的方式,以及使用add_custom_command
的二種等價寫法。
在正式進入CMakeLists.txt
之前,建議先了解一下cmake -E命令行工具。cmake -E
除了可以在命令行中使用,在CMakeLists.txt
中,也可以通過add_custom_target
及add_custom_command
這兩個指令來呼叫執行。
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
, remove
和remove_directory
等指令,詳見支援指令測試章節。
另外API當中還有個DEPENDS
參數,可以透過這個參數來讓目標依賴於add_custom_command
指令的輸出。具體可見add_custom_command
的第一種使用方式。
為了以下測試方便,先在CMakeLists.txt
所在的目錄下新增config/config.txt
及log.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_directory
和copy
指令的順序,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
cmake - add_custom_command
add_custom_command
有兩種API,分別用於Generating files及Build Events。這兩種API都需要搭配add_custom_target
使用。
以下為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_RES
,add_custom_target
則會生成一個依賴於COPY_RES
的目標。如此一來,便可以透過運行CopyTask
間接地執行COPY_RES
。
以上寫法等價於在add_custom_target
的測試章節看到的寫法。
編譯(編譯之前記得刪掉剛剛生成的etc
目錄):
cd build && cmake ..
make CopyTask
結果與剛剛在add_custom_target
處看到的一致。
另外一個值得注意的地方是:這裡add_custom_command
和add_custom_target
的順序不重要,可以對調。在torch/CMakeLists.txt中就是先add_custom_target
再add_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}"
)
以下為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.txt
,CMakeFiles/CMakeTmp
目錄中多了原來在CMakeFiles/TestTask.dir
裡的東西,CMakeFiles
及CMakeFiles/TestTask.dir
則被刪除了。
【CMake】cmake的add_custom_command和add_custom_target指令
cmake:添加自定义操作