用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
排除.vs
与out
,随后上传。
本仓库地址是 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库的源码。