手把手教你构建vcpkg私有仓库

 

用vcpkg也有一段时间了,非常喜欢。这一年也打了不少比赛,写了很多很多重复的POC代码,想着能不能把以前打CTF或者写其他程序时,自己写的一些工具代码弄成一堆vcpkg包,并做成私有的vcpkg源里,方便管理,也供以后比赛或者写程序使用,于是就搜了一堆资料,同时也感谢朋友的帮助,在这里分享出来。

 

创建一个CMake工程

或许有人没用过CMake,放心我以前也没用过,这玩意儿学习曲线比较陡峭。但不用担心,你的vcpkg库用CMake做也是可以在VS工程里使用的,而且这个教程里学的CMake知识只是皮毛,但也完全够用,当作一个起点就行。而且CMake工程比VS工程适配性更好,还能直接编译到WSL里运行,一些要求环境的CTF题也能用,岂不美哉?

在这里创建一个k1ee库为例子

新建好后的目录结构如下

在这个例子工程里,打算写一个简单的读取文件所有字节的函数,做成库,因此首先删除VS为我们自动创建的文件,然后添加文件夹与文件,直到下面这个结构。

其中test_data.dat是给测试用的,随便输入什么。

 

根目录配置

首先打开根目录的CMakeLists.txt进行编辑,定义工程

cmake_minimum_required (VERSION 3.15)
project (k1ee VERSION 1.0 LANGUAGES CXX)

设置C++版本

set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

最后引用子文件夹里的CMakeLists.txt,提供一个选项是否编译Test

option(BUILD_K1EE_TESTS "Build Tests" ON)

include(GNUInstallDirs)

add_subdirectory(src)
add_subdirectory(include)

if(BUILD_K1EE_TESTS)
    enable_testing()
    add_subdirectory(test)
endif()

 

子目录配置

cmake配置

不知道为什么需要,但如果想让vcpkg正确配置引用工程的CMake必须得添加,新建k1eeConfig.cmake,并编辑内容如下

@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/k1eeTargets.cmake")
check_required_components("@PROJECT_NAME@")

src配置

这里存放所有代码的源文件,本文创建了一个file.cpp,如下

#include "k1ee/file.h"  
#include <fstream>

#include "k1ee/exception.h"

std::vector<uint8_t> k1ee::read_all_bytes(const std::filesystem::path& path)
{
    using namespace std::filesystem;

    if(!exists(path) || is_directory(path))
        throw k1ee::k1ee_runtime_error("file doesn't exist."); 

    auto size = file_size(path);

    std::vector<uint8_t> ret(size);

    std::ifstream fin(path, std::ios::binary);

    if(fin.fail())
        throw k1ee::k1ee_runtime_error("open file failed");

    fin.read(reinterpret_cast<char*>(ret.data()), size);

    if(fin.fail())
        throw k1ee::k1ee_runtime_error("read file failed");

    return ret;
}

配置CMakeLists.txt,首先通过add_library添加target并添加文件,第二个是给k1ee一个别名,设置版本号

cmake_minimum_required (VERSION 3.15)

add_library(k1ee
    "file.cpp"
)

add_library(k1ee::k1ee ALIAS k1ee) 

set_target_properties(k1ee
     PROPERTIES
     VERSION ${${PROJECT_NAME}_VERSION}
     SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR})

安装k1ee库本身并导出Target配置文件,生成Config文件

set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") 

install(TARGETS k1ee
  EXPORT ${TARGETS_EXPORT_NAME}
  RUNTIME DESTINATION bin
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib 
  PUBLIC_HEADER DESTINATION include
)

include(CMakePackageConfigHelpers)
configure_package_config_file(
    "${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake"
    "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
    INSTALL_DESTINATION lib/cmake/${PROJECT_NAME}
)

install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
        DESTINATION lib/cmake/${PROJECT_NAME})

install(
  EXPORT ${TARGETS_EXPORT_NAME} 
  DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
)

通过install函数把这个工程加入安装这一行为(也就是从生成目录复制一些binary和文件到一个指定的release目录),指定一些安装目录。为了让vcpkg顺利配置还需要导出一个k1eeConfig.cmake。最后对于Debug编译不要导出include目录(vcpkg要求)

include配置

把头文件都放在这里,为了在vcpkg用的时候以#include<k1ee/xxx.h>的方式引用,因此还需要放在一个k1ee文件夹里。在k1ee/file.h里写简单的函数声明

