VSCode和CMake构建跨平台的C/C++开发环境

示例项目
这里给出例子工程的文件目录情况,并不完整,但具有一定的代表性,不仅涉及源代码的编译,同样包含了静态库,动态库的加载,以及资源文件的读取等问题

Breakout
├── 3rd // 第三方库
│ ├── glfw // 一个静态库目录
│ ├── irrKlang-1.6.0 // 一个动态库目录
│ └── FindIrrKlang.cmake // cmake文件
├── resources // 资源目录
│ ├── textures // 存放贴图文件
│ └── shaders // 存放shader文件
├── src // 源代码目录
│ ├── game // 源代码子目录
│ │ ├── game.h
│ │ └── game.cc
│ └── main.cc
└── CMakeLists.txt // cmake文件
使用CMake
本文仅着重介绍为了完成示例项目开发,解决特定问题而使用的一些cmake语句,详细的cmake介绍可以查看这份还不错的文档或者这篇博文

cmake的所有语句都写在CMakeLists.txt中,cmake会根据该文件中的配置完成最终的编译,构建,打包,测试等一系列任务

一个简单的CMakeLists.txt如下所示,完整的文件可以查看这里

cmake最低版本号要求

cmake_minimum_required (VERSION 2.8)

设置PROJECT_NAME变量

set(PROJECT_NAME Breakout)

设置工程名

project (${PROJECT_NAME})

查找当前目录下的所有源文件并存入DIR_SRCS变量

aux_source_directory(src DIR_SRCS)

添加一个可编译的目标到工程

add_executable (${PROJECT_NAME} ${DIR_SRCS})
如何编译一个文件夹下的所有源代码
在开发过程中,由于架构设计或是为了便于管理与查找,源文件一般会根据不同的功能存放在不同的文件夹中,文件夹中又可能嵌入文件夹,所以需要一条语句能够获取所有的源文件进行编译,而不用每新创建一个源文件,就修改一次编译指令

递归列出所有源文件

file (GLOB_RECURSE SOURCE_FILES *.cc)

添加一个可编译的目标到工程

add_executable (${PROJECT_NAME} ${SOURCE_FILES})
上面这条file命令会递归列出所有.cc文件,并存入SOURCE_FILES变量,然后将SOURCE_FILES表示的所有.cc文件添加到目标即可,从而解决多源文件编译问题

如何引入一个第三方静态库
为了不重复造轮子,开发中不可避免的要引入其他第三方库。正常情况下,这个第三方库也会是一个CMake工程(或是库的开发者直接提供已经编译好的库)

以示例项目引入的glfw库为例

添加submodule引入glfw(glfw正好是github上的一个开源项目),或是直接将第三方库的源码放到自己的目录中
git submodule add https://github.com/glfw/glfw.git
使用add_subdirectory命令,将glfw所在的文件夹添加到编译的任务列表中

保证glfw dir被编译

add_subdirectory (${GLFW_DIR})
将glfw的头文件目录添加到头文件搜索路径中

添加头文件搜索路径

include_directories (${GLFW_DIR}/include)
链接glfw库,target_link_libraries命令用来链接目标与库文件,第一个参数就是我们的构建目标,后面可以跟多个参数,来表示链接多个库

添加链接库

target_link_libraries (${PROJECT_NAME} glfw)
动态库的加载问题
以示例项目引入的irrKlang库为例,它并不是一个开源项目,不过好在它提供了已经在多个平台上编译好的库,所以我们需要根据不同的平台来设置引入不同的库文件

利用find_package引入外部依赖包,它可以帮我们找到官方预定义的许多依赖包模块,当未在官方预定义的依赖中找到时,会再查找FindXXX.cmake文件,执行该文件从而找到XXX库。更详细的介绍可以查看这篇文章

IrrKlang

