在vscode中使用cmake+vcpkg

考虑到C++主流还是用CMake做构建,最近也稍微折腾了下CMake
然后C++的话还是要用些包管理的,不然手动管理太抽象了
所以还需要配置下vcpkg的东西
虽然微软这确实有文档讲怎么配,不过讲得不是很清楚

<0x00> 前置准备

系统方面

CMake

既然是CMake配置,所以系统里肯定要安装CMake的
命令行敲cmake能找到就行,我这里采用scoop安装

scoop install cmake

vcpkg

然后是包管理器vcpkg
网上很多教程是下载源码的,我这里也是采用scoop安装

scoop install vcpkg

采用scoop安装的话会自动添加VCPKG_ROOT的环境变量,指向的就是安装路径
虽然这个主要是方便MSBuild找vcpkg的,跟这篇博客没关系
(自己下载源码的话手动添加一个就可以了)

VSCode方面


主要是C/C++ Extension PackCMake Tools

<0x01> CMake工程配置

首先新建一个文件夹,相当于是工程文件夹了

快速创建CMake工程

VSCode中Ctrl+Shift+P调出命令面板
输入CMake: Quick Start后回车
在输入完名字之类的东西之后,会让你设置一个preset
这个比较关键,选择最后从编译器创建

(windows平台下貌似直接绑定MSVC,选别的编译器也会换成MSVC)
这时候会创建一些文件,先不管

初始化vcpkg

打开终端窗口,输入下面的命令

vcpkg new --application

这时候也会创建一些文件,也不需要管

让CMake认识vcpkg

打开CMakePresets.json,里面可以看到一些配置

{
    "version": 8,
    "configurePresets": [
        {
            "name": "clang",
            "displayName": "Clang 18.1.8 x86_64-pc-windows-msvc",
            "description": "Using compilers: C = C:\\Users\\cookie\\scoop\\apps\\llvm\\current\\bin\\clang.exe, CXX = C:\\Users\\cookie\\scoop\\apps\\llvm\\current\\bin\\clang++.exe",
            "binaryDir": "${sourceDir}/out/build/${presetName}",
            "cacheVariables": {
                "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}",
                "CMAKE_C_COMPILER": "C:/Users/cookie/scoop/apps/llvm/current/bin/clang.exe",
                "CMAKE_CXX_COMPILER": "C:/Users/cookie/scoop/apps/llvm/current/bin/clang++.exe",
                "CMAKE_BUILD_TYPE": "Debug"
            }
        }
    ]
}

(这里贴出我的结果,指定为clang)

这时候,CMake其实不知道vcpkg在哪里,我们需要让它知道vcpkg在哪
我先贴出最后的结果

{
    "version": 8,
    "configurePresets": [
        {
            "name": "default",
            "binaryDir": "${sourceDir}/out/build",
            // 添加的环境变量
            "environment": {
	            // 这里写上vcpkg的安装路径
                "VCPKG_ROOT": "C:\\Users\\cookie\\scoop\\apps\\vcpkg\\current",
                // 魔法上网的话
                "HTTP_PROXY": "127.0.0.1:1000",
                "HTTPS_PROXY": "127.0.0.1:1000"
            },
            "cacheVariables": {
	            // 让CMake可以找到vcpkg,一般这样写就可以
                "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
                "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}",
                // 因为windows下vcpkg自己会覆盖编译器设置为MSVC,所以也可以不指定
                "CMAKE_BUILD_TYPE": "Debug"
            }
        }
    ]
}

已经通过注释指出需要添加的部分,写完就好了,顺便删点不需要的
这里相当于是一个虚拟环境,如果有其他要用到的环境变量的话也要写进environment

<0x02> 实战测试

这里做一个简单的opengl开发演示

vcpkg中添加需要的包

打开终端,指定vcpkg需要glfw3和glad这两个包

vcpkg add port glfw3
vcpkg add port glad

CMake中引用并链接包

然后,打开CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(CmakeTest)

add_executable(CmakeTest main.cpp)

# 引用包
find_package(glad CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)

# 链接包
target_link_libraries(CmakeTest PRIVATE glfw)
target_link_libraries(CmakeTest PRIVATE glad::glad)

在代码中使用

