The best practice for vcpkg & Qt QML application -- manage C++ libraries with an ease

vcpkg is a C++ Library Manager for Windows, Linux, and MacOS. vcpkg for C++ is the same as npm for node.js. On the Windows platform, for a long time, people have to manually compile various libraries with different parameters. When library A depends on library B, library B must be compiled too. Therefore, the process of building the project is very time-consuming. vcpkg can solve this problem, all dependencies can be installed just use one command vcpkg install.

On Windows, I always prefer to use VSCode instead of Visual Studio, because VSCode is much faster! So, I try to find a best practice to develop C++ applications on VSCode with the help of vcpkg. You can just grab the sample source code here.

Using vcpkg in manifest mode

The documentation says we have two ways to use vcpkg, ony way is global-shared (such as C:\src\vcpkg), one way is per-project-wide. One vcpkg instance per project, aka the manifest mode. I think the latter is the best practice because we can avoid version conflicts for different projects.

In the manifest mode, the vcpkg.json for vcpkg is pretty the same with package.json for npm. We can declare all the dependencies we need, just like this:

{
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
"name": "vscode-qt-qml-vcpkg-template",
"version-string": "0.0.1",
"license": "MIT",
"builtin-baseline": "030cfaa24de9ea1bbf0a4d9c615ce7312ba77af1",
"dependencies": [
{
"name": "sqlite3",
"version>=": "3.35.2",
"default-features": true,
"platform": "windows & x64"
}
]
}

In the vcpkg.json above, we want to install sqlite3:x64-windows. Note that currently there some problems with vcpkg’s QML support, so we should install Qt manually.

Once you have created you project and CMakeLists.txt, you can add vcpkg instance using git submodules:

git submodule add https://github.com/microsoft/vcpkg

And then you need to add the vcpkg’s toolchain file, the file will care about downloading and build dependencies, copy DLLs, etc. You can add this in you CMakeLists.txt:

list(APPEND VCPKG_FEATURE_FLAGS "versions")
# Setup vcpkg script with CMake (note: should be placed before project() call)
if(DEFINED ENV{VCPKG_ROOT})
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")
else()
set(CMAKE_TOOLCHAIN_FILE "./vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")
endif()

In the CMake code above, we enable the versions feature for the dependencies.version>= in vcpkg.json to work properly. And we use the toolchain file of the cloned vcpkg submodule in the current directory if there is no VCPKG_ROOT environment variable define. It means you can define VCPKG_ROOT to force CMake to use the global vcpkg instance.

All you need now is just modifying you CMakeLists.txt with find_package and target_link_libraries, then write you C++ code, and build again to see the results, no more cumbersome work!

VSCode Intelligence

For a small project, you may just use hard-coded include folder for your intelligence to work. According to the c_cpp_properties.json reference docs, you may use this configuration for Qt & STL intelligence to work:

{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}",
"${workspaceFolder}/**",
"C:/Qt/Qt5.7.1/5.7/msvc2015_64/include/**",
"C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include",
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.18362.0",
"compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/Hostx64/x64/cl.exe",
"cStandard": "c11",
"cppStandard": "c++17"
}
],
"version": 4
}

In this way, we have to change this configuration every time if we add a new library in vcpkg.json and CMakeLists.txt, so this is not ideal.

Actually, we can make use of the compileCommands property, and feed VSCode with the include meta-info generated by CMake.

First, we need to enable CMake’s CMAKE_EXPORT_COMPILE_COMMANDS:

# https://stackoverflow.com/a/50360945/8242705
# https://code.visualstudio.com/docs/cpp/faq-cpp#_how-do-i-get-intellisense-to-work-correctly
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

Then run CMake configure again, you will get an compile_commands.json file on your build output folder. We can feed it into the VSCode C++ extension. In .vscode/c_cpp_properties.json, just use:

{
"configurations": [
{
"name": "Win32",
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
}
],
"version": 4
}

This is all you need to do for the intelligence.

Build & Debug

For faster build time, we can use Ninja generator instead of Visual Studio generator, which is bring to you with VC Tools, you can start an
“x64 Native Tools Command Prompt for VS2019” and type “ninja” to check:

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community>ninja --version
1.10.2

You can just use CMake Tools extension on VSCode, with the following configuration:

{
"cmake.configureOnOpen": false,
"cmake.configureOnEdit": false,
"cmake.cmakePath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin/cmake.exe",
// `Ninja` is much more faster than `Visual Studio`
"cmake.generator": "Ninja",
}

Then add the following tasks to .vscode/tasks.json because we want to use windeployqt to deploy qt apps on Windows, and run build task on each debug session:

{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"options": {
// https://stackoverflow.com/a/54960358/8242705
"shell": {
"executable": "cmd.exe",
"args": [
"/d",
"/c",
"C:\\Program^ Files^ ^(x86^)\\Microsoft^ Visual^ Studio\\2019\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat",
"&&"
]
}
},
"tasks": [
// https://stackoverflow.com/a/57470981/8242705
// https://github.com/microsoft/vscode-cmake-tools/issues/1680
{
"label": "CMake Configure",
"type": "shell",
"command": "cmake -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=Debug -H${workspaceFolder} -B${workspaceFolder}/build -G Ninja",
"problemMatcher": []
},
{
"label": "CMake Build",
"type": "shell",
"command": "cmake --build build --config Debug --target all -- -j 10",
"problemMatcher": []
},
{
"label": "Run windeployqt",
"type": "shell",
"command": "C:/Qt/5.15.2/msvc2019_64/bin/windeployqt.exe --debug --qmldir . ${workspaceFolder}/build/vscode-qt-qml-vcpkg-template.exe",
"args": [],
"problemMatcher": []
},
{
"label": "Before Launch",
"dependsOn": ["CMake Build"],
"dependsOrder": "sequence"
}
]
}

Finally you just add the debug configuration to .vscode/launch.json:

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(Windows) Launch",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/build/vscode-qt-qml-vcpkg-template.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"console": "integratedTerminal",
"preLaunchTask": "Before Launch"
}
]
}

Now you can press F5 to start debugging your application. And, don’t forget to run windeployqt task on the first successful build.

CI build with cache & artifact uploading

We can use GitHub Actions to run build task on each commit. The key points we need are:

  1. Cache vcpkg packages for reuse on next CI run. This will make the CI run much more faster.

    # Restore from cache the previously built ports. If cache-miss, download and build vcpkg (aka "bootstrap vcpkg").
    - name: Restore from cache and install vcpkg
    # Download and build vcpkg, without installing any port. If content is cached already, it is a no-op.
    uses: lukka/run-vcpkg@v6
    with:
    # Just install vcpkg for now, do not install any ports in this step yet.
    setupOnly: true
    # Location of the vcpkg submodule in the Git repository.
    vcpkgDirectory: '${{ github.workspace }}/vcpkg'
    # Since the cache must be invalidated when content of the vcpkg.json file changes, let's
    # compute its hash and append this to the computed cache's key.
    appendedCacheKey: ${{ hashFiles( '**/vcpkg_manifest/vcpkg.json' ) }}
    vcpkgTriplet: ${{ matrix.arch }}-windows
    # Ensure the vcpkg artifacts are cached, they are generated in the 'CMAKE_BINARY_DIR/vcpkg_installed' directory.
    additionalCachedPaths: ${{ env.buildDir }}/vcpkg_installed
  2. Upload Built package to GitHub Artifact area. This will make your release procedure easier.

    - name: Set outputs
    id: vars
    run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
    - name: Get Time
    id: time
    uses: nanzm/get-time-action@v1.1
    with:
    timeZone: 8
    format: 'YYYY-MM-DD-HH-mm-ss'

    - name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - uploading artifact (whole zip)
    uses: actions/upload-artifact@master
    with:
    name: ${{ env.name }}-${{ steps.time.outputs.time }}-${{ steps.vars.outputs.sha_short }}-windows-${{ matrix.arch }}.zip
    path: build

    - name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - uploading artifact (only the exe)
    uses: actions/upload-artifact@master
    with:
    name: ${{ env.name }}-${{ steps.time.outputs.time }}-${{ steps.vars.outputs.sha_short }}-windows-${{ matrix.arch }}.exe
    path: build/${{ env.name }}.exe

Get the template

The template is on GitHub: https://github.com/upupming/vscode-qt-qml-vcpkg-template/ , if you have any questions and suggestions, please fell free to open an issue, thanks!

文章作者: upupming
文章链接: https://upupming.site/2021/04/03/vcpkg-best-practice/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 upupming 的博客