#pragma once

#include <iostream>
#include <vector>
#include <cstdint>
#include <filesystem>

namespace k1ee
{
std::vector<uint8_t> read_all_bytes(const std::filesystem::path& path);
}

把CMakeLists.txt放在k1ee文件夹外面

这样编写他

cmake_minimum_required (VERSION 3.15)

target_include_directories(k1ee PUBLIC  
    $<INSTALL_INTERFACE:include>    
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)

target_sources(k1ee PRIVATE
     # Header files (useful in IDEs)
    "k1ee/file.h"
    "k1ee/exception.h"
)

install(DIRECTORY k1ee DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

通过target_include_directories会把include目录加入这个工程的引用目录

把头文件加入工程,然后通过install注册到引用目录,这样就会复制头文件到指定目录,如vcpkg的installed那里了。

test配置

简单的测试API能不能用

把assert的大小判断改为你生成的dat文件大小

#include "k1ee/file.h"

#include <cassert>

int main()
{
    auto ret = k1ee::read_all_bytes(R"(./data/test_data.dat)");

    assert(ret.size() == 264);
}

配置CMakeLists.txt,并复制data文件夹到test目录

cmake_minimum_required(VERSION 3.15) 

macro(add_k1ee_test _TEST_NAME)
    add_executable(test_${_TEST_NAME} test_${_TEST_NAME}.cpp)
    target_link_libraries(test_${_TEST_NAME} PRIVATE
        k1ee::k1ee)
    add_test(NAME k1ee_test_${_TEST_NAME} COMMAND test_${_TEST_NAME})
    # Group under the "tests" project folder in IDEs such as Visual Studio.
    set_property(TARGET test_${_TEST_NAME} PROPERTY FOLDER "Tests")
    if(WIN32 AND BUILD_SHARED_LIBS)
        add_custom_command(TARGET test_${_TEST_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND}
            -E copy $<TARGET_FILE:k1ee> $<TARGET_FILE_DIR:test_${_TEST_NAME}>)
    endif()
endmacro()

add_k1ee_test(file)

add_custom_command(TARGET test_file POST_BUILD COMMAND ${CMAKE_COMMAND}
    -E make_directory $<TARGET_FILE_DIR:test_file>/data)
add_custom_command(TARGET test_file POST_BUILD COMMAND ${CMAKE_COMMAND}
    -E copy ${CMAKE_CURRENT_SOURCE_DIR}/data/test_data.dat
    $<TARGET_FILE_DIR:test_file>/data/test_data.dat)

首先定义一个macro用来方便添加测试项,然后add一个可执行程序,并且链接我们的库k1ee::k1ee(别名,用原名也可以),随后add_test,这样VS里的测试功能就可以用了。后面如果打开了编译为动态链接库还需要复制到测试目录。

这样就配置好了,可以在顶部进行测试

 

上传至github仓库

这个小工程已经实现好了,下一步我们需要上传到一个github仓库,当然也可以放在本地或是使用其他vcpkg的下载源方法,本文使用从github下载源。

上传之前需要创建LICENSE文件,不然后面编译不过,可以直接在github上添加

在k1ee文件夹git init,并且创建.gitignore排除.vsout,随后上传。

本仓库地址是 k1ee

 

创建ports仓库

我们完成了库源码,还需要仿照vcpkg为这个库建立一个ports来帮助vcpkg下载和安装上面这个库,因此本地创建一个文件夹,姑且叫做vcpkg-ports,里面需要有ports文件夹提供库安装信息

在ports文件夹里,我们按照vcpkg的结构,创建k1ee文件夹,表示k1ee库。接下来还需要创建vcpkg.json以及portfile.cmake

其中vcpkg.json记录了这个库的基本信息

portfile.cmake就记录了k1ee这个库的下载、编译、安装的信息,我是仿照vcpkg官方源里cpr这个库,以及vcpkg官方文档编写的

首先编写下载方法,需要设置REF以及SHA512,参考下图

vcpkg_from_github(
    OUT_SOURCE_PATH SOURCE_PATH
    REPO cnSchwarzer/k1ee
    REF 83f1f19e88e1074fdb9e688721cac55af55791f1
    SHA512 e34af619b60bbb0f1171102d39dafe69edd0e170bf83eaa49a1021c94142d9f1ec57fa4ee5e6e6eaaa45cba86b75033155fe2ee9f1021c70b0fbc79747650213
    HEAD_REF main
)

你也可以不指定这两个,而指定HEAD_REF为main,并在vcpkg install时指定 —head,这样就直接使用仓库最新版本进行编译了,不用每次麻烦填REF与SHA512

vcpkg_from_github(
   OUT_SOURCE_PATH SOURCE_PATH
   REPO cnSchwarzer/k1ee
   HEAD_REF main
)

其中REF点击最新commit看地址即可查看

SHA512可以通过先设为1然后看报错信息查看

接下来配置CMake,对于Debug需要关闭include安装(vcpkg要求)

vcpkg_configure_cmake(
    SOURCE_PATH ${SOURCE_PATH}
    PREFER_NINJA
    OPTIONS 
        -DBUILD_K1EE_TESTS=OFF   
)

然后使用CMake安装,复制config文件,并且复制pdb供调试,移除debug目录下的include(官方推荐流程)

vcpkg_install_cmake() 
 
vcpkg_fixup_cmake_targets(CONFIG_PATH lib/cmake/k1ee)

vcpkg_copy_pdbs()

file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")

file(INSTALL ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/${PORT} RENAME copyright)

然后还需要设置版权信息,输出到指定目录

file(INSTALL ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/${PORT} RENAME copyright)

 

使用overlay-ports安装私有库

我们用overlay-ports进行测试安装,详情请看官方文档。主要就是通过–overlay-ports参数指定我们刚才创建的vcpkg-ports文件夹里的ports目录,vcpkg会检索其中所有的vcpkg.json进行安装。

需要注意的是,以后我们对vcpkg的每一个命令都需要指定overlay-ports了,这点体验并不是很好。

我们可以通过在环境变量中添加 VCPKG_OVERLAY_PORTS=E:\Work\vcpkg-ports\ports 来避免每次都要写的麻烦。

 

使用registries新功能安装

最后,我们可以使用vcpkg的manifests与registries这两个新功能,避免全局安装,进行工程局部安装

添加环境变量 VCPKG_FEATURE_FLAGS=manifests,registries,binarycaching,versions

对于VS工程,需要在工程属性的vcpkg选项卡打开Use Vcpkg Manifest

如果你在上面的vcpkg_from_github不想用REF以及SHA512,这里的Additional Options需要加–head

然后在工程的目录下创建vcpkg.json以及vcpkg-configuration.json

其中,vcpkg.json记录了这个工程的信息、版本、以及需要使用的vcpkg库

{
    "name": "2-re",
    "version-string": "0.0.1",
    "dependencies": [
        "k1ee",
        "cpp-base64"
      ]
}

vcpkg-configuration里记录registries,意义是注册一些vcpkg库的下载源,比如k1ee不在官方库里,因此需要注册。

{
    "registries": [
        {
            "kind": "git",
            "repository": "https://github.com/k1ee/vcpkg-ports.git",
            "packages": [ "k1ee" ]
        }
    ]
}

对于CMake工程,此时双击CMakeSettings.json,确保存在这几个变量,如果没有,尝试清除CMake缓存(右键CMakeLists.txt,或检查环境变量是否正确),或者重启VS

然后对工程进行生成操作,就会自动下载依赖到项目的vcpkg_installed,局部安装。

 

使用安装的库

我们测试一下安装的k1ee库,随便新建一个VS工程

编译通过,链接库也正确复制了

再创建一个CMake工程测试,在CMakeLists.txt里找到我们的库

cmake_minimum_required (VERSION 3.15)

project ("k1ee-test")

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) 

# 将源代码添加到此项目的可执行文件。
add_executable (k1ee-test "k1ee-test.cpp" "k1ee-test.h")

find_package(k1ee CONFIG REQUIRED)
target_link_libraries(k1ee-test PRIVATE k1ee)

此时cmake就会自动寻找刚才安装的k1ee库,同样也能正确运行

 

结论

本文介绍了如何把自己的代码构建成一个CMake工程,并配置成vcpkg私有库。然后介绍了如何制作vcpkg-ports的私有源目录。把库与源目录上传github后,使用overlay-ports进行全局安装,或者registries进行本地安装。 最后,如果读者有任何不懂,可以参考这两个github库的源码。

(完)