跳转至

cmake

导言

ipcc初赛的项目代码有些混乱,只是简单分类。想好好学习一下cmake和make。规范项目结构,优化编译运行流程,提高效率。

cmake简介

CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。

安装

pip install --upgrade cmake
export PATH=/home/anaconda3/bin:$PATH

自动生成CMakeLists.txt

ccmake

sudo apt-get install cmake-curses-gui
ccmake ..

打印详细编译信息

  • 执行命令cmake时追加:-DCMAKE_VERBOSE_MAKEFILE=ON
  • 查看整个编译过程。你可以使用 cmake --trace-expand . 选项来详细追踪 CMake 的执行。
  • 在CMakeLists.txt中添加:set(CMAKE_VERBOSE_MAKEFILE ON)

cmake结束之后,单独运行make时追加: VERBOSE=1就会显示最基本的编译命令。加上--dry-run选项 能快速只打印编译命令。

make --dry-run VERBOSE=1

list all option

查看cmake选项

mkdir build
cd build
cmake ..
cmake -LA | awk '{if(f)print} /-- Cache values/{f=1}'
cmake -LAH # adding -H to show more help information about each option

快速编译

0. Fast Recompile

由于cmake实际就是将一堆cpp编译成.so或者exe的目标文件;为此CMake生成的Makefile,对每个target提供了fast编译选项, 例如 make cpprinter/fast

# target_link_libraries(your_project PRIVATE cpprinter)

# Target rules for targets named cpprinter

# Build rule for target.
cpprinter: cmake_check_build_system
    $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 cpprinter
.PHONY : cpprinter

# fast build rule for target.
cpprinter/fast:
    $(MAKE) $(MAKESILENT) -f _deps/cpp_printer-build/CMakeFiles/cpprinter.dir/build.make _deps/cpp_printer-build/CMakeFiles/cpprinter.dir/build
.PHONY : cpprinter/fast

1. 剔除不必要的组件和功能

PyTorch 是一个庞大的项目,可能包含很多你不需要的模块。你可以通过调整 CMake 配置选项来禁用不必要的部分。例如,如果你不需要 GPU 支持,可以禁用 CUDA:

cmake -DBUILD_CUDA=OFF .

其他常见的选项还包括禁用 distributedtestpython 等模块。

2. 使用 Ninja 作为构建系统

  • 如果你还在使用 make,可以考虑切换到 ninja,因为 ninja 通常比 make 更快,尤其是对大型项目。
  • 首先确保系统安装了 ninja,然后在运行 cmake 时指定 ninja 作为生成器:
cmake -G Ninja .
ninja

Ninja 还能更好地利用并行编译,加速整个过程。

已有大型项目很难自动迁移,各种语法问题

3. 使用 ccache 缓存编译结果

ccache 是一个编译结果缓存工具,可以显著加快重复编译的速度。你可以安装并启用 ccache,它会缓存已编译过的目标文件,下次编译时直接复用:

sudo apt/yum install ccache
export CC="ccache gcc"
export CXX="ccache g++"

接着重新配置并编译项目。

常用选项

-S <path-to-source>          = Explicitly specify a source directory.CMakeLists.txt路径
-B <path-to-build>           = Explicitly specify a build directory.
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_INSTALL_PREFIX=directory --- Specify for directory the full path name of where you want thetools and libraries to be installed (default /usr/local)

-G <generator> 基于不同平台的makefile生成方式

  • -G "Unix Makefiles"
  • -G "MinGW"
  • 之类的

CMakeList.txt基本语法

基础设置

# cmake required version
cmake_minimum_required(VERSION 3.0.0)

# more error message
SET( CMAKE_VERBOSE_MAKEFILE on )

#project name
PROJECT(ipcc_test)

# compile
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_C_COMPILER gcc)
set(CMAKE_CXX_COMPILER g++)


# flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O3  -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3  -pthread -fopenmp")

#head file path
INCLUDE_DIRECTORIES(include)

#source directory
AUX_SOURCE_DIRECTORY(src DIR_SRCS) # 搜集所有在指定路径下的源文件的文件名,将输出结果列表储存在指定的变量DIR_SRCS中