find_package (IrrKlang REQUIRED)
先新建FindIrrKlang.cmake文件,由它来负责具体的irrKlang库加载。部分语句如下所示,主要是根据当前平台的不同,设置不同的头文件路径,库路径,库所在目录等变量。用到的find_library语句可以实现直接根据库的base name(即不需要lib,so等),找到对应的库,并存入IRRKLANG_LIBRARY变量
find_path(IRRKLANG_INCLUDE_DIR NAMES irrKlang.h PATHS “KaTeX parse error: Expected 'EOF', got '#' at position 47: …e") IF(WIN32) #̲ win32平台 if…{CMAKE_CXX_COMPILER_ID}” STREQUAL “MSVC”)
message(STATUS “Using MSVC”)
set (IRRKLANG_LIB_DIR “ 3 R D D I R / i r r K l a n g − 1.6.0 / l i b / W i n 32 − v i s u a l S t u d i o " ) s e t ( I R R K L A N G B I N D I R " {3RD_DIR}/irrKlang-1.6.0/lib/Win32-visualStudio") set (IRRKLANG_BIN_DIR " 3RDDIR/irrKlang1.6.0/lib/Win32visualStudio")set(IRRKLANGBINDIR"{3RD_DIR}/irrKlang-1.6.0/bin/Win32-visualStudio”)
find_library(IRRKLANG_LIBRARY NAMES irrKlang PATHS I R R K L A N G L I B D I R ) e l s e i f ( " {IRRKLANG_LIB_DIR}) elseif(" IRRKLANGLIBDIR)elseif("{CMAKE_CXX_COMPILED_ID}" STREQUAL “GNU”)
message(STATUS “Using GCC”)
set (IRRKLANG_LIB_DIR “ 3 R D D I R / i r r K l a n g − 1.6.0 / l i b / W i n 32 − g c c " ) s e t ( I R R K L A N G B I N D I R " {3RD_DIR}/irrKlang-1.6.0/lib/Win32-gcc") set (IRRKLANG_BIN_DIR " 3RDDIR/irrKlang1.6.0/lib/Win32gcc")set(IRRKLANGBINDIR"{3RD_DIR}/irrKlang-1.6.0/bin/Win32-gcc”)
find_library(IRRKLANG_LIBRARY NAMES libirrKlang.a PATHS ${IRRKLANG_LIB_DIR})
endif()

elseif(APPLE) # mac平台
set (IRRKLANG_BIN_DIR “ 3 R D D I R / i r r K l a n g − 1.6.0 / b i n / m a c o s x − g c c " ) f i n d l i b r a r y ( I R R K L A N G L I B R A R Y N A M E S l i b i r r k l a n g . d y l i b P A T H S " {3RD_DIR}/irrKlang-1.6.0/bin/macosx-gcc") find_library(IRRKLANG_LIBRARY NAMES libirrklang.dylib PATHS " 3RDDIR/irrKlang1.6.0/bin/macosxgcc")findlibrary(IRRKLANGLIBRARYNAMESlibirrklang.dylibPATHS"{3RD_DIR}/irrKlang-1.6.0/bin/macosx-gcc”)

elseif(UNIX AND NOT APPLE) # 等同于linux平台
set (IRRKLANG_BIN_DIR “ 3 R D D I R / i r r K l a n g − 1.6.0 / b i n / i n u x − g c c " ) f i n d l i b r a r y ( I R R K L A N G L I B R A R Y N A M E S I r r K l a n g P A T H S " {3RD_DIR}/irrKlang-1.6.0/bin/inux-gcc") find_library(IRRKLANG_LIBRARY NAMES IrrKlang PATHS " 3RDDIR/irrKlang1.6.0/bin/inuxgcc")findlibrary(IRRKLANGLIBRARYNAMESIrrKlangPATHS"{3RD_DIR}/irrKlang-1.6.0/bin/linux-gcc”)

endif()
将找到的irrKlan头文件添加到头文件搜索路径中
include_directories (${IRRKLANG_INCLUDE_DIR})
链接irrKlang库

添加链接库

target_link_libraries (${PROJECT_NAME} glfw ${IRRKLANG_LIBRARY})
utf-8编码格式的代码通过visual studio编译报错问题
跨平台的代码,一般使用utf-8编码格式的代码,更加通用,也可以保证在MacOS或者Linux平台上的正常编译。

但是visual studio默认编译文件的编码是utf-8 with bom,在没有中文的情况下,直接编译是没有问题的,然而当源文件含有中文时(比如中文注释),则可能会出现异常,报一些莫名其妙的语法错误。解决办法是通过CMake语句通知MSVC编译时采用utf-8编码

设置MSVC编译编码

add_compile_options(" < < <<CXX_COMPILER_ID:MSVC>:/source-charset:utf-8>")
可执行文件的工作目录问题
当编译构建生成可执行文件后,我们希望可以直接通过命令行命令启动可执行文件来查看效果。但是由于工作目录的问题,可能会导致出现资源文件找不到,或者库加载失败问题

对于visual studio工程,可以通过CMake语句设置其工作目录,但是这个工作目录仅在通过visual studio调试启动时才会生效,但对于直接启动可执行文件的情况仍然是没用的

设置工作目录

set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY
${CMAKE_SOURCE_DIR}/resources
)
所以只能将资源文件目录放置到可执行文件所在目录下,以保证一定能加载到需要的资源,这可以通过CMake的自定义命令实现

