CMAKE:方便CUDA与其他语言(C、C++、RUST)联合编程

在CUDA编程中,尤其是涉及多语言混合编程时,如C/C++和CUDA或者RUST和CUDA等,使用NVCC编译器在编译一些大项目时,还是比较麻烦的。使用cmake配置编译选项还是比较简单并且强大的。

cmake 是一个强大的自动化配置工具,它是开源的,跨平台的,它通过读取脚本文件CMakeLists.txt 中的配置来构建编译系统。

一、使用方法

在 linux 平台下使用 CMake 进行项目构建的流程如下:

  1. 编写C/C++/CUDA C++源码。
  2. 根据源码结构编写 CMake 配置文件 CMakeLists.txt 。
  3. 执行命令 cmake PATH 生成 Makefile。其中, PATH 是 CMakeLists.txt 所在的目录。这里一般会单独建立一个文件夹存放构建文件,如在根目录下建一个build文件夹,然后进入到build目录执行cmake PATH(如果 CMakeLists.txt 在上一层目录,直接执行cmake ..)。
  4. 构建文件夹中执行 make 命令进行编译。
  5. 使用./project_name运行代码。project_name一般为编译的项目名称,项目名词在CMakeLists.txt中给定。

二、CMakeLists.txt编写

    编写 CMakeLists.txt 文件,保存在与主函数文件相同目录下。CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的。使用“ # “进行注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。一个简单的CMakeLists.txt如下:

# 最低版本号要求    
cmake_minimum_required (VERSION 3.27)
# 项目名称 和编程语言
project(project LANGUAGES CXX CUDA)

# 设置编译类型为RELEASE还是DEBUG(两者区别自行了解)

set(CMAKE_BUILD_TYPE RELEASE)

# 指定C++版本

set(CMAKE_CXX_STANDARD 14)

set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 指定目标执行文件
add_executable(project main.cpp)

其中cmake_minimum_required();project();add_executable();set();都是命令(可以看为函数)。cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本,例子是最低版本3.27;
project:参数值是 project,该命令表示项目的名称是 project  ,后面LANGUAGES关键词之后指明编程语言。
add_executable:将名为 main.cpp的源文件编译成一个名称为 project 的可执行文件。

三、编写CMakeLists.txt常用的命令