# message
message(STATUS "PROJECT_SOURCE_DIR is ${PROJECT_SOURCE_DIR}")
message(STATUS "PROJECT_BINARY_DIR is ${PROJECT_BINARY_DIR}")
message(STATUS "CMAKE_CURRENT_SOURCE_DIR is ${CMAKE_CURRENT_SOURCE_DIR}")

#output path
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

#set environment variable
SET(TEST ${DIR_SRCS})  # 此处用于显示如何用环境变量对环境变量进行赋值

自定义命令

add_custom_target 是 CMake 中用于定义一个自定义目标的命令。这个目标不直接对应于生成一个输出文件,而是可以用于执行自定义命令或操作。下面是你提供的代码的解释和如何使用 add_custom_target 的说明:

  1. 自定义命令
    add_custom_command(
        COMMAND make clean
        COMMAND make
        OUTPUT ${CMAN_DRV_KO}
        WORKING_DIRECTORY ${CMAN_DRV_DIR})
    
  2. 这个命令会在指定的工作目录下运行 make cleanmake,并生成 cman.ko 文件。

  3. 自定义目标

    add_custom_target(drv
        DEPENDS ${CMAN_DRV_KO}
        WORKING_DIRECTORY ${CMAN_DRV_DIR}
        COMMENT "Compiling cman driver")
    

  4. drv 是自定义目标的名称。
  5. 该目标依赖于 ${CMAN_DRV_KO},这意味着在构建 drv 时,会确保 cman.ko 已经被生成。
  6. 如果 cman.ko 不存在或是过期的,CMake 会自动执行上一个自定义命令来生成它。

  7. 安装目标

    add_custom_target(drv-install
        DEPENDS ${CMAN_DRV_KO}
        COMMAND make install
        WORKING_DIRECTORY ${CMAN_DRV_DIR}
        COMMENT "Installing cman driver")
    

  8. drv-install 是另一个自定义目标,用于安装驱动。
  9. 这个目标也依赖于 ${CMAN_DRV_KO},并在构建时运行 make install

add_custom_target 允许你定义在构建过程中特定的自定义操作,比如编译和安装操作,而不直接生成输出文件。通过设置依赖关系,可以确保在运行自定义目标时所需的文件已经生成。这种方法在需要执行多个步骤或使用外部工具时特别有用。

目标设置

#add target/final executable file
ADD_EXECUTABLE(./bin/exe ${TEST})

# 指定最后生成类似.so的文件
add_library(${PLUGIN_NAME} SHARED ${CPP_SRCS})

# 继续构建的子目录
add_subdirectory(dir)

链接库

# add lib path to search lib file
link_directories(${CMAKE_SOURCE_DIR}/lib)

add_executable(foo ${FOO_SRCS})

#set extern libraries
SET(LIBRARIES LIBRARIESlibm.so)

#add link library
TARGET_LINK_LIBRARIES(../bin/bin ${LIBRARIES})
target_link_libraries(foo bar) # libbar.so is found in ${CMAKE_SOURCE_DIR}/lib

安装到系统

# 安装目标, 在构建完成后,运行 `make install` 命令将自动执行 CMake 中定义的安装规则。

## 指定了将 `cman.ko` 文件安装到系统的 `/lib/modules/{architecture}/kernel/drivers` 目录中。
install(FILES ${CMAN_DRV_KO} DESTINATION /lib/modules/${CMAKE_SYSTEM_PROCESSOR}/kernel/drivers)

## DESTINATION include:这表示安装路径将是系统的 include 目录,通常是 `/usr/local/include` 或者根据 `CMAKE_INSTALL_PREFIX` 的设置。
install(DIRECTORY ${CMAN_INCLUDE_DIR}/ DESTINATION include)

## ARCHIVE 表示将该目标作为一个归档文件(通常是静态库,如 .a 文件)进行安装。默认情况下,库文件会安装到 `/usr/local/lib` 或根据 `CMAKE_INSTALL_PREFIX` 的设置。
install(TARGETS cman ARCHIVE)

