【CMake实战】构建LearnOpenGL环境


前言

记录这段时间对CMake的学习浅显认识,实际生产没使用过,当作抛砖引玉,希望各位大触指出我的错误。
关联项目:https://github.com/Lzeyuan/LearnOpenGLWithCMake


1 快速开始

cmake_minimum_required(VERSION 3.25)

project(00_QuickStart VERSION 1.0 LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(
    ${PROJECT_NAME}
    main.cpp
)

2 子目录(模块)

复杂度不会消失,但是可以转移,分而治之

2.1 include和add_subdirectory

include等价于#include

stackoverflow的QA:cmake: add_subdirectory() vs include()

主要区别:

  • 使用add_subdirectory会把目录结构体现在生成目录里
  • 使用add_subdirectory会创建一个新作用域

子项目和正常项目差不多,不过要考虑共享信息给引用项目。

# glad子项目
project(glad)

# 默认STATIC,可不写
add_library(
    ${PROJECT_NAME}
    STATIC
    src/glad.c
)

# 这里把头文件 PUBLIC 共享给引用的项目
target_include_directories(
    ${PROJECT_NAME}
    PUBLIC
    include
)

如果是纯动态库项目,应该怎么封装呢?请看下一节。

3 动态库使用和Install指令

一般自己项目拆成静态库。

3.1 动态库

3.1.1 封装动态库文件

书接上回,包装只有动态库文件的项目应该怎么做,这里把上节的glfw项目替换成现在项目这样只有动态库和头文件。

那么现在要做的重点就是用INTERFACE选项,见下面CMakeLists.txt代码

该选项和PUBLIC相似,不过内容不给自身项目使用

也就说target_include_directories添加头文件,然后使用这个选项,自身项目写代码时还去include这头文件会提示找不到。

# glfw
#     │  CMakeLists.txt
#     │
#     ├─bin
#     │      glfw3.dll
#     │
#     ├─include
#     │  └─GLFW
#     │          glfw3.h
#     │          glfw3native.h
#     │
#     └─lib
#             glfw3dll.lib
# glfw动态库子项目
project(glfw)

# 仅共享动态库,没有源文件
add_library(
    ${PROJECT_NAME}
    INTERFACE
)

# 这里把头文件共享给引用的项目
target_include_directories(
    ${PROJECT_NAME}
    INTERFACE
    include
)

target_link_directories(
    ${PROJECT_NAME}
    INTERFACE
    lib
)

target_link_libraries(
    ${PROJECT_NAME}
    INTERFACE
    glfw3dll
)

3.1.2 使用场景

开头所写 一般自己项目拆成静态库。 为什么呢?

因为动态库巨麻烦,有一堆需要考虑的事情,作者没有全面学习,这里写的是个人见解

目的:

  • 节省内存:需要时加载
  • 节省外存:只有一份副本,不会链接进执行文件里
  • 热更新
  • 加快生成速度,编译可以并行,链接只能串行,把链接时机推后
  • 统一库的版本,不同库可能链接统一库的不同版本导致符号冲突,或者执行时因为内存布局不同导致崩溃,比如MSVC例子

缺点:

  • 符号冲突编译器不能帮忙检测,比如:两个动态库写了一样的函数名。
  • 依赖关系:典型的protobuf库版本问题,要手动处理依赖库。

CMake默认链接MSVC的动态库,xmake则是静态库。

在这里插入图片描述

3.2 Install 指令

3.2.1 找不到glfw3.dll

修改完后运行一下项目,会报错找不到glfw3.dll

本地路径\LearnOpenGLWithCMake\02_DynamicAndInstall\3rd\glfw\lib\glfw3.dll

复制到

本地路径\LearnOpenGLWithCMake\02_DynamicAndInstall\out\build\x64-debug

复制到这个目录下,然后在运行程序,可以正常运行。

当然也可以把DLL所在目录加入到系统环境里,或者在程序使用系统api路径加载动态库。

那为什么选择第一种方法?因为我看其它软件都是这么做的。

Windosw加载顺序参考:https://learn.microsoft.com/zh-cn/windows/win32/dlls/dynamic-link-library-search-order

3.2.2 Install使用

上面步骤可以可以用Install指令简化:

project(glfw)

add_library(
    ${PROJECT_NAME}
    INTERFACE
)

target_include_directories(
    ${PROJECT_NAME}
    INTERFACE
    include
)

target_link_directories(
    ${PROJECT_NAME}
    INTERFACE
    lib
)

target_link_libraries(
    ${PROJECT_NAME}
    INTERFACE
    glfw3dll
)

# 安装dll
install(
    FILES  ${CMAKE_CURRENT_SOURCE_DIR}/lib/glfw3.dll
    DESTINATION bin
)

现在在VS的运行目标找到02_DynamicAndInstall(安装)然后运行,找不到DLL的提示就消失了,取而代之的是正常运行。

其它IDE在运行安装后可以到out/install下查看,程序和dll都在一个目录下,直接运行即可。

3.3 简化 git clone

那么介绍了回字的两种写法(导入第三方库的两种方法),现在在介绍一种。

这等同于 git clone https://github.com/glfw/glfw.git 然后 git checkout 自取分支名 3.3.9

...
include(fetchcontent)      # 照写,不需要修改
 fetchcontent_declare(
    glfw        #库名字
    GIT_REPOSITORY https://github.com/glfw/glfw.git        # 仓库地址
    GIT_TAG 3.3.9 # 库版本
    SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/glfw         # 指定库下载地址
)

fetchcontent_makeavailable(glfw)
...

不过我还是推荐第一节课的方法使用 git submodule 管理版本,然后视情况编译成动态库还是静态库。

当然包管理的方法我也是推荐的,下节会介绍。

4 CMakePresets和Vcpkg

仍然是分而治之

4.1 CMakePresets

CMakePresets这个东西前几个项目就有了,现在终于写到他了。

4.1.1 作用

还是那句话,转移复杂度,分而治之。

便于用户指定通用的配置、生成和测试选项,并与他人共享

目前只用到了配置和生成。

4.1.2 CMakePresets和CMakeUserPresets

CMakePreset.json 加入版本控制,配置宏之类的参数

CMakeUserPreset.json 不加入版本控制,配置本地开发环境参数

写死的配置写在CMakeLists.txt里,根据情况配置的写在CMakePresets.json里,和开发者环境有关的写在CMakeUserPreset.json里。

为了演示,这一节项目上传了CMakeUserPresets.json,记得修改VCPKG_TOOLCHAIN_FILE

比如:

  • CMAKE_POLICY_DEFAULT_CMP0091这个规则和Windows平台相关的就写在CMakePresets.json
  • 生成目录、安装目录、平台架构等等的配置也写在CMakePresets.json
  • VCPKG_TOOLCHAIN_FILE是配置vcpkg .cmake的文件路径,应写在CMakeUserPreset.json

具体查看文件,都是KV值很好懂。

4.2 Vcpkg

不依赖cmake版本,因为他是作为工具链传入cmake。

4.2.1 vcpkg.json管理独立环境

和python的conda一样,每个项目都可以创建专属的环境安装特定版本的包,不和其它环境冲突。

教程:从清单文件安装依赖项

必须要使用overrides才能指定版本号。

不要指定版本号,详见吐槽。

必须添加builtin-baseline,若要添加初始 “builtin-baseline”,请使用 vcpkg x-update-baseline --add-initial-baseline。 若要更新清单中的基线,请使用 vcpkg x-update-baseline

{
  "name": "cmake-learn",
  "version-semver": "0.0.1",
  "dependencies": [
    {
      "name": "glfw3",
      "platform": "(windows & x64)"
    },
    {
      "name": "assimp",
      "version>=": "5.3.1",
      "platform": "(windows & x64)"
    }
  ],
  "overrides": [
    {
      "name": "glfw3",
      "version": "3.3.9"
    }
  ],
  "builtin-baseline": "7032c5759f823fc8699a5c37d5f7072464ccb9a8"
}

4.2.2 指定平台

平台表达式

很坑,这个选项意思是:满足这个条件才安装,具体安装配置参考:三元组参考

比如:"platform": "(windows & x64 & static)",但是没有配置
VCPKG_TARGET_TRIPLET
x64-windows-static他不会安装到项目环境里,因为默认是x64-windows

甚至不写都行,直接看VCPKG_TARGET_TRIPLET设置。

4.3 吐槽

和构建构建工具一样C++没几个工具是用的舒服的。

4.3.1 vcpkg

每个库都会有版本号更新,而对于vcpkg,某个版本号下修bug的提交(不改版本号),会视为一次port-version记录起来。

这就导致一个坑,在使用指定版本的时候,比如某个库在3.3.9版本下更新了10次,就是说会有0~9的port-version,你指定3.3.9版本,他不会去安装最新的修复bug那一次提交(3.3.9#10),而是安装第一次发布时的提交,即3.3.9#0,而这种情况一般就是想要最新的更新。

本来是有选项显示某个库某个版本的port-version,但是删除了Remove command x-history
那怎么解决呢?查看 vcpkg安装目录下/version/安装库名开头字母-/安装库名.json,里面就有对应port-version记录了。

4.3.2 导入第三方

目前介绍了四种协回字的方法:

  1. 复制项目到子项目
  2. CMake自带git克隆,简化第一个方法的流程
  3. 配置编译好的动态库项目
  4. 包管理,比如vcpkg

这里我还推荐第三种方法,理由如下:

  • 方便处理版本依赖问题,这个时候可以手动配置依赖关系,经典的protobuf版本问题,还有标准库实现。
  • 减少编译速度,比如后面会用到的assimp库编译很久,每次删除编译缓存都要重新构建,而且库还很大,每次链接都很久。
  • 减少了一层包管理器坑,这个和C++特性有关,java用的maven就没有这种担心。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值