最后,打开main.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }    

    while (!glfwWindowShouldClose(window))
    {
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glfwTerminate();
    return 0;
}

void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

(这段代码是Learn OpenGL的代码,直接复制就能跑,所以就删注释了)

编译成功并运行的话应该是这样的

一切正常的话,恭喜完成配置,之后这样配置也能用

<0x03> 进阶设置

如果使用的包有可选特性

在vcpkg中,有一些包是有可选特性的
比如说imgui这个库,有很多可选特性,比如docking,opengl3,win32等等
比方说做opengl的开发,添加包的时候可以这么写

vcpkg add port imgui[opengl3-binding,glfw-binding,docking-experimental]

然后修改CMakeLists.txt部分,加上下面的东西

find_package(imgui CONFIG REQUIRED)
# example改成自己的项目
target_link_libraries(example PRIVATE imgui::imgui)

然后打开main.cpp测试下

#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <fmt/core.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

// glfw错误回调
static void glfw_error_callback(int error, const char *description)
{
    fmt::println("GLFW Error {}: {}", error, description);
}
int main(int, char **)
{
    glfwSetErrorCallback(glfw_error_callback);
    if (!glfwInit())
    {
        return -1;
    }
    // 设定opengl版本
    const char *glsl_version = "#version 130";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
	// 创建窗口
    GLFWwindow *window = glfwCreateWindow(1280, 720, "ImGui GLFW+OpenGL3 with docking example", nullptr, nullptr);
    if (window == nullptr)
    {
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);
	// 初始化glad
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        fmt::println("Failed to initialize GLAD");
        return -1;
    }
	// 初始化imgui
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO &io = ImGui::GetIO();
    (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
    // 启用docking
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
    io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
    ImGui::StyleColorsDark();
	// 启用docking的后处理
    ImGuiStyle& style = ImGui::GetStyle();
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
    {
        style.WindowRounding = 0.0f;
        style.Colors[ImGuiCol_WindowBg].w = 1.0f;
    }
	
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init(glsl_version);
    // 渲染循环
    while (!glfwWindowShouldClose(window))
    {
	    // 初始化渲染
        glfwPollEvents();
        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();
		// 渲染demo窗口
        ImGui::ShowDemoWindow();
		// 真正开始渲染
        ImGui::Render();
        int display_w, display_h;
        glfwGetFramebufferSize(window, &display_w, &display_h);
        glViewport(0, 0, display_w, display_h);
        glClearColor(.1f, .1f, .1f, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
		// docking处理
        if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
        {
            GLFWwindow* backup_current_context = glfwGetCurrentContext();
            ImGui::UpdatePlatformWindows();
            ImGui::RenderPlatformWindowsDefault();
            glfwMakeContextCurrent(backup_current_context);
        }
        glfwSwapBuffers(window);
    }
	// 退出的处理
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();

    glfwDestroyWindow(window);
    glfwTerminate();

    return 0;
}

(稍微修改了imgui给的示例,使用了glad和fmt)

效果演示

对于多人开发的情况

前面提到,我们可以用CMakePresets.json来统一设定CMake的变量
但对于多人开发的情况,这个方式有一些弊端
比如,windows下,不同人编译器的位置就可能不一样
这时候,可以引入CMakeUserPresets.json文件做具体的设定

比方说接着上面的配置,我们可以把CMakePresets.json改造成这样

{
    "version": 8,
    "configurePresets": [
        {
            "name": "default",
            "binaryDir": "${sourceDir}/out/build",
            "cacheVariables": {
                "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
                "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}",
                "CMAKE_BUILD_TYPE": "Debug"
            }
        }
    ]
}

加上CMakeUserPresets.json,内容为

{
    "version": 8,
    "configurePresets": [
        {
            "name": "My default",
			"inherits": "default",
            "environment": {
                "SCOOP_ROOT": "C:/Users/cookie/scoop",
                "VCPKG_ROOT": "$env{SCOOP_ROOT}/apps/vcpkg/current",
                "HTTP_PROXY": "127.0.0.1:1000",
                "HTTPS_PROXY": "127.0.0.1:1000"
            }
        }
    ]
}

CMakePresets.json定义总体的框架,CMakeUserPresets.json定义具体的位置
在git设置中,排除CMakeUserPresets.json就可以了