复制资源文件到工作目录

add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/resources
< T A R G E T F I L E D I R : <TARGET_FILE_DIR: <TARGETFILEDIR:{PROJECT_NAME}>/resources
)
这条语句的功能是在编译结束后,将指定的资源目录复制到生成的可执行文件所在的目录。

同样的,对于一些动态库,比如dylib,dll等也需要复制,不过注意最好在编译前就将它们复制到目标目录,使用PRE_BUILD指明命令执行的时机

复制库到工作目录

add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${IRRKLANG_BIN_DIR}
< T A R G E T F I L E D I R : <TARGET_FILE_DIR: <TARGETFILEDIR:{PROJECT_NAME}>
)
关于add_custom_command详细介绍可以查看这里

如何修改Mac上动态库的加载路径
在Mac上启动可执行文件时,一直遇到一个动态库无法加载的报错

dyld: Library not loaded: /usr/local/lib/libirrklang.dylib
Referenced from: …
Reason: image not found
这是由于在编译生成动态库时,可以指定动态库的加载路径,比如我们引入的libirrklang库默认会到/usr/local/lib目录下查找dylib文件

简单的解决方式,自然是通过install命令,将libirrklang.dylib文件安装到/usr/local/lib目录下,不过为了不“污染”其他目录,更希望可执行文件加载的是我们放置在其所在目录下的libirrklang.dylib文件。为了实现这个目标,我们添加下面的CMake语句

if(APPLE)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND install_name_tool -change /usr/local/lib/libirrklang.dylib @executable_path/libirrklang.dylib ${PROJECT_NAME}
)
endif()
判断如果是Mac平台,则通过add_custom_command调用install_name_tool命令,来修改应该应用程序对动态库的查找路径。其中的@executable_path就表示可执行文件所在目录

关于动态库加载路径涉及到的rpath,executable_path,install_name_tool的详细介绍可以查看这里

利用VSCode的task.json执行指定命令
CMake的配置文件已经基本准备完毕了,接下来就是怎样结合VSCode更方便的调用CMake的问题了

通过Ctrl+Shift+B执行CMake编译本地工程
Ctrl+Shift+B是VSCode调用task的默认快捷键,task定义在.vscode目录下的task.json文件中,一般使用task来完成编译等任务,VSCode任务系统提供了丰富的参数配置,我们可以利用它完成很多自定义任务

