vcpkg安装库的导出和使用

  对于C++程序员来说,最痛苦的莫过于想要使用一个第三方库的时候,不能做到开箱即用而是需要自己使用源码手动编译。对于linux平台而言,主流的第三方库大多都能做好很好的支持,甚至使用apt命令便可以直接安装;对windows上的开发而言,想要编译使用第三方库很多时候可能编译都搞不定,尤其是遇上编译器版本不匹配或者使用到其他依赖库的情况。对此,我只想说:“vcpkg, yes!”
  随着vcpkg功能的不断完善,使用vcpkg编译安装第三方库更加方便快捷。vcpkg可以通过集成安装或者作为cmake的工具链等方式快捷地添加到自己的项目中。早些时候学习vcpkg,我一直把整个vcpkg文件目录作为一个cmake工程的一个第三方库目录。一方面,这个目录中包含了众多缓存文件,或者某些可能并不需要用到的第三方库,不同项目需要单独配置一个vcpkg工具链目录;另一方面,我又不想让这个目录作为一个全局共享的工具链使用,可能一个操作影响了不同的项目。
  最近,发现vcpkg有一个很好用的功能,可以导出安装好的第三方库。可以全局维护一个vcpkg目录,在其中管理需要使用的各种第三方库,然后将项目需要使用的第三方库单独导出,并解压到对应的项目中或者分发给合作开发的其他成员。导出第三方库的功能极大地简化和方便了对第三方库的管理和使用。
本文以windows操作系统为例,介绍vcpkg导出和导入库的使用方法。

vcpkg简介

  Vcpkg 是一个开源的 C/C++ 依赖库管理工具,旨在简化在 Windows、Linux 和 macOS 等操作系统上构建 C/C++ 项目时管理第三方库的过程。Vcpkg 提供了一个简单的方式来下载、安装和管理各种 C/C++ 库,使开发人员能够轻松地集成这些库到他们的项目中。
  vcpkg的安装和使用可以参考官方教程,或者参考我的另一篇博客vcpkg的安装与使用

vcpkg安装第三方库

  在windows操作系统中,vcpkg默认使用MSVC编译并安装x86版本的库,如果需要安装x64版本,则需要指定:

vcpkg install package_name:x64-windows

  例如,安装jsoncpp库的命令为:

vcpkg install jsoncpp:x64-windows

vcpkg导出第三方库

  vcpkg从已安装的目录中导出第三方库的命令为:

vcpkg export [options] {<package>... | --x-all-installed}

  其中,options可以通过参数指定导出的格式或者目录,紧跟着为导出的包名称,或者使用--x-all-installed导出所有已经安装的包。常用的选项参数有:
  1. --zip:指定导出包为zip压缩文件;
  2. --output-dir:指定导出的路径。
  例如,把安装的jsoncpp库导出到当前目录中的export目录中可以使用以下命令:

vcpkg export --zip --output-dir=./export jsoncpp:x64-windows

  执行后,vpckg会将jsoncpp第三方库和vcpkg的cmake工具链相关文件压缩打包输出到指定的目录中。--x-all-installed是实验性的功能,可能随时取消,使用时请确定该选项是否可用。

使用导出的第三方库

  将导出的第三方库压缩包解压到cmake工程的目录中命名为vcpkg,并在cmake工程中最顶级的CMakelists文件的起始位置指定CMAKE_TOOLCHAIN_FILE变量为vcpkg的工具链文件即可:(早期版本还需要设定三元组x64-windows之类的,新版本似乎可以自动推断了。

set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake"
  CACHE STRING "Vcpkg toolchain file")

  然后就可以在cmkae项目中使用find_package命令查找并使用导入的第三方库。

one more thing

  我暂时还没有找到一种简单方便的方法单独导入某个第三方库(或许可以根据vcpkg包的管理方式,自己编写一个脚本处理包中的文件)。原来和export对应的还有一个import命令可以用,但最新的版本中该命令似乎被废弃了。假如某天你的项目中新增了一个第三方库,那么导出vcpkg包时则需要重新把所有的依赖包都进行导出。这样可能需要使用很长很长的命令,除非vcpkg中的包都是项目依赖的,可以使用全部导出的命令。
  为此,我编写了一个简单的c++的可执行程序,通过读取yaml文件的方式导出你所需要的包:

# 下载后可以添加到环境变量中,使用终端执行程序。主要有三种形式
vcpkg_export
vcpkg_export --help
vcpkg_export -c <config-file-path>

  其中,前两种命令自动在执行命令的当前目录中生成export-config.yaml文件,并显示可用的后两种命令格式。第三种格式的调用会根据提供的配置文件调用vcpkg以默认zip的方式导出配置文件中指定的第三方库(请确保已经安装了这些需要导出的库)。yaml配置文件的格式如下:

triplet: x64-windows
packages: []
outputDir: D:/export

  triplet表示库的三元组;packages表示需要导出的库(只需要库名称,不需要triplet后缀,程序会自动添加);outputDir表示导出库的输出目录,支持绝对路径和相对路径(相对路径以执行vcpkg_export命令所在目录为参考)。
  上述可执行文件的功能比较简陋,没有做输入检查,也没有错误提示,不支持导出不同triplet包(感觉是个伪需求)。所以,提供该程序的源代码以供参考:

#include <iostream>
#include <fstream>
#include <yaml-cpp/yaml.h>

// 根据提供的yaml文件,导出指定的包
// vcpkg_export --help  # 查看帮助,生成export-config.yaml文件
// vcpkg_export -c export-config.yaml  # 根据指定的export-config.yaml文件导出指定的包

void generateTemplateConfig(){
    YAML::Node config;
    config["triplet"] = "x64-windows";
    config["packages"] = YAML::Node(YAML::NodeType::Sequence);
    config["outputDir"] = "D:/export";
    // 保存到文件
    std::ofstream out("export-config.yaml");
    out << config;
    out.close();
}

int main(int argc, char* argv[]){
    if (argc < 2 || (argc == 2 && std::string(argv[1]) == "--help")){
        std::cout << "Usage command as listed below:" << std::endl;
        std::cout << "vcpkg_export --help" << std::endl;
        std::cout << "vcpkg_export -c <config-file-path>" << std::endl;
        generateTemplateConfig();
    } else if (argc == 3 && std::string(argv[1]) == "-c"){
        std::string config_file_path = argv[2];
        YAML::Node config = YAML::LoadFile(config_file_path);

        std::string triplet;
        std::string output_dir;
        std::vector<std::string> packages;
        if (config["triplet"].IsDefined()){
            triplet = config["triplet"].as<std::string>();
        }
        if (config["outputDir"].IsDefined()){
            output_dir = config["outputDir"].as<std::string>();
        }
        if (config["packages"].IsDefined()){
            for (auto package : config["packages"]){
                packages.push_back(package.as<std::string>() + ":" + triplet);
            }
        }
        if (!packages.empty()){
            // 组织命令
            std::string command = "vcpkg export --zip --output-dir=" + output_dir + " ";
            for (auto package : packages){
                command += package + " ";
            }
            std::cout << "Exporting packages: " << command << std::endl;
            system(command.c_str());
        } else {
            std::cout << "No packages specified in config file." << std::endl;
        }
    }
    return 0;
}

当珍惜每一片时光~