1.指定cmake的最小版本
cmake_minimum_required(version 版本号)
例如:
cmake_minimum_required(version 3.27)
2.定义工程名称
#定义工程名称 
project(项目名称)
例如:
project(project)
3 .显示定义变量
set(var [value])
例如:
# 第一种用法,生成代码文件列表
#先直接设置SRC_LIST的值
set(SRC_LIST XXX.h XXX.cpp)
#然后再在SRC_LIST中追加main.cpp 
set(SRC_LIST ${SRC_LIST} main.cpp)
# 第二中用法,设置库生成目录或者可执行文件生成目录
set( LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) 
set( EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
4. 设置编译类型
# 编译静态库
add_library(库名称 STATIC 代码文件名称) 
# 编译动态库
add_library(库名称 SHARED 代码文件名称) 
# 编译可执行程序
add_executable(可执行程序名 代码文件名称)
例如:
# 编译静态库
add_library(add STATIC add.h add.cpp)
add_library(add STATIC ${ADD_SRC} ${ADD_HDR})
# 编译动态库
add_library(add  SHARED add.h add.cpp) 
add_library(add SHARED  ${ADD_SRC} ${ADD_HDR})
# 编译可执行程序
add_executable(main add.h add.cpp mai.cpp)
add_executable(main ${MAIN_SRC} ${MAIN_HDR})
5. 指定静态库或者动态库编译输出目录
例如将当前编译的静态库或者动态库输出到当前项目文件夹lib子目录下
set(LIBRARY_OUTPUT_PATH  ${PROJECT_SOURCE_DIR}/lib)
6. 指定可执行程序编译输出目录
例如将当前可执行程序输出到当前项目文件夹的bin子目录下
#设定可执行二进制文件的目录
set( EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
7. 设置链接库搜索目录
例如将链接库搜索目录设置为当前项目文件夹下lib/linux文件夹
link_directories( ${PROJECT_SOURCE_DIR}/lib/linux)
8. 设置包含目录
例如将包含目录设置为当前项目文件夹下include文件夹
include_directories(${PROJECT_SOURCE_DIR}/include)

或使用target_include_directories(${PROJECT_NAME} PUBLIC "./include")
9. 设置宏定义
#预定义宏
add_definitions(-D宏名称)
例如:
add_definitions(-DWINDOWS)
add_definitions(-DLINUX)
10. 链接静态库
link_libraries(
    静态库1
    静态库2
    静态库3
    ...
)
注意,link_libraries中的静态库为全路径,常与link_directories 搭配使用,例如:
lib1.a lib2.a在目录${PROJECT_SOURCE_DIR}/lib/linux下,则先设置链接目录,再链接相应的库
#设置链接目录
link_directories( ${PROJECT_SOURCE_DIR}/lib/linux)
link_libraries(
        lib1.a
        lib2.a
)
11 .链接动态库
target_link_libraries(所需生成的文件名称 所需链接的动态库名称)
例如
target_link_libraries(main dl)
12 .link_libraries 和 target_link_libraries 区别
在cmake语法中,link_libraries和target_link_libraries是很重要的两个链接库的方式,虽然写法上很相似,但是功能上有很大区别:
(1) link_libraries用在add_executable之前,target_link_libraries用在add_executable之后
(2) link_libraries用来链接静态库,target_link_libraries用来链接导入库,即按照header file + .lib + .dll方式隐式调用动态库的.lib库
13. file语法
13.1 将文件夹所有的类型的文件添加到文件列表
例如将当前文件夹下所有.cpp文件的文件名加入到MAIN_SRC中,将当前文件夹下所有.h加入到MAIN_HDR中。
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
file(GLOB MAIN_HDR ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
例如将当前文件夹子目录src文件夹下所有.cpp文件的文件名加入到MAIN_SRC中,将当前文件夹子目录src文件夹下所有.h加入到MAIN_HDR中。
file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB MAIN_HDR ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h)
13.2 递归搜索该文件夹,将文件夹下(包含子目录)符合类型的文件添加到文件列表
例如将当前文件夹下(包括子目录下)所有.cpp文件的文件名加入到MAIN_SRC中,所有.h加入到MAIN_HDR中
file(GLOB_RECURSE MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
file(GLOB_RECURSE MAIN_HDR ${CMAKE_CURRENT_SOURCE_DIR}/*.h)
14.添加子文件夹
例如
add_subdirectory(src)
该语句会在执行完当前文件夹CMakeLists.txt之后执行src子目录下的CMakeLists.txt
15. message输出消息机制
输出正常:
message(STATUS "Enter cmake ${CMAKE_CURRENT_LIST_DIR}")
输出警告
message(WARNING "Enter cmake ${CMAKE_CURRENT_LIST_DIR}")
输出错误:
message(FATAL_ERROR "Enter cmake ${CMAKE_CURRENT_LIST_DIR}")
16. 安装
install 指令用于定义安装规则,安装的内容包括二进制可执行文件、动态库、静态库以及文件、目录、脚本等。
16.1 目标文件安装
例如:
install(TARGETS util
  RUNTIME DESTINATION bin
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib)
ARCHIVE指静态库,LIBRARY指动态库,RUNTIME指可执行目标二进制,上述示例的意思是:
如果目标util是可执行二进制目标,则安装到${CMAKE_INSTALL_PREFIX}/bin目录 如果目标util是静态库,则安装到安装到${CMAKE_INSTALL_PREFIX}/lib目录 如果目标util是动态库,则安装到安装到${CMAKE_INSTALL_PREFIX}/lib目录
16.2 文件夹安装
install(DIRECTORY include/ DESTINATION include/util)
这个语句的意思是将include/目录安装到include/util目录
17. 设置编译选项
设置编译选项可以通过add_compile_options命令,也可以通过set命令修改CMAKE_CXX_FLAGS或CMAKE_C_FLAGS。

方式1
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -march=native -O3 -frtti -fpermissive -fexceptions -pthread")
方式2
add_compile_options(-march=native -O3 -fexceptions -pthread -fPIC)
这两种方式的区别在于:
add_compile_options命令添加的编译选项是针对所有编译器的(包括c和c++编译器),而set命令设置CMAKE_C_FLAGS或CMAKE_CXX_FLAGS变量则是分别只针对c和c++编译器的。
18. 预定义变量(cmke本身已有的关键字)
18.1 基本变量
PROJECT_SOURCE_DIR:一般是工程的根目录;
PROJECT_BINARY_DIR:执行cmake命令的目录,通常是${PROJECT_SOURCE_DIR}/build;
CMAKE_INCLUDE_PATH:系统环境变量;
CMAKE_LIBRARY_PATH:系统环境变量;
CMAKE_CURRENT_SOURCE_DIR:当前处理的CMakeLists.txt所在的路径;
CMAKE_CURRENT_BINARY_DIR:target编译目录(使用ADD_SURDIRECTORY(src bin)可以更改此变量的值 ,SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对此变量有影响,只是改变了最终目标文件的存储路径);
PROJECT_NAME:通过PROJECT指令定义的项目名称;
18.2 操作系统变量
UNIX:在所有的类UNIX平台为TRUE,包括OS X和cygwin,Linux/Unix操作系统;
WIN32:在所有的win32平台为TRUE,包括Windows32和64位操作系统;
APPLE:苹果操作系统;
例如操作系统判断方式一:
if(WIN32)
    message(STATUS “This operating system is Windows.”)
elseif(UNIX)
    message(STATUS “This operating system is Linux.”)
elseif(APPLE)
    message(STATUS “This operating system is APPLE.”)
endif(WIN32)
18.3 环境变量
设置环境变量:
set(env{name} value)
调用环境变量:
$env{name}
例如
message(STATUS "$env{name}")
19.条件判断
19.1 逻辑判断和比较
if (expression):expression 不为空时为真,false的值包括(0,N,NO,OFF,FALSE,NOTFOUND);
if (var1 AND var2):如果两个变量都为真时为真;
if (var1 OR var2):如果两个变量有一个为真时为真;
if (EXISTS dir) if (EXISTS file):如果目录或文件存在为真;
if (IS_DIRECTORY dir):当 dir 是目录时为真;
19.2 数字比较
if (variable LESS number):如果variable小于number时为真;
if (string LESS number):如果string小于number时为真;
if (variable GREATER number):如果variable大于number时为真;
if (string GREATER number):如果string大于number时为真;
if (variable EQUAL number):如果variable等于number时为真;
if (string EQUAL number):如果string等于number时为真。
19.3 字母表顺序比较
if (variable STRLESS string)
if (string STRLESS string)
if (variable STRGREATER string)
if (string STRGREATER string)
if (variable STREQUAL string)
if (string STREQUAL string)

下面是C++和CUDA联合编程的一个CMakeLists.txt例子:

cmake_minimum_required(VERSION 3.27)
project(project LANGUAGES CXX CUDA)
set(CMAKE_BUILD_TYPE RELEASE)

# 指定C++版本
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)


# 检查CUDA开发工具包,并将开发工具包加入路径
include(CheckLanguage)
if(NOT CUDAToolkit_FOUND)
    include(FindCUDAToolkit)
endif()
include_directories(${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
# 指定CUDA C++版本
check_language(CUDA)
set(CMAKE_CUDA_STANDARD 14)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)

ADD_EXECUTABLE(${PROJECT_NAME}  kernel.cpp)

#指定目标包含的头文件目录
target_include_directories(${PROJECT_NAME}  PUBLIC "./cuda/include")

#添加库文件目录,如果不添加,找不到库文件
target_link_directories(${PROJECT_NAME}  PUBLIC "./cuda/build")

#指定目标链接的库
target_link_libraries(${PROJECT_NAME}  PUBLIC cuda_lib)
target_link_libraries(${PROJECT_NAME} PUBLIC CUDA::cudart)

cuda代码已被编译成静态库“libcuda_lib.a”存放到“./cuda/build”文件下,主函数所这文件为kernal.cpp。各种头文件放在“./cuda/include”文件夹中。

四、总结

CMAKE在linux系统下编译cuda与其它语言联合编写的程序非常好用,文中的常用命令基本都能用上。

你可能感兴趣的:(CUDA,与硬件加速,经验分享,c++)