定义一个task,执行cmake …命令,完成本地项目的编译生成。Linux会生成Makefile,MacOS生成Makefile或Xcode工程,Windows下生成Visual Studio工程。注意这里的…表示的是上层目录,因为我们会在项目根目录下新建一个build文件夹,然后在这个文件夹内完成一系列的编译工作,这样cmake生成的中间文件都在build目录,不会“污染”开发目录(将build目录加入.gitignorej即可忽略CMake所产生的所有中间文件),在编译出问题的时候也可以直接删除buidl目录重新编译
{
“label”: “cmake”, // task的名字
“type”: “shell”,
“command”: “cmake”,
“args”: [
// “-DCMAKE_BUILD_TYPE= i n p u t : C M A K E B U I L D T Y P E " , " . . " ] , " o p t i o n s " : " c w d " : " b u i l d " / / 表 示 当 前 执 行 目 录 b u i l d 文 件 夹 , " g r o u p " : " b u i l d " , " p r e s e n t a t i o n " : / / 一 些 控 制 台 显 示 配 置 " e c h o " : t r u e , " r e v e a l " : " a l w a y s " , " f o c u s " : f a l s e , " p a n e l " : " s h a r e d " , " s h o w R e u s e M e s s a g e " : t r u e , " c l e a r " : t r u e , / / U s e t h e s t a n d a r d M S c o m p i l e r p a t t e r n t o d e t e c t e r r o r s , w a r n i n g s a n d i n f o s " p r o b l e m M a t c h e r " : " {input:CMAKE_BUILD_TYPE}", ".." ], "options": { "cwd": "build" // 表示当前执行目录build文件夹 }, "group": "build", "presentation": { // 一些控制台显示配置 "echo": true, "reveal": "always", "focus": false, "panel": "shared", "showReuseMessage": true, "clear": true }, // Use the standard MS compiler pattern to detect errors, warnings and infos "problemMatcher": " input:CMAKEBUILDTYPE",".."],"options":"cwd":"build"//build,"group":"build","presentation"://"echo":true,"reveal":"always","focus":false,"panel":"shared","showReuseMessage":true,"clear":true,//UsethestandardMScompilerpatterntodetecterrors,warningsandinfos"problemMatcher":"msCompile”,
“dependsOn”:[“mkbuild”] // 依赖的任务,在本任务执行前先执行mkbuild任务
}
在cmake …命令执行前,先通过task执行mkdir新建build文件夹。其中通过windows参数,区分不同平台设置不同的参数
{
“label”: “mkbuild”,
“type”: “shell”,
“command”: “mkdir”, // 调用的命令
“args”: [ // 命令参数
“-p”,
“build”
],
“windows”:{ // windows平台使用mkdir -Force build新建文件夹
“args”: [
“-Force”,
“build”
]
},
“group”: “build”,
“presentation”: {
“echo”: true,
“reveal”: “always”,
“focus”: false,
“panel”: “shared”,
“showReuseMessage”: true,
“clear”: true
},
// Use the standard MS compiler pattern to detect errors, warnings and infos
“problemMatcher”: “$msCompile”,
}
当按下Ctrl+Shift+B时,VSCode将弹出所有可执行的task,选择执行cmake task即可。由于定义了依赖(dependsOn),在cmak task执行前,将自动先执行mkbuild task

通过Ctrl+B构建与运行可执行文件
在启动可执行文件前,先通过cmake --build .构建生成可执行文件
{
“label”: “compile”,
“type”: “shell”,
“command”: “cmake --build .”,
“options”: {
“cwd”: “build”
},
“group”: “build”,
“presentation”: {
// Reveal the output only if unrecognized errors occur.
“reveal”: “always”,
“clear”: true
},
// Use the standard MS compiler pattern to detect errors, warnings and infos
“problemMatcher”: "KaTeX parse error: Expected 'EOF', got '}' at position 16: msCompile" }̲ 在可执行文件生成后,通过ta…{workspaceFolderBasename}是VSCode的内置变量,表示工程名。更多的内置变量介绍可以查看这里
{
“label”: “run”,
“type”: “shell”,
“command”: “./ w o r k s p a c e F o l d e r B a s e n a m e " , " g r o u p " : " b u i l d " , " p r e s e n t a t i o n " : " e c h o " : t r u e , " r e v e a l " : " a l w a y s " , " f o c u s " : f a l s e , " p a n e l " : " s h a r e d " , " s h o w R e u s e M e s s a g e " : t r u e , " c l e a r " : t r u e , " o p t i o n s " : " c w d " : " b u i l d " , " w i n d o w s " : " o p t i o n s " : " c w d " : " b u i l d / D e b u g " / / w i n d o w s v i s u a l s t u d i o 项 目 会 默 认 多 生 成 D e b u g / R e l e a s e 目 录 , , / / U s e t h e s t a n d a r d M S c o m p i l e r p a t t e r n t o d e t e c t e r r o r s , w a r n i n g s a n d i n f o s " p r o b l e m M a t c h e r " : " {workspaceFolderBasename}", "group": "build", "presentation": { "echo": true, "reveal": "always", "focus": false, "panel": "shared", "showReuseMessage": true, "clear": true }, "options": { "cwd": "build" }, "windows":{ "options": { "cwd": "build/Debug" // windows visual studio项目会默认多生成Debug/Release目录 }, }, // Use the standard MS compiler pattern to detect errors, warnings and infos "problemMatcher": " workspaceFolderBasename","group":"build","presentation":"echo":true,"reveal":"always","focus":false,"panel":"shared","showReuseMessage":true,"clear":true,"options":"cwd":"build","windows":"options":"cwd":"build/Debug"//windowsvisualstudioDebug/Release,,//UsethestandardMScompilerpatterntodetecterrors,warningsandinfos"problemMatcher":"msCompile”,
“dependsOn”:[“compile”] // 在run任务执行前先执行compile任务,确保修改的代码生效
}
通过Ctrl+B直接调用run任务。由于编译运行这个任务经常要用到,如果依然通过Ctrl+Shift+B弹出所有任务,再进一步选择就会有些麻烦,所以定义一个快捷键(读者可以用同样的方法设置自己喜欢的快捷键)来直接运行run任务
打开VSCode快捷键设置

在弹出的界面中输入“run build task”搜索,并修改其快捷键

点击右上角的翻转按钮,进入快捷键文件配置

在打开的文件中,为刚配置的快捷键添加参数,告诉它直接启动名字叫“run”的task
{
“key”: “ctrl+b”,
“command”: “workbench.action.tasks.runTask”,
“args” : “run”
}
利用VSCode的launch.json完成调试
VSCode通过.vscode目录下的launch.json实现对多个平台多种语言的调试支持

通过F5完成项目的调试与运行
配置launch.json文件以支持C/C++项目的调试
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
“version”: “0.2.0”,
“configurations”: [
{
“name”: “Launch Debug”, //名称
“type”: “cppdbg”, //调试类型,除使用msvc进行调试外,均为该类型
“request”: “launch”,
“program”: “ w o r k s p a c e F o l d e r / b u i l d / {workspaceFolder}/build/ workspaceFolder/build/{workspaceFolderBasename}”, //指定C/C++程序位置
“args”: [], //指定运行参数
“stopAtEntry”: false,
“cwd”: “KaTeX parse error: Expected '}', got 'EOF' at end of input: … "program": "{workspaceFolder}/build/Debug/${workspaceFolderBasename}.exe”, //指定C/C++程序位置
}
}
]
}
F5是VSCode调试运行的默认快捷键,不需要额外配置,启动调试后的界面如下所示

通过使用本地工具完成调试
由于cmake在windows平台会默认生成Visual Stduio工程,所以也可以直接打开生成的解决方案,通过Visual Studio进行调试
Mac平台可以使用cmake … -GXcode命令指定生成XCode工程,然后通过XCode进行调试
深圳网站建设www.sz886.com

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值