## EXPORT 关键字用于将目标的信息(例如目标的路径、依赖等)导出到一个 CMake 文件中,这样其他项目就可以通过 find_package(Caffe2Targets) 来引用这些目标。
install(TARGETS cpprinter EXPORT Caffe2Targets)

# 可选:指定安装后的处理
install(CODE "message(STATUS \"Driver installed to /lib/modules/${CMAKE_SYSTEM_PROCESSOR}/kernel/drivers\")")
  • 自定义安装路径:如果希望安装到其他目录,可以修改 DESTINATION 参数的值,或者在运行 CMake 时指定 CMAKE_INSTALL_PREFIX 变量。例如:
    cmake -DCMAKE_INSTALL_PREFIX=/your/custom/path ..
    

通过这种方式,CMake 生成的 Makefile 将能够支持 make install 命令将文件安装到指定的系统目录。

技巧

多级目录编译

CMakeLists.txt*.cpp一定会被变量包含AUX_SOURCE_DIRECTORY或者FILE,然后跨层传递add_subdirectory,变量组合SET,直至被添加到可执行文件ADD_EXECUTABLE或者add_library

上一层CMakeLists.txt
set(exampleDIR "xxx")

# pre-define
set(dir1_srcs)
set(dir2_srcs)
set(dir3_srcs)

# cmake in subdir (重要)
add_subdirectory(${exampleDIR}/subdir)

SET(CPP_SRCS ${dir1_srcs} ${dir2_srcs} ${dir3_srcs})
add_library(${PLUGIN_NAME} SHARED ${CPP_SRCS})
CMakeLists.txt in ${exampleDIR}/subdir
FILE(GLOB _dir1_srcs *.cpp)

LIST(APPEND dir1_srcs ${_dir1_srcs})

# pass to parent(the CMakeLists.txt using add_subdirectory)
SET(dir1_srcs ${dir1_srcs} PARENT_SCOPE)

实例

目录下所有源文件编译成单独的可执行文件

实现头文件、执行文件、源代码都分开存储。

file(GLOB files "*/*.cpp")
foreach (file ${files})
    message(STATUS "file is ${file}")
    string(REGEX REPLACE ".+/(.+)\\..*" "\\1" exe ${file})
    message(STATUS "file name is ${exe}")
    add_executable(${exe} ${file})
    TARGET_LINK_LIBRARIES(${exe} ${CONAN_LIBS})
endforeach ()
string(REGEX REPLACE <regular_expression>
    <replacement_expression> <output_variable>
    <input> [<input>...])

尽可能匹配 <regular_expression> 多次,并用 <replacement_expression> 替换输出中的匹配项。匹配之前将所有 <input> 参数连接在一起。

所述 <replacement_expression> 可以是指使用匹配的括号分隔子表达式 \1 , \2 ,..., \9 。请注意,CMake代码中需要两个反斜杠( \\1 )才能通过参数解析获得反斜杠。同理 \\.是匹配 .的意思。

# Find all main*.cpp files and store in list mains
file(GLOB_RECURSE mains RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/main*.cpp")

foreach(mainfile IN LISTS mains)
    # Get file name without directory
    get_filename_component(mainname ${mainfile} NAME_WE)
    add_executable(${mainname} ${mainfile})
endforeach()
file( GLOB APP_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/src/ *.c )

foreach( sourcefile IN LISTS APP_SOURCES )
    string( REPLACE ".c" "" program ${sourcefile} )
    add_executable( ${program} ${sourcefile} )
    #target_link_libraries( ${program} zmq )
endforeach( sourcefile )
将指定的文件编译成单独的可执行文件

example code

运行n次并求平均时间

example code

比较时间的Cmake优化小项目

https://github.com/Kirrito-k423/StencilAcc

需要进一步的研究学习

  • 学习Quest cmake 添加openmp的编译选项核compile option

参考文献

https://www.cnblogs.com/lyq105/archive/2010/12/03/1895067.html

https://blog.csdn.net/ET_Endeavoring/article/details/98989066

———————————————— 版权声明:本文为CSDN博主「商汤科技」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq295109601/article